Changes

16,355 bytes removed ,  14:29, 28 February 2022
ES link
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]].
  −
 
  −
{| class="wikitable"
  −
|-
  −
!colspan="2"| Basic fields
  −
|-
  −
! 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! (The <tt>Build</tt> field is a semantic version label, like <tt>beta.2</tt> in <tt>1.0-beta.2</tt>.) SMAPI uses this for update checks, mod dependencies, and compatibility blacklists (if the mod breaks in a future version of the game). Example:
  −
<source lang="javascript">
  −
"Version": {
  −
  "MajorVersion": 1,
  −
  "MinorVersion": 0,
  −
  "PatchVersion": 0,
  −
  "Build": "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>
  −
|-
  −
!colspan="2"| Optional fields
  −
|-
  −
! field
  −
! description
  −
|-
  −
| <tt>MinimumApiVersion</tt>
  −
| 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>
  −
|-
  −
| <tt>Dependencies</tt>
  −
| '''[SMAPI 1.14+ only]''' The 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"
  −
  }
  −
]
  −
</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
  −
<!--
  −
|-
  −
| AssetLoading || '''[SMAPI 2.0+ only]''' Raised when an XNB file is being read into the cache. Mods can change the data here before it's cached.
  −
-->
  −
|-
  −
| 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
  −
|-
  −
| GameLoaded || Raised when the game is ready and initialised. At this point the game data (like <tt>Game1.objectInformation</tt>) is in memory and ready for use.
  −
|-
  −
| 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.
  −
 
  −
{| class="wikitable"
  −
|-
  −
! event !! summary
  −
|-
  −
| OnPreRenderEvent<br />OnPostRenderEvent || Raised before and after drawing the world to the screen.
  −
|-
  −
| 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.
  −
|-
  −
| 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.)
  −
|-
  −
| Resize || Raised after the game window is resized.
  −
|}
  −
 
  −
===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
  −
|-
  −
| CurrentLocationChanged || Raised after the player warps to a new location. Handlers are given the previous and new locations as arguments.
  −
|-
  −
| 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.
  −
|-
  −
| LocationsChanged || Raised after a game location is added or removed. Handlers are passed the new list of locations as an argument.
  −
|}
  −
 
  −
===Menu events===
  −
<tt>MenuEvents</tt> are raised when a game menu is opened or closed (including internal menus like the title screen).
      +
==Basic APIs==
 
{| class="wikitable"
 
{| class="wikitable"
 
|-
 
|-
! event !! summary
+
! page
|-
+
! summary
| 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).
   
|-
 
|-
| MenuClosed || Raised after a game menu is closed. Handlers are given the previous menu.
+
| [[/Manifest|Manifest]]
|}
+
| A file needed for every mod or content pack which describes the mod, lists dependencies, enables update checks, etc.
 
  −
===Mine events===
  −
<tt>MineEvents</tt> are raised when something happens in [[The Mines]].
  −
 
  −
{| class="wikitable"
   
|-
 
|-
! event !! summary
+
| [[/Events|Events]]
 +
| Respond when something happens in the game (''e.g.,'' when a save is loaded), and often include details about what happened.
 
|-
 
|-
| MineLevelChanged || Raised after the player warps to a new level of the mine. Handlers are passed the previous and new mine level as arguments.
+
| [[/Config|Configuration]]
|}
+
| Let players edit a <samp>config.json</samp> file to configure your mod.
 
  −
===Player events===
  −
<tt>PlayerEvents</tt> are raised when the player data changes.
  −
 
  −
{| class="wikitable"
   
|-
 
|-
! event !! summary
+
| [[/Content|Content]]
 +
| Load images/maps/data, and edit or replace the game's images/maps/data.
 
|-
 
|-
| InventoryChanged || Raised after the player's inventory changes in any way (added or removed item, sorted, etc).
+
| [[/Data|Data]]
 +
| Store arbitrary data and retrieve it later.
 
|-
 
|-
| 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.
+
| [[/Input|Input]]
|}
+
| Check and suppress keyboard, controller, and mouse state.
 
  −
===Save events===
  −
<tt>SaveEvents</tt> are raised when the player saves or loads the game.
  −
 
  −
{| class="wikitable"
   
|-
 
|-
! event !! summary
+
| [[/Logging|Logging]]
 +
| Write messages to the SMAPI console and log.
 
|-
 
|-
| AfterLoad || Raised after the player loads a saved game. The world is ready for mods to modify at this point.
+
| [[/Reflection|Reflection]]
 +
| Access fields, properties, or methods which are normally inaccessible.
 
|-
 
|-
| BeforeSave || Raised before the game updates the save file. (The save won't be written until all mods have finished handling this event.)
+
| [[/Multiplayer|Multiplayer]]
 +
| Provides methods for supporting multiplayer.
 
|-
 
|-
| AfterSave || Raised after the game finishes updating the save file.
+
| [[/Translation|Translation]]
 +
| Translate your mod text into any game language.
 
|-
 
|-
| AfterReturnToTitle || Raised after the player exits to the title screen.
+
| [[/Utilities|Utilities]]
 +
| Use constants, contextual information, date logic, and semantic versions.
 
|}
 
|}
   −
===Time events===
+
==Advanced APIs==
<tt>TimeEvents</tt> are raised when the in-game date or time changes.
  −
 
   
{| class="wikitable"
 
{| class="wikitable"
 
|-
 
|-
! event !! summary
+
! page
|-
+
! summary
| AfterDayStarted || Raised after the game begins a new day, including when loading a save.
   
|-
 
|-
| TimeOfDayChanged || Raised after the in-game clock changes.
+
| [[/Content Packs|Content packs]]
 +
| Let other modders provide files for your mod to read, which players can install like any other mod.
 
|-
 
|-
| DayOfMonthChanged || '''Not recommended; most mods should use <tt>AfterDayStarted</tt> instead.''' Raised when <tt>Game1::dayOfMonth</tt> changes value. This happens before the new day starts. If you exit to title and then reload the same save, it will ''not'' trigger since the value hasn't changed.
+
| [[/Console|Console commands]]
 +
| Add custom commands to the SMAPI console.
 
|-
 
|-
| SeasonOfYearChanged || Raised after the season changes.
+
| [[/Integrations|Mod integrations]]
 +
| Get information about loaded mods, and integrate with mods using mod-provided APIs.
 
|-
 
|-
| YearOfGameChanged || Raised after the year changes.
+
| [[/Harmony|Harmony patching]]
 +
| Harmony lets you patch or replace methods, effectively rewriting the game code.
 
|}
 
|}
   −
==Mod APIs==
+
[[es:Modding:Guía del Modder/APIs]]
===Configuration===
+
[[zh:模组:制作指南/APIs]]
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.
  −
 
  −
====Basic configuration====
  −
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.SaveConfig(config)</tt>. You can access the helper in other methods using <tt>this.Helper</tt>.
  −
 
  −
====Custom JSON files====
  −
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>
  −
 
  −
====Per-save JSON files====
  −
You can also specify a directory path (relative to your mod directory) instead of just the file name. The directories will be created automatically if needed. For example, here's how you'd use per-save config files:
  −
 
  −
<source lang="c#">
  −
// read file
  −
var model = this.Helper.ReadJsonFile<ModData>($"{Constants.SaveFolderName}/config.json") ?? new ModData();
  −
 
  −
// write file (if needed)
  −
this.Helper.WriteJsonFile($"{Constants.SaveFolderName}/config.json", model);
  −
</source>
  −
 
  −
===Content===
  −
If your mod needs custom textures or maps, you can load them with SMAPI's content API. You can load any <tt>.xnb</tt> file the game supports, or load a <tt>.png</tt> file into a texture.
  −
 
  −
Example usage:
  −
<ul>
  −
<li>Load an image from your mod folder (from an <tt>assets</tt> subfolder in this example):
  −
<source lang="c#">
  −
// load an XNB file
  −
var texture = helper.Content.Load<Texture2D>(@"assets\texture.xnb", ContentSource.ModFolder);
  −
 
  −
// load a PNG file
  −
var texture = helper.Content.Load<Texture2D>(@"assets\texture.png", ContentSource.ModFolder);
  −
</source></li>
  −
 
  −
<li>Load an asset from game's content folder:
  −
<source lang="c#">
  −
var data = helper.Content.Load<Dictionary<int, string>>(@"Data\ObjectInformation.xnb", ContentSource.GameContent);
  −
</source></li>
  −
 
  −
<li>Load a custom tilesheet for a map:
  −
<source lang="c#">
  −
tilesheet.ImageSource = helper.Content.GetActualAssetKey(@"assets\tilesheet.png");
  −
</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.
  −
* Don't worry about which path separators you use; SMAPI will normalise them automatically.
  −
 
  −
===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>
  −
 
  −
[[Category:Modding]]
 
232

edits