Plan mod dependency support
Pathoschild opened this issue ยท 8 comments
Sometimes mods depend on another mod being loaded first, such as Entoarox Framework or SCCL.
Since SMAPI doesn't handle dependencies, mods use fragile and non-scalable hacks to specify load order (like prefixing the folder name with !
), and missing dependencies cause unfriendly errors like this:
A mod failed handling the GameEvents.LoadContent event:
System.IO.FileNotFoundException: Could not load file or assembly 'EntoaroxFramework, Version=1.6.1.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
File name: 'EntoaroxFramework, Version=1.6.1.0, Culture=neutral, PublicKeyToken=null'
at Entoarox.ShopExpander.ShopExpanderMod.Event_LoadContent(Object sender, EventArgs e)
at StardewModdingAPI.Framework.InternalExtensions.SafelyRaisePlainEvent(IMonitor monitor, String name, IEnumerable`1 handlers, Object sender, EventArgs args) in D:\source_Stardew\SMAPI\src\StardewModdingAPI\Framework\InternalExtensions.cs:line 32
Add support for specifying mod dependencies which are validated and loaded first.
One possible manifest.json
format involves a Dependencies
section where you list the mods that must be loaded first, with an npm-like version range (or "*" for any version):
{
"Name": "SomeMod",
...,
"Dependencies": {
"EntoaroxFramework": ">=1.6.6 <1.7"
}
}
Another possibility is to match Farmhand's undocumented dependency format:
{
"Name": "SomeMod",
...,
"Dependencies": [
{
"UniqueId": "EntoaroxFramework",
"MinimumVersion": "1.6.6",
"MaximumVersion": "1.7",
"IsRequired": true
}
]
}
I would go with the FarmHand like format, simply because that way stuff remains the same once FH comes out.
But keep in mind that this will require that SMAPI verifies the UniqueId field (It currently doesnt really do that) and makes sure that no 2 mods with the same Id are loaded in the first place.
Per discussion on Discord, consider an extra field which lets you specify whether the dependency should be loaded before or after the current mod (default Before
).
{
"Name": "SomeMod",
...,
"Dependencies": [
{
"UniqueID": "EntoaroxFramework",
"LoadOrder": "Before|After",
"IsRequired": true
}
]
}
Per discussion with @Entoarox, consider replacing IsRequired
with a Validate
field having three possible values:
Required
(default)- Enforce dependency.
- Fail conditions: mod isn't installed, OR mod doesn't match
MinVersion
and/orMaxVersion
(if set).
IfInstalled
- Enforce dependency if it's installed.
- Fail conditions: mod doesn't match
MinVersion
and/orMaxVersion
(if set).
IfVersion
- Enforce dependency if it's installed and (if set) matches the
MinVersion
and/orMaxVersion
. If the mod is installed with a different version, the dependency is simply ignored. - Fail conditions: none. (Maybe fail if no version is specified, to avoid confusion?)
- Enforce dependency if it's installed and (if set) matches the
Another thing that would be useful is the ability to have interfaces/methods that are stripped if a mod is not present. (I assume this is possible since some rewriting is done anyways). An attribute could specify a mod ID and an interface; if the mod with the given ID is not present, the interface is stripped. (Similar could apply for methods, except the method would not need to be specified since the attribute would be attached to it.)
The methods stripping isn't necessary in some cases, however if a parameter/return type needs to be of a type from another mod, it would be necessary. (Unless everything is passed as object
I guess?)
This idea is stolen from Minecraft Forge. A couple things I did were integration with energy mods (Interfaces, Methods) and integrating features from other mod armor with my own (Interfaces, Methods).
Then again, this might be overkill for now. It was more useful in MC since we had many large-scale content mods.
@spacechase0 Thanks for the suggestion. It's an interesting idea, but it's a bit complicated since SMAPI would need to remove all references to the other mod before it could even load your mod. That could unlock cleaner integrations though, so it's worth considering for a future version.
Not necessarily. Putting the references directly in a used function won't work, but delegating them to a function that otherwise won't be called will:
using StardewModdingAPI;
// I didn't feel like making a new project just for this test
namespace BreakStardew
{
public class Mod : StardewModdingAPI.Mod
{
public static Mod instance;
public override void Entry(IModHelper helper)
{
if ( helper.ModRegistry.IsLoaded( "spacechase0.StardewValleyMP" ) )
{
func();
}
else
{
Monitor.Log("No MP mod");
}
}
private void func()
{
Monitor.Log("Found MP: " + StardewValleyMP.MultiplayerMod.instance.ModManifest.Version);
}
}
}
With the MP mod (although I had to name the folder zBreakStardew since dependencies don't exist yet):
[09:29:09 DEBUG SMAPI] Mods go here: C:\Users\Chase\Desktop\StardewValley\WORKSPACE\Mods
[09:29:09 DEBUG SMAPI] Preparing SMAPI...
[09:29:09 DEBUG SMAPI] Starting game...
[09:29:19 INFO SMAPI] You configured SMAPI to run in developer mode. The console may be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI, or by editing C:\Users\Chase\Desktop\StardewValley\WORKSPACE\StardewModdingAPI.config.json.
[09:29:19 DEBUG SMAPI] Detecting common issues...
[09:29:19 DEBUG SMAPI] Loading mods...
[09:29:19 TRACE SMAPI] Loading StardewValleyMP.dll...
[09:29:19 INFO SMAPI] Loaded Makeshift Multiplayer by , v0.3.3 | Multiplayer test
[09:29:19 TRACE SMAPI] Loading BreakStardew.dll...
[09:29:19 INFO SMAPI] Loaded Break Stardew by , v1.0 |
[09:29:19 INFO Makeshift Multiplayer] Loading Config
[09:29:19 DEBUG Break Stardew] Found MP: 0.3.3
[09:29:19 DEBUG SMAPI] Loaded 2 mods.
[09:29:19 DEBUG SMAPI] Starting console...
[09:29:19 INFO SMAPI] Type 'help' for help, or 'help <cmd>' for a command's usage
[09:29:19 WARN SMAPI] an unknown mod used GameEvents.LoadContent, which is deprecated since SMAPI 1.10. This will break in a future version of SMAPI.
[09:29:19 INFO Makeshift Multiplayer] Initializing Steam integration...
[09:29:20 ALERT SMAPI] You can update SMAPI from version 1.12-prerelease.1 to 1.12
Without the the MP mod:
[09:21:10 INFO SMAPI] SMAPI 1.12-prerelease.1 with Stardew Valley 1.2.29 on Microsoft Windows 10 Home
[09:21:10 DEBUG SMAPI] Mods go here: C:\Users\Chase\Desktop\StardewValley\WORKSPACE\Mods
[09:21:10 DEBUG SMAPI] Preparing SMAPI...
[09:21:10 DEBUG SMAPI] Starting game...
[09:21:23 INFO SMAPI] You configured SMAPI to run in developer mode. The console may be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI, or by editing C:\Users\Chase\Desktop\StardewValley\WORKSPACE\StardewModdingAPI.config.json.
[09:21:23 DEBUG SMAPI] Detecting common issues...
[09:21:23 DEBUG SMAPI] Loading mods...
[09:21:23 TRACE SMAPI] Loading BreakStardew.dll...
[09:21:23 INFO SMAPI] Loaded Break Stardew by , v1.0 |
[09:21:23 DEBUG Break Stardew] No MP mod
[09:21:23 DEBUG SMAPI] Loaded 1 mods.
[09:21:23 DEBUG SMAPI] Starting console...
[09:21:24 INFO SMAPI] Type 'help' for help, or 'help <cmd>' for a command's usage
[09:21:24 ALERT SMAPI] You can update SMAPI from version 1.12-prerelease.1 to 1.12
I split this into several smaller tickets, which should cover everything we discussed except...
-
The proposal for a
Validate: "Required|IfInstalled|IfVersion"
field. I think this approach is unclear โ for example, shouldIfVersion
error if the mod isn't installed at all? The field would be confusing to explain, which is undesirable for one of the core fields.Instead I'll add
IsRequired: true|false
just like Farmhand, and we can consider a separate field in the future if some mods need finer control over the dependency validation (likeValidate: "IgnoreIfWrongVersion"
). -
The proposal to rewrite mods to remove references to optional dependencies. That's a really cool idea we should consider later, but it's out of scope for the coming releases.