Changes

m
→‎Constants: Updated to match SMAPI 4.0.6 Constants Class
Line 3: Line 3:  
SMAPI provides some C# objects you can use to simplify your code.
 
SMAPI provides some C# objects you can use to simplify your code.
   −
==Constants==
+
==Metadata==
The <tt>Constants</tt> class provides metadata about SMAPI and the game.
+
===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 <samp>Mods/Your Mod Name 1.27.5-541-1-27-5-1598664794/YourModFolder</samp> by default, and players can organize their mod folders like <samp>Mods/For single-player/YourModFolder</samp>.
 +
* 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:
 +
<syntaxhighlight lang="c#">
 +
var data = this.Helper.Data.ReadJsonFile<SomeDataModel>("assets/data.json");
 +
</syntaxhighlight>
 +
 
 +
If you really need a full path, you should use <samp>this.Helper.DirectoryPath</samp> and <samp>Path.Combine</samp> to get it:
 +
<syntaxhighlight lang="c#">
 +
string path = Path.Combine(this.Helper.DirectoryPath, "assets", "data.json"); // "assets/data.json" in the current mod's folder
 +
var file = new FileInfo(path);
 +
</syntaxhighlight>
 +
 
 +
See ''[[#Constants|Constants]]'' for other paths like the game folder.
 +
 
 +
===Constants===
 +
The <samp>Constants</samp> class provides metadata about SMAPI and the game.
    
{| class="wikitable"
 
{| class="wikitable"
Line 11: Line 30:  
! meaning
 
! meaning
 
|-
 
|-
| <tt>Constants.ApiVersion</tt>
+
| <samp>Constants.ApiVersion</samp>
 
| The version of the running SMAPI instance.
 
| The version of the running SMAPI instance.
 
|-
 
|-
| <tt>Constants.MinimumGameVersion</tt><br /><tt>Constants.MaximumGameVersion</tt>
+
| <samp>Constants.MinimumGameVersion</samp><br /><samp>Constants.MaximumGameVersion</samp>
 
| The game versions supported by the running SMAPI instance.
 
| The game versions supported by the running SMAPI instance.
 
|-
 
|-
| <tt>Constants.ExecutionPath</tt>
+
| <samp>Constants.TargetPlatform</samp>
| The absolute path to the <tt>Stardew Valley</tt> folder.
+
| The current operating system (one of <samp>Android</samp>, <samp>Linux</samp>, <samp>Mac</samp>, or <samp>Windows</samp>).
 
|-
 
|-
| <tt>Constants.DataPath</tt>
+
| <samp>Constants.GameFramework</samp>
 +
| The game framework running the game (one of <samp>Xna</samp> or <samp>MonoGame</samp>).
 +
|-
 +
| <samp>Constants.GamePath</samp>
 +
| The absolute path to the <samp>Stardew Valley</samp> folder.
 +
|-
 +
| <samp>Constants.ContentPath</samp>
 +
| The absolute path to the game's <samp>Content</samp> folder.
 +
|-
 +
| <samp>Constants.DataPath</samp>
 
| The absolute path to the game's data folder (which contains the [[Saves|save folder]]).
 
| The absolute path to the game's data folder (which contains the [[Saves|save folder]]).
 
|-
 
|-
| <tt>Constants.LogDir</tt>
+
| <samp>Constants.LogDir</samp>
 
| The absolute path to the folder containing the game and SMAPI logs.
 
| The absolute path to the folder containing the game and SMAPI logs.
 
|-
 
|-
| <tt>Constants.SavesPath</tt>
+
| <samp>Constants.SavesPath</samp>
 
| The absolute path to the [[Saves|save folder]].
 
| The absolute path to the [[Saves|save folder]].
 
|-
 
|-
| <tt>Constants.CurrentSavePath</tt>
+
| <samp>Constants.CurrentSavePath</samp>
 
| The absolute path to the current save folder, if a save is loaded.
 
| The absolute path to the current save folder, if a save is loaded.
 
|-
 
|-
| <tt>Constants.SaveFolderName</tt>
+
| <samp>Constants.SaveFolderName</samp>
| The name of the current save folder (like <tt>Name_012345789</tt>), if a save is loaded.
+
| The name of the current save folder (like <samp>Name_012345789</samp>), if a save is loaded.
 
|}
 
|}
   −
==Context==
+
===Context===
The <tt>Context</tt> class provides information about the game state and player control:
+
The <samp>Context</samp> class provides information about the game state and player control.
   −
{| class="wikitable"
+
; Game/player state&#58;
 +
: {| class="wikitable"
 
|-
 
|-
 
! value
 
! value
 
! meaning
 
! meaning
 
|-
 
|-
| <tt>Context.IsWorldReady</tt>
+
| <samp>Context.IsGameLaunched</samp>
 +
| Whether the game has been launched and initialised. This becomes true immediately before the first update tick.
 +
|-
 +
| <samp>Context.IsWorldReady</samp>
 
| Whether the player has loaded a save and the world has finished initialising. Useful for ignoring events before the save is loaded.
 
| Whether the player has loaded a save and the world has finished initialising. Useful for ignoring events before the save is loaded.
 
|-
 
|-
| <tt>Context.IsPlayerFree</tt>
+
| <samp>Context.IsPlayerFree</samp>
| Whether <tt>Context.IsWorldReady</tt> and the player is free to act on the world (no menu is displayed, no cutscene is in progress, etc).
+
| Whether <samp>Context.IsWorldReady</samp> and the player is free to act on the world (no menu is displayed, no cutscene is in progress, etc).
 
|-
 
|-
| <tt>Context.CanPlayerMove</tt>
+
| <samp>Context.CanPlayerMove</samp>
| Whether <tt>Context.IsPlayerFree</tt> and the player is free to move (e.g. not using a tool).
+
| Whether <samp>Context.IsPlayerFree</samp> and the player is free to move (''e.g.,'' not using a tool).
 
|-
 
|-
| <tt>Context.IsMultiplayer</tt>
+
| <samp>Context.IsInDrawLoop</samp>
| {{SMAPI upcoming|2.6|content=Whether <tt>Context.IsWorldReady</tt>, and the player loaded the save in multiplayer mode (regardless of whether any other players are connected).}}
+
| Whether the game is currently running the draw loop. This isn't relevant to most mods, since you should use [[Modding:Modder Guide/APIs/Events|display events]] to draw to the screen.
 +
|}
 +
 
 +
; [[Multiplayer]]&#58;
 +
: {| class="wikitable"
 +
|-
 +
! value
 +
! meaning
 +
|-
 +
| <samp>Context.IsMultiplayer</samp>
 +
| Whether <samp>Context.IsWorldReady</samp>, and the world was loaded in multiplayer mode (regardless of whether any other players are connected) or is currently in split-screen mode.
 +
|-
 +
| <samp>Context.IsSplitScreen</samp>
 +
| Whether <samp>Context.IsMultiplayer</samp> and the ''current player'' is in split-screen mode. This doesn't apply for remote players.
 
|-
 
|-
| <tt>Context.IsMainPlayer</tt>
+
| <samp>Context.HasRemotePlayers</samp>
| {{SMAPI upcoming|2.6|content=Whether <tt>Context.IsWorldReady</tt>, and the player is the main player. This is always true in single-player, and true when hosting in multiplayer.}}
+
| Whether <samp>Context.IsMultiplayer</samp> and any players are connected over the network.
 +
|-
 +
| <samp>Context.IsMainPlayer</samp>
 +
| Whether <samp>Context.IsWorldReady</samp>, and the player is the main player. This is always true in single-player, and true when hosting in multiplayer.
 +
|-
 +
| <samp>Context.IsOnHostComputer</samp>
 +
| Whether the current player is on the host computer. This is true when <samp>Context.IsMainPlayer</samp>, or for farmhands in split-screen mode.
 +
|-
 +
| <samp>Context.ScreenId</samp>
 +
| The unique ID of the current screen in split-screen mode. The main player always has ID 0. A screen is always assigned a new ID when it's opened (so a player who quits and rejoins will get a new screen ID).
 
|}
 
|}
   −
==Dates==
+
==Helpers==
Use <tt>SDate</tt> for calculating in-game dates. You start by creating a date:
+
===Dates===
<source lang="c#">
+
Use <samp>SDate</samp> for calculating in-game dates. You start by creating a date:
 +
<syntaxhighlight lang="c#">
 
var date = SDate.Now(); // current date
 
var date = SDate.Now(); // current date
 
var date = new SDate(28, "spring"); // date in the current year
 
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 = new SDate(28, "spring", 2); // date in the given year
</source>
+
var date = SDate.From(Game1.Date); // from a game date
 +
</syntaxhighlight>
    
Then you can calculate offsets from any date:
 
Then you can calculate offsets from any date:
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
// add days
 
// add days
 
new SDate(28, "spring", 1).AddDays(370); // 06 fall in year 4
 
new SDate(28, "spring", 1).AddDays(370); // 06 fall in year 4
Line 75: Line 131:  
// subtract days
 
// subtract days
 
new SDate(01, "spring", 2).AddDays(-1); // 28 winter in year 1
 
new SDate(01, "spring", 2).AddDays(-1); // 28 winter in year 1
</source>
+
</syntaxhighlight>
    
...and compare dates:
 
...and compare dates:
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
var a = new SDate(01, "spring");
 
var a = new SDate(01, "spring");
 
var b = new SDate(02, "spring");
 
var b = new SDate(02, "spring");
 
if (a < b) // true
 
if (a < b) // true
 
   ...
 
   ...
</source>
+
</syntaxhighlight>
 +
 
 +
...and get a translated date string:
 +
<syntaxhighlight lang="c#">
 +
var date = new SDate(15, "summer");
 +
string message = $"See you on {date.ToLocaleString(withYear: false)}!"; // See you on Summer 15!
 +
</syntaxhighlight>
   −
Note that <tt>SDate</tt> won't let you create invalid dates:
+
Note that <samp>SDate</samp> won't let you create invalid dates:
<source lang="C#">
+
<syntaxhighlight lang="C#">
 
// ArgumentException: Invalid day '30', must be a value from 1 to 28.
 
// ArgumentException: Invalid day '30', must be a value from 1 to 28.
 
new SDate(30, "spring");
 
new SDate(30, "spring");
Line 92: Line 154:  
// ArithmeticException: Adding -1 days to 01 spring Y1 would result in invalid date 28 winter Y0.
 
// ArithmeticException: Adding -1 days to 01 spring Y1 would result in invalid date 28 winter Y0.
 
new SDate(01, "spring", 1).AddDays(-1);
 
new SDate(01, "spring", 1).AddDays(-1);
</source>
+
</syntaxhighlight>
    
Once created, dates have a few properties you can use:
 
Once created, dates have a few properties you can use:
Line 100: Line 162:  
! meaning
 
! meaning
 
|-
 
|-
| <tt>Day</tt>
+
| <samp>Day</samp>
 
| The day of month.
 
| The day of month.
 
|-
 
|-
| <tt>Season</tt>
+
| <samp>Season</samp>
 
| The normalised season name.
 
| The normalised season name.
 
|-
 
|-
| <tt>Year</tt>
+
| <samp>SeasonIndex</samp>
 +
| The zero-based season index recognised by game methods like <samp>Utility.getSeasonNameFromNumber</samp>.
 +
|-
 +
| <samp>Year</samp>
 
| The year number.
 
| The year number.
 
|-
 
|-
| <tt>DayOfWeek</tt>
+
| <samp>DayOfWeek</samp>
| The day of week (like <tt>Monday</tt>).
+
| The day of week (like <samp>Monday</samp>).
 
|-
 
|-
| <tt>DaysSinceStart</tt>
+
| <samp>DaysSinceStart</samp>
| The number of days since the first day, inclusively (i.e. 01 spring Y1 = 1).
+
| The number of days since the first day, inclusively (''i.e.,'' 01 spring Y1 = 1).
 
|}
 
|}
   −
==Input==
+
===File paths===
SMAPI's <tt>SButton</tt> constants unify the [https://msdn.microsoft.com/en-us/library/microsoft.xna.framework.input.buttons.aspx <tt>Buttons</tt>], [https://msdn.microsoft.com/en-us/library/microsoft.xna.framework.input.keys.aspx <tt>Keys</tt>], [https://msdn.microsoft.com/en-us/library/microsoft.xna.framework.input.mousestate.aspx <tt>MouseState</tt>], and <tt>InputButton</tt> constants. SMAPI events use this to let you handle controller, keyboard, and mouse input without needing separate code for each. See [[Modding:Key bindings]] for a list of values.
+
<samp>PathUtilities</samp> provides utility methods for working with file paths and [[Modding:Modder Guide/APIs/Content|asset names]], complementing the <samp>Path</samp> class provided by .NET:
   −
SMAPI provides extensions to convert any of the other constants to <tt>SButton</tt>:
+
{| class="wikitable"
<source lang="c#">
+
|-
SButton key = Keys.A.ToSButton(); // SButton.A
+
! method
SButton button = Buttons.A.ToSButton(); // SButton.ControllerA
+
! usage
SButton input = new InputButton(true).ToSButton(); // SButton.MouseLeft
+
|-
</source>
+
| <code>GetSegments</code>
 +
| Split a path into its delimited segments, like <code>/usr/bin/example</code> &rarr; <code>usr</code>, <code>bin</code>, and <code>example</code>. For example:
   −
You can also convert <tt>SButton</tt> to the other constants. This uses a <tt>TryGet</tt> approach since <tt>SButton</tt> is a superset of the others (e.g. you can't convert <tt>SButton.ControllerA</tt> to a keyboard value):
+
<syntaxhighlight lang="c#">
<source lang="c#">
+
string[] segments = PathUtilities.GetSegments(Constants.ExecutionPath);
SButton value = SButton.A;
+
</syntaxhighlight>
if (value.TryGetKeyboard(out Keys key))
+
|-
  ...;
+
| <code>IsSafeRelativePath</code>
if (value.TryGetController(out Buttons button))
+
| Get whether a path is relative and doesn't contain directory climbing (<code>../</code>), so it's guaranteed to be within the parent folder.
  ...;
+
|-
if (value.TryGetStardewInput(out InputButton input))
+
| <code>IsSlug</code>
  ...;
+
| 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).
</source>
+
|-
 +
| <code>NormalizePath</code>
 +
| Normalize file paths or asset names to match the format used by the current OS. For example:
   −
Two last extensions let you check how the button is mapped in the game:
+
<syntaxhighlight lang="c#">string path = PathUtilities.NormalizePathSeparators(@"Characters\Dialogue//Abigail");
<source lang="c#">
+
// Linux/Mac: "Characters/Dialogue/Abigail"
SButton button = SButton.MouseLeft;
+
// Windows: "Characters\Dialogue\Abigail"
if (button.IsUseToolButton())
+
</syntaxhighlight>
  // use tool
+
|}
else if (button.IsActionButton())
  −
  // perform action
  −
</source>
     −
You can use <tt>SButton</tt> values directly in your [[../Config|config model]], and they'll be represented by their names:
+
===Per-screen data===
{| class="wikitable"
+
SMAPI's <code>PerScreen&lt;T&gt;</code> utility manages a separate value for each local screen in split-screen mode. See [[Modding:Modder Guide/APIs/Multiplayer#Per-screen data|<samp>PerScreen&lt;T&gt;</samp> in the multiplayer API]] for details.
| <source lang="c#">
  −
internal class ModConfig
  −
{
  −
  public SButton DoThingButton { get; set; } = SButton.LeftControl;
  −
}
  −
</source>
  −
| &rarr;
  −
| <source lang="json">
  −
{
  −
  "DoThingButton": "LeftControl"
  −
}
  −
</source>
  −
|}
     −
==Semantic versions==
+
===Semantic versions===
Use <tt>SemanticVersion</tt> to manipulate and compare versions per the [http://semver.org/ Semantic Versioning 2.0 standard]. Example usage:
+
Use <samp>SemanticVersion</samp> to manipulate and compare versions per the [http://semver.org/ Semantic Versioning 2.0 standard]. Example usage:
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
// build version from parts
 
// build version from parts
 
ISemanticVersion version = new SemanticVersion(5, 1, 0, "beta");
 
ISemanticVersion version = new SemanticVersion(5, 1, 0, "beta");
Line 175: Line 227:  
new SemanticVersion("5.10").IsNewerThan("5.10-beta"); // true
 
new SemanticVersion("5.10").IsNewerThan("5.10-beta"); // true
 
new SemanticVersion("5.1").IsBetween("5.0", "5.2"); // true
 
new SemanticVersion("5.1").IsBetween("5.0", "5.2"); // true
</source>
+
</syntaxhighlight>
 +
 
 +
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.
   −
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 <samp>SButton</samp> constants uniquely represent controller, keyboard, and mouse button presses or clicks. See the [[../Input|Input]] page for more info.
2

edits