UGantryModSubsystem
Finds the mods players install, checks them, and makes their experiences part of the game.
A mod is one game feature plugin in its own folder, or that folder packed as one .gantry file. Its experiences join the experience tree like any side-loaded content: by naming their parent. A game that lists its rounds from the tree offers a mod's new round without any change to the game.
What the game configures
In Project Settings > Plugins > Gantry Experience, under Mods:
- Mod Directories: the folders players put mods in. Relative paths start at the project directory.
- Mount Mods At Startup: mount every mod when the engine finishes starting. Off by default, so a game that takes mods turns it on. Games and servers only: the editor mounts on request.
- Mod Rescan Interval: seconds between looks for new mods while the game runs. Zero looks only at startup.
Installing a mod
A player puts one of these in a mod directory:
- A folder holding the mod's .uplugin. In a cooked game the mod's content is in
Content/Paks/(platform)/inside that folder. In an uncooked run the plugin's loose content is read directly. - A
.gantryfile: the same folder as one file, an uncompressed zip of its .uplugin, Config/ and Content/Paks/(platform)/. Gantry unpacks it into a folder of the same name beside it, which is where its paks expect to be, and mounts that folder. Remove the file and the folder goes too, at the next pass or start. A folder of that name that did not come from the file is never touched: the file is skipped, with a warning.
Mounting
Mount Mods (Gantry.Mods.Mount at the console) runs a pass. A pass:
- finds every mod not mounted yet, and leaves out the ones the player turned off;
- orders them by how many other mods each builds on (the mods its .uplugin depends on);
- checks each one, and refuses it before anything of it is mounted (see below);
- mounts each mod's content, above the mods it builds on and above every earlier pass;
- registers each mod's plugin with the game features subsystem;
- lists the collisions, and has every world read its experience tree again at its next activation. The running experience is untouched.
The pass is asynchronous. On Mods Mounted fires when it ends, successful or not. Running it again picks up new mods and leaves mounted ones alone. A mod that failed is tried again once its folder has changed, since it may have been fixed.
Registering a mod does not activate it. Its game features are activated by the stages of the experiences that list them, exactly like the game's own.
What is checked before mounting
A mod is refused, with nothing of it mounted, when:
- its .uplugin is not Explicitly Loaded;
- its name already belongs to another plugin or content root (the game's, the engine's or a mod's);
- it carries code and the game cannot load code (see Code and content mods);
- a package it uses is not in the game, in a mod it builds on, or in itself. A mod cooked against another build of the game is refused, not left to load broken;
- in an uncooked run, its content was saved by a newer engine.
A mod can also be refused after its content is mounted: when the compatibility policy refuses its plugin (see UGantryGameFeaturesProjectPolicies), or when registration fails. Gantry then releases it again. It terminates the plugin and unmounts the content, so none of it stays readable. Every refusal is logged and kept on the mod as its Error (see Get Units).
Collisions
Two mods may provide the same asset, for example when one deliberately replaces another's. A mod may also replace the game's own. Both are allowed, and neither is silent. After every pass, Get Collisions lists them and the log reports them:
- Assets: the same cooked package or the same file. The highest pak order wins. Within a pass that is the mod that builds on the most others; across passes it is the later one. A pak named like a patch mounts above its own mod.
- Experiences: experiences sharing a tag, a guid or an asset name (primary asset id), across mods and the game.
- Config: a key two mods set differently, or a mod sets differently from the game.
- Redirects: a redirect that renames content outside the mod's own content root.
- Data patches: a value (a property or a table row) that Patch Data actions of two game feature plugins change. The one activated last is in force.
A mod's config goes in Config/<Mod><Branch>.ini, for example Config/MyModGame.ini. The engine layers that file over its branch when the mod's game feature activates. Any other ini there, a DefaultGame.ini say, is never applied, and the mod's Notes say so. A container whose index cannot be read, such as an encrypted one, is noted on its mod.
Gantry.Mods.Report prints every mod and collision.
Unmounting
Unmount Mod and Unmount Mods take mods out without a restart (Gantry.Mods.Unmount, with a mod's name or without one for all). Gantry terminates the plugin, unmounts the content and has the experience trees read again. Remount Mods (Gantry.Mods.Remount) unmounts, then mounts what the mod directories hold now.
A mod stays mounted while an experience using it is running, or while another mounted mod builds on it. The reason says which. Objects already loaded from a mod stay in memory until nothing references them.
Turning mods off
Players can turn a mod off without removing it: Set Mod Enabled (Gantry.Mods.Enable and Gantry.Mods.Disable at the console, with the mod's name). The choice is saved per user (see UGantryModPreferences). A mod turned off is listed in Get Units as Disabled and is not mounted, and neither is any mod that builds on it. Turning a mounted mod off unmounts it when it can go, as Unmount Mod does; otherwise it stays until the next start.
Dropping mods in while the game runs
With Mod Rescan Interval above zero, Gantry looks at the mod directories that often. When what they hold changes (a mod folder or .gantry file added or replaced), a pass mounts the newcomers. Their experiences join the tree at the next activation, so a game that lists rounds from the tree offers a dropped-in round at its next lobby. Mods already mounted stay as they are: nothing is unmounted or reloaded.
Code and content mods
Content mods (assets, Blueprints, data) work in every game, Blueprint-only projects included. A mod with code modules needs a game that can load code from a plugin: the editor, or a game built modular from a source engine. A packaged game is normally one monolithic executable. There a code mod is refused, with a reason saying so, before anything of it is mounted.
Making and cooking a mod
In the editor, a new mod without game feature data cannot register yet. Gantry opens its content folder so you can make one (Add > Gantry > Game Feature Data, named like the mod, at the folder's root). Then mount again with Gantry.Mods.Mount.
A mod cooks as DLC against a released build of the game, with the stock BuildCookRun and:
-dlcname=<path to its .uplugin> -basedonreleaseversion=<release>
When the cook runs with -dlcname, Gantry mounts that mod and the mods it builds on before cooking starts, so the cooker can find it. The staged containers then go in the mod's Content/Paks/(platform)/.
- Inherits
UEngineSubsystem
Properties
| Property | Type | Default | Description |
|---|---|---|---|
OnModsMounted | FGantryOnModsMounted | () | Fired when a MountMods pass finishes, successful or not. |
OnModsUnmounted | FGantryOnModsMounted | () | Fired when an UnmountMod or UnmountMods finishes. |
OnModsChanged | FGantryOnModsMounted | () | Fired whenever the mods or their report change: after every pass and unmount, and when turning a failed mod off marks it Disabled. Turning a mounted mod off fires it once the unmount finishes; turning a mod on fires it once the pass it starts finishes. What a mod list (a settings page, a menu) rebuilds on. |
Functions
GetCollisions
What more than one provider contests (assets, experience identities, config, redirects, data patches), as of the last pass or unmount.
| Pin | Type | Direction | Description |
|---|---|---|---|
| Return Value | TArray<FGantryModCollision> | out | The collisions, by kind, then by path. |
GetContentId
A registered mod's content id (FGantryModUnit::ContentId).
| Pin | Type | Direction | Description |
|---|---|---|---|
Name | FString | in | The mod's name, which is its content root. |
| Return Value | FString | out | The id, or empty when no registered mod has that name (the game's own content). |
GetUnits
Every mod any pass has seen, in mount order, with how far each got and why it failed.
| Pin | Type | Direction | Description |
|---|---|---|---|
| Return Value | TArray<FGantryModUnit> | out | The mods. |
IsModEnabled
Whether this player has a mod turned on.
| Pin | Type | Direction | Description |
|---|---|---|---|
Name | FString | in | The mod's name. |
| Return Value | bool | out | False when the player turned it off; true otherwise, found or not. |
IsMounting
Whether a mount pass or an unmount is running.
| Pin | Type | Direction | Description |
|---|---|---|---|
| Return Value | bool | out | True from MountMods or an unmount until OnModsMounted or OnModsUnmounted fires. |
MountMods
Finds, checks, mounts and registers every mod not mounted yet.
Asynchronous: On Mods Mounted fires when done. Ignored, with a warning, while a pass is running.
RemountMods
Unmount Mods, then Mount Mods: picks up changed and removed mods without a restart.
SetModEnabled
Turns a mod on or off for this player, and saves the choice.
Off takes it out now when it can go (as Unmount Mod does; otherwise at the next start); on mounts it (as Mount Mods does), unless a pass is already running, in which case call Mount Mods again once it ends. Either way, the choice stands from the next pass on.
| Pin | Type | Direction | Description |
|---|---|---|---|
Name | FString | in | The mod's name. |
bEnabled | bool | in | On or off. |
OutReason | FString | out | When turning off, why it stays mounted for now; empty otherwise. |
| Return Value | bool | out | False, with the reason, when it was turned off but stays mounted until it can go. |
UnmountMod
Takes one mod out without a restart: terminates its plugin, unmounts its content and has the experience trees read again.
Asynchronous: On Mods Unmounted fires when done.
| Pin | Type | Direction | Description |
|---|---|---|---|
Name | FString | in | The mod's name. |
OutReason | FString | out | Why it stays, when refused. |
| Return Value | bool | out | False, with the reason, when it is not mounted, an experience using it is running, another mounted mod builds on it, or a pass is running. |
UnmountMods
Takes out every mod that can go, the mods that build on others first.
A mod in use stays, with the reason logged. Asynchronous: On Mods Unmounted fires when done.
C++
Public C++ members that Blueprint does not see.
OnModsReported
FSimpleMulticastDelegate
Fired after every pass and unmount, once mods and collisions are current.
Tools read it to show refusals and collisions, as the editor's Gantry message log does. On Mods Changed is the same moment, for Blueprint.
Initialize
virtual void
Arranges the startup mount when the settings ask for one.
Deinitialize
virtual void
Drops the startup binding if it never fired.
GetLeavingUnits
const TSet< FString > &
Mods being released right now (unmounted, or refused after mounting), by name.
LogReport
void
Logs every mod and collision.
What Gantry.Mods.Report prints.
Get
static UGantryModSubsystem *
The engine's instance, or null before the engine exists.