Difference between revisions of "Modding:Modder Guide/APIs/Integrations"

From Stardew Valley Wiki
Jump to navigation Jump to search
 
(21 intermediate revisions by 6 users not shown)
Line 1: Line 1:
 
{{../../header}}
 
{{../../header}}
  
A ''mod integration'' refers to two mods communicating or cooperating in some way. SMAPI provides three distinct features to support this.
+
A ''mod integration'' refers to two mods communicating or cooperating in some way. SMAPI provides a few features to support this.
  
 
==Dependencies==
 
==Dependencies==
You can define dependencies in your <tt>manifest.json</tt> file, which are other mods that must be loaded before yours. If the dependency is required and missing, SMAPI will show a friendly error to the player. See [[../Manifest|the manifest page]] for more info.
+
You can define dependencies in your <samp>manifest.json</samp> file, which are other mods that must be loaded before yours. If the dependency is required and missing, SMAPI will show a friendly error to the player. See [[../Manifest|the manifest page]] for more info.
  
 
==Mod registry==
 
==Mod registry==
Your mod can get information about loaded mods, or check if a particular mod is loaded. (All mods are loaded by the time your mod's <tt>Entry(…)</tt> method is called.)
+
Your mod can get information about loaded mods, or check if a particular mod is loaded. (All mods are loaded by the time your mod's <samp>Entry(…)</samp> method is called.)
  
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
// check if a mod is loaded
 
// check if a mod is loaded
 
bool isLoaded = this.Helper.ModRegistry.IsLoaded("UniqueModID");
 
bool isLoaded = this.Helper.ModRegistry.IsLoaded("UniqueModID");
Line 20: Line 20:
 
// get info for all loaded mods
 
// get info for all loaded mods
 
foreach(IModInfo mod in this.Helper.ModRegistry.GetAll()) { … }
 
foreach(IModInfo mod in this.Helper.ModRegistry.GetAll()) { … }
</source>
+
</syntaxhighlight>
  
 
==Mod-provided APIs==
 
==Mod-provided APIs==
Line 29: Line 29:
 
<ol>
 
<ol>
 
<li>Add a normal class to your mod project with the methods and properties you want to expose.
 
<li>Add a normal class to your mod project with the methods and properties you want to expose.
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
public class YourModApi
 
public class YourModApi
 
{
 
{
Line 39: Line 39:
 
     }
 
     }
 
}
 
}
</source>
+
</syntaxhighlight>
 
(You can use a [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/constructors constructor] to initialise the API if desired.)</li>
 
(You can use a [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/constructors constructor] to initialise the API if desired.)</li>
<li>Override <tt>GetApi</tt> in your mod's entry class and return an instance of your API:
+
<li>Override <samp>GetApi</samp> in your mod's entry class and return an instance of your API. You can choose between two versions of this method:
<source lang="c#">
+
<ul>
 +
<li>Override <samp>GetApi()</samp> to provide one instance of the API to all mods. This will only be called once, and SMAPI will cache the mod instance.
 +
<syntaxhighlight lang="c#">
 
   public override object GetApi()
 
   public override object GetApi()
 
   {
 
   {
 
       return new YourModApi();
 
       return new YourModApi();
 
   }
 
   }
</source></li>
+
</syntaxhighlight>
 +
</li>
 +
<li>''Or'' Override <samp>GetApi(IModInfo mod)</samp> to provide one instance per mod. This will be called once for each mod that requests an API.
 +
 
 +
Note that this info is provided for informational purposes only (e.g. to log errors). Denying API access to specific mods is strongly discouraged and may be considered abusive.
 +
<syntaxhighlight lang="c#">
 +
  public override object GetApi(IModInfo mod)
 +
  {
 +
      return new YourModApi(mod.Manifest);
 +
  }
 +
</syntaxhighlight>
 +
</li>
 +
</ul>
 +
</li>
 
</ol>
 
</ol>
  
Line 53: Line 68:
  
 
Notes:
 
Notes:
* <tt>GetApi</tt> is always called after <tt>Entry</tt>, so it's safe to pass in your mod's initialised fields.
+
* <samp>GetApi</samp> is always called after <samp>Entry</samp>, so it's safe to pass in your mod's initialised fields.
 
* Be careful when changing your API! If you make a breaking change, other mods may need an update before they can access your API again.
 
* Be careful when changing your API! If you make a breaking change, other mods may need an update before they can access your API again.
 
* You can optionally add a public [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface interface] to your API. If a mod directly references your mod DLL, they'll be able to use your interface instead of creating their own.
 
* You can optionally add a public [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface interface] to your API. If a mod directly references your mod DLL, they'll be able to use your interface instead of creating their own.
* '''Known issue:''' SMAPI doesn't currently support non-public API classes.
 
  
 
===Using an API===
 
===Using an API===
 
You can use a mod-provided API by mapping it to an [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface interface]:
 
You can use a mod-provided API by mapping it to an [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface interface]:
 
<ol>
 
<ol>
<li>Create an interface with only the properties and methods you want to access. (If you directly reference the mod DLL and it provides a public API interface, you can use that instead.)
+
<li>Create an interface with only the properties and methods you want to access. This interface must be public and cannot be an inner interface. (If you directly reference the mod DLL and it provides a public API interface, you can use that instead.)
<source lang="C#">
+
<syntaxhighlight lang="C#">
 
public interface ISomeModApi
 
public interface ISomeModApi
 
{
 
{
 
   bool GetThing(string example);
 
   bool GetThing(string example);
 
}
 
}
</source></li>
+
</syntaxhighlight></li>
<li>In your mod code (after <tt>Entry</tt>), you can get the API by specifying the interface you created in step 1, and the [[#Basic fields|mod's unique ID]]:
+
<li>In your mod code (after <samp>Entry</samp>), you can get the API by specifying the interface you created in step 1, and the [[../Manifest#Basic fields|mod's unique ID]]:
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
ISomeModApi api = helper.ModRegistry.GetApi<ISomeModApi>("other-mod-ID");
 
ISomeModApi api = helper.ModRegistry.GetApi<ISomeModApi>("other-mod-ID");
 
if (api != null)
 
if (api != null)
 
   bool result = api.GetThing("boop");
 
   bool result = api.GetThing("boop");
</source></li>
+
</syntaxhighlight></li>
 
</ol>
 
</ol>
  
 
For a quick integration, you can also use reflection instead of creating an interface:
 
For a quick integration, you can also use reflection instead of creating an interface:
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
object api = helper.ModRegistry.GetApi("other-mod-id");
 
object api = helper.ModRegistry.GetApi("other-mod-id");
 
if (api != null)
 
if (api != null)
 
   bool result = helper.Reflection.GetMethod(api, "GetThing").Invoke<bool>("boop");
 
   bool result = helper.Reflection.GetMethod(api, "GetThing").Invoke<bool>("boop");
</source>
+
</syntaxhighlight>
  
 
Notes:
 
Notes:
* You can't call <tt>GetApi</tt> until all mods are initialised and their <tt>Entry</tt> methods called. You can use the <tt>GameLoop.GameLauncher</tt> [[#Events|event]] if you need to access mod APIs early; this is guaranteed to happen after all mods are initialised.
+
* You can't call <samp>GetApi</samp> until all mods are initialised and their <samp>Entry</samp> methods called. You can use the <samp>GameLoop.GameLaunched</samp> [[#Events|event]] if you need to access mod APIs early; this is guaranteed to happen after all mods are initialised.
* You should always null-check APIs you consume. <tt>GetApi</tt> will return <tt>null</tt> if the API isn't available (e.g. because the mod isn't already installed or doesn't have one), or if an error occurs fetching the API.
+
* You should always null-check APIs you consume. <samp>GetApi</samp> will return <samp>null</samp> if the API isn't available (''e.g.,'' because the mod isn't already installed or doesn't have one). If <samp>GetAPi</smap> cannot map an API, it will throw an exception.
 +
* Keep in mind that mods may change their API interfaces over time; it may be useful to check the version of the other mod before trying to map the interface.
 +
 
 +
===Known limitations===
 +
* When providing an API, the interface and implementation must be public.
 +
 
 +
==Message sending==
 +
You can send messages using [[Modding:Modder Guide/APIs/Multiplayer|the multiplayer API]], with a destination ranging from very narrow (''e.g.,'' one mod on one connected computer) to very broad (all mods on all computers). Messages can also be sent to the current computer, ''e.g.,'' to communicate between two mods. This can be used for a variety of integrations.
  
{{modding guide footer
+
For example:
|prev = [[Modding:Modder Guide/APIs|SMAPI reference]]
+
* Request something from a host mod. (Tractor Mod uses this to summon a tractor to the current player in multiplayer, even if the tractor isn't in a location synced to that player.)
|next =
+
* Notify another mod about something. (Chests Anywhere uses this to notify Automate when a chest's automation options are edited.)
}}
 

Latest revision as of 19:53, 26 November 2022

Creating SMAPI mods SMAPI mascot.png


Modding:Index

A mod integration refers to two mods communicating or cooperating in some way. SMAPI provides a few features to support this.

Dependencies

You can define dependencies in your manifest.json file, which are other mods that must be loaded before yours. If the dependency is required and missing, SMAPI will show a friendly error to the player. See the manifest page for more info.

Mod registry

Your mod can get information about loaded mods, or check if a particular mod is loaded. (All mods are loaded by the time your mod's Entry(…) method is called.)

// check if a mod is loaded
bool isLoaded = this.Helper.ModRegistry.IsLoaded("UniqueModID");

// get info for a mod
IModInfo mod = this.Helper.ModRegistry.Get("UniqueModID");
bool isContentPack = mod.IsContentPack;
IManifest manifest = mod.Manifest; // name, description, version, etc

// get info for all loaded mods
foreach(IModInfo mod in this.Helper.ModRegistry.GetAll()) {  }

Mod-provided APIs

Mods can provide their own APIs to other mods, even without a dependency or assembly reference. This can be used to integrate mods, provide custom information, or provide new framework APIs beyond those offered by SMAPI itself.

Providing an API

To provide an API for other mods to use:

  1. Add a normal class to your mod project with the methods and properties you want to expose.
    public class YourModApi
    {
        public string ExampleProperty { get; set; } = "Example value";
    
        public bool GetThing(string example)
        {
           return true;
        }
    }
    
    (You can use a constructor to initialise the API if desired.)
  2. Override GetApi in your mod's entry class and return an instance of your API. You can choose between two versions of this method:
    • Override GetApi() to provide one instance of the API to all mods. This will only be called once, and SMAPI will cache the mod instance.
         public override object GetApi()
         {
            return new YourModApi();
         }
      
    • Or Override GetApi(IModInfo mod) to provide one instance per mod. This will be called once for each mod that requests an API. Note that this info is provided for informational purposes only (e.g. to log errors). Denying API access to specific mods is strongly discouraged and may be considered abusive.
         public override object GetApi(IModInfo mod)
         {
            return new YourModApi(mod.Manifest);
         }
      

That's it! SMAPI will get one instance of your API and cache it.

Notes:

  • GetApi is always called after Entry, so it's safe to pass in your mod's initialised fields.
  • Be careful when changing your API! If you make a breaking change, other mods may need an update before they can access your API again.
  • You can optionally add a public interface to your API. If a mod directly references your mod DLL, they'll be able to use your interface instead of creating their own.

Using an API

You can use a mod-provided API by mapping it to an interface:

  1. Create an interface with only the properties and methods you want to access. This interface must be public and cannot be an inner interface. (If you directly reference the mod DLL and it provides a public API interface, you can use that instead.)
    public interface ISomeModApi
    {
       bool GetThing(string example);
    }
    
  2. In your mod code (after Entry), you can get the API by specifying the interface you created in step 1, and the mod's unique ID:
    ISomeModApi api = helper.ModRegistry.GetApi<ISomeModApi>("other-mod-ID");
    if (api != null)
       bool result = api.GetThing("boop");
    

For a quick integration, you can also use reflection instead of creating an interface:

object api = helper.ModRegistry.GetApi("other-mod-id");
if (api != null)
   bool result = helper.Reflection.GetMethod(api, "GetThing").Invoke<bool>("boop");

Notes:

  • You can't call GetApi until all mods are initialised and their Entry methods called. You can use the GameLoop.GameLaunched event if you need to access mod APIs early; this is guaranteed to happen after all mods are initialised.
  • You should always null-check APIs you consume. GetApi will return null if the API isn't available (e.g., because the mod isn't already installed or doesn't have one). If GetAPi</smap> cannot map an API, it will throw an exception.
  • Keep in mind that mods may change their API interfaces over time; it may be useful to check the version of the other mod before trying to map the interface.

Known limitations

  • When providing an API, the interface and implementation must be public.

Message sending

You can send messages using the multiplayer API, with a destination ranging from very narrow (e.g., one mod on one connected computer) to very broad (all mods on all computers). Messages can also be sent to the current computer, e.g., to communicate between two mods. This can be used for a variety of integrations.

For example:

  • Request something from a host mod. (Tractor Mod uses this to summon a tractor to the current player in multiplayer, even if the tractor isn't in a location synced to that player.)
  • Notify another mod about something. (Chests Anywhere uses this to notify Automate when a chest's automation options are edited.)