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

From Stardew Valley Wiki
Jump to navigation Jump to search
(→‎Game events: + FirstUpdateTick in SMAPI 2.3)
(ES link)
 
(61 intermediate revisions by 5 users not shown)
Line 1: Line 1:
←[[Modding:Index|Index]]
+
{{../header}}
  
SMAPI simplifies mod development by providing APIs and events you can use, which are documented below. See [[Modding:Creating a SMAPI mod]] for a guide to creating a new SMAPI mod.
+
SMAPI provides a number of APIs for mods to use. Click a section on the right or below for more details.
 
 
==Manifest==
 
Each SMAPI mod has a <tt>manifest.json</tt> file with metadata about it. The mod can read its own metadata using <tt>this.ModManifest</tt>, and read metadata for loaded mods using the [[#Mod registry|mod registry]].
 
 
 
===Basic fields===
 
All mods should specify the following fields.
 
 
 
{| class="wikitable"
 
|-
 
! field
 
! description
 
|-
 
| <tt>Name</tt>
 
| The mod name. SMAPI uses this in player messages, logs, and errors. Example: <source lang="javascript">"Name": "Lookup Anything"</source>
 
|-
 
| <tt>Author</tt>
 
| The name of the person who created the mod. Ideally this should include the username used to publish mods. Example: <source lang="javascript">"Author": "Pathoschild"</source>
 
|-
 
| <tt>Version</tt>
 
| The mod's [http://semver.org/ semantic version]. Make sure you update this for each release! SMAPI uses this for update checks, mod dependencies, and compatibility blacklists (if the mod breaks in a future version of the game). Examples:
 
<source lang="javascript">
 
"Version": "1.0"
 
</source>
 
<source lang="javascript">
 
"Version": "1.0.1-beta.2"
 
</source>
 
|-
 
| <tt>Description</tt>
 
| A short explanation of what your mod does (one or two sentences), shown in the SMAPI log. Example: <source lang="javascript">"Description": "View metadata about anything by pressing a button."</source>
 
|-
 
| <tt>UniqueID</tt>
 
| A unique identifier for your mod. The recommended format is <tt>&lt;your name&gt;.&lt;mod name&gt;</tt>, with no spaces or special characters. SMAPI uses this for update checks, mod dependencies, and compatibility blacklists (if the mod breaks in a future version of the game). When another mod needs to reference this mod, it uses the unique ID. Example: <source lang="javascript">"UniqueID": "Pathoschild.LookupAnything"</source>
 
|-
 
| <tt>EntryDll</tt>
 
| The name of the mod's compiled DLL in the mod folder. Example: <source lang="javascript">"EntryDll": "LookupAnything.dll"</source>
 
|}
 
 
 
===Minimum SMAPI version===
 
The <tt>MinimumApiVersion</tt> fields sets the minimum SMAPI version needed to use this mod. If a player tries to use the mod with an older SMAPI version, they'll see a friendly message saying they need to update SMAPI. This also serves as a proxy for the minimum game version, since SMAPI itself enforces a minimum game version. Example: <source lang="javascript">"MinimumApiVersion": "1.10"</source>
 
 
 
===Dependencies===
 
The <tt>Dependencies</tt> field specifies other mods required to use this mod. If a player tries to use the mod and the listed mods aren't installed, they'll see a friendly message saying they need to install those. Example:
 
<source lang="javascript">
 
"Dependencies": [
 
  {
 
      "UniqueID": "Entoarox.Framework",
 
      "MinimumVersion": "1.7.9" // optional. If specified, older versions won't meet the requirement.
 
  }
 
]
 
</source>
 
 
 
You can mark a dependency as optional. It will be loaded first if it's installed, otherwise it'll be ignored.
 
<source lang="javascript">
 
"Dependencies": [
 
  {
 
      "UniqueID": "Entoarox.Framework",
 
      "IsRequired": false
 
  }
 
]
 
</source>
 
 
 
===Update checks===
 
SMAPI can detect new versions of your mod and alert the user with a link to your mod page. You can enable this by setting the <tt>UpdateKeys</tt> field with one of the following values, which tells SMAPI where to check.
 
 
 
; Chucklefish mod site
 
: Make sure you have a mod release page (with a URL containing <tt>/resources/</tt> instead of <tt>/thread/</tt>) and it has a [http://semver.org/ semantic version], then specify the mod ID (it's in the mod page URL): <source lang="javascript">"UpdateKeys": [ "Chucklefish:4250" ]</source>
 
 
 
; Nexus Mods
 
: Make sure the Nexus mod has a [http://semver.org/ semantic version], then specify the mod ID (it's in the mod page URL): <source lang="javascript">"UpdateKeys": [ "Nexus:541" ]</source>
 
 
 
; GitHub project
 
: Make sure your [https://help.github.com/articles/creating-releases/ GitHub project has at least one release] and the latest release's tag is a [http://semver.org/ semantic version], then specify your GitHub username and project name: <source lang="javascript">"UpdateKeys": [ "GitHub:Pathoschild/LookupAnything" ]</source>
 
 
 
You can specify multiple values, in which case SMAPI will check them all and list the latest version it finds:
 
<source lang="javascript">"UpdateKeys": [ "Chucklefish:4250", "Nexus:541", "GitHub:Pathoschild/LookupAnything" ]</source>
 
 
 
===Anything else===
 
Any other fields will be stored in the <tt>IManifest.ExtraFields</tt> dictionary, which is available through the [[#Mod registry|mod registry]]. Extra fields are ignored by SMAPI, but may be useful for extended metadata intended for other mods.
 
 
 
==Events==
 
SMAPI publishes several C# events that tell you when something happens. For example, if you want to do something after the player loads their save, you can add this to your <tt>Entry</tt> method:
 
 
 
<source lang="c#">
 
SaveEvents.AfterLoad += this.SaveEvents_AfterLoad;
 
</source>
 
 
 
Then declare a method like this. (The <tt>EventArgs e</tt> argument contains any extra data about the event.)
 
 
 
<source lang="c#">
 
/// <summary>The method called after the player loads their save.</summary>
 
/// <param name="sender">The event sender.</param>
 
/// <param name="e">The event arguments.</param>
 
public void SaveEvents_AfterLoad(object sender, EventArgs e)
 
{
 
  this.Monitor.Log("The player loaded their game! This is a good time to do things.");
 
  this.Monitor.Log("Everything in the world is ready to interact with at this point.");
 
}
 
</source>
 
 
 
The available events are documented below.
 
 
 
===Content events===
 
<tt>ContentEvents</tt> are raised when the game loads content from its XNB files or changes locale.
 
{| class="wikitable"
 
|-
 
! event !! summary
 
|-
 
| AfterLocaleChanged || Raised after the content language changes.
 
|}
 
 
 
===Control events===
 
<tt>ControlEvents</tt> are raised when the player uses a controller, keyboard, or mouse. They're raised before the game handles the input, so it's possible to selectively prevent the game from responding to it. (That's beyond the scope of this guide, but it involves overwriting <tt>Game1.oldKBState</tt>, <tt>Game1.oldMouseState</tt>, and <tt>Game1.oldPadState</tt>.)
 
 
 
Most of these events are split into two variants, <tt>XPressed</tt> and <tt>XReleased</tt>. The <tt>Pressed</tt> variant is raised when the player presses the button (holding the button down only triggers the event once), and the <tt>Released</tt> variant is raised when they release it.
 
 
 
{| class="wikitable"
 
|-
 
! event !! summary
 
|-
 
| ControllerButtonPressed<br />ControllerButtonReleased || Raised after the player pressed/released a button on a gamepad or controller. These events aren't raised for trigger buttons.
 
|-
 
| ControllerTriggerPressed<br />ControllerTriggerReleased || Raised after the player pressed/released a trigger button on a gamepad or controller.
 
|-
 
| KeyPressed<br />KeyReleased || Raised after the player pressed/released a keyboard key.
 
|-
 
| KeyboardChanged || Raised after the game's <tt>KeyboardState</tt> changed. That happens when the player presses or releases a key.
 
|-
 
| MouseChanged || Raised after the game's <tt>MouseState</tt> changed. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button.
 
|}
 
 
 
===Game events===
 
<tt>GameEvents</tt> are raised when the game changes state.
 
 
 
{| class="wikitable"
 
|-
 
! event !! summary
 
|-
 
| FirstUpdateTick || '''[SMAPI 2.3+]''' Raised after the game first updates its state. This happens once per game session (unrelated to loading saves).
 
|-
 
| UpdateTick || Raised when the game updates its state (≈60 times per second).
 
|-
 
| SecondUpdateTick || Raised every other tick (≈30 times per second).
 
|-
 
| FourthUpdateTick || Raised every fourth tick (≈15 times per second).
 
|-
 
| EighthUpdateTick || Raised every eighth tick (≈8 times per second).
 
|-
 
| QuarterSecondTick || Raised every 15th tick (≈4 times per second).
 
|-
 
| HalfSecondTick || Raised every 30th tick (≈twice per second).
 
|-
 
| OneSecondTick || Raised every 60th tick (≈once per second).
 
|}
 
 
 
===Graphics events===
 
<tt>GraphicsEvents</tt> are raised during the game's draw loop, when the game is rendering content to the window.
 
  
 +
==Basic APIs==
 
{| class="wikitable"
 
{| class="wikitable"
 
|-
 
|-
! event !! summary
+
! page
 +
! summary
 
|-
 
|-
| OnPreRenderEvent<br />OnPostRenderEvent || Raised before and after drawing the world to the screen.
+
| [[/Manifest|Manifest]]
 +
| A file needed for every mod or content pack which describes the mod, lists dependencies, enables update checks, etc.
 
|-
 
|-
| OnPreRenderGuiEvent<br />OnPostRenderGuiEvent || When a menu is open (<tt>Game1.activeClickableMenu != null</tt>), raised before and after drawing that menu to the screen. This includes the game's internal menus like the title screen.
+
| [[/Events|Events]]
 +
| Respond when something happens in the game (''e.g.,'' when a save is loaded), and often include details about what happened.
 
|-
 
|-
| OnPreRenderHudEvent<br />OnPostRenderHudEvent || Raised before and after drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is called even if a menu is open.)
+
| [[/Config|Configuration]]
 +
| Let players edit a <samp>config.json</samp> file to configure your mod.
 
|-
 
|-
| Resize || Raised after the game window is resized.
+
| [[/Content|Content]]
|}
+
| Load images/maps/data, and edit or replace the game's images/maps/data.
 
 
===Input events===
 
<tt>InputEvents</tt> are raised when the player uses a controller, keyboard, or mouse.
 
<table class="wikitable">
 
<tr>
 
<th>event</th>
 
<th>summary</th>
 
</tr>
 
<tr>
 
<td>ButtonPressed<br />ButtonReleased</td>
 
<td>Raised after the player pressed/released a keyboard, mouse, or controller button. The event arguments include:
 
* the button pressed (as an <tt>SButton</tt> constant);
 
* whether the input counts as a 'click' (including the controller equivalent);
 
* the cursor position on the screen;
 
* the tile under the cursor;
 
* the 'grab tile' (i.e. the tile the game considers to be clicked).
 
 
 
You can also...
 
* get the equivalent XNA button constants using <tt>button.TryGetKeyboard(…)</tt> and <tt>button.TryGetController(…)</tt>;
 
* prevent the game (but not other mods) from handling the input using <tt>eventArgs.SuppressButton()</tt>.</td>
 
</tr>
 
</table>
 
 
 
===Location events===
 
<tt>LocationEvents</tt> are raised when the player transitions between game locations, a location is added or removed, or the objects in the current location change.
 
 
 
{| class="wikitable"
 
 
|-
 
|-
! event !! summary
+
| [[/Data|Data]]
 +
| Store arbitrary data and retrieve it later.
 
|-
 
|-
| CurrentLocationChanged || Raised after the player warps to a new location. Handlers are given the previous and new locations as arguments.
+
| [[/Input|Input]]
 +
| Check and suppress keyboard, controller, and mouse state.
 
|-
 
|-
| LocationObjectsChanged || Raised after the list of objects in the current location changes (e.g. an object is added or removed). Handlers are given the new list of objects as an argument.
+
| [[/Logging|Logging]]
 +
| Write messages to the SMAPI console and log.
 
|-
 
|-
| LocationsChanged || Raised after a game location is added or removed. Handlers are passed the new list of locations as an argument.
+
| [[/Reflection|Reflection]]
|}
+
| Access fields, properties, or methods which are normally inaccessible.
 
 
===Menu events===
 
<tt>MenuEvents</tt> are raised when a game menu is opened or closed (including internal menus like the title screen).
 
 
 
{| class="wikitable"
 
 
|-
 
|-
! event !! summary
+
| [[/Multiplayer|Multiplayer]]
 +
| Provides methods for supporting multiplayer.
 
|-
 
|-
| MenuChanged || Raised after a game menu is opened or replaced with another menu. This event is not invoked when a menu is closed. Handlers are given the previous menu (if any) and new menu (if any).
+
| [[/Translation|Translation]]
 +
| Translate your mod text into any game language.
 
|-
 
|-
| MenuClosed || Raised after a game menu is closed. Handlers are given the previous menu.
+
| [[/Utilities|Utilities]]
 +
| Use constants, contextual information, date logic, and semantic versions.
 
|}
 
|}
  
===Mine events===
+
==Advanced APIs==
<tt>MineEvents</tt> are raised when something happens in [[The Mines]].
 
 
 
 
{| class="wikitable"
 
{| class="wikitable"
 
|-
 
|-
! event !! summary
+
! page
 +
! summary
 
|-
 
|-
| MineLevelChanged || Raised after the player warps to a new level of the mine. Handlers are passed the previous and new mine level as arguments.
+
| [[/Content Packs|Content packs]]
|}
+
| Let other modders provide files for your mod to read, which players can install like any other mod.
 
 
===Player events===
 
<tt>PlayerEvents</tt> are raised when the player data changes.
 
 
 
{| class="wikitable"
 
 
|-
 
|-
! event !! summary
+
| [[/Console|Console commands]]
 +
| Add custom commands to the SMAPI console.
 
|-
 
|-
| InventoryChanged || Raised after the player's inventory changes in any way (added or removed item, sorted, etc).
+
| [[/Integrations|Mod integrations]]
 +
| Get information about loaded mods, and integrate with mods using mod-provided APIs.
 
|-
 
|-
| LeveledUp || Raised after the player levels up a skill. This happens as soon as they level up, not when the game notifies the player after their character goes to bed.
+
| [[/Harmony|Harmony patching]]
 +
| Harmony lets you patch or replace methods, effectively rewriting the game code.
 
|}
 
|}
  
===Save events===
+
[[es:Modding:Guía del Modder/APIs]]
<tt>SaveEvents</tt> are raised when the player saves or loads the game.
+
[[zh:模组:制作指南/APIs]]
 
 
{| class="wikitable"
 
|-
 
! event !! summary
 
|-
 
| AfterLoad || Raised after the player loads a saved game. The world is ready for mods to modify at this point.
 
|-
 
| BeforeSave || Raised before the game updates the save file. (The save won't be written until all mods have finished handling this event.)
 
|-
 
| AfterSave || Raised after the game finishes updating the save file.
 
|-
 
| AfterReturnToTitle || Raised after the player exits to the title screen.
 
|}
 
 
 
===Time events===
 
<tt>TimeEvents</tt> are raised when the in-game date or time changes.
 
 
 
{| class="wikitable"
 
|-
 
! event !! summary
 
|-
 
| AfterDayStarted || Raised after the game begins a new day, including when loading a save.
 
|-
 
| TimeOfDayChanged || Raised after the in-game clock changes.
 
|}
 
 
 
==Mod APIs==
 
===Configuration===
 
You can let users configure your mod through a <tt>config.json</tt> file. SMAPI will automatically create the file and take care of reading, normalising, and updating it.
 
 
 
<dl>
 
<dt>Basic configuration</dt>
 
<dd>Here's the simplest way to use <tt>config.json</tt>:
 
 
 
<ol>
 
<li>Create your model. This is just a class with properties for the config options you want, and it can contain almost anything from a few boolean fields to a complex object graph. (You should try to keep it simple for your users, though.)
 
 
 
You can set defaults directly:
 
 
 
<source lang="c#">
 
class ModConfig
 
{
 
  public bool ExampleBoolean { get; set; } = true;
 
  public float ExampleFloat { get; set; } = 0.5;
 
}
 
</source>
 
 
 
...or with a constructor:
 
 
 
<source lang="c#">
 
class ModConfig
 
{
 
  public bool ExampleBoolean { get; set; }
 
  public float ExampleFloat { get; set; }
 
 
 
  public ModConfig()
 
  {
 
      this.ExampleBoolean = true;
 
      this.ExampleFloat = 0.5;
 
  }
 
}
 
</source></li>
 
 
 
<li>In your <tt>ModEntry::Entry</tt> method, add this line to read the config options:
 
 
 
<source lang="c#">
 
ModConfig config = helper.ReadConfig<ModConfig>();
 
</source>
 
</li>
 
</ol>
 
 
 
That's it! When the player launches the game, SMAPI will create the <tt>config.json</tt> file automatically if it doesn't exist yet, using the default config options you provided in your model. If you need to edit and save the config, you can use <tt>helper.WriteConfig(config)</tt>. You can access the helper in other methods using <tt>this.Helper</tt>.</dd>
 
 
 
<dt>Custom JSON files</dt>
 
<dd>
 
Sometimes one <tt>config.json</tt> isn't enough, or you need to store data that's not meant to be edited by the user. This is pretty easy using the <tt>ModHelper</tt>:
 
 
 
<ol>
 
<li>Create your model (just like the previous section).</li>
 
<li>In your mod code, use <tt>this.Helper</tt> to read and write to a named file:
 
 
 
<source lang="c#">
 
// read file
 
var model = this.Helper.ReadJsonFile<ModData>("data.json") ?? new ModData();
 
 
 
// save file (if needed)
 
this.Helper.WriteJsonFile("data.json", model);
 
</source>
 
 
 
Note that <tt>ReadJsonFile</tt> will return <tt>null</tt> if the file doesn't exist. The above example will create a default instance if that happens; if you don't want to do that, just remove the <code>?? new ModData()</code> part.</li>
 
</ol></dd>
 
 
 
<dt>Per-save JSON files</dt>
 
<dd>
 
You can create per-save files by using the save ID in the name. If you specify a folder path (relative to your mod folder), SMAPI will create the folders automatically if needed.
 
 
 
For example, here's a typical per-save data file:
 
 
 
<source lang="c#">
 
// read file
 
var model = this.Helper.ReadJsonFile<ModData>($"data/{Constants.SaveFolderName}.json") ?? new ModData();
 
 
 
// write file (if needed)
 
this.Helper.WriteJsonFile($"data/{Constants.SaveFolderName}.json", model);
 
</source></dd>
 
</dl>
 
 
 
===Console commands===
 
You can add commands to the SMAPI console (the terminal window that opens alongside the game), and invoke them by typing directly into the console. Note that most players aren't comfortable with a command-line interface, so you should prefer in-game interfaces for player-facing features.
 
 
 
Each console command must have:
 
* a name which the player types to invoke the command.
 
* a description shown when the player uses the <tt>help</tt> command. This should explain what the command does, how to use it, and what arguments it accepts. The example below shows the recommended convention.
 
* the code to run when the command is called.
 
 
 
The code below creates a <tt>player_setmoney</tt> command (but don't forget to validate input, this is just an example).
 
<source lang="C#">
 
public override void Entry(IModHelper helper)
 
{
 
  helper.ConsoleCommands.Add("player_setmoney", "Sets the player's money.\n\nUsage: player_setmoney <value>\n- value: the integer amount.", this.SetMoney);
 
}
 
 
 
/// <summary>Set the player's money when the 'player_setmoney' command is invoked.</summary>
 
/// <param name="command">The name of the command invoked.</param>
 
/// <param name="args">The arguments received by the command. Each word after the command name is a separate argument.</param>
 
private void SetMoney(string command, string[] args)
 
{
 
  Game1.player.money = int.Parse(args[0]);
 
  this.Monitor.Log($"OK, set your money to {args[0]}.");
 
}
 
</source>
 
 
 
Here's how the player would use it:
 
<pre>
 
help player_setmoney
 
> player_setmoney: Sets the player's money.
 
>
 
> Usage: player_setmoney <value>
 
> - value: the integer amount.
 
 
 
player_setmoney 5000
 
> OK, set your money to 5000.
 
</pre>
 
 
 
===Content===
 
====Read assets====
 
You can read custom images or maps to use from your mod folder or game content, with support for <tt>.xnb</tt>, <tt>.png</tt>, and <tt>.tbin</tt> files.
 
 
 
Example usage:
 
<ul>
 
<li>Read an image from your mod folder (from an <tt>assets</tt> subfolder in this example):
 
<source lang="c#">
 
// read an XNB file
 
var texture = helper.Content.Load<Texture2D>(@"assets\texture.xnb", ContentSource.ModFolder);
 
 
 
// read a PNG file
 
var texture = helper.Content.Load<Texture2D>(@"assets\texture.png", ContentSource.ModFolder);
 
</source></li>
 
 
 
<li>Read a map from your mod folder (will automatically fix tilesheet references):
 
<source lang="c#">
 
Map map = helper.Content.Load<Map>(@"assets\map.tbin", ContentSource.ModFolder);
 
</source></li>
 
 
 
<li>Read a new tilesheet for a map from your mod folder:
 
<source lang="c#">
 
tilesheet.ImageSource = helper.Content.GetActualAssetKey(@"assets\tilesheet.png", ContentSource.ModFolder);
 
</source></li>
 
 
 
<li>Read an asset from the game's content folder:
 
<source lang="c#">
 
var data = helper.Content.Load<Dictionary<int, string>>(@"Data\ObjectInformation.xnb", ContentSource.GameContent);
 
</source></li>
 
</ul>
 
 
 
Notes:
 
* Don't call <tt>content.Load<T></tt> in draw code for performance reasons, since drawing happens ≈60 times per second. Instead, load your content ahead of time and reuse it.
 
* When loading a mod file, it's automatically relative to your mod folder. SMAPI won't accept an absolute file path.
 
* Don't worry about which path separators you use; SMAPI will normalise them automatically.
 
 
 
====Edit assets====
 
You can intercept and change any texture or data loaded by the game. (In other words, you can edit XNB data while the game is running without changing the original files.)
 
 
 
In most cases, you can just implement <tt>IAssetEditor</tt> in your <tt>Mod</tt> class to do it. This adds two methods: <tt>CanEdit&lt;T&gt;</tt> returns whether the mod can edit a particular asset, and <tt>Edit&lt;T&gt;</tt> makes any changes needed. For example, this mod lets crops grow in any season (example only, doesn't handle edge cases):
 
<source lang="c#">
 
public class ModEntry : Mod, IAssetEditor
 
{
 
    /// <summary>Get whether this instance can edit the given asset.</summary>
 
    /// <param name="asset">Basic metadata about the asset being loaded.</param>
 
    public bool CanEdit<T>(IAssetInfo asset)
 
    {
 
        return asset.AssetNameEquals(@"Data\Crops");
 
    }
 
 
 
    /// <summary>Edit a matched asset.</summary>
 
    /// <param name="asset">A helper which encapsulates metadata about an asset and enables changes to it.</param>
 
    public void Edit<T>(IAssetData asset)
 
    {
 
        asset
 
            .AsDictionary<int, string>()
 
            .Set((id, data) =>
 
            {
 
                string[] fields = data.Split('/');
 
                fields[1] = "spring summer fall winter";
 
                return string.Join("/", fields);
 
            });
 
    }
 
}
 
</source>
 
 
 
The <tt>IAssetData</tt> method passed into the <tt>Edit&lt;T&gt;</tt> method contains helpers for various changes. For example, this code replaces part of a game image:
 
<source lang="c#">
 
/// <summary>Edit a matched asset.</summary>
 
/// <param name="asset">A helper which encapsulates metadata about an asset and enables changes to it.</param>
 
public void Edit<T>(IAssetData asset)
 
{
 
    Texture2D customTexture = this.Helper.Content.Load<Texture2D>("custom-texture.png", ContentSource.ModFolder);
 
    asset
 
        .AsImage()
 
        .PatchImage(customTexture, targetArea: new Rectangle(300, 100, 200, 200));
 
}
 
</source>
 
 
 
For more advanced scenarios, you can inject multiple <tt>IAssetEditor</tt> instances instead. When you inject a new editor, SMAPI will automatically reload all game assets so it can intercept them. (This is resource-intensive when done outside your <tt>Entry</tt> method, so avoid adding editors unnecessarily.)
 
<source lang="c#">
 
public class ModEntry : Mod
 
{
 
    /// <summary>The mod entry point, called after the mod is first loaded.</summary>
 
    /// <param name="helper">Provides simplified APIs for writing mods.</param>
 
    public override void Entry(IModHelper helper)
 
    {
 
        helper.Content.AssetEditors.Add(new MyCropEditor());
 
    }
 
}
 
</source>
 
 
 
====Inject assets====
 
You can inject new assets for the game to use (e.g. to add dialogue for a custom NPC). '''To edit existing assets, you should use [[#Edit assets|asset editors]] instead.''' If multiple loaders match the same asset, SMAPI will show an error and use none of them.
 
 
 
In most cases, you can just implement <tt>IAssetLoader</tt> in your <tt>Mod</tt> class to do it. This adds two methods: <tt>CanLoad&lt;T&gt;</tt> returns whether the mod can load a particular asset, and <tt>Load&lt;T&gt;</tt> provides the asset data. For example, this code adds a new dialogue file for a custom NPC.
 
<source lang="c#">
 
public class ModEntry : Mod, IAssetLoader
 
{
 
    /// <summary>Get whether this instance can load the initial version of the given asset.</summary>
 
    /// <param name="asset">Basic metadata about the asset being loaded.</param>
 
    public bool CanLoad<T>(IAssetInfo asset)
 
    {
 
        return asset.AssetNameEquals(@"Characters\Dialogue\John");
 
    }
 
 
 
    /// <summary>Load a matched asset.</summary>
 
    /// <param name="asset">Basic metadata about the asset being loaded.</param>
 
    public T Load<T>(IAssetInfo asset)
 
    {
 
        return (T)(object)new Dictionary<string, string> // (T)(object) is a trick to cast anything to T if we know it's compatible
 
        {
 
            ["Introduction"] = "Hi there! My name is Jonathan."
 
        };
 
    }
 
}
 
</source>
 
 
 
For more advanced scenarios, you can inject multiple <tt>IAssetLoader</tt> instances instead. When you inject a new loader, SMAPI will automatically reload all game assets so it can intercept them. (This is resource-intensive when done outside your <tt>Entry</tt> method, so avoid adding loaders unnecessarily.)
 
<source lang="c#">
 
public class ModEntry : Mod
 
{
 
    /// <summary>The mod entry point, called after the mod is first loaded.</summary>
 
    /// <param name="helper">Provides simplified APIs for writing mods.</param>
 
    public override void Entry(IModHelper helper)
 
    {
 
        helper.Content.AssetLoaders.Add(new MyDialogueLoader());
 
    }
 
}
 
</source>
 
 
 
====Reload assets====
 
'''Caution:''' reloading assets is fairly expensive, so use this API judiciously to avoid impacting game performance.
 
 
 
You can reload an asset by invalidating it from the cache. It will be reloaded next time the game requests it (and mods will have another chance to intercept it), and SMAPI will reload it automatically if it's a core asset that's kept in memory by the game. For example, this lets you change what clothes an NPC is wearing (by invalidating their cached sprites or portraits).
 
 
 
Typically you'll invalidate a specific asset key:
 
<source lang="c#">
 
helper.Content.InvalidateCache("Data\ObjectInformation.xnb"); // path separators and capitalisation don't matter
 
</source>
 
 
 
You can also invalidate assets matching a lambda:
 
<source lang="c#">
 
helper.Content.InvalidateCache(asset => asset.DataType == typeof(Texture2D) && asset.AssetNameEquals("Data\ObjectInformation.xnb"));
 
</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 the game's private fields or methods. You can use it from <tt>helper.Reflection</tt> in your entry method, or <tt>this.Helper.Reflection</tt> elsewhere in your entry class. It consists of three methods:
 
 
 
* <tt>GetPrivateValue<TValue>(...)</tt> returns the value of a private field.
 
* <tt>GetPrivateField<TValue>(...)</tt> returns an object you can use to get or set a field's value.
 
* <tt>GetPrivateMethod(...)</tt> returns an object you can use to invoke a method.
 
 
 
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.GetPrivateValue<bool>(pet, "wasPetToday");
 
 
 
// what is the spirit forecast today?
 
string forecast = this.Helper.Reflection
 
  .GetPrivateMethod(new TV(), "getFortuneForecast")
 
  .Invoke<string>();
 
 
 
// randomise the mines
 
if(Game1.currentLocation is MineShaft)
 
  this.Helper.Reflection.GetPrivateField<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 also switch to C#'s underlying reflection API:
 
 
 
<source lang="c#">
 
FieldInfo field = this.Helper.Reflection.GetPrivateField<string>(…).FieldInfo;
 
MethodInfo method = this.Helper.Reflection.GetPrivateMethod(…).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>
 
 
 
===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 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>
 
 
 
==Utilities==
 
SMAPI provides some C# objects you can use to simplify your code.
 
 
 
===Constants===
 
The <tt>Constants</tt> class provides metadata about SMAPI and the game.
 
 
 
{| class="wikitable"
 
|-
 
! value
 
! meaning
 
|-
 
| <tt>Constants.ApiVersion</tt>
 
| The version of the running SMAPI instance.
 
|-
 
| <tt>Constants.MinimumGameVersion</tt><br /><tt>Constants.MaximumGameVersion</tt>
 
| The game versions supported by the running SMAPI instance.
 
|-
 
| <tt>Constants.ExecutionPath</tt>
 
| The absolute path to the <tt>Stardew Valley</tt> folder.
 
|-
 
| <tt>Constants.DataPath</tt>
 
| The absolute path to the game's data folder (which contains the [[Saves|save folder]]).
 
|-
 
| <tt>Constants.LogDir</tt>
 
| The absolute path to the folder containing the game and SMAPI logs.
 
|-
 
| <tt>Constants.SavesPath</tt>
 
| The absolute path to the [[Saves|save folder]].
 
|-
 
| <tt>Constants.CurrentSavePath</tt>
 
| The absolute path to the current save folder, if a save is loaded.
 
|-
 
| <tt>Constants.SaveFolderName</tt>
 
| The name of the current save folder (like <tt>Name_012345789</tt>), if a save is loaded.
 
|}
 
 
 
===Context===
 
The <tt>Context</tt> class provides information about the game state and player control:
 
 
 
{| class="wikitable"
 
|-
 
! value
 
! meaning
 
|-
 
| <tt>Context.IsWorldReady</tt>
 
| The player has loaded a save and the world has finished initialising. Useful for ignoring events before the save is loaded.
 
|-
 
| <tt>Context.IsPlayerFree</tt>
 
| <tt>Context.IsWorldReady</tt> and the player is free to act on the world (no menu is displayed, no cutscene is in progress, etc).
 
|-
 
| <tt>Context.CanPlayerMove</tt>
 
| <tt>Context.IsPlayerFree</tt> and the player is free to move (e.g. not using a tool).
 
|}
 
 
 
===Dates===
 
Use <tt>SDate</tt> for calculating in-game dates. You start by creating a date:
 
<source lang="c#">
 
var date = SDate.Now(); // current date
 
var date = new SDate(28, "spring"); // date in the current year
 
var date = new SDate(28, "spring", 2); // date in the given year
 
</source>
 
 
 
Then you can calculate offsets from any date:
 
<source lang="c#">
 
// add days
 
new SDate(28, "spring", 1).AddDays(370); // 06 fall in year 4
 
 
 
// subtract days
 
new SDate(01, "spring", 2).AddDays(-1); // 28 winter in year 1
 
</source>
 
 
 
...and compare dates:
 
<source lang="c#">
 
var a = new SDate(01, "spring");
 
var b = new SDate(02, "spring");
 
if (a < b) // true
 
  ...
 
</source>
 
 
 
Note that <tt>SDate</tt> won't let you create invalid dates:
 
<source lang="C#">
 
// ArgumentException: Invalid day '30', must be a value from 1 to 28.
 
new SDate(30, "spring");
 
 
 
// ArithmeticException: Adding -1 days to 01 spring Y1 would result in invalid date 28 winter Y0.
 
new SDate(01, "spring", 1).AddDays(-1);
 
</source>
 
 
 
Once created, dates have a few properties you can use:
 
{| class="wikitable"
 
|-
 
! property
 
! meaning
 
|-
 
| <tt>Day</tt>
 
| The day of month.
 
|-
 
| <tt>Season</tt>
 
| The normalised season name.
 
|-
 
| <tt>Year</tt>
 
| The year number.
 
|-
 
| <tt>DayOfWeek</tt>
 
| The day of week (like <tt>Monday</tt>).
 
|-
 
| <tt>DaysSinceStart</tt>
 
| '''[SMAPI 2.2+]''' The number of days since the first day, inclusively (i.e. 01 spring Y1 = 1).
 
|}
 
 
 
===Semantic versions===
 
Use <tt>SemanticVersion</tt> to manipulate and compare versions per the [http://semver.org/ Semantic Versioning 2.0 standard]. Example usage:
 
<source lang="c#">
 
// build version from parts
 
ISemanticVersion version = new SemanticVersion(5, 1, 0, "beta");
 
 
 
// build version from string
 
ISemanticVersion version = new SemanticVersion("5.1.0-beta");
 
 
 
// compare versions (also works with SemanticVersion instances instead of strings)
 
new SemanticVersion("5.2").IsOlderThan("5.10"); // true
 
new SemanticVersion("5.10").IsNewerThan("5.10-beta"); // true
 
new SemanticVersion("5.1").IsBetween("5.0", "5.2"); // true
 
</source>
 
 
 
Note that game versions before 1.2.0 and some mod versions are non-standard (e.g. Stardew Valley 1.11 comes ''before'' 1.2). All SMAPI versions are standard.
 
 
 
[[Category:Modding]]
 

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.