Pathoschild / SMAPI

The modding API for Stardew Valley.
https://smapi.io/
GNU Lesser General Public License v3.0
1.72k stars 259 forks source link

Plan mod dependency support #248

Closed Pathoschild closed 7 years ago

Pathoschild commented 7 years ago

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.

Pathoschild commented 7 years ago

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
    }
  ]
}
Entoarox commented 7 years ago

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.

Pathoschild commented 7 years ago

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
    }
  ]
}
Pathoschild commented 7 years ago

Per discussion with @Entoarox, consider replacing IsRequired with a Validate field having three possible values:

spacechase0 commented 7 years ago

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.

Pathoschild commented 7 years ago

@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.

spacechase0 commented 7 years ago

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
Pathoschild commented 7 years ago

I split this into several smaller tickets, which should cover everything we discussed except...