-
Notifications
You must be signed in to change notification settings - Fork 11
Frequently asked questions
Most games that support modding have a specific "mods directory" where mods must be placed - the game will then search through all mod directories under this directory and load mods from them. This could be a path under user://
, so that users can add and remove mods as they wish; eg. user://Mods
.
In addition, official DLCs and patches may be present under a different directory (but not always) - usually to separate them from user-created content and prevent users from simply copying over the DLC files. This could be a path under res://
; eg. res://DLCs
.
As an example, here is how directory paths of all mods under user://Mods
could be found:
using Godot;
string modsDirectory = ProjectSettings.GlobalizePath("user://Mods"); // Convert user://Mods to a native OS path
string[] modDirectories = System.IO.Directory.GetDirectories(modsDirectory); // Get paths of all mod directories under user://Mods
Note that due to the way assembly and XML document loading works in C#, these paths must be expressed as native OS paths. Meaning that Modot's ModLoader
class will not recognise paths beginning with user://
or res://
- they must first be converted to native OS paths using ProjectSettings.GlobalizePath(path)
or something similar, as shown in the example above.
Modot requires that all paths passed to it must be native OS paths. However, Godot's documentation states that paths beginning with res://
may not actually have a native OS equivalent, as the application may be compressed into a .pck
file or present in a "virtual" filesystem. This means that loading mods from any path under res://
is technically impossible.
However, there is a workaround - at runtime, these mods' files can simply be copied over into a corresponding user://
path. Godot's documentation guarantees that user://
paths will always be writable to and have a native OS equivalent. Modot offers some convenient extension methods to copy data from one Godot directory to another, and get the paths of sub-directories and files inside a Godot directory - these are very similar to C#'s own filesystem API.
As an example, here is how all mods under res://Mods
could be loaded:
using Godot;
using Godot.Utility.Extensions;
using Directory directory = new(); // Create a new Godot directory
directory.CopyContents("res://Mods", "user://Mods", true); // Copy all directories and files recursively from res://Mods to user://Mods
string modsDirectory = ProjectSettings.GlobalizePath("user://Mods"); // Convert user://Mods to a native OS path
string[] modDirectories = System.IO.Directory.GetDirectories(modsDirectory); // Get paths of all mod directories under user://Mods
ModLoader.Load(modDirectories); // Load all mods under user://Mods, which now also includes mods from res://Mods
Mods can annotate static methods in their assemblies with the [ModStartup]
attribute to execute their code when they are loaded.
Since these methods must be marked static
, they will not have any access to instance methods or information - meaning they cannot use functions on nodes like GetTree()
. References to the scene tree must be obtained through other ways, such as setting a static property in the main scene before loading mods, or by using Engine.GetMainLoop()
.
For example, here is how the scene tree could be obtained using Engine
:
using Godot;
using Godot.Modding;
[ModStartup]
private static void OnStartup() // This static method exists in a mod's assembly and will be executed when all mods are loaded
{
SceneTree sceneTree = (SceneTree)Engine.GetMainLoop(); // Get a reference to the scene tree
// Do something with the scene tree here...
}
M mod may not be loaded for various reasons - invalid metadata files, missing dependencies, present incompatibilities, etc. In such cases, the mod sorting process begins again - taking into account the fact that there may be other mods that depend on the mod that just failed to load. A message containing details about the issue is also logged to the log file (user://Log.txt
by default).
For example, take three mods A
, B
, and C
, where C
has A
as a dependency. ModLoader
will try to load all three of them, but if A
fails to load, it'll try to load B
and C
. B
will load without any issues, but C
will not be loaded as it has A
as a dependency and A
could not be loaded. Therefore B
is the only mod that will be loaded at the end.
Modot only catches exceptions that arise due to invalid metadata or other data definition-related reasons. It does not attempt to catch exceptions thrown by mod code, so if a static method annotated with [ModStartup]
throws an exception, the application will crash.
Any unhandled exceptions thrown this way will usually (but not always) be logged to the log file (user://Log.txt
by default).