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

From Stardew Valley Wiki
Jump to navigation Jump to search
(ES link)
 
(20 intermediate revisions by 4 users not shown)
Line 13: Line 13:
 
|-
 
|-
 
| [[/Events|Events]]
 
| [[/Events|Events]]
| Respond when something happens in the game (e.g. when a save is loaded), and often include details about what happened.
+
| Respond when something happens in the game (''e.g.,'' when a save is loaded), and often include details about what happened.
 
|-
 
|-
| [[/Config|Mod configuration]]
+
| [[/Config|Configuration]]
| Let players edit a <tt>config.json</tt> file to configure your mod.
+
| Let players edit a <samp>config.json</samp> file to configure your mod.
 
|-
 
|-
 
| [[/Content|Content]]
 
| [[/Content|Content]]
 
| Load images/maps/data, and edit or replace the game's images/maps/data.
 
| Load images/maps/data, and edit or replace the game's images/maps/data.
 +
|-
 +
| [[/Data|Data]]
 +
| Store arbitrary data and retrieve it later.
 +
|-
 +
| [[/Input|Input]]
 +
| Check and suppress keyboard, controller, and mouse state.
 
|-
 
|-
 
| [[/Logging|Logging]]
 
| [[/Logging|Logging]]
Line 51: Line 57:
 
| [[/Integrations|Mod integrations]]
 
| [[/Integrations|Mod integrations]]
 
| Get information about loaded mods, and integrate with mods using mod-provided APIs.
 
| Get information about loaded mods, and integrate with mods using mod-provided APIs.
 +
|-
 +
| [[/Harmony|Harmony patching]]
 +
| Harmony lets you patch or replace methods, effectively rewriting the game code.
 
|}
 
|}
  
==Mod APIs==
+
[[es:Modding:Guía del Modder/APIs]]
===Content packs===
+
[[zh:模组:制作指南/APIs]]
A content pack is a sub-mod containing files your mod can read. These are installed just like a regular SMAPI mod, but don't do anything on their own. These must specify your mod in their <tt>manifest.json</tt>. See [[Modding:Content packs]] for more info about content packs.
 
 
 
SMAPI provides a method to get content packs loaded for your mod, and each content pack has an API you can use to read its files:
 
<source lang="c#">
 
foreach(IContentPack contentPack in this.Helper.GetContentPacks())
 
{
 
    // read content pack manifest
 
    this.Monitor.Log($"Reading content pack: {contentPack.Manifest.Name} {contentPack.Manifest.Version}");
 
 
 
    // read a JSON file
 
    YourDataFile data = contentPack.ReadJsonFile<YourDataFile>("content.json");
 
 
 
    // load an asset or image
 
    Texture2D image = contentPack.LoadAsset<Texture2D>("image.png");
 
}
 
</source>
 
 
 
===Logging===
 
Your mod can write messages to the console window and log file using the monitor. For example, this code:
 
 
 
<source lang="c#">
 
this.Monitor.Log("a trace message", LogLevel.Trace);
 
this.Monitor.Log("a debug message", LogLevel.Debug);
 
this.Monitor.Log("an info message", LogLevel.Info);
 
this.Monitor.Log("a warning message", LogLevel.Warn);
 
this.Monitor.Log("an error message", LogLevel.Error);
 
</source>
 
 
 
will log something like this:
 
 
 
<div style="font-family: monospace;">
 
<span style="color:#666;">[18:00:00 TRACE Mod Name] a trace message</span><br />
 
<span style="color:#666;">[18:00:00 DEBUG Mod Name] a debug message</span><br />
 
<span style="color:black;">[18:00:00 INFO  Mod Name] an info message</span><br />
 
<span style="color:darkorange;">[18:00:00 WARN  Mod Name] a warning message</span><br />
 
<span style="color:red;">[18:00:00 ERROR Mod Name] an error message</span>
 
</div>
 
 
 
Note that <tt>LogLevel.Trace</tt> messages won't appear in the console window by default, they'll only be written to the log file. Trace messages are for troubleshooting details that are useful when someone sends you their error log, but which the player normally doesn't need to see. (You can see trace messages in the console if you install the "SMAPI for developers" version.)
 
 
 
===Reflection===
 
SMAPI provides an API for robustly accessing fields, properties, or methods you otherwise couldn't access, such as private fields. You can use it from <tt>helper.Reflection</tt> in your entry method, or <tt>this.Helper.Reflection</tt> elsewhere in your entry class.
 
 
 
Here are a few examples of what this lets you do:
 
 
 
<source lang="c#">
 
// did you pet your pet today?
 
bool wasPet = this.Helper.Reflection.GetField<bool>(pet, "wasPetToday").GetValue();
 
 
 
// what is the spirit forecast today?
 
string forecast = this.Helper.Reflection
 
  .GetMethod(new TV(), "getFortuneForecast")
 
  .Invoke<string>();
 
 
 
// randomise the mines
 
if(Game1.currentLocation is MineShaft)
 
  this.Helper.Reflection.GetField<Random>(Game1.currentLocation, "mineRandom").SetValue(new Random());
 
</source>
 
 
 
This works with static or instance fields/methods, caches the reflection to improve performance, and will throw useful errors automatically when reflection fails.
 
 
 
If you need to do more, you can switch to [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection C#'s underlying reflection API]:
 
 
 
<source lang="c#">
 
FieldInfo field = this.Helper.Reflection.GetField<string>(…).FieldInfo;
 
MethodInfo method = this.Helper.Reflection.GetMethod(…).MethodInfo;
 
</source>
 
 
 
===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.)
 
 
 
<source lang="c#">
 
// check if a mod is loaded
 
bool isLoaded = this.Helper.ModRegistry.IsLoaded("UniqueModID");
 
 
 
// get manifest info for a mod (name, description, version, etc.)
 
IManifest manifest = this.Helper.ModRegistry.Get("UniqueModID");
 
 
 
// get manifest info for all loaded mods
 
foreach(IManifest manifest in this.Helper.ModRegistry.GetAll()) { … }
 
</source>
 
 
 
===Multiplayer===
 
The multiplayer API provides methods to support modding in a multiplayer context:
 
<source lang="c#">
 
// get a unique multiplayer ID (e.g. for animal IDs)
 
int uniqueID = this.Helper.Multiplayer.GetNewID();
 
 
 
// get the locations being sync'd from the main player
 
foreach (GameLocation location in this.Helper.Multiplayer.GetActiveLocations())
 
</source>
 
 
 
===Translation===
 
The translation API lets you translate your mod into the player's current language, accounting for locale fallback automatically (e.g. if a translation isn't available in <tt>pt-BR.json</tt>, SMAPI will check <tt>pt.json</tt> and <tt>default.json</tt>). Translations can be a simple string, or they can include tokens to inject values into the translation.
 
 
 
<dl>
 
<dt>File structure</dt>
 
<dd>SMAPI reads translations from JSON files in a structure like this:
 
<pre>
 
YourMod/
 
  i18n/
 
      default.json
 
      es.json
 
      pt.json
 
  manifest.json
 
  YourMod.dll
 
</pre>
 
 
 
The <tt>default.json</tt> file should contain a flat key→value lookup with your default text. Each key is case-insensitive, and can contain alphanumeric, underscore, hyphen, and dot characters. Feel free to add JavaScript comments to organise or document your translations. For example:
 
<source lang="javascript">
 
{
 
    // example translations
 
    "item-type.label": "Item type",
 
    "item-type.fruit-tree": "{{fruitName}} tree",
 
}
 
</source>
 
 
 
You can then add translation files for each language you want to support, by copying the <tt>default.json</tt> file and translating its values. Each translation file should have one of these file names:
 
 
 
{| class="wikitable"
 
|-
 
! Language
 
! File name
 
|-
 
| Chinese
 
| <tt>zh.json</tt>
 
|-
 
| German
 
| <tt>de.json</tt>
 
|-
 
| Japanese
 
| <tt>ja.json</tt>
 
|-
 
| Portuguese
 
| <tt>pt.json</tt>
 
|-
 
| Russian
 
| <tt>ru.json</tt>
 
|-
 
| Spanish
 
| <tt>es.json</tt>
 
|}</dd>
 
 
 
<dt>Reading translations</dt>
 
<dd>Once your i18n files are set up, you can read translations for the current locale:
 
<source lang="c#">
 
// read a simple translation
 
string label = helper.Translation.Get("item-type.label");
 
 
 
// read a translation which uses tokens (accepts an anonymous object, dictionary, or model)
 
string text = helper.Translation.Get("item-type.fruit-tree", new { fruitName = "apple" });
 
</source>
 
 
 
The <tt>helper.Translate(…)</tt> method returns a fluent interface — you can keep calling methods on its return value to customise the translation. (See IntelliSense for a description of the available methods.) To get the text, simply assign it to a string:
 
<source lang="c#">
 
// use fluent chain
 
string text = helper.Translate(key).Tokens(tokens).Tokens(moreTokens).Assert();
 
</source>
 
 
 
If your code has a lot of translation calls, you can make it less verbose by aliasing the translation helper:
 
<source lang="c#">
 
var i18n = helper.Translation;
 
 
 
i18n.Get("item-type.fruit-tree", new { fruitName = i18n.Get("apple") });
 
</source></dd>
 
 
 
<dt>Tips for translators</dt>
 
<dd>
 
* Save i18n files with UTF-8 encoding to avoid broken symbols in-game.
 
* Type <code>reload_i18n</code> into the SMAPI console and hit enter to reload translations without exiting the game. (If a mod internally cached a translation, it may not be updated.)
 
</dd>
 
</dl>
 
 
 
==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:
 
<ol>
 
<li>Add a normal class to your mod project with the methods and properties you want to expose.
 
<source lang="c#">
 
public class YourModApi
 
{
 
    public string ExampleProperty { get; set; } = "Example value";
 
 
 
    public bool GetThing(string example)
 
    {
 
      return true;
 
    }
 
}
 
</source>
 
(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:
 
<source lang="c#">
 
  public override object GetApi()
 
  {
 
      return new YourModApi();
 
  }
 
</source></li>
 
</ol>
 
 
 
That's it! SMAPI will get one instance of your API and cache it.
 
 
 
Notes:
 
* <tt>GetApi</tt> is always called after <tt>Entry</tt>, 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 [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===
 
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>
 
<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.)
 
<source lang="C#">
 
internal interface ISomeModApi
 
{
 
  bool GetThing(string example);
 
}
 
</source></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]]:
 
<source lang="c#">
 
ISomeModApi api = helper.ModRegistry.GetApi<ISomeModApi>("other-mod-ID");
 
if (api != null)
 
  bool result = api.GetThing("boop");
 
</source></li>
 
</ol>
 
 
 
For a quick integration, you can also use reflection instead of creating an interface:
 
<source lang="c#">
 
object api = helper.ModRegistry.GetApi("other-mod-id");
 
if (api != null)
 
  bool result = helper.Reflection.GetMethod(api, "GetThing").Invoke<bool>("boop");
 
</source>
 
 
 
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>GameEvents.FirstUpdateTick</tt> [[#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.
 

Latest revision as of 14:29, 28 February 2022

Creating SMAPI mods SMAPI mascot.png


Modding:Index

SMAPI provides a number of APIs for mods to use. Click a section on the right or below for more details.

Basic APIs

page summary
Manifest A file needed for every mod or content pack which describes the mod, lists dependencies, enables update checks, etc.
Events Respond when something happens in the game (e.g., when a save is loaded), and often include details about what happened.
Configuration Let players edit a config.json file to configure your mod.
Content Load images/maps/data, and edit or replace the game's images/maps/data.
Data Store arbitrary data and retrieve it later.
Input Check and suppress keyboard, controller, and mouse state.
Logging Write messages to the SMAPI console and log.
Reflection Access fields, properties, or methods which are normally inaccessible.
Multiplayer Provides methods for supporting multiplayer.
Translation Translate your mod text into any game language.
Utilities Use constants, contextual information, date logic, and semantic versions.

Advanced APIs

page summary
Content packs Let other modders provide files for your mod to read, which players can install like any other mod.
Console commands Add custom commands to the SMAPI console.
Mod integrations Get information about loaded mods, and integrate with mods using mod-provided APIs.
Harmony patching Harmony lets you patch or replace methods, effectively rewriting the game code.