Difference between revisions of "Modding:Modder Guide/Get Started"

From Stardew Valley Wiki
Jump to navigation Jump to search
m (Mark sender object as nullable. This prevents the code showing a warning. Note: other wiki pages like https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#How_do_I_use_them.3F have correct code snippets.)
 
(89 intermediate revisions by 22 users not shown)
Line 1: Line 1:
 
{{../header}}
 
{{../header}}
  
Do you want to create SMAPI mods for Stardew Valley? This guide is for you! '''For using mods, see [[Modding:Player Guide/Getting Started|Modding:Player Guide]].'''
+
'''If you're making a mod for the first time,''' see [[Modding:Index#Creating mods]] for a short description of the differences between C# mods and content packs. '''You don't need to use C# to make mods.'''
 +
 
 +
Do you want to create '''SMAPI mods for Stardew Valley with C#?''' This guide is for you!
  
 
==Intro==
 
==Intro==
 
===What is a SMAPI mod?===
 
===What is a SMAPI mod?===
A SMAPI mod uses the [https://smapi.io/ SMAPI] modding API to extend the game logic. The mod can respond when something happens in the game (like when an object is placed in the world), run code periodically (like once per update tick), change the game's assets and data, etc. SMAPI mods are written in C# using the .NET Framework, and Stardew Valley uses XNA/MonoGame for the game logic (drawing to the screen, user input, etc).
+
A SMAPI mod uses the [https://smapi.io/ SMAPI] modding API to extend the game logic. The mod can respond when something happens in the game (like when an object is placed in the world), run code periodically (like once per update tick), change the game's assets and data, etc. SMAPI mods are written in C# using .NET, and Stardew Valley uses MonoGame for the game logic (drawing to the screen, user input, etc).
  
 
===Why do mods use SMAPI?===
 
===Why do mods use SMAPI?===
Line 19: Line 21:
  
 
===Can I make a mod?===
 
===Can I make a mod?===
Yes! This guide (and the rest of this wiki) will help you create a simple mod step-by-step. If you follow along, you'll have created a mod! Then you'll just need to make it do what you want.
+
Yes! This guide will help you create a simple mod step-by-step. If you follow along, you'll have created a mod! Then you'll just need to make it do what you want.
  
 
If 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!
 
If 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!
Line 26: Line 28:
  
 
===Can I make a mod ''without SMAPI''?===
 
===Can I make a mod ''without SMAPI''?===
Yep. Many SMAPI mods support 'content packs', which let you provide JSON text files, images, etc which they use. For example, you can use [https://www.nexusmods.com/stardewvalley/mods/1915 Content Patcher] to edit the game's images and data with zero programming needed. The rest of this guide is about creating a new SMAPI mod; for content packs, see the documentation for the mod that'll read it.
+
Yep. Many SMAPI mods support '[[Modding:Content packs|content packs]]', which let you provide JSON text files, images, etc which they use. For example, you can [[Modding:Content Patcher|use Content Patcher]] to edit the game's images and data with zero programming needed. The rest of this guide is about creating a new SMAPI mod; for content packs, see [[Modding:Content Patcher]] or [[Modding:Content pack frameworks]] for information on other available frameworks (or the mod documentation if creating a content pack for a different mod).
  
 
===Where can I get help?===
 
===Where can I get help?===
<span id="help"></span>The Stardew Valley modding community is very welcoming. Feel free to ask for help in [[Modding:Community#Discord|#modding on the Stardew Valley Discord]].
+
<span id="help"></span>The Stardew Valley modding community is very welcoming. Feel free to ask for help in [[Modding:Community#Discord|#making-mods on the Stardew Valley Discord]].
  
 
==Get started==
 
==Get started==
Line 38: Line 40:
 
* [https://docs.microsoft.com/en-us/dotnet/csharp/quick-starts/ ''C# Quickstarts''] teaches the basics of C# with interactive examples.
 
* [https://docs.microsoft.com/en-us/dotnet/csharp/quick-starts/ ''C# Quickstarts''] teaches the basics of C# with interactive examples.
 
* [https://mva.microsoft.com/en-us/training-courses/c-fundamentals-for-absolute-beginners-16169 ''C# Fundamentals for Absolute Beginners''] is a video guide which will walk you through C#, from the basic concepts to event-driven programming (which is what SMAPI mods mostly use).
 
* [https://mva.microsoft.com/en-us/training-courses/c-fundamentals-for-absolute-beginners-16169 ''C# Fundamentals for Absolute Beginners''] is a video guide which will walk you through C#, from the basic concepts to event-driven programming (which is what SMAPI mods mostly use).
 +
* Already know how to program? Get a quick overview of C# syntax and concepts at [https://learnxinyminutes.com/docs/csharp/ LearnXinYMinutes.]
 +
 +
A couple of concepts SMAPI uses frequently
 +
* [https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/generics ''Generics'']
 +
* [https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/events/ ''Event-based programming'']
 +
* For serialization, SMAPI usually uses [https://www.newtonsoft.com/json ''NewtonSoft'']
  
 
===Requirements===
 
===Requirements===
Line 45: Line 53:
 
# Install [[Modding:Player Guide/Getting Started#Install SMAPI|SMAPI]].
 
# Install [[Modding:Player Guide/Getting Started#Install SMAPI|SMAPI]].
 
# Install the IDE (''integrated development environment'').
 
# Install the IDE (''integrated development environment'').
#* On Linux: install [http://www.monodevelop.com/ MonoDevelop].
+
#* On Linux: install [http://www.monodevelop.com/ MonoDevelop] or [https://www.jetbrains.com/rider/ Rider].
#* On Mac: install [https://www.visualstudio.com/vs/visual-studio-mac/ Visual Studio 2017 for Mac]. (This is a rebranded MonoDevelop.)
+
#* On Mac: install [https://visualstudio.microsoft.com/vs/mac/ Visual Studio for Mac]. (This is a rebranded MonoDevelop.)
#* On Windows: install [https://www.visualstudio.com/vs/community/ Visual Studio 2017 Community]. When the installer asks about workloads, enable ''.NET Desktop Development''.  
+
#* On Windows: install [https://visualstudio.microsoft.com/vs/community/ Visual Studio Community]. When the installer asks about workloads, enable ''.NET Desktop Development''.
 +
# Install the [https://dotnet.microsoft.com/en-us/download/dotnet/6.0 NET 6.0 SDK (x64 version)].
  
If you're not familiar with Visual Studio 2017 (on Windows/Mac) or MonoDevelop (on Linux), [[Modding:IDE reference]] explains how to do the important stuff you need for this guide.
+
If you're not familiar with Visual Studio (on Windows/Mac) or MonoDevelop (on Linux), [[Modding:IDE reference]] explains how to do the important stuff you need for this guide.
  
 
==Create a basic mod==
 
==Create a basic mod==
Line 55: Line 64:
 
If you're experienced enough to skip the tutorial, here's a quick summary of this section:
 
If you're experienced enough to skip the tutorial, here's a quick summary of this section:
 
{{collapse|expand for quick start|content=&#32;
 
{{collapse|expand for quick start|content=&#32;
# Create an empty C# class library project.
+
# Create an empty ''Class Library'' project. (Don't select ''Class Library (.NET Framework)''!)
# Target .NET Framework 4.5, 4.5.1, or 4.5.2 for best compatibility.
+
# Target .NET 6.
# 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.
+
# Reference the [https://smapi.io/package/readme <samp>Pathoschild.Stardew.ModBuildConfig</samp> NuGet package] to automatically add the right references depending on the platform the mod is being compiled on.
# Create a <tt>ModEntry</tt> class which subclasses <tt>StardewModdingAPI.Mod</tt>.
+
# Create a <samp>ModEntry</samp> class which subclasses <samp>StardewModdingAPI.Mod</samp>.
# Override the <tt>Entry</tt> method, and write your code using the [[#Mod APIs|SMAPI events and APIs]].
+
# Override the <samp>Entry</samp> method, and write your code using the [[#Mod APIs|SMAPI events and APIs]].
# Create a [[#Add your manifest|<tt>manifest.json</tt> file]] which describes your mod for SMAPI.
+
# Create a [[#Add your manifest|<samp>manifest.json</samp> file]] which describes your mod for SMAPI.
 
# Create [[#Release your mod|a zip file containing the mod files]] for release.
 
# Create [[#Release your mod|a zip file containing the mod files]] for release.
 
}}
 
}}
Line 67: Line 76:
 
A SMAPI mod is a compiled library (DLL) with an entry method that gets called by SMAPI, so let's set that up.
 
A SMAPI mod is a compiled library (DLL) with an entry method that gets called by SMAPI, so let's set that up.
  
# Open Visual Studio 2017 or MonoDevelop.
+
# Open Visual Studio or MonoDevelop.
# Create a solution with a .NET Framework class library project (see [[Modding:IDE reference#create-project|how to create a project]]). '''Make sure you choose .NET Framework, not .NET Core or .NET Standard.'''
+
# Create a solution with a ''Class Library'' project (see [[Modding:IDE reference#create-project|how to create a project]]). (Don't select ''Class Library (.NET Framework)''! That's a separate thing with a similar name.)
# Change the target framework to .NET Framework 4.5, 4.5.1, or 4.5.2 for best compatibility (see [[Modding:IDE reference#set-target-framework|how to change target framework]]).
+
# Target .NET 6 (see [[Modding:IDE reference#set-target-framework|how to change target framework]]).You may need to [https://dotnet.microsoft.com/en-us/download/dotnet/6.0 install the SDK]. <br /> <small>That's the version installed and used by the game. Newer versions may not be installed for players, and SMAPI may not be able to load them. [[#What does "target NET 6.0" mean?|Yes we know it's EOL]].</small>
# Reference the [https://www.nuget.org/packages/Pathoschild.Stardew.ModBuildConfig <tt>Pathoschild.Stardew.ModBuildConfig</tt> NuGet package] (see [[Modding:IDE reference#add-nuget|how to add the package]]).
+
# Reference the [https://www.nuget.org/packages/Pathoschild.Stardew.ModBuildConfig <samp>Pathoschild.Stardew.ModBuildConfig</samp> NuGet package] (see [[Modding:IDE reference#add-nuget|how to add the package]]).
 +
#* If you are getting an error stating ''The type or namespace name "StardewModdingAPI" could not be found'', then it's possible that your game path is not being detected. You will need to set the GamePath property to the game's executable directory. This can be done by adding a ''GamePath'' property to the ''PropertyGroup'' in your ''.csproj'' settings.
 
# Restart Visual Studio/MonoDevelop after installing the package.
 
# Restart Visual Studio/MonoDevelop after installing the package.
  
Line 77: Line 87:
  
 
<ol>
 
<ol>
<li>Delete the <tt>Class1.cs</tt> or <tt>MyClass.cs</tt> file (see [[Modding:IDE reference#delete-file|how to delete a file]]).</li>
+
<li>Delete the <samp>Class1.cs</samp> or <samp>MyClass.cs</samp> file (see [[Modding:IDE reference#delete-file|how to delete a file]]).</li>
<li>Add a C# class file called <tt>ModEntry.cs</tt> to your project (see [[Modding:IDE reference#Add a file|how to add a file]]).</li>
+
<li>Add a C# class file called <samp>ModEntry.cs</samp> to your project (see [[Modding:IDE reference#Add a file|how to add a file]]).</li>
<li>Put this code in the file (replace <tt>YourProjectName</tt> with the name of your project):
+
<li>Put this code in the file (replace <samp>YourProjectName</samp> with the name of your project):
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
using System;
 
using System;
 
using Microsoft.Xna.Framework;
 
using Microsoft.Xna.Framework;
Line 91: Line 101:
 
{
 
{
 
     /// <summary>The mod entry point.</summary>
 
     /// <summary>The mod entry point.</summary>
     public class ModEntry : Mod
+
     internal sealed class ModEntry : Mod
 
     {
 
     {
 
         /*********
 
         /*********
Line 110: Line 120:
 
         /// <param name="sender">The event sender.</param>
 
         /// <param name="sender">The event sender.</param>
 
         /// <param name="e">The event data.</param>
 
         /// <param name="e">The event data.</param>
         private void OnButtonPressed(object sender, ButtonPressedEventArgs e)
+
         private void OnButtonPressed(object? sender, ButtonPressedEventArgs e)
 
         {
 
         {
 
             // ignore if player hasn't loaded a save yet
 
             // ignore if player hasn't loaded a save yet
Line 117: Line 127:
  
 
             // print button presses to the console window
 
             // print button presses to the console window
             this.Monitor.Log($"{Game1.player.Name} pressed {e.Button}.");
+
             this.Monitor.Log($"{Game1.player.Name} pressed {e.Button}.", LogLevel.Debug);
 
         }
 
         }
 
     }
 
     }
 
}
 
}
</source></li>
+
</syntaxhighlight></li>
 
</ol>
 
</ol>
  
Line 128: Line 138:
 
# <code>using X;</code> (see [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive using directive]) makes classes in that namespace available in your code.
 
# <code>using X;</code> (see [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive using directive]) makes classes in that namespace available in your code.
 
# <code>namespace YourProjectName</code> (see [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/namespace namespace keyword]) defines the scope for your mod code. Don't worry about this when you're starting out, Visual Studio or MonoDevelop will add it automatically when you add a file.
 
# <code>namespace YourProjectName</code> (see [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/namespace namespace keyword]) defines the scope for your mod code. Don't worry about this when you're starting out, Visual Studio or MonoDevelop will add it automatically when you add a file.
# <code>public class ModEntry : Mod</code> (see [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/class class keyword]) creates your mod's main class, and subclasses SMAPI's <tt>Mod</tt> class. SMAPI will detect your <tt>Mod</tt> subclass automatically, and <tt>Mod</tt> gives you access to SMAPI's APIs.
+
# <code>public class ModEntry : Mod</code> (see [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/class class keyword]) creates your mod's main class, and subclasses SMAPI's <samp>Mod</samp> class. SMAPI will detect your <samp>Mod</samp> subclass automatically, and <samp>Mod</samp> gives you access to SMAPI's APIs.
 
# <code>public override void Entry(IModHelper helper)</code> is the method SMAPI will call when your mod is loaded into the game. The <code>helper</code> provides convenient access to many of SMAPI's APIs.
 
# <code>public override void Entry(IModHelper helper)</code> is the method SMAPI will call when your mod is loaded into the game. The <code>helper</code> provides convenient access to many of SMAPI's APIs.
# <code>helper.Events.Input.ButtonPressed += this.OnButtonPressed;</code> adds an 'event handler' (i.e. a method to call) when the button-pressed event happens. In other words, when a button is pressed (the <tt>helper.Events.Input.ButtonPressed</tt> event), SMAPI will call your <tt>this.OnButtonPressed</tt> method. See [[Modding:Modder Guide/APIs/Events|events in the SMAPI reference]] for more info.
+
# <code>helper.Events.Input.ButtonPressed += this.OnButtonPressed;</code> adds an 'event handler' (''i.e.,'' a method to call) when the button-pressed event happens. In other words, when a button is pressed (the <samp>helper.Events.Input.ButtonPressed</samp> event), SMAPI will call your <samp>this.OnButtonPressed</samp> method. See [[Modding:Modder Guide/APIs/Events|events in the SMAPI reference]] for more info.
 +
 
 +
Note: if you get compile errors along the lines of "Feature XX is not available in C# <number>. Please use language version <number> or greater", add <samp>&lt;LangVersion&gt;Latest&lt;/LangVersion&gt;</samp> to your <samp>.csproj</samp>.
  
 
===Add your manifest===
 
===Add your manifest===
Line 136: Line 148:
  
 
<ol>
 
<ol>
<li>Add a file named <tt>manifest.json</tt> to your project.</li>
+
<li>Add a file named <samp>manifest.json</samp> to your project.</li>
 
<li>Paste this code into the file:
 
<li>Paste this code into the file:
<source lang="json">
+
<syntaxhighlight lang="json">
 
{
 
{
 
   "Name": "<your project name>",
 
   "Name": "<your project name>",
Line 146: Line 158:
 
   "UniqueID": "<your name>.<your project name>",
 
   "UniqueID": "<your name>.<your project name>",
 
   "EntryDll": "<your project name>.dll",
 
   "EntryDll": "<your project name>.dll",
   "MinimumApiVersion": "2.10.0",
+
   "MinimumApiVersion": "3.0.0",
 
   "UpdateKeys": []
 
   "UpdateKeys": []
 
}
 
}
</source></li>
+
</syntaxhighlight></li>
<li>Replace the <tt>&lt;...&gt;</tt> placeholders with the correct info. Don't leave any <tt>&lt;&gt;</tt> symbols!</li>
+
<li>Replace the <samp>&lt;...&gt;</samp> placeholders with the correct info. Don't leave any <samp>&lt;&gt;</samp> symbols!</li>
 
</ol>
 
</ol>
  
Line 156: Line 168:
  
 
===Try your mod===
 
===Try your mod===
# Build the project.<br /><small>If you did the ''[[#Create the project|create the project]]'' steps correctly, this will automatically add your mod to the game's <tt>Mods</tt> folder.</small>
+
# Build the project.<br /><small>If you did the ''[[#Create the project|create the project]]'' steps correctly, this will automatically add your mod to the game's <samp>Mods</samp> folder.</small>
 
# Run the game through SMAPI.
 
# 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.
 
The mod so far will just send a message to the console window whenever you press a key in the game.
 +
 +
'''Changing console text color'''
 +
 +
The default text-colors in the console may be set in a way that makes them unreadable. To change text color:
 +
 +
*'''Steam''': Open Steam and right-click the game in your library. Click "Browse local files" under "Manage" and then open the <samp>config.json</samp> file in your "/Stardew Valley/smapi-internal/" folder, search for "ConsoleColors" and then edit the "Trace" and "Debug" colours so that they become visible within your console.
 +
 +
*'''Linux''': The default path on linux is <samp>~/.local/share/Steam/steamapps/common/Stardew Valley/smapi-internal/config.json</samp>, unless you have installed your game on a different drive/outside the standard steam library folder.
 +
 +
*'''Windows''': The default path on Windows should be "C:\Program files (x86)\Steam\steamapps\common\Stardew Valley\smapi-internal\config.json", unless you have installed your game on a different drive/outside the standard steam library folder.
  
 
===Troubleshoot===
 
===Troubleshoot===
Line 168: Line 190:
 
#* In Visual Studio, click ''Build > Rebuild Solution'' and check the ''Output'' pane or ''Error'' list.
 
#* In Visual Studio, click ''Build > Rebuild Solution'' and check the ''Output'' pane or ''Error'' list.
 
#* In MonoDevelop, click ''Build > Rebuild All'' and wait until it's done. Then click the "Build: XX errors, XX warnings" bar at the top, and check the ''XX Errors'' and ''Build Output'' tabs.
 
#* In MonoDevelop, click ''Build > Rebuild All'' and wait until it's done. Then click the "Build: XX errors, XX warnings" bar at the top, and check the ''XX Errors'' and ''Build Output'' tabs.
 +
# If one of the errors (not necessarily the first one) says the package can't find your game folder, see [[Modding:Modder Guide/Test and Troubleshoot#Visual Studio can't find the game/SMAPI/MonoGame DLLs|''Visual Studio can't find the game/SMAPI/MonoGame DLLs'']].
 
# See the [[Modding:Modder Guide/Test and Troubleshoot|troubleshooting guide]].
 
# See the [[Modding:Modder Guide/Test and Troubleshoot|troubleshooting guide]].
# If all else fails, come ask for help in the [[Modding:Community#Discord|#modding in the Stardew Valley Discord]]. :)
+
# If all else fails, come ask for help in the [[Modding:Community#Discord|#making-mods in the Stardew Valley Discord]]. :)
  
==Go further==
+
==FAQs==
===SMAPI APIs===
+
===Where's the SMAPI documentation?===
SMAPI provides a set of APIs you can use to do more. See [[Modding:Modder Guide/APIs|SMAPI reference]] for more info.
+
This is just the 'getting started' tutorial. For more documentation, see the [[Modding:Modder Guide/APIs|SMAPI reference]] and the [[Modding:Index|topics listed on the index page]].
  
===Crossplatform support===
+
===Can I look at other mods' code?===
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:
+
Yep, nearly 70% of SMAPI mods have public source code. To find their code:
 +
 
 +
# Open the [https://smapi.io/mods/ mod compatibility page].
 +
# Click "show advanced info" under the search box.
 +
# Look in the "code" column for links.
 +
 
 +
===How do I make my mod work crossplatform?===
 +
SMAPI will automatically adjust your mod so it works on Linux, MacOS, and Windows. However, there are a few things you should do to avoid problems:
  
 
<ol>
 
<ol>
<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>
+
<li>Use the [https://smapi.io/package/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>
  
<li>Use <tt>Path.Combine</tt> to build file paths, don't hardcode path separators since they won't work on all platforms.
+
<li>Use <samp>System.IO.Path.Combine</samp> to build file paths, don't hardcode path separators since they won't work on all platforms.
  
<source lang="c#">
+
<syntaxhighlight lang="c#">
// ✘ Don't do this! It will crash on Linux/Mac.
+
// ✘ Don't do this! It won't work on Linux/Mac.
string path = Helper.DirectoryPath + "\assets\asset.xnb";
+
string path = this.Helper.DirectoryPath + "\assets\image.png";
  
 
// ✓ This is OK
 
// ✓ This is OK
string path = Path.Combine(Helper.DirectoryPath, "assets", "asset.xnb");
+
string path = Path.Combine(this.Helper.DirectoryPath, "assets", "image.png");
</source></li>
+
</syntaxhighlight></li>
  
<li>Use <tt>Helper.DirectoryPath</tt>, don't try to determine the mod path yourself.
+
<li>Use <samp>this.Helper.DirectoryPath</samp>, don't try to determine the mod path yourself.
  
<source lang="c#">
+
<syntaxhighlight lang="c#">
// ✘ Don't do this! It will crash if SMAPI rewrites the assembly (e.g. to update or crossplatform it).
+
// ✘ Don't do this! It will crash if SMAPI rewrites the assembly (''e.g.,'' to update or crossplatform it).
 
string modFolder = Assembly.GetCallingAssembly().Location;
 
string modFolder = Assembly.GetCallingAssembly().Location;
  
 
// ✓ This is OK
 
// ✓ This is OK
string modFolder = Helper.DirectoryPath;
+
string modFolder = this.Helper.DirectoryPath;
</source></li>
+
</syntaxhighlight></li>
 +
<li>An ''asset name'' identifies an asset you can load through a content API like <code><nowiki>Game1.content.Load<T>("asset name")</nowiki></code>. This is ''not'' a file path, and the asset name format doesn't always match the file path format. When comparing asset names, make sure you use <code>PathUtilities.NormalizeAssetName("some/path")</code> instead of path helpers.
 +
 
 +
<syntaxhighlight lang="c#">
 +
// ✘ Don't do this! It won't work on Windows.
 +
bool isAbigail = (asset.Name == Path.Combine("Characters", "Abigail"));
 +
bool isAbigail2 = (asset.Name == PathUtilities.NormalizePath("Characters", "Abigail"));
 +
 
 +
// ✓ This is OK
 +
bool isAbigail = (asset.Name == PathUtilities.NormalizeAssetName("Characters", "Abigail"));
 +
</syntaxhighlight>
 +
 
 +
Note that there's no need to normalize asset names you pass into SMAPI APIs, which normalize them automatically (though it doesn't hurt to do it anyway):
 +
<syntaxhighlight lang="c#">
 +
// ✓ This is OK
 +
helper.Content.Load<Texture2D>("Characters/Abigail");
 +
helper.Content.Load<Texture2D>(@"Characters\Abigail");
 +
</syntaxhighlight>
 +
</li>
 
</ol>
 
</ol>
  
===Decompile the game code===
+
===How do I decompile the game code?<span id="decompile"></span>===
When you start working on more complex mods, you may need to look at how the game code works.
+
It's often useful to see how the game code works. The game code is compiled into <samp>StardewValley.dll</samp> (''i.e.,'' converted to a machine-readable format), but you can decompile it get a mostly-readable approximation of the original code. (This might not be fully functional due to decompiler limitations, but you'll be able to see what it's doing.)
  
To decompile the game code so you can read it (though it won't be fully functional due to decompiler limitations):
+
To decompile the game code...
* On Windows:
 
*# Open <tt>StardewValley.exe</tt> in [https://www.jetbrains.com/decompiler/ dotPeek].
 
*# 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.
 
* On Linux/Mac:
 
*# Open <tt>StardewValley.exe</tt> in MonoDevelop through ''File &gt; Open''.
 
*# Change ''Language'' from ''Summary'' to ''C#''.
 
  
To unpack the XNB data/image files, see [[Modding:Editing XNB files]].
+
:# First-time setup:
 +
:## Install {{github|icsharpcode/ILSpy/releases|ILSpy}} for Windows (get the "ILSpy_binaries" file under assets), or [https://github.com/icsharpcode/AvaloniaILSpy/releases Avalonia ILSpy] for Linux and macOS.
 +
:## Open ILSpy.
 +
:## Click ''View > Options'', scroll to the "Other" section at the bottom, and enable "Always qualify member references".
 +
:# Open <samp>Stardew Valley.dll</samp> in ILSpy.
 +
:# Make sure ''C#'' is selected in the language drop-down (not IL, IL with C#, or ReadyToRun).
 +
:# Right-click on ''Stardew Valley'' and choose ''Save Code'' to create a decompiled project you can open in Visual Studio.
 +
:## If you're using Avalonia ILSpy, make sure to add the <samp>.csproj</samp> file extension to the filename in the save dialog, like so: <samp>Stardew-Valley.csproj</samp> (Otherwise, the project won't decompile properly.)
  
{{modding guide footer
+
To unpack the XNB data/image/map files, see [[Modding:Editing XNB files]].
|prev =
+
 
|next = [[Modding:Modder Guide/Test and Troubleshoot|Test and Troubleshoot]]
+
===What does "target NET 6.0" mean?===
}}
+
 
 +
There are multiple different things here.
 +
 
 +
* target version: this is the version of .NET your binary is compiled against. You have to target net 6.0 for stardew (and in general, you can at maximum use the version of .NET the person loading you uses.)
 +
* sdk version: this is the version of the .NET you installed. You can target any version less than your sdk version. If I have net 7.0 installed with VS 2022, I can still target net 6.0.
 +
* C# version - guess what? the language is versioned separately from the .NET version (although there is a correspondence) and you can pick your language version with <code><langversion></code> in the .csproj.
  
 +
[[es:Modding:Guía del Modder/Introducción]]
 +
[[fr:Modding:Guide du Moddeur/Commencer]]
 
[[pt:Modificações:Guia do Modder/Começando]]
 
[[pt:Modificações:Guia do Modder/Começando]]
 +
[[ru:Модификации:Моддер гайд/Приступая к работе]]
 +
[[zh:模组:创建 SMAPI 模组]]

Latest revision as of 12:19, 1 April 2024

Creating SMAPI mods SMAPI mascot.png


Modding:Index

If you're making a mod for the first time, see Modding:Index#Creating mods for a short description of the differences between C# mods and content packs. You don't need to use C# to make mods.

Do you want to create SMAPI mods for Stardew Valley with C#? This guide is for you!

Intro

What is a SMAPI mod?

A SMAPI mod uses the SMAPI modding API to extend the game logic. The mod can respond when something happens in the game (like when an object is placed in the world), run code periodically (like once per update tick), change the game's assets and data, etc. SMAPI mods are written in C# using .NET, and Stardew Valley uses MonoGame for the game logic (drawing to the screen, user input, etc).

Why do mods use SMAPI?

SMAPI does a lot for you! For example, SMAPI will...

  1. Load your mod into the game. Code mods aren't possible without SMAPI to load them.
  2. Provide APIs and events which let you interact with the game in ways you otherwise couldn't. There are simplified APIs for game asset/data changes, player configuration, translation, reflection, etc. These are covered later in the guide.
  3. Rewrite your mod for crossplatform compatibility when it's loaded. That lets you write mod code without worrying about the differences between the Linux/Mac/Windows versions of the game.
  4. Rewrite your mod to update it. SMAPI detects and fixes mod code broken by a game update in common cases.
  5. Intercept errors. If your mod crashes or causes an error, SMAPI will intercept the error, show the error details in the console window, and in most cases automatically recover the game. This means your mod won't accidentally crash the game, and it makes it much easier to troubleshoot errors.
  6. Provide update checks. SMAPI automatically alerts players when a new version of your mod is available.
  7. Provide compatibility checks. SMAPI automatically detects when your mod is incompatible and disables it before it causes problems, so players aren't left with broken games.

Can I make a mod?

Yes! This guide will help you create a simple mod step-by-step. If you follow along, you'll have created a mod! Then you'll just need to make it do what you want.

If 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!

If 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 the Learning C# references below to fill in any gaps.

Can I make a mod without SMAPI?

Yep. Many SMAPI mods support 'content packs', which let you provide JSON text files, images, etc which they use. For example, you can use Content Patcher to edit the game's images and data with zero programming needed. The rest of this guide is about creating a new SMAPI mod; for content packs, see Modding:Content Patcher or Modding:Content pack frameworks for information on other available frameworks (or the mod documentation if creating a content pack for a different mod).

Where can I get help?

The Stardew Valley modding community is very welcoming. Feel free to ask for help in #making-mods on the Stardew Valley Discord.

Get started

Learn C#

Since mods are written in C#, it's a good idea to get acquainted with it first. You don't need to memorise everything, but a grasp of the basics (like fields, methods, variables, and classes) will make everything else much easier.

Some useful resources:

A couple of concepts SMAPI uses frequently

Requirements

Before you start:

  1. Read the Player Guide. The rest of this guide assumes you're already familiar with using mods.
  2. Install Stardew Valley.
  3. Install SMAPI.
  4. Install the IDE (integrated development environment).
  5. Install the NET 6.0 SDK (x64 version).

If you're not familiar with Visual Studio (on Windows/Mac) or MonoDevelop (on Linux), Modding:IDE reference explains how to do the important stuff you need for this guide.

Create a basic mod

Quick start

If you're experienced enough to skip the tutorial, here's a quick summary of this section:

expand for quick start 
  1. Create an empty Class Library project. (Don't select Class Library (.NET Framework)!)
  2. Target .NET 6.
  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 a ModEntry 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.

Create the project

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

  1. Open Visual Studio or MonoDevelop.
  2. Create a solution with a Class Library project (see how to create a project). (Don't select Class Library (.NET Framework)! That's a separate thing with a similar name.)
  3. Target .NET 6 (see how to change target framework).You may need to install the SDK.
    That's the version installed and used by the game. Newer versions may not be installed for players, and SMAPI may not be able to load them. Yes we know it's EOL.
  4. Reference the Pathoschild.Stardew.ModBuildConfig NuGet package (see how to add the package).
    • If you are getting an error stating The type or namespace name "StardewModdingAPI" could not be found, then it's possible that your game path is not being detected. You will need to set the GamePath property to the game's executable directory. This can be done by adding a GamePath property to the PropertyGroup in your .csproj settings.
  5. Restart Visual Studio/MonoDevelop after installing the package.

Add the code

Next let's add some code SMAPI will run.

  1. Delete the Class1.cs or MyClass.cs file (see how to delete a file).
  2. Add a C# class file called ModEntry.cs to your project (see how to add a file).
  3. Put this code in the file (replace YourProjectName with the name of your project):
    using System;
    using Microsoft.Xna.Framework;
    using StardewModdingAPI;
    using StardewModdingAPI.Events;
    using StardewModdingAPI.Utilities;
    using StardewValley;
    
    namespace YourProjectName
    {
        /// <summary>The mod entry point.</summary>
        internal sealed class ModEntry : Mod
        {
            /*********
            ** Public methods
            *********/
            /// <summary>The mod entry point, called after the mod is first loaded.</summary>
            /// <param name="helper">Provides simplified APIs for writing mods.</param>
            public override void Entry(IModHelper helper)
            {
                helper.Events.Input.ButtonPressed += this.OnButtonPressed;
            }
    
    
            /*********
            ** Private methods
            *********/
            /// <summary>Raised after the player presses a button on the keyboard, controller, or mouse.</summary>
            /// <param name="sender">The event sender.</param>
            /// <param name="e">The event data.</param>
            private void OnButtonPressed(object? sender, ButtonPressedEventArgs e)
            {
                // ignore if player hasn't loaded a save yet
                if (!Context.IsWorldReady)
                    return;
    
                // print button presses to the console window
                this.Monitor.Log($"{Game1.player.Name} pressed {e.Button}.", LogLevel.Debug);
            }
        }
    }
    

Here's a breakdown of what that code is doing:

  1. using X; (see using directive) makes classes in that namespace available in your code.
  2. namespace YourProjectName (see namespace keyword) defines the scope for your mod code. Don't worry about this when you're starting out, Visual Studio or MonoDevelop will add it automatically when you add a file.
  3. public class ModEntry : Mod (see class keyword) creates your mod's main class, and subclasses SMAPI's Mod class. SMAPI will detect your Mod subclass automatically, and Mod gives you access to SMAPI's APIs.
  4. public override void Entry(IModHelper helper) is the method SMAPI will call when your mod is loaded into the game. The helper provides convenient access to many of SMAPI's APIs.
  5. helper.Events.Input.ButtonPressed += this.OnButtonPressed; adds an 'event handler' (i.e., a method to call) when the button-pressed event happens. In other words, when a button is pressed (the helper.Events.Input.ButtonPressed event), SMAPI will call your this.OnButtonPressed method. See events in the SMAPI reference for more info.

Note: if you get compile errors along the lines of "Feature XX is not available in C# <number>. Please use language version <number> or greater", add <LangVersion>Latest</LangVersion> to your .csproj.

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:
    {
      "Name": "<your project name>",
      "Author": "<your name>",
      "Version": "1.0.0",
      "Description": "<One or two sentences about the mod>",
      "UniqueID": "<your name>.<your project name>",
      "EntryDll": "<your project name>.dll",
      "MinimumApiVersion": "3.0.0",
      "UpdateKeys": []
    }
    
  3. Replace the <...> placeholders with the correct info. Don't leave any <> symbols!

This will be listed in the console output when the game is launching. For more info, see the manifest docs.

Try your mod

  1. Build the project.
    If you did the create the project steps correctly, this will automatically add your mod to the game's Mods folder.
  2. 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.

Changing console text color

The default text-colors in the console may be set in a way that makes them unreadable. To change text color:

  • Steam: Open Steam and right-click the game in your library. Click "Browse local files" under "Manage" and then open the config.json file in your "/Stardew Valley/smapi-internal/" folder, search for "ConsoleColors" and then edit the "Trace" and "Debug" colours so that they become visible within your console.
  • Linux: The default path on linux is ~/.local/share/Steam/steamapps/common/Stardew Valley/smapi-internal/config.json, unless you have installed your game on a different drive/outside the standard steam library folder.
  • Windows: The default path on Windows should be "C:\Program files (x86)\Steam\steamapps\common\Stardew Valley\smapi-internal\config.json", unless you have installed your game on a different drive/outside the standard steam library folder.

Troubleshoot

If the tutorial mod doesn't work...

  1. Review the above steps to make sure you didn't skip something.
  2. Check for error messages which may explain why it's not working:
    • In Visual Studio, click Build > Rebuild Solution and check the Output pane or Error list.
    • In MonoDevelop, click Build > Rebuild All and wait until it's done. Then click the "Build: XX errors, XX warnings" bar at the top, and check the XX Errors and Build Output tabs.
  3. If one of the errors (not necessarily the first one) says the package can't find your game folder, see Visual Studio can't find the game/SMAPI/MonoGame DLLs.
  4. See the troubleshooting guide.
  5. If all else fails, come ask for help in the #making-mods in the Stardew Valley Discord. :)

FAQs

Where's the SMAPI documentation?

This is just the 'getting started' tutorial. For more documentation, see the SMAPI reference and the topics listed on the index page.

Can I look at other mods' code?

Yep, nearly 70% of SMAPI mods have public source code. To find their code:

  1. Open the mod compatibility page.
  2. Click "show advanced info" under the search box.
  3. Look in the "code" column for links.

How do I make my mod work crossplatform?

SMAPI will automatically adjust your mod so it works on Linux, MacOS, 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 System.IO.Path.Combine to build file paths, don't hardcode path separators since they won't work on all platforms.
    // ✘ Don't do this! It won't work on Linux/Mac.
    string path = this.Helper.DirectoryPath + "\assets\image.png";
    
    // ✓ This is OK
    string path = Path.Combine(this.Helper.DirectoryPath, "assets", "image.png");
    
  3. Use this.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 = this.Helper.DirectoryPath;
    
  4. An asset name identifies an asset you can load through a content API like Game1.content.Load<T>("asset name"). This is not a file path, and the asset name format doesn't always match the file path format. When comparing asset names, make sure you use PathUtilities.NormalizeAssetName("some/path") instead of path helpers.
    // ✘ Don't do this! It won't work on Windows.
    bool isAbigail = (asset.Name == Path.Combine("Characters", "Abigail"));
    bool isAbigail2 = (asset.Name == PathUtilities.NormalizePath("Characters", "Abigail"));
    
    // ✓ This is OK
    bool isAbigail = (asset.Name == PathUtilities.NormalizeAssetName("Characters", "Abigail"));
    

    Note that there's no need to normalize asset names you pass into SMAPI APIs, which normalize them automatically (though it doesn't hurt to do it anyway):

    // ✓ This is OK
    helper.Content.Load<Texture2D>("Characters/Abigail");
    helper.Content.Load<Texture2D>(@"Characters\Abigail");
    

How do I decompile the game code?

It's often useful to see how the game code works. The game code is compiled into StardewValley.dll (i.e., converted to a machine-readable format), but you can decompile it get a mostly-readable approximation of the original code. (This might not be fully functional due to decompiler limitations, but you'll be able to see what it's doing.)

To decompile the game code...

  1. First-time setup:
    1. Install ILSpy for Windows (get the "ILSpy_binaries" file under assets), or Avalonia ILSpy for Linux and macOS.
    2. Open ILSpy.
    3. Click View > Options, scroll to the "Other" section at the bottom, and enable "Always qualify member references".
  2. Open Stardew Valley.dll in ILSpy.
  3. Make sure C# is selected in the language drop-down (not IL, IL with C#, or ReadyToRun).
  4. Right-click on Stardew Valley and choose Save Code to create a decompiled project you can open in Visual Studio.
    1. If you're using Avalonia ILSpy, make sure to add the .csproj file extension to the filename in the save dialog, like so: Stardew-Valley.csproj (Otherwise, the project won't decompile properly.)

To unpack the XNB data/image/map files, see Modding:Editing XNB files.

What does "target NET 6.0" mean?

There are multiple different things here.

  • target version: this is the version of .NET your binary is compiled against. You have to target net 6.0 for stardew (and in general, you can at maximum use the version of .NET the person loading you uses.)
  • sdk version: this is the version of the .NET you installed. You can target any version less than your sdk version. If I have net 7.0 installed with VS 2022, I can still target net 6.0.
  • C# version - guess what? the language is versioned separately from the .NET version (although there is a correspondence) and you can pick your language version with <langversion> in the .csproj.