Difference between revisions of "Modding:Modder Guide/Tutorial Mod"

From Stardew Valley Wiki
Jump to navigation Jump to search
(copy from canimod.com with permission, dual-licensed CC BY-NC-SA for compatibility with wiki. Main author is Pathoschild, with contributions from Kithi, JohnsonNicholas, and Tondorian.)
 
(wikify)
Line 1: Line 1:
<pre>
+
←[[Modding:Index|Index]]
---
 
layout: default
 
title: Creating a SMAPI mod
 
intro: >
 
  Ready to make your own mod? This page will help you create your first mod and use the
 
  available APIs and events.
 
permalink: /for-devs/creating-a-smapi-mod
 
redirect_from:
 
    - /guides/creating-a-smapi-mod
 
---
 
 
 
## Quick start
 
The rest of this page will help you create a mod. If you're experienced enough to skip the tutorial,
 
here's a quick summary of what this page will walk you through:
 
 
 
1. Create an empty C# class library project.
 
2. Target .NET Framework 4.5 (for Linux compatibility).
 
3. Reference the [`Pathoschild.Stardew.ModBuildConfig` NuGet package](https://github.com/Pathoschild/Stardew.ModBuildConfig)
 
  to automatically add the right references depending on the platform the mod is being compiled on.
 
4. Create an entry class which subclasses `StardewModdingAPI.Mod`.
 
5. Override the `Entry` method, and write your code using the [SMAPI events and APIs](#mod-apis).
 
6. Create a [`manifest.json` file](#add-your-manifest) which describes your mod for SMAPI.
 
6. Create [a zip file containing the mod files](#share-your-mod) for release.
 
  
## Intro
+
Ready to make your own mod? This page will help you create your first SMAPI mod and use the available APIs and events.
  
<dl>
+
==Quick start==
<dt>What is SMAPI?</dt>
+
The rest of this page will help you create a mod. If you're experienced enough to skip the tutorial, here's a quick summary of what this page will walk you through:
<dd>
 
  <p>A SMAPI mod uses the <a href="https://github.com/Pathoschild/SMAPI">SMAPI</a> modding API to
 
  extend the game logic. You can run code when something happens (e.g. mouse clicked or menu
 
  opened), or periodically (e.g. once per game tick).</p>
 
 
 
  <p>SMAPI mods are written in C# using the .NET Framework. Stardew Valley also uses XNA (on
 
  Windows) or MonoGame (on Linux and Mac) for the fundamental game logic (drawing to the screen,
 
  user input, etc).</p>
 
</dd>
 
  
<dt>Can I make a mod?</dt>
+
# Create an empty C# class library project.
<dd>
+
# Target .NET Framework 4.5 (for Linux compatibility).
  <ul>
+
# Reference the [https://github.com/Pathoschild/Stardew.ModBuildConfig <tt>Pathoschild.Stardew.ModBuildConfig</tt> NuGet package] to automatically add the right references depending on the platform the mod is being compiled on.
      <li>
+
# Create an entry class which subclasses <tt>StardewModdingAPI.Mod</tt>.
        <p><strong>Scenario A: you're new to programming.</strong><br />
+
# Override the <tt>Entry</tt> method, and write your code using the [[#Mod APIs|SMAPI events and APIs]].
        Many mod developers start with little or no programming experience. You can certainly learn
+
# Create a [[#Add your manifest|<tt>manifest.json</tt> file]] which describes your mod for SMAPI.
        along the way if you're determined, but you should be prepared for a steep learning curve.
+
# Create [[#Share your mod|a zip file containing the mod files]] for release.
        Don't be too ambitious at first; it's better to start with a small mod when you're figuring
 
        it out. It's easy to become overwhelmed at first and give up. The modding community is very
 
        welcoming, so don't be afraid to ask questions!</p>
 
       
 
        <p>Since mods are written in C#, it's a good idea to get acquainted with it first.
 
        <a href="https://mva.microsoft.com/en-us/training-courses/c-fundamentals-for-absolute-beginners-16169"><em>C# Fundamentals for Absolute Beginners</em></a>
 
        will walk you through the basics of C# needed to write SMAPI mods, from the basic concepts to
 
        event-driven programming (which is what SMAPI mods use).</p>
 
      </li>
 
      <li>
 
        <strong>Scenario B: you already have programming experience.</strong><br />
 
        You should be fine. Programming experience in C# or Java will make things easier, but it
 
        isn't critical. If you're unfamiliar with C#, you can skim through
 
        <a href="https://mva.microsoft.com/en-us/training-courses/c-fundamentals-for-absolute-beginners-16169"><em>C# Fundamentals for Absolute Beginners</em></a>
 
        to fill in any gaps.
 
      </li>
 
  </ul>
 
 
 
  The next few sections will walk you through creating a very simple mod. If you follow along,
 
  you'll have created a mod! All that will be left is making it do what you want. :)
 
</dd>
 
  
<dt>What do I need?</dt>
+
==Intro==
<dd>
+
* '''What is SMAPI?'''
  Before you start:
+
*: A SMAPI mod uses the [https://github.com/Pathoschild/SMAPI SMAPI] modding API to extend the game logic. You can run code when something happens (e.g. mouse clicked or menu opened), or periodically (e.g. once per game tick). SMAPI mods are written in C# using the .NET Framework. Stardew Valley also uses XNA (on Windows) or MonoGame (on Linux and Mac) for the fundamental game logic (drawing to the screen, user input, etc).
  <ul>
 
      <li>You should read the <a href="/for-players/intro"><em>intro to using mods</em></a> to
 
      learn the basic concepts and install SMAPI.</li>
 
      <li>You should install:<ul>
 
        <li>Stardew Valley;</li>
 
        <li>SMAPI;</li>
 
        <li>and <a href="https://www.visualstudio.com/vs/community/">Visual Studio 2017 Community</a> (on Windows)
 
        or <a href="http://www.monodevelop.com/">MonoDevelop</a> (on Linux/Mac).</li>
 
      </ul></li>
 
      <li>If you're not familiar with Visual Studio 2017 (on Windows) or MonoDevelop (on Linux/Mac),
 
      the <a href="creating-a-smapi-mod-ide-primer"><em>IDE primer</em> subguide</a> explains how
 
      to do the important stuff you need for this guide.</li>
 
  </ul>
 
</dd>
 
  
<dt id="help">Where can I get help?</dt>
+
* '''Can I make a mod?'''
<dd>
+
** <p>'''Scenario A: you're new to programming.'''<br />Many mod developers start with little or no programming experience. You can certainly learn along the way if you're determined, but you should be prepared for a steep learning curve. Don't be too ambitious at first; it's better to start with a small mod when you're figuring it out. It's easy to become overwhelmed at first and give up. The modding community is very welcoming, so don't be afraid to ask questions!</p><p>Since mods are written in C#, it's a good idea to get acquainted with it first. [https://mva.microsoft.com/en-us/training-courses/c-fundamentals-for-absolute-beginners-16169 ''C# Fundamentals for Absolute Beginners''] will walk you through the basics of C# needed to write SMAPI mods, from the basic concepts to event-driven programming (which is what SMAPI mods use).</p>
  The Stardew Valley modding community is very welcoming. Feel free to
+
** '''Scenario B: you already have programming experience.'''<br />You should be fine. Programming experience in C# or Java will make things easier, but it isn't critical. If you're unfamiliar with C#, you can skim through [https://mva.microsoft.com/en-us/training-courses/c-fundamentals-for-absolute-beginners-16169 ''C# Fundamentals for Absolute Beginners''] to fill in any gaps.
  <a href="https://discord.gg/kH55QXP">come chat on Discord</a> or
+
*: The next few sections will walk you through creating a very simple mod. If you follow along, you'll have created a mod! All that will be left is making it do what you want. :)
  <a href="http://community.playstarbound.com/forums/mods.215/">post in the forums</a>.
 
</dd>
 
</dl>
 
  
## Create a mod
+
* '''What do I need?'''
A SMAPI mod is a compiled library (DLL) with an entry method that gets called by SMAPI, so let's
+
*: Before you start:
set that up.
+
*:# You should read the [http://canimod.com intro for players] to learn the basic concepts and install SMAPI.
 +
*:# You should install:
 +
*:## Stardew Valley;
 +
*:## SMAPI;
 +
*:## and [https://www.visualstudio.com/vs/community/ Visual Studio 2017 Community] (on Windows) or [http://www.monodevelop.com/ MonoDevelop] (on Linux/Mac).
 +
*:# If you're not familiar with Visual Studio 2017 (on Windows) or MonoDevelop (on Linux/Mac), the [[Modding:IDE primer|IDE primer]] explains how to do the important stuff you need for this guide.
  
### Create the project
+
* '''<span id="help">Where can I get help?</span>'''
 +
*: The Stardew Valley modding community is very welcoming. Feel free to [https://discord.gg/kH55QXP come chat on Discord] or [http://community.playstarbound.com/forums/mods.215/ post in the forums].
  
1. Open Visual Studio 2017 or MonoDevelop.
+
==Create a mod==
2. Create a solution with a C# class library project (see [how to](creating-a-smapi-mod-ide-primer#create-project)).
+
A SMAPI mod is a compiled library (DLL) with an entry method that gets called by SMAPI, so let's set that up.
3. Change the target framework to .NET Framework 4.5 for compatibility with Linux (see [how to](creating-a-smapi-mod-ide-primer#set-target-framework)).
 
3. Delete the `Class1.cs` or `MyClass.cs` file (see [how to](creating-a-smapi-mod-ide-primer#delete-file)).
 
  
### Configure the build
+
===Create the project===
 +
# Open Visual Studio 2017 or MonoDevelop.
 +
# Create a solution with a C# class library project (see [[Modding:IDE primer#create project|how to]]).
 +
# Change the target framework to .NET Framework 4.5 for compatibility with Linux (see [[Modding:IDE primer#set target framework|how to]]).
 +
# Delete the <tt>Class1.cs</tt> or <tt>MyClass.cs</tt> file (see [[Modding:IDE primer#delete file|how to]]).
  
1. Reference the [`Pathoschild.Stardew.ModBuildConfig` NuGet package](https://github.com/Pathoschild/Stardew.ModBuildConfig)
+
===Configure the build===
  (see [how to](creating-a-smapi-mod-ide-primer#add-nuget)).
+
# Reference the [https://github.com/Pathoschild/Stardew.ModBuildConfig <tt>Pathoschild.Stardew.ModBuildConfig</tt> NuGet package] (see [[Modding:IDE primer#add nuget|how to]]). This will automatically configure your project to load the right modding dependencies for the current platform, so your mod can be built on Linux, Mac, or Windows. It also adds support for debugging the mod in-game.
  This will automatically configure your project to load the right modding dependencies for the
+
# ''(optional)'' See ''[https://github.com/Pathoschild/Stardew.ModBuildConfig#simplify-mod-development simplify mod development]'' to automatically package your mod into your mod folder and enable debugging while the game is running.
  current platform, so your mod can be built on Linux, Mac, or Windows. It also adds support for
 
  debugging the mod in-game.
 
2. _(optional)_ See _[simplify mod development](https://github.com/Pathoschild/Stardew.ModBuildConfig#simplify-mod-development)_
 
  to automatically package your mod into your mod folder and enable debugging while the game is running.
 
  
That's it! Try building the project and make sure you don't get any errors. If you get an error
+
That's it! Try building the project and make sure you don't get any errors. If you get an error like "failed to find the game install path", see [https://github.com/Pathoschild/Stardew.ModBuildConfig#troubleshoot the package's ''Troubleshooting'' section].
like "failed to find the game install path", see [the package's _Troubleshooting_ section](https://github.com/Pathoschild/Stardew.ModBuildConfig#troubleshoot).
 
  
### Add your manifest
+
===Add your manifest===
 
The mod manifest tells SMAPI about your mod.
 
The mod manifest tells SMAPI about your mod.
  
1. Add a file named `manifest.json` to your project.
+
<ol>
2. Paste this code into the file (replacing the `<...>` placeholders):
+
<li>Add a file named <tt>manifest.json</tt> to your project.</li>
 +
<li>Paste this code into the file (replacing the <tt>&lt;...&gt;</tt> placeholders):
 +
<pre lang="json">
 +
{
 +
  "Name": "<your project name>",
 +
  "Author": "<your name>",
 +
  "Version": {
 +
      "MajorVersion": 1,
 +
      "MinorVersion": 0,
 +
      "PatchVersion": 0,
 +
      "Build": null
 +
  },
 +
  "Description": "<One or two sentences about the mod>",
 +
  "UniqueID": "<your name>.<your project name>",
 +
  "EntryDll": "<your project name>.dll"
 +
}
 +
</pre></li>
 +
</ol>
  
  ```json
+
This will be listed in the console output when the game is launching. (For more info about the version number, see [http://semver.org/ semantic versioning].)
  {
 
      "Name": "<your project name>",
 
      "Author": "<your name>",
 
      "Version": {
 
        "MajorVersion": 1,
 
        "MinorVersion": 0,
 
        "PatchVersion": 0,
 
        "Build": null
 
      },
 
      "Description": "<One or two sentences about the mod>",
 
      "UniqueID": "<your name>.<your project name>",
 
      "EntryDll": "<your project name>.dll"
 
  }
 
  ```
 
  This will be listed in the console output when the game is launching. (For more info about the
 
  version number, see [semantic versioning](http://semver.org/).)
 
  
### Write the code
+
===Write the code===
 
Almost done! Now for the code SMAPI will run.
 
Almost done! Now for the code SMAPI will run.
  
1. Add a C# class file called `ModEntry.cs` to your project.
+
<ol>
2. Put this code in the file (replace `<your project name>` with the name of your project):
+
<li>Add a C# class file called <tt>ModEntry.cs</tt> to your project.</li>
 
+
<li>Put this code in the file (replace <tt>&lt;your project name&gt;</tt> with the name of your project):
  ```c#
+
<pre lang="c#">
  using System;
+
using System;
  using Microsoft.Xna.Framework;
+
using Microsoft.Xna.Framework;
  using StardewModdingAPI;
+
using StardewModdingAPI;
  using StardewModdingAPI.Events;
+
using StardewModdingAPI.Events;
  using StardewValley;
+
using StardewValley;
 +
namespace <your project name>
 +
{
 +
    /// <summary>The mod entry point.</summary>
 +
    public class ModEntry : Mod
 +
    {
 +
        /*********
 +
        ** Public methods
 +
        *********/
 +
        /// <summary>Initialise the mod.</summary>
 +
        /// <param name="helper">Provides methods for interacting with the mod directory, such as read/writing a config file or custom JSON files.</param>
 +
        public override void Entry(IModHelper helper)
 +
        {
 +
            ControlEvents.KeyPressed += this.ReceiveKeyPress;
 +
        }
 +
        /*********
 +
        ** Private methods
 +
        *********/
 +
        /// <summary>The method invoked when the player presses a keyboard button.</summary>
 +
        /// <param name="sender">The event sender.</param>
 +
        /// <param name="e">The event data.</param>
 +
        private void ReceiveKeyPress(object sender, EventArgsKeyPressed e)
 +
        {
 +
            this.Monitor.Log($"Player pressed {e.KeyPressed}.");
 +
        }
 +
    }
 +
}
 +
</pre></li>
 +
</ol>
  
  namespace <your project name>
+
===Try your mod===
  {
+
# Build the project.
      /// <summary>The mod entry point.</summary>
+
# Copy your mod into your game's <tt>Mods</tt> folder (only if you didn't do step 2 of ''[[#Configure the build|configure the build]]'').
      public class ModEntry : Mod
+
# In the game's <tt>Mods</tt> directory, add a folder with your mod's name.
      {
+
# Copy your <tt>manifest.json</tt> and compiled files (see [[Modding:IDE primer#build output|how to find them]]) into the folder you created.
          /*********
+
# Run the game through SMAPI.
          ** Public methods
 
          *********/
 
          /// <summary>Initialise the mod.</summary>
 
          /// <param name="helper">Provides methods for interacting with the mod directory, such as read/writing a config file or custom JSON files.</param>
 
          public override void Entry(IModHelper helper)
 
          {
 
              ControlEvents.KeyPressed += this.ReceiveKeyPress;
 
          }
 
  
 +
The mod so far will just send a message to the console window whenever you press a key in the game. If that didn't work, something went wrong. Try reviewing the above instructions, or [[#help|ask for help]]. :)
  
          /*********
+
==Mod APIs==
          ** Private methods
 
          *********/
 
          /// <summary>The method invoked when the player presses a keyboard button.</summary>
 
          /// <param name="sender">The event sender.</param>
 
          /// <param name="e">The event data.</param>
 
          private void ReceiveKeyPress(object sender, EventArgsKeyPressed e)
 
          {
 
              this.Monitor.Log($"Player pressed {e.KeyPressed}.");
 
          }
 
      }
 
  }
 
  ```
 
 
 
### Try your mod
 
 
 
1. Build the project.
 
2. Copy your mod into your game's `Mods` folder (only if you didn't do step 2 of _[configure the build](#configure-the-build)_).
 
  1. In the game's `Mods` directory, add a folder with your mod's name.
 
  2. Copy your `manifest.json` and compiled files (see [how to find them](creating-a-smapi-mod-ide-primer#build-output))
 
      into the folder you created.
 
3. Run the game through SMAPI.
 
 
 
The mod so far will just send a message to the console window whenever you press a key in the game:
 
 
 
> ![example log output](images/creating-a-smapi-mod/keypress-log.png)
 
 
 
If that didn't work, something went wrong. Try reviewing the above instructions, or
 
[ask for help](#help). :)
 
 
 
## Mod APIs
 
 
Now that you have a basic mod, here are the SMAPI features you can use to do more.
 
Now that you have a basic mod, here are the SMAPI features you can use to do more.
  
### Events
+
===Events===
<span id="available-events"></span>
+
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:
  
SMAPI publishes several C# events that tell you when something happens. For example, if you want
+
<pre lang="c#">
to do something after the player loads their save, you can add this to your `Entry` method:
 
 
 
```c#
 
 
SaveEvents.AfterLoad += this.ReceiveAfterLoad;
 
SaveEvents.AfterLoad += this.ReceiveAfterLoad;
```
+
</pre>
  
Then declare a method like this. (The `EventArgs e` argument will often provide more details about
+
Then declare a method like this. (The <tt>EventArgs e</tt> argument will often provide more details about what happened, if there are any.)
what happened, if there are any.)
 
  
```c#
+
<pre lang="c#">
 
/// <summmary>The event handler called after the player loads their save.</summary>
 
/// <summmary>The event handler called after the player loads their save.</summary>
 
/// <param name="sender">The event sender.</param>
 
/// <param name="sender">The event sender.</param>
Line 225: Line 146:
 
   this.Monitor.Log("Everything in the world is ready to interact with at this point.");
 
   this.Monitor.Log("Everything in the world is ready to interact with at this point.");
 
}
 
}
```
+
</pre>
  
 
Here are the available events:
 
Here are the available events:
 +
<ul>
 +
<li id="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.
 +
|}
 +
</li>
  
* <span id="content-events"></span>
+
<li id="control-events">
  **`ContentEvents`** are raised when the game loads content from its XNB files or changes locale.
+
'''<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>.)
  
  | event | summary |
+
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.
  |:----- |:------- |
 
  | 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.
 
  
* <span id="control-events"></span>
+
{| class="wikitable"
  **`ControlEvents`** 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
+
! event !! summary
  responding to it. (That's beyond the scope of this guide, but it involves overwriting
+
|-
  `Game1.oldKBState`, `Game1.oldMouseState`, and `Game1.oldPadState`.)
+
| 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.
 +
|}
 +
</li>
  
  Most of these events are split into two variants, `XPressed` and `XReleased`. The `Pressed`
+
<li id="game-events">
  variant is raised when the player presses the button (holding the button down only triggers the
+
'''<tt>GameEvents</tt>''' are raised when the game changes state.
  event once), and the `Released` variant is raised when they release it.
 
  
  | event | summary |
+
{| class="wikitable"
  |:----- |:------- |
+
|-
  | ControllerButtonPressed<br />ControllerButtonReleased | Raised after the player pressed/released a button on a gamepad or controller. These events aren't raised for trigger buttons. |
+
! event !! summary
  | 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. |
+
| 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.
  | KeyboardChanged | Raised after the game's `KeyboardState` changed. That happens when the player presses or releases a key. |
+
|-
  | MouseChanged | Raised after the game's `MouseState` changed. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button. |
+
| 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).
 +
|}
 +
</li>
  
* <span id="game-events"></span>
+
<li id="graphics-events">
  **`GameEvents`** are raised when the game changes state.
+
'''<tt>GraphicsEvents</tt>''' are raised during the game's draw loop, when the game is rendering content to the window.
  
  | event | summary |
+
{| class="wikitable"
  |:----- |:------- |
+
|-
  | Initialize | Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called from [XNA's `Game.Initialize` method](https://msdn.microsoft.com/en-us/library/microsoft.xna.framework.game.initialize.aspx). |
+
! event !! summary
  | LoadContent | Raised before XNA loads or reloads graphics resources. Called from [XNA's `Game.LoadContent` method](https://msdn.microsoft.com/en-us/library/microsoft.xna.framework.game.loadcontent.aspx).
+
|-
  | GameLoaded | Raised when the game is ready and initialised. At this point the game data (like `Game1.objectInformation`) is in memory and ready for use. |
+
| OnPreRenderEvent<br />OnPostRenderEvent || Raised before and after drawing the world to the screen.
  | UpdateTick | Raised when the game updates its state (≈60 times per second). |
+
|-
  | SecondUpdateTick | Raised every other tick (≈30 times per second). |
+
| 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.
  | FourthUpdateTick | Raised every fourth tick (≈15 times per second). |
+
|-
  | EighthUpdateTick | Raised every eighth tick (≈8 times per second). |
+
| 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.)
  | QuarterSecondTick | Raised every 15th tick (≈4 times per second). |
+
|-
  | HalfSecondTick | Raised every 30th tick (≈twice per second). |
+
| Resize || Raised after the game window is resized.
  | OneSecondTick | Raised every 60th tick (≈once per second). |
+
|}
 +
</li>
  
* <span id="graphics-events"></span>
+
<li id="location-events">
  **`GraphicsEvents`** are raised during the game's draw loop, when the game is rendering content
+
'''<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.
  to the window.
 
  
  | event | summary |
+
{| class="wikitable"
  |:----- |:------- |
+
|-
  | OnPreRenderEvent<br />OnPostRenderEvent | Raised before and after drawing the world to the screen.
+
! event !! summary
  | OnPreRenderGuiEvent<br />OnPostRenderGuiEvent | When a menu is open (`Game1.activeClickableMenu != null`), 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.) |
+
| CurrentLocationChanged || Raised after the player warps to a new location. Handlers are given the previous and new locations as arguments.
  | Resize | Raised after the game window is resized. |
+
|-
 +
| 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.
 +
|}
 +
</li>
  
* <span id="location-events"></span>
+
<li id="control-events">
  **`LocationEvents`** are raised when the player transitions between game locations, a location is
+
'''<tt>MenuEvents</tt>''' are raised when a game menu is opened or closed (including internal menus like the title screen).
  added or removed, or the objects in the current location change.
 
  
  | event | summary |
+
{| class="wikitable"
  |:----- |:------- |
+
|-
  | CurrentLocationChanged | Raised after the player warps to a new location. Handlers are given the previous and new locations as arguments. |
+
! event !! summary
  | 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. |
+
| 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.
 +
|}
 +
</li>
  
* <span id="control-events"></span>
+
<li id="mine-events">
  **`MenuEvents`** are raised when a game menu is opened or closed (including internal menus like
+
'''<tt>MineEvents</tt>''' are raised when something happens in [[The Mines]].
  the title screen).
 
  
  | event | summary |
+
{| class="wikitable"
  |:----- |:------- |
+
|-
  | 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). |
+
! event !! summary
  | MenuClosed | Raised after a game menu is closed. Handlers are given the previous menu. |
+
|-
 +
| MineLevelChanged || Raised after the player warps to a new level of the mine. Handlers are passed the previous and new mine level as arguments.
 +
|}
 +
</li>
  
* <span id="mine-events"></span>
+
<li id="player-events">
  **`MineEvents`** are raised when something happens in [The Mines](http://stardewvalleywiki.com/The_Mines).
+
'''<tt>PlayerEvents</tt>''' are raised when the player data changes.
  
  | event | summary |
+
{| class="wikitable"
  |:----- |:------- |
+
|-
  | MineLevelChanged | Raised after the player warps to a new level of the mine. Handlers are passed the previous and new mine level as arguments. |
+
! event !! summary
 +
|-
 +
| InventoryChanged || Raised after the player's inventory changes in any way (added or removed item, sorted, etc).
 +
|-
 +
| 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.
 +
|}
 +
</li>
  
* <span id="player-events"></span>
+
<li id="save-events">
  **`PlayerEvents`** are raised when the player data changes.
+
'''<tt>SaveEvents</tt>''' are raised when the player saves or loads the game.
  
  | event | summary |
+
{| class="wikitable"
  |:----- |:------- |
+
|-
  | InventoryChanged | Raised after the player's inventory changes in any way (added or removed item, sorted, etc). |
+
! event !! summary
  | 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. |
+
|-
 +
| 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.
 +
|}
 +
</li>
  
  Notable bug: the `InventoryChanged` and `LeveledUp` events are raised at various times before
+
<li id="time-events">
  the game is loaded, when there's no character yet.
+
'''<tt>TimeEvents</tt>''' are raised when the in-game date or time changes.
  
* <span id="save-events"></span>
+
{| class="wikitable"
  **`SaveEvents`** are raised when the player saves or loads the game.
+
|-
 +
! event !! summary
 +
|-
 +
| AfterDayStarted || Raised after the game begins a new day, including when loading a save.
 +
|-
 +
| TimeOfDayChanged || Raised after the in-game clock changes.
 +
|-
 +
| DayOfMonthChanged || Raised after the day-of-month value changes (including when the player loads a save). This may happen before an end-of-day save; in most cases you should use <tt>AfterDayStarted</tt> instead.
 +
|-
 +
| SeasonOfYearChanged || Raised after the season changes.
 +
|-
 +
| YearOfGameChanged || Raised after the year changes.
 +
|}
 +
</li>
 +
</ul>
  
  | event | summary |
+
===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.
  | 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.
 
  
* <span id="time-events"></span>
+
Here's the simplest way to use <tt>config.json</tt>:
  **`TimeEvents`** are raised when the in-game date or time changes.
 
  
  | event | summary |
+
<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.)
  | AfterDayStarted | Raised after the game begins a new day, including when loading a save. |
 
  | TimeOfDayChanged | Raised after the in-game clock changes. |
 
  | DayOfMonthChanged | Raised after the day-of-month value changes (including when the player loads a save). This may happen before an end-of-day save; in most cases you should use `AfterDayStarted` instead. |
 
  | SeasonOfYearChanged | Raised after the season changes. |
 
  | YearOfGameChanged | Raised after the year changes. |
 
  
### Configuration
+
You can set defaults directly:
You can let users configure your mod through a `config.json` file. SMAPI will automatically create
 
the file and take care of reading, normalising, and updating it.
 
  
Here's the simplest way to use `config.json`:
+
<pre lang="c#">
 +
class ModConfig
 +
{
 +
  public bool ExampleBoolean { get; set; } = true;
 +
  public float ExampleFloat { get; set; } = 0.5;
 +
}
 +
</pre>
  
1. Create your model. This is just a class with properties for the config options you want, and it
+
...or with a constructor:
  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:
+
<pre lang="c#">
 +
class ModConfig
 +
{
 +
   public bool ExampleBoolean { get; set; }
 +
  public float ExampleFloat { get; set; }
  
   ```c#
+
   public ModConfig()
  class ModConfig
 
 
   {
 
   {
       public bool ExampleBoolean { get; set; } = true;
+
       this.ExampleBoolean = true;
       public float ExampleFloat { get; set; } = 0.5;
+
       this.ExampleFloat = 0.5;
 
   }
 
   }
  ```
+
}
 +
</pre></li>
  
  ...or with a constructor:
+
<li>In your <tt>ModEntry::Entry</tt> method, add this line to read the config options:
  
  ```c#
+
<pre lang="c#">
  class ModConfig
+
ModConfig config = helper.ReadConfig<ModConfig>();
  {
+
</pre>
      public bool ExampleBoolean { get; set; }
+
</li>
      public float ExampleFloat { get; set; }
+
</ol>
  
      public ModConfig()
+
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.
      {
 
        this.ExampleBoolean = true;
 
        this.ExampleFloat = 0.5;
 
      }
 
  }
 
  ```
 
  
2. In your `ModEntry::Entry` method, add this line to read the config options:
+
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>.
 
 
  ```c#
 
  ModConfig config = helper.ReadConfig<ModConfig>();
 
  ```
 
 
 
That's it! When the player launches the game, SMAPI will create the `config.json` 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 `helper.SaveConfig(config)`. You can access
 
the helper in other methods using `this.Helper`.
 
 
 
For more advanced config and JSON scenarios, see _[advanced configuration](creating-a-smapi-mod-advanced-config)_
 
which covers...
 
  
 +
For more advanced config and JSON scenarios, see [[Modding:Advanced mod configuration]] which covers...
 
* adding custom JSON files;
 
* adding custom JSON files;
 
* adding per-save JSON files;
 
* adding per-save JSON files;
Line 396: Line 366:
 
* overriding JSON serialization.
 
* overriding JSON serialization.
  
### Content
+
===Content===
<section id="content-api"></section>
+
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.
  
If your mod needs custom textures or maps, you can load them with SMAPI's content API. You can load
+
Example usage:
any `.xnb` file the game supports, or load a `.png` file into a texture.
+
<ul>
 +
<li>Load an image from your mod folder (from an <tt>assets</tt> subfolder in this example):
 +
<pre lang="c#">
 +
// load an XNB file
 +
var texture = helper.Content.Load<Texture2D>(@"assets\texture.xnb", ContentSource.ModFolder);
  
Example usage:
+
// load a PNG file
 +
var texture = helper.Content.Load<Texture2D>(@"assets\texture.png", ContentSource.ModFolder);
 +
</pre></li>
  
* Load an image from your mod folder (from an `assets` subfolder in this example):
+
<li>Load an asset from game's content folder:
  ```c#
+
<pre lang="c#">
  // load an XNB file
+
var data = helper.Content.Load<Dictionary<int, string>>(@"Data\ObjectInformation.xnb", ContentSource.GameContent);
  var texture = helper.Content.Load<Texture2D>(@"assets\texture.xnb", ContentSource.ModFolder);
+
</pre></li>
  
  // load a PNG file
+
<li>Load a custom tilesheet for a map:
  var texture = helper.Content.Load<Texture2D>(@"assets\texture.png", ContentSource.ModFolder);
+
<pre lang="c#">
  ```
+
tilesheet.ImageSource = helper.Content.GetActualAssetKey(@"assets\tilesheet.png");
* Load an asset from game's content folder:
+
</pre></li>
  ```c#
+
</ul>
  var data = helper.Content.Load<Dictionary<int, string>>(@"Data\ObjectInformation.xnb", ContentSource.GameContent);
 
  ```
 
* Load a custom tilesheet for a map:
 
  ```c#
 
  tilesheet.ImageSource = helper.Content.GetActualAssetKey(@"assets\tilesheet.png");
 
  ```
 
  
You should avoid calling `content.Load<T>` in draw code for performance reasons, since drawing
+
You should avoid calling <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.
happens ≈60 times per second. Instead, load your content ahead of time and reuse it.
 
  
### Logging
+
===Logging===
Your mod can write messages to the console window and log file using the monitor. For example,
+
Your mod can write messages to the console window and log file using the monitor. For example, this code:
this code:
 
  
```c#
+
<pre lang="c#">
 
this.Monitor.Log("a trace message", LogLevel.Trace);
 
this.Monitor.Log("a trace message", LogLevel.Trace);
 
this.Monitor.Log("a debug message", LogLevel.Debug);
 
this.Monitor.Log("a debug message", LogLevel.Debug);
Line 434: Line 402:
 
this.Monitor.Log("a warning message", LogLevel.Warn);
 
this.Monitor.Log("a warning message", LogLevel.Warn);
 
this.Monitor.Log("an error message", LogLevel.Error);
 
this.Monitor.Log("an error message", LogLevel.Error);
```
+
</pre>
  
 
will log something like this:
 
will log something like this:
  
&lt;pre&gt;
+
<div style="font-family: monospace;">
<span style="color:#666;">[18:00:00 TRACE Mod Name] a trace message</span>
+
<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>
+
<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>
+
<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>
+
<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>
 
<span style="color:red;">[18:00:00 ERROR Mod Name] an error message</span>
&lt;/pre&gt;
+
</div>
  
Note that `LogLevel.Trace` messages won't appear in the console window by default, they'll only
+
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.)
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
+
===Reflection===
SMAPI provides an API for robustly accessing the game's private fields or methods. You can use it
+
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:
from `helper.Reflection` in your entry method, or `this.Helper.Reflection` elsewhere in your
 
entry class. It consists of three methods:
 
  
* `GetPrivateValue<TValue>(...)` returns the value of a private field.
+
* <tt>GetPrivateValue<TValue>(...)</tt> returns the value of a private field.
* `GetPrivateField<TValue>(...)` returns an object you can use to get or set a field's value.
+
* <tt>GetPrivateField<TValue>(...)</tt> returns an object you can use to get or set a field's value.
* `GetPrivateMethod(...)` returns an object you can use to invoke a method.
+
* <tt>GetPrivateMethod(...)</tt> returns an object you can use to invoke a method.
  
 
Here are a few examples of what this lets you do:
 
Here are a few examples of what this lets you do:
  
```c#
+
<pre lang="c#">
 
// did you pet your pet today?
 
// did you pet your pet today?
 
bool wasPet = this.Helper.Reflection.GetPrivateValue<bool>(pet, "wasPetToday");
 
bool wasPet = this.Helper.Reflection.GetPrivateValue<bool>(pet, "wasPetToday");
Line 474: Line 437:
 
if(Game1.currentLocation is MineShaft)
 
if(Game1.currentLocation is MineShaft)
 
   this.Helper.Reflection.GetPrivateField<Random>(Game1.currentLocation, "mineRandom").SetValue(new Random());
 
   this.Helper.Reflection.GetPrivateField<Random>(Game1.currentLocation, "mineRandom").SetValue(new Random());
```
+
</pre>
  
This works with static or instance fields/methods, caches the reflection to improve performance, and will
+
This works with static or instance fields/methods, caches the reflection to improve performance, and will throw useful errors automatically when reflection fails.
throw useful errors automatically when reflection fails.
 
  
 
If you need to do more, you can also switch to C#'s underlying reflection API:
 
If you need to do more, you can also switch to C#'s underlying reflection API:
  
```c#
+
<pre lang="c#">
 
FieldInfo field = this.Helper.Reflection.GetPrivateField<string>(…).FieldInfo;
 
FieldInfo field = this.Helper.Reflection.GetPrivateField<string>(…).FieldInfo;
 
MethodInfo method = this.Helper.Reflection.GetPrivateMethod(…).MethodInfo;
 
MethodInfo method = this.Helper.Reflection.GetPrivateMethod(…).MethodInfo;
```
+
</pre>
  
### Mod registry
+
===Mod registry===
Your mod can get information about loaded mods, or check if a particular mod is loaded. (All mods
+
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.)
are loaded by the time your mod's `Entry(…)` method is called.)
 
  
```c#
+
<pre 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 499: Line 460:
 
// get manifest info for all loaded mods
 
// get manifest info for all loaded mods
 
foreach(IManifest manifest in this.Helper.ModRegistry.GetAll()) { … }
 
foreach(IManifest manifest in this.Helper.ModRegistry.GetAll()) { … }
```
+
</pre>
  
## Final considerations
+
==Final considerations==
 +
===Crossplatform support===
 +
SMAPI will automatically adjust your mod so it works on Linux, Mac, and Windows. However, there are a few things you should do to avoid problems:
  
### Crossplatform support
+
<ol>
SMAPI will automatically adjust your mod so it works on Linux, Mac, and Windows. However, there are
+
<li>Use the [https://github.com/Pathoschild/Stardew.ModBuildConfig#readme crossplatform build config] package to automatically set up your project references. This makes crossplatform compatibility easier and lets your code compile on any platform. (If you followed the above guide, you already have this.)</li>
a few things you should do to avoid problems:
 
  
1. Use the [crossplatform build config](https://github.com/Pathoschild/Stardew.ModBuildConfig#readme)
+
<li>Use <tt>Path.Combine</tt> to build file paths, don't hardcode path separators since they won't work on all platforms.
  package to automatically set up your project references. This makes crossplatform compatibility
 
  easier and lets your code compile on any platform. (If you followed the above guide, you already
 
  have this.)
 
  
2. Use `Path.Combine` to build file paths, don't hardcode path separators since they won't work on
+
<pre lang="c#">
  all platforms.
+
// ✘ Don't do this! It will crash on Linux/Mac.
 +
string path = helper.DirectoryPath + "\assets\asset.xnb";
  
  ```cs
+
// ✓ This is OK
  // ✘ Don't do this! It will crash on Linux/Mac.
+
string path = Path.Combine(helper.DirectoryPath, "assets", "asset.xnb");
  string path = helper.DirectoryPath + "\assets\asset.xnb";
+
</pre></li>
  
  // ✓ This is OK
+
<li>Use <tt>helper.DirectoryPath</tt>, don't try to determine the mod path yourself.
  string path = Path.Combine(helper.DirectoryPath, "assets", "asset.xnb");
 
  ```
 
  
3. Use `helper.DirectoryPath`, don't try to determine the mod path yourself.
+
<pre lang="c#">
 +
// ✘ Don't do this! It will crash if SMAPI rewrites the assembly (e.g. to update or crossplatform it).
 +
string modFolder = Assembly.GetCallingAssembly().Location;
  
  ```cs
+
// ✓ This is OK
  // ✘ Don't do this! It will crash if SMAPI rewrites the assembly (e.g. to update or crossplatform it).
+
string modFolder = helper.DirectoryPath;
  string modFolder = Assembly.GetCallingAssembly().Location;
+
</pre></li>
 +
</ol>
  
  // ✓ This is OK
+
===Test on all platforms===
  string modFolder = helper.DirectoryPath;
+
If you want to test your mod on all platforms, there's some first-time setup you need to get out of the way. Essentially you need to test your mod twice: once on Windows, and again on Linux or Mac. You can do that by testing one version on your computer, and the other in a virtual machine.
  ```
 
  
### Test on all platforms
+
* '''If your main computer is Windows:'''
If you want to test your mod on all platforms, there's some first-time setup you need to get out of
+
*# Install [https://www.virtualbox.org/ VirtualBox].
the way. Essentially you need to test your mod twice: once on Windows, and again on Linux or Mac.
+
*# Add [https://www.dropbox.com/s/nrq9xsde2afp4ey/StardewValleyLinuxModding.7z this premade Linux virtual machine] (requires a 64-bit computer).<br /><small>''In VirtualBox, click Machine » Add and choose the downloaded <tt>.vbox</tt> file. This is a [https://manjaro.org/ Manjaro] virtual machine with Chromium (web browser), Steam, and [http://www.monodevelop.com/ MonoDevelop] preinstalled.''</small>
You can do that by testing one version on your computer, and the other in a virtual machine.
+
*# Launch the virtual machine, and install Stardew Valley from the Steam client (preinstalled) or GOG website.<br /><small>''Tip: don't change the default install path, or you'll need to customise the mod's build configuration.''</small>
  
#### If your main computer is Windows
+
* '''If your main computer is Linux or Mac:'''
 +
*# Install [https://www.virtualbox.org/ VirtualBox].
 +
*# [http://www.macworld.co.uk/how-to/mac-software/run-windows-10-on-your-mac-using-virtualbox-3621650/ Create a VM with Windows].
 +
*# Install [https://www.visualstudio.com/vs/community/ Visual Studio Community] in your VM.
 +
*# Install Stardew Valley in your VM.
  
1. Install [VirtualBox](https://www.virtualbox.org/).
+
===Release your mod===
2. Add [this premade Linux virtual machine](https://www.dropbox.com/s/nrq9xsde2afp4ey/StardewValleyLinuxModding.7z)
+
Ready to share your mod with the world? Let's say you created a mod named ''Pineapples Everywhere''
  (requires a 64-bit computer). 
+
which turns all NPCs into pineapples; here's how you would release it for others to use.
  _<small>In VirtualBox, click Machine » Add and choose the downloaded `.vbox` file. This is a
 
  [Manjaro](https://manjaro.org/) virtual machine with Chromium (web browser), Steam, and
 
  [MonoDevelop](http://www.monodevelop.com/) preinstalled.</small>_
 
4. Launch the virtual machine, and install Stardew Valley from the Steam client (preinstalled) or GOG website. 
 
  _<small>Tip: don't change the default install path, or you'll need to customise the mod's build
 
  configuration.</small>_
 
  
#### If your main computer is Linux or Mac
+
<ol>
 +
<li>Copy your compiled mod and <tt>manifest.json</tt> into a folder matching your mod's name.</li>
  
1. Install [VirtualBox](https://www.virtualbox.org/).
+
<li>Create a zip archive with your mod's name and version.
2. [Create a VM with Windows](http://www.macworld.co.uk/how-to/mac-software/run-windows-10-on-your-mac-using-virtualbox-3621650/).
 
3. Install [Visual Studio Community](https://www.visualstudio.com/vs/community/) in your VM.
 
4. Install Stardew Valley in your VM.
 
  
### Release your mod
+
Your mod structure should look something like this:
Ready to share your mod with the world? Let's say you created a mod named _Pineapples Everywhere_
 
which turns all NPCs into pineapples; here's how you would release it for others to use.
 
  
1. Copy your compiled mod and `manifest.json` into a folder matching your mod's name.
+
<pre>
2. Create a zip archive with your mod's name, version, and platform.
+
PineapplesEverywhere-1.0.zip
 +
  PineapplesEverywhere/
 +
      PineapplesEverywhere.dll
 +
      PineapplesEverywhere.pdb
 +
      config.json
 +
      manifest.json
 +
</pre></li>
  
  Your mod structure should look something like this:
+
<li>Upload your mod to [http://www.nexusmods.com/stardewvalley Nexus Mods], the [http://community.playstarbound.com/forums/mods.215/ official modding forums], or both.</li>
 +
</ul>
  
  ```
+
==Decompile the game code==
  PineapplesEverywhere-1.0-Windows.zip
 
      PineapplesEverywhere/
 
        PineapplesEverywhere.dll
 
        PineapplesEverywhere.pdb
 
        config.json
 
        manifest.json
 
  ```
 
 
 
3. Upload your mod to [Nexus Mods](http://www.nexusmods.com/stardewvalley), the
 
  [official modding forums](http://community.playstarbound.com/forums/mods.215/), or both.
 
 
 
## Decompile the game code
 
 
When you start working on more complex mods, you may need to look at how the game code works.
 
When you start working on more complex mods, you may need to look at how the game code works.
  
 
Here's how to decompile the game code so you can look at it:
 
Here's how to decompile the game code so you can look at it:
  
1. Open `StardewValley.exe` in [dotPeek](https://www.jetbrains.com/decompiler/).
+
# Open <tt>StardewValley.exe</tt> in [https://www.jetbrains.com/decompiler/ dotPeek].
2. Right-click on _Stardew Valley_ and choose _Export to Project_. Accept the default options to
+
# Right-click on ''Stardew Valley'' and choose ''Export to Project''. Accept the default options to create a decompiled project you can open in Visual Studio. (Note that the decompiled code will not be functional due to limitations of the decompiler, but you'll be able to read the game code.)
  create a decompiled project you can open in Visual Studio. (Note that the decompiled code will
 
  not be functional due to limitations of the decompiler, but you'll be able to read the game code.)
 
  
 
Here's how to unpack the XNB data files:
 
Here's how to unpack the XNB data files:
  
1. Download the [Easy XNB Pack/UnPack Toolkit](http://community.playstarbound.com/threads/modding-guides-and-general-modding-discussion-redux.109131/page-6#post-2837587).
+
# Download the [http://community.playstarbound.com/threads/modding-guides-and-general-modding-discussion-redux.109131/page-6#post-2837587 Easy XNB Pack/UnPack Toolkit].
2. Copy the entire `Stardew Valley\Content` game folder into `XNB-Mod-Toolkit\Packed`.
+
# Copy the entire <tt>Stardew Valley\Content</tt> game folder into <tt>XNB-Mod-Toolkit\Packed</tt>.
3. Run `XNB-Mod-Toolkit\UNPACK FILES.bat` to unpack the files into `XNB-Mod-Toolkit\Unpacked`.
+
# Run <tt>XNB-Mod-Toolkit\UNPACK FILES.bat</tt> to unpack the files into <tt>XNB-Mod-Toolkit\Unpacked</tt>.
</pre>
 

Revision as of 18:52, 1 May 2017

Index

Ready to make your own mod? This page will help you create your first SMAPI mod and use the available APIs and events.

Quick start

The rest of this page will help you create a mod. If you're experienced enough to skip the tutorial, here's a quick summary of what this page will walk you through:

  1. Create an empty C# class library project.
  2. Target .NET Framework 4.5 (for Linux compatibility).
  3. Reference the Pathoschild.Stardew.ModBuildConfig NuGet package to automatically add the right references depending on the platform the mod is being compiled on.
  4. Create an entry class which subclasses StardewModdingAPI.Mod.
  5. Override the Entry method, and write your code using the SMAPI events and APIs.
  6. Create a manifest.json file which describes your mod for SMAPI.
  7. Create a zip file containing the mod files for release.

Intro

  • What is SMAPI?
    A SMAPI mod uses the SMAPI modding API to extend the game logic. You can run code when something happens (e.g. mouse clicked or menu opened), or periodically (e.g. once per game tick). SMAPI mods are written in C# using the .NET Framework. Stardew Valley also uses XNA (on Windows) or MonoGame (on Linux and Mac) for the fundamental game logic (drawing to the screen, user input, etc).
  • Can I make a mod?
    • Scenario A: you're new to programming.
      Many mod developers start with little or no programming experience. You can certainly learn along the way if you're determined, but you should be prepared for a steep learning curve. Don't be too ambitious at first; it's better to start with a small mod when you're figuring it out. It's easy to become overwhelmed at first and give up. The modding community is very welcoming, so don't be afraid to ask questions!

      Since mods are written in C#, it's a good idea to get acquainted with it first. C# Fundamentals for Absolute Beginners will walk you through the basics of C# needed to write SMAPI mods, from the basic concepts to event-driven programming (which is what SMAPI mods use).

    • Scenario B: you already have programming experience.
      You should be fine. Programming experience in C# or Java will make things easier, but it isn't critical. If you're unfamiliar with C#, you can skim through C# Fundamentals for Absolute Beginners to fill in any gaps.
    The next few sections will walk you through creating a very simple mod. If you follow along, you'll have created a mod! All that will be left is making it do what you want. :)
  • What do I need?
    Before you start:
    1. You should read the intro for players to learn the basic concepts and install SMAPI.
    2. You should install:
      1. Stardew Valley;
      2. SMAPI;
      3. and Visual Studio 2017 Community (on Windows) or MonoDevelop (on Linux/Mac).
    3. If you're not familiar with Visual Studio 2017 (on Windows) or MonoDevelop (on Linux/Mac), the IDE primer explains how to do the important stuff you need for this guide.

Create a mod

A SMAPI mod is a compiled library (DLL) with an entry method that gets called by SMAPI, so let's set that up.

Create the project

  1. Open Visual Studio 2017 or MonoDevelop.
  2. Create a solution with a C# class library project (see how to).
  3. Change the target framework to .NET Framework 4.5 for compatibility with Linux (see how to).
  4. Delete the Class1.cs or MyClass.cs file (see how to).

Configure the build

  1. Reference the Pathoschild.Stardew.ModBuildConfig NuGet package (see how to). This will automatically configure your project to load the right modding dependencies for the current platform, so your mod can be built on Linux, Mac, or Windows. It also adds support for debugging the mod in-game.
  2. (optional) See simplify mod development to automatically package your mod into your mod folder and enable debugging while the game is running.

That's it! Try building the project and make sure you don't get any errors. If you get an error like "failed to find the game install path", see the package's Troubleshooting section.

Add your manifest

The mod manifest tells SMAPI about your mod.

  1. Add a file named manifest.json to your project.
  2. Paste this code into the file (replacing the <...> placeholders):
    {
       "Name": "<your project name>",
       "Author": "<your name>",
       "Version": {
          "MajorVersion": 1,
          "MinorVersion": 0,
          "PatchVersion": 0,
          "Build": null
       },
       "Description": "<One or two sentences about the mod>",
       "UniqueID": "<your name>.<your project name>",
       "EntryDll": "<your project name>.dll"
    }
    

This will be listed in the console output when the game is launching. (For more info about the version number, see semantic versioning.)

Write the code

Almost done! Now for the code SMAPI will run.

  1. Add a C# class file called ModEntry.cs to your project.
  2. Put this code in the file (replace <your project name> with the name of your project):
    using System;
    using Microsoft.Xna.Framework;
    using StardewModdingAPI;
    using StardewModdingAPI.Events;
    using StardewValley;
    namespace <your project name>
    {
        /// <summary>The mod entry point.</summary>
        public class ModEntry : Mod
        {
            /*********
            ** Public methods
            *********/
            /// <summary>Initialise the mod.</summary>
            /// <param name="helper">Provides methods for interacting with the mod directory, such as read/writing a config file or custom JSON files.</param>
            public override void Entry(IModHelper helper)
            {
                ControlEvents.KeyPressed += this.ReceiveKeyPress;
            }
            /*********
            ** Private methods
            *********/
            /// <summary>The method invoked when the player presses a keyboard button.</summary>
            /// <param name="sender">The event sender.</param>
            /// <param name="e">The event data.</param>
            private void ReceiveKeyPress(object sender, EventArgsKeyPressed e)
            {
                this.Monitor.Log($"Player pressed {e.KeyPressed}.");
            }
        }
    }
    

Try your mod

  1. Build the project.
  2. Copy your mod into your game's Mods folder (only if you didn't do step 2 of configure the build).
  3. In the game's Mods directory, add a folder with your mod's name.
  4. Copy your manifest.json and compiled files (see how to find them) into the folder you created.
  5. Run the game through SMAPI.

The mod so far will just send a message to the console window whenever you press a key in the game. If that didn't work, something went wrong. Try reviewing the above instructions, or ask for help. :)

Mod APIs

Now that you have a basic mod, here are the SMAPI features you can use to do more.

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 Entry method:

SaveEvents.AfterLoad += this.ReceiveAfterLoad;

Then declare a method like this. (The EventArgs e argument will often provide more details about what happened, if there are any.)

/// <summmary>The event handler called after the player loads their save.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
public void ReceiveAfterLoad(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.");
}

Here are the available events:

  • ContentEvents are raised when the game loads content from its XNB files or changes locale.
    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.
  • ControlEvents 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 Game1.oldKBState, Game1.oldMouseState, and Game1.oldPadState.) Most of these events are split into two variants, XPressed and XReleased. The Pressed variant is raised when the player presses the button (holding the button down only triggers the event once), and the Released variant is raised when they release it.
    event summary
    ControllerButtonPressed
    ControllerButtonReleased
    Raised after the player pressed/released a button on a gamepad or controller. These events aren't raised for trigger buttons.
    ControllerTriggerPressed
    ControllerTriggerReleased
    Raised after the player pressed/released a trigger button on a gamepad or controller.
    KeyPressed
    KeyReleased
    Raised after the player pressed/released a keyboard key.
    KeyboardChanged Raised after the game's KeyboardState changed. That happens when the player presses or releases a key.
    MouseChanged Raised after the game's MouseState changed. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button.
  • GameEvents are raised when the game changes state.
    event summary
    GameLoaded Raised when the game is ready and initialised. At this point the game data (like Game1.objectInformation) 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).
  • GraphicsEvents are raised during the game's draw loop, when the game is rendering content to the window.
    event summary
    OnPreRenderEvent
    OnPostRenderEvent
    Raised before and after drawing the world to the screen.
    OnPreRenderGuiEvent
    OnPostRenderGuiEvent
    When a menu is open (Game1.activeClickableMenu != null), raised before and after drawing that menu to the screen. This includes the game's internal menus like the title screen.
    OnPreRenderHudEvent
    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.
  • LocationEvents are raised when the player transitions between game locations, a location is added or removed, or the objects in the current location change.
    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.
  • MenuEvents are raised when a game menu is opened or closed (including internal menus like the title screen).
    event 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.
  • MineEvents are raised when something happens in The Mines.
    event 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.
  • PlayerEvents are raised when the player data changes.
    event summary
    InventoryChanged Raised after the player's inventory changes in any way (added or removed item, sorted, etc).
    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.
  • SaveEvents are raised when the player saves or loads the game.
    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.
  • TimeEvents are raised when the in-game date or time changes.
    event summary
    AfterDayStarted Raised after the game begins a new day, including when loading a save.
    TimeOfDayChanged Raised after the in-game clock changes.
    DayOfMonthChanged Raised after the day-of-month value changes (including when the player loads a save). This may happen before an end-of-day save; in most cases you should use AfterDayStarted instead.
    SeasonOfYearChanged Raised after the season changes.
    YearOfGameChanged Raised after the year changes.

Configuration

You can let users configure your mod through a config.json file. SMAPI will automatically create the file and take care of reading, normalising, and updating it.

Here's the simplest way to use config.json:

  1. 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:
    class ModConfig
    {
       public bool ExampleBoolean { get; set; } = true;
       public float ExampleFloat { get; set; } = 0.5;
    }
    

    ...or with a constructor:

    class ModConfig
    {
       public bool ExampleBoolean { get; set; }
       public float ExampleFloat { get; set; }
    
       public ModConfig()
       {
          this.ExampleBoolean = true;
          this.ExampleFloat = 0.5;
       }
    }
    
  2. In your ModEntry::Entry method, add this line to read the config options:
    ModConfig config = helper.ReadConfig<ModConfig>();
    

That's it! When the player launches the game, SMAPI will create the config.json 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 helper.SaveConfig(config). You can access the helper in other methods using this.Helper.

For more advanced config and JSON scenarios, see Modding:Advanced mod configuration which covers...

  • adding custom JSON files;
  • adding per-save JSON files;
  • using a config wrapper for file I/O;
  • overriding JSON serialization.

Content

If your mod needs custom textures or maps, you can load them with SMAPI's content API. You can load any .xnb file the game supports, or load a .png file into a texture.

Example usage:

  • Load an image from your mod folder (from an assets subfolder in this example):
    // 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);
    
  • Load an asset from game's content folder:
    var data = helper.Content.Load<Dictionary<int, string>>(@"Data\ObjectInformation.xnb", ContentSource.GameContent);
    
  • Load a custom tilesheet for a map:
    tilesheet.ImageSource = helper.Content.GetActualAssetKey(@"assets\tilesheet.png");
    

You should avoid calling content.Load<T> in draw code for performance reasons, since drawing happens ≈60 times per second. Instead, load your content ahead of time and reuse it.

Logging

Your mod can write messages to the console window and log file using the monitor. For example, this code:

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);

will log something like this:

[18:00:00 TRACE Mod Name] a trace message
[18:00:00 DEBUG Mod Name] a debug message
[18:00:00 INFO Mod Name] an info message
[18:00:00 WARN Mod Name] a warning message
[18:00:00 ERROR Mod Name] an error message

Note that LogLevel.Trace 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 helper.Reflection in your entry method, or this.Helper.Reflection elsewhere in your entry class. It consists of three methods:

  • GetPrivateValue<TValue>(...) returns the value of a private field.
  • GetPrivateField<TValue>(...) returns an object you can use to get or set a field's value.
  • GetPrivateMethod(...) returns an object you can use to invoke a method.

Here are a few examples of what this lets you do:

// 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());

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:

FieldInfo field = this.Helper.Reflection.GetPrivateField<string>(…).FieldInfo;
MethodInfo method = this.Helper.Reflection.GetPrivateMethod(…).MethodInfo;

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 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()) { … }

Final considerations

Crossplatform support

SMAPI will automatically adjust your mod so it works on Linux, Mac, and Windows. However, there are a few things you should do to avoid problems:

  1. Use the crossplatform build config package to automatically set up your project references. This makes crossplatform compatibility easier and lets your code compile on any platform. (If you followed the above guide, you already have this.)
  2. Use Path.Combine to build file paths, don't hardcode path separators since they won't work on all platforms.
    // ✘ Don't do this! It will crash on Linux/Mac.
    string path = helper.DirectoryPath + "\assets\asset.xnb";
    
    // ✓ This is OK
    string path = Path.Combine(helper.DirectoryPath, "assets", "asset.xnb");
    
  3. Use helper.DirectoryPath, don't try to determine the mod path yourself.
    // ✘ Don't do this! It will crash if SMAPI rewrites the assembly (e.g. to update or crossplatform it).
    string modFolder = Assembly.GetCallingAssembly().Location;
    
    // ✓ This is OK
    string modFolder = helper.DirectoryPath;
    

Test on all platforms

If you want to test your mod on all platforms, there's some first-time setup you need to get out of the way. Essentially you need to test your mod twice: once on Windows, and again on Linux or Mac. You can do that by testing one version on your computer, and the other in a virtual machine.

  • If your main computer is Windows:
    1. Install VirtualBox.
    2. Add this premade Linux virtual machine (requires a 64-bit computer).
      In VirtualBox, click Machine » Add and choose the downloaded .vbox file. This is a Manjaro virtual machine with Chromium (web browser), Steam, and MonoDevelop preinstalled.
    3. Launch the virtual machine, and install Stardew Valley from the Steam client (preinstalled) or GOG website.
      Tip: don't change the default install path, or you'll need to customise the mod's build configuration.

Release your mod

Ready to share your mod with the world? Let's say you created a mod named Pineapples Everywhere which turns all NPCs into pineapples; here's how you would release it for others to use.

  1. Copy your compiled mod and manifest.json into a folder matching your mod's name.
  2. Create a zip archive with your mod's name and version. Your mod structure should look something like this:
    PineapplesEverywhere-1.0.zip
       PineapplesEverywhere/
          PineapplesEverywhere.dll
          PineapplesEverywhere.pdb
          config.json
          manifest.json
    
  3. Upload your mod to Nexus Mods, the official modding forums, or both.
  4. Decompile the game code

    When you start working on more complex mods, you may need to look at how the game code works.

    Here's how to decompile the game code so you can look at it:

    1. Open StardewValley.exe in dotPeek.
    2. Right-click on Stardew Valley and choose Export to Project. Accept the default options to create a decompiled project you can open in Visual Studio. (Note that the decompiled code will not be functional due to limitations of the decompiler, but you'll be able to read the game code.)

    Here's how to unpack the XNB data files:

    1. Download the Easy XNB Pack/UnPack Toolkit.
    2. Copy the entire Stardew Valley\Content game folder into XNB-Mod-Toolkit\Packed.
    3. Run XNB-Mod-Toolkit\UNPACK FILES.bat to unpack the files into XNB-Mod-Toolkit\Unpacked.