We are in the process of transferring the hosting and maintenance responsibilities of the wiki from Chucklefish to ConcernedApe, which is why the wiki will temporarily be hosted at stardewcommunitywiki.com. Soon the official wiki will resume at stardewvalleywiki.com and will be hosted going forward by ConcernedApe. Prior to the transfer to ConcernedApe we will update this site with the date on which the transfer will occur.

Modding:Modder Guide/APIs/Utilities

From Stardew Valley Wiki
Jump to: navigation, search

Creating SMAPI mods SMAPI mascot.png


Modding:Index

SMAPI provides some C# objects you can use to simplify your code.

Metadata

Mod path

Before handling mod folder paths, be aware that:

  • The mod's folder path is not consistent. The game is installed to different folders, Nexus mods are often unzipped into a folder like Mods/Your Mod Name 1.27.5-541-1-27-5-1598664794/YourModFolder by default, and players can organize their mod folders like Mods/For single-player/YourModFolder.
  • Paths are formatted differently on Linux/Mac/Android vs Windows.

You don't need to worry about that when using SMAPI APIs, which take relative paths and automatically fix the format if needed:

var data = this.Helper.Data.ReadJsonFile<SomeDataModel>("assets/data.json");

If you really need a full path, you should use this.Helper.DirectoryPath and Path.Combine to get it:

string path = Path.Combine(this.Helper.DirectoryPath, "assets", "data.json"); // "assets/data.json" in the current mod's folder
var file = new FileInfo(path);

See Constants for other paths like the game folder.

Constants

The Constants class provides metadata about SMAPI and the game.

value meaning
Constants.ApiVersion The version of the running SMAPI instance.
Constants.MinimumGameVersion
Constants.MaximumGameVersion
The game versions supported by the running SMAPI instance.
Constants.TargetPlatform The current operating system (one of Android, Linux, Mac, or Windows).
Constants.ExecutionPath The absolute path to the Stardew Valley folder.
Constants.DataPath The absolute path to the game's data folder (which contains the save folder).
Constants.LogDir The absolute path to the folder containing the game and SMAPI logs.
Constants.SavesPath The absolute path to the save folder.
Constants.CurrentSavePath The absolute path to the current save folder, if a save is loaded.
Constants.SaveFolderName The name of the current save folder (like Name_012345789), if a save is loaded.

Context

The Context class provides information about the game state and player control:

value meaning
Context.IsGameLaunched Whether the game has been launched and initialised. This becomes true immediately before the first update tick.
Context.IsWorldReady Whether the player has loaded a save and the world has finished initialising. Useful for ignoring events before the save is loaded.
Context.IsPlayerFree Whether Context.IsWorldReady and the player is free to act on the world (no menu is displayed, no cutscene is in progress, etc).
Context.CanPlayerMove Whether Context.IsPlayerFree and the player is free to move (e.g. not using a tool).
Context.IsMultiplayer Whether Context.IsWorldReady, and the player loaded the save in multiplayer mode (regardless of whether any other players are connected).
Context.IsMainPlayer Whether Context.IsWorldReady, and the player is the main player. This is always true in single-player, and true when hosting in multiplayer.

Helpers

Dates

Use SDate for calculating in-game dates. You start by creating a date:

var date = SDate.Now(); // current date
var date = new SDate(28, "spring"); // date in the current year
var date = new SDate(28, "spring", 2); // date in the given year
var date = SDate.From(Game1.Date); // from a game date

Then you can calculate offsets from any date:

// add days
new SDate(28, "spring", 1).AddDays(370); // 06 fall in year 4

// subtract days
new SDate(01, "spring", 2).AddDays(-1); // 28 winter in year 1

...and compare dates:

var a = new SDate(01, "spring");
var b = new SDate(02, "spring");
if (a < b) // true
  ...

...and get a translated date string:

var date = new SDate(15, "summer");
string message = $"See you on {date.ToLocaleString(withYear: false)}!"; // See you on Summer 15!

Note that SDate won't let you create invalid dates:

// ArgumentException: Invalid day '30', must be a value from 1 to 28.
new SDate(30, "spring");

// ArithmeticException: Adding -1 days to 01 spring Y1 would result in invalid date 28 winter Y0.
new SDate(01, "spring", 1).AddDays(-1);

Once created, dates have a few properties you can use:

property meaning
Day The day of month.
Season The normalised season name.
SeasonIndex The zero-based season index recognised by game methods like Utility.getSeasonNameFromNumber.
Year The year number.
DayOfWeek The day of week (like Monday).
DaysSinceStart The number of days since the first day, inclusively (i.e. 01 spring Y1 = 1).

File paths

PathUtilities provides utility methods for working with file paths and asset names, complementing the Path class provided by .NET:

method usage
GetSegments Split a path into its delimited segments, like /usr/bin/exampleusr, bin, and example. For example:
string[] segments = PathUtilities.GetSegments(Constants.ExecutionPath);
IsSafeRelativePath Get whether a path is relative and doesn't contain directory climbing (../), so it's guaranteed to be within the parent folder.
IsSlug Get whether a string can be used as a 'slug', containing only basic characters that are safe in all contexts (e.g. filenames, URLs, SMAPI IDs, etc).
NormalizePath Normalize file paths or asset names to match the format used by the current OS. For example:
string path = PathUtilities.NormalizePathSeparators(@"Characters\Dialogue//Abigail");
// Linux/Mac: "Characters/Dialogue/Abigail"
// Windows: "Characters\Dialogue\Abigail"

Per-screen data

Split-screen multiplayer swaps the entire game state between each player, so each mod runs across every player. For example, with four local players the UpdateTicked event would be raised four times per tick. The game manages its own state automatically (e.g. Game1.activeClickableMenu), but if your mod stores info in its own fields you may need to handle those yourself.

That can be tricky, so SMAPI provides PerScreen<T> to take care of it. A PerScreen<T> field manages a separate value for each local screen.

For example, this mod would keep track of the last button pressed by each player:

internal class ModEntry : Mod
{
    private readonly PerScreen<SButton> LastButtonPressed = new PerScreen<SButton>();


    public override void Entry(IModHelper helper)
    {
        helper.Events.Input.ButtonPressed += this.OnButtonPressed;
    }

    private void OnButtonPressed(object sender, ButtonPressedEventArgs e)
    {
        this.LastButtonPressed.Value = e.Button;
    }
}

If you haven't set a value for the current player, PerScreen<T> will return the default value for the type (e.g. 0 for int). You can change that by specifying your own default-value logic:

private readonly PerScreen<SButton> LastButtonPressed = new PerScreen<SButton>(createNewState: () => SButton.None);

Tip: you should almost always mark a per-screen field readonly. Overwriting the entire field (instead of setting the Value property) will clear the data for all players, instead of setting it for the current one.

Semantic versions

Use SemanticVersion to manipulate and compare versions per the Semantic Versioning 2.0 standard. Example usage:

// build version from parts
ISemanticVersion version = new SemanticVersion(5, 1, 0, "beta");

// build version from string
ISemanticVersion version = new SemanticVersion("5.1.0-beta");

// compare versions (also works with SemanticVersion instances instead of strings)
new SemanticVersion("5.2").IsOlderThan("5.10"); // true
new SemanticVersion("5.10").IsNewerThan("5.10-beta"); // true
new SemanticVersion("5.1").IsBetween("5.0", "5.2"); // true

Note that game versions before 1.2.0 and some mod versions are non-standard (e.g. Stardew Valley 1.11 comes before 1.2). All SMAPI versions are standard.

Input

SMAPI's SButton constants uniquely represent controller, keyboard, and mouse button presses or clicks. See the Input page for more info.