Changes

Jump to navigation Jump to search
m
Text replacement - "i.e. " to "''i.e.,'' "
Line 1: Line 1:  
←[[Modding:Index|Index]]
 
←[[Modding:Index|Index]]
   −
'''This page is for modders. Players: see [[Modding:SMAPI compatibility]] instead.''' For updating Content Patcher or XNB mods, see [[Modding:Migrate XNB changes to Stardew Valley 1.3|''Migrate XNB changes to Stardew Valley 1.3'']].
+
{{Modder compatibility header}}
 +
For updating Content Patcher or XNB mods, see [[Modding:Migrate XNB changes to Stardew Valley 1.3|''Migrate XNB changes to Stardew Valley 1.3'']].
    
This page explains how to update your SMAPI mod code for compatibility with Stardew Valley 1.3.
 
This page explains how to update your SMAPI mod code for compatibility with Stardew Valley 1.3.
Line 30: Line 31:  
==Major changes==
 
==Major changes==
 
===⚠ Net fields===
 
===⚠ Net fields===
A 'net type' is any of several new classes which Stardew Valley 1.3 uses to sync data between players, named for the <code>Net</code> prefix in their name. A net type can represent a simple value like <tt>NetBool</tt>, or complex values like <tt>NetFieldDictionary</tt>. Many existing fields have been converted to net types (called 'net fields'), each wrapping the underlying value:
+
A 'net type' is any of several new classes which Stardew Valley 1.3 uses to sync data between players, named for the <code>Net</code> prefix in their name. A net type can represent a simple value like <samp>NetBool</samp>, or complex values like <samp>NetFieldDictionary</samp>. Many existing fields have been converted to net types (called 'net fields'), each wrapping the underlying value:
: <source lang="C#">
+
: <syntaxhighlight lang="C#">
 
NetString str = new NetString("bar");
 
NetString str = new NetString("bar");
 
if (str.Value == "bar") // true
 
if (str.Value == "bar") // true
</source>
+
</syntaxhighlight>
    
Impact on mods:
 
Impact on mods:
* The game will regularly collect all the net fields reachable from <tt>Game1.netWorldState</tt> and sync them with other players. That means that many mod changes will be synchronised automatically in multiplayer.
+
* The game will regularly collect all the net fields reachable from <samp>Game1.netWorldState</samp> and sync them with other players. That means that many mod changes will be synchronised automatically in multiplayer.
 
* Net fields can implicitly convert to their equivalent normal values (like <code>bool x = new NetBool(true)</code>), but their conversion rules can be counterintuitive and error-prone. For example, <code>item?.category == null && item?.category != null</code> can both be true at once. '''Always avoid implicit casts to minimise bugs.'''
 
* Net fields can implicitly convert to their equivalent normal values (like <code>bool x = new NetBool(true)</code>), but their conversion rules can be counterintuitive and error-prone. For example, <code>item?.category == null && item?.category != null</code> can both be true at once. '''Always avoid implicit casts to minimise bugs.'''
   Line 45: Line 46:  
===⚠ Location changes for farmhands===
 
===⚠ Location changes for farmhands===
 
In multiplayer, if the current player isn't the main player:
 
In multiplayer, if the current player isn't the main player:
* '''<tt>Game1.locations</tt> does not contain the actual list of locations'''. It contains a set of locations generated locally, which don't match the actual in-game locations. This also affects related functionality like <tt>Utility.getAllCharacters()</tt>, which searches the in-game locations. There's no fix for this yet. You can use SMAPI's <tt>helper.Multiplayer.GetActiveLocations()</tt> to get the list of locations currently being sync'd from the host, but there's currently no way to fetch all locations. That means SMAPI mods installed by a non-main player have no way to fetch all NPCs, locations, objects, etc.
+
* '''<samp>Game1.locations</samp> does not contain the actual list of locations'''. It contains a set of locations generated locally, which don't match the actual in-game locations. This also affects related functionality like <samp>Utility.getAllCharacters()</samp>, which searches the in-game locations. There's no fix for this yet. You can use SMAPI's <samp>helper.Multiplayer.GetActiveLocations()</samp> to get the list of locations currently being sync'd from the host, but there's currently no way to fetch all locations. That means SMAPI mods installed by a non-main player have no way to fetch all NPCs, locations, objects, etc.
* <tt>Game1.currentLocation</tt> is always an active location, but may be null when the player transition between locations. Make sure any references to that field can handle it being null.
+
* <samp>Game1.currentLocation</samp> is always an active location, but may be null when the player transition between locations. Make sure any references to that field can handle it being null.
    
===Game1.player.friendships is obsolete===
 
===Game1.player.friendships is obsolete===
The <tt>Game1.player.friendships</tt> field is always null in Stardew Valley 1.3. Use the new <tt>Game1.player.friendshipData</tt> field instead, which wraps the raw data with a more useful model and more data.
+
The <samp>Game1.player.friendships</samp> field is always null in Stardew Valley 1.3. Use the new <samp>Game1.player.friendshipData</samp> field instead, which wraps the raw data with a more useful model and more data.
 +
 
 +
To convert old code:
 +
{| class="wikitable"
 +
! old field
 +
! new equivalent
 +
|-
 +
| <code>Game1.player.friendships[name][0]</code>
 +
| <code>Game1.player.friendshipData[name].Points</code>
 +
|-
 +
| <code>Game1.player.friendships[name][1]</code>
 +
| <code>Game1.player.friendshipData[name].GiftsThisWeek</code>
 +
|-
 +
| <code>Game1.player.friendships[name][2]</code>
 +
| <code>Game1.player.friendshipData[name].TalkedToToday</code> (0 → false, 1 → true)
 +
|-
 +
| <code>Game1.player.friendships[name][3]</code>
 +
| <code>Game1.player.friendshipData[name].GiftsToday</code> (0 → false, 1 → true)
 +
 
 +
|}
    
===Texture constructor arguments===
 
===Texture constructor arguments===
Many constructors which previously accepted <tt>Texture2D texture</tt> arguments now take a <tt>string textureName</tt> argument instead. It's usually better to use SMAPI's content API to override textures instead. You can change the cached texture after the object is constructed (may need [[Modding:SMAPI APIs#Reflection|reflection]]), but don't change the texture name to avoid multiplayer sync issues.
+
Many constructors which previously accepted <samp>Texture2D texture</samp> arguments now take a <samp>string textureName</samp> argument instead. It's usually better to use SMAPI's content API to override textures instead. You can change the cached texture after the object is constructed (may need [[Modding:Modder Guide/APIs|reflection]]), but don't change the texture name to avoid multiplayer sync issues.
    
===Reserved key bindings===
 
===Reserved key bindings===
Line 63: Line 83:     
===Overlay objects===
 
===Overlay objects===
Stardew Valley 1.3 adds an <tt>overlayObjects</tt> field to <tt>GameLocation</tt> instances. These have two special properties:
+
Stardew Valley 1.3 adds an <samp>overlayObjects</samp> field to <samp>GameLocation</samp> instances. These have two special properties:
 
* They're not synced to other players, so each player has their own overlay objects. (That's used for special quest items, so other players can't take your item.)
 
* They're not synced to other players, so each player has their own overlay objects. (That's used for special quest items, so other players can't take your item.)
 
* They're positioned on top of the normal object layer. (If there was already an object where the item is placed, the previous object will be hidden until you pick up the overlay object instead of being deleted.)
 
* They're positioned on top of the normal object layer. (If there was already an object where the item is placed, the previous object will be hidden until you pick up the overlay object instead of being deleted.)
Line 77: Line 97:  
|-
 
|-
 
| 2.3
 
| 2.3
| <tt>IReflectionHelper.GetPrivateField</tt><br /><tt>IReflectionHelper.GetPrivateMethod</tt><br /><tt>IReflectionHelper.GetPrivateProperty</tt>
+
| <samp>IReflectionHelper.GetPrivateField</samp><br /><samp>IReflectionHelper.GetPrivateMethod</samp><br /><samp>IReflectionHelper.GetPrivateProperty</samp>
| renamed to <tt>GetField</tt>, <tt>GetMethod</tt>, and <tt>GetProperty</tt> respectively; their return values have also been renamed (<tt>IPrivateField</tt> → <tt>IReflectedField</tt>, <tt>IPrivateProperty</tt> → <tt>IReflectedProperty</tt>, and <tt>IPrivateMethod</tt> → <tt>IReflectedMethod</tt>).
+
| renamed to <samp>GetField</samp>, <samp>GetMethod</samp>, and <samp>GetProperty</samp> respectively; their return values have also been renamed (<samp>IPrivateField</samp> → <samp>IReflectedField</samp>, <samp>IPrivateProperty</samp> → <samp>IReflectedProperty</samp>, and <samp>IPrivateMethod</samp> → <samp>IReflectedMethod</samp>).
 
|-
 
|-
 
| 2.3
 
| 2.3
| <tt>IReflectionHelper.GetPrivateValue</tt>
+
| <samp>IReflectionHelper.GetPrivateValue</samp>
| use <tt>GetPrivateField(...).GetValue()</tt> instead.
+
| use <samp>GetPrivateField(...).GetValue()</samp> instead.
 
|}
 
|}
    
===SMAPI event changes===
 
===SMAPI event changes===
Some SMAPI events were rewritten so they make sense in multiplayer. These also use a new architecture under the hood, so they provide much more useful event data (e.g. added/removed instead of just current values). The following events have breaking changes:
+
Some SMAPI events were rewritten so they make sense in multiplayer. These also use a new architecture under the hood, so they provide much more useful event data (''e.g.,'' added/removed instead of just current values). The following events have breaking changes:
    
{| class="wikitable"
 
{| class="wikitable"
Line 95: Line 115:  
! migration notes
 
! migration notes
 
|-
 
|-
| <tt>LocationEvents.CurrentLocationChanged</tt>
+
| <samp>LocationEvents.CurrentLocationChanged</samp>
 
| &rarr;
 
| &rarr;
| <tt>PlayerEvents.Warped</tt>
+
| <samp>PlayerEvents.Warped</samp>
 
| &#32;
 
| &#32;
* Change <tt>EventArgsCurrentLocationChanged</tt> to <tt>EventArgsPlayerWarped</tt>.
+
* Change <samp>EventArgsCurrentLocationChanged</samp> to <samp>EventArgsPlayerWarped</samp>.
 
|-
 
|-
| <tt>LocationEvents.LocationsChanged</tt>
+
| <samp>LocationEvents.LocationsChanged</samp>
 
|  
 
|  
 
| ''(same name)''
 
| ''(same name)''
 
| &#32;
 
| &#32;
* Change <tt>EventArgsGameLocationsChanged</tt> to <tt>EventArgsLocationsChanged</tt>.
+
* Change <samp>EventArgsGameLocationsChanged</samp> to <samp>EventArgsLocationsChanged</samp>.
* The event is now raised when ''any'' location is added/removed (including building interiors), not just the main world locations in <tt>Game1.locations</tt>. If you need to handle only main world locations, you can check <code>if (Game1.locations.Contains(e.NewLocation))</code>.
+
* The event is now raised when ''any'' location is added/removed (including building interiors), not just the main world locations in <samp>Game1.locations</samp>. If you need to handle only main world locations, you can check <code>if (Game1.locations.Contains(e.NewLocation))</code>.
* The event data previously contained the current list of locations; it now contains the locations added or removed since the last tick. If you previously used <tt>e.NewLocations</tt>, you can replace it with <tt>Game1.locations</tt>.
+
* The event data previously contained the current list of locations; it now contains the locations added or removed since the last tick. If you previously used <samp>e.NewLocations</samp>, you can replace it with <samp>Game1.locations</samp>.
 
|-
 
|-
| <tt>LocationEvents.LocationObjectsChanged</tt>
+
| <samp>LocationEvents.LocationObjectsChanged</samp>
 
| →
 
| →
| <tt>LocationEvents.ObjectsChanged</tt>
+
| <samp>LocationEvents.ObjectsChanged</samp>
 
| &#32;
 
| &#32;
 
* The event is now raised when objects are added/removed to ''any'' location (including building interiors), not just the current player's location. If you need to handle only the current player's location, you can check <code>if (e.Location == Game1.player.currentLocation)</code>.
 
* The event is now raised when objects are added/removed to ''any'' location (including building interiors), not just the current player's location. If you need to handle only the current player's location, you can check <code>if (e.Location == Game1.player.currentLocation)</code>.
* The event data previously contained the current location's list of objects; it now contains the location, and the objects added/removed in it since the last tick. If you previously used <tt>e.NewObjects</tt>, you can use <tt>e.Location.netObjects.FieldDict</tt> instead.
+
* The event data previously contained the current location's list of objects; it now contains the location, and the objects added/removed in it since the last tick. If you previously used <samp>e.NewObjects</samp>, you can use <samp>e.Location.netObjects.FieldDict</samp> instead.
 
|}
 
|}
   Line 120: Line 140:  
Stardew Valley 1.3 includes several changes which benefit modders. These aren't disruptive, but worth noting for use. Some of the most relevant are...
 
Stardew Valley 1.3 includes several changes which benefit modders. These aren't disruptive, but worth noting for use. Some of the most relevant are...
 
* Many more methods and properties are now virtual.
 
* Many more methods and properties are now virtual.
* <tt>Game1.WorldDate</tt> is a new field which provides a more useful way to check the date. This combines the day, season, and year with useful logic like day-of-week and date comparisons. This incorporates many of the features from SMAPI's <tt>SDate</tt> class.
+
* <samp>Game1.WorldDate</samp> is a new field which provides a more useful way to check the date. This combines the day, season, and year with useful logic like day-of-week and date comparisons. This incorporates many of the features from SMAPI's <samp>SDate</samp> class.
 
* Many type checks now allow subclasses (like <code>item.GetType() == typeof(Axe)</code> &rarr; <code>item is Axe</code>).
 
* Many type checks now allow subclasses (like <code>item.GetType() == typeof(Axe)</code> &rarr; <code>item is Axe</code>).
* Any <tt>GameLocation</tt> can now set <tt>IsGreenhouse = true</tt>, and crops will grow there all year.
+
* Any <samp>GameLocation</samp> can now set <samp>IsGreenhouse = true</samp>, and crops will grow there all year.
* Any <tt>NPC</tt> can now set <tt>IsSocial</tt> to determine whether they appear in the social menu.
+
* Any <samp>NPC</samp> can now set <samp>IsSocial</samp> to determine whether they appear in the social menu.
 
* Bee houses now find nearby flowers in any location, not only when placed on the farm.
 
* Bee houses now find nearby flowers in any location, not only when placed on the farm.
 
* Custom map tilesheets no longer need hacks to avoid the game's seasonal logic. Tilesheets which don't start with a season name and underscore won't be seasonalised.
 
* Custom map tilesheets no longer need hacks to avoid the game's seasonal logic. Tilesheets which don't start with a season name and underscore won't be seasonalised.
Line 137: Line 157:     
===This implicitly converts...===
 
===This implicitly converts...===
Sample warning: "''This implicitly converts '{0}' from Net{1} to {2}, but Net{1} has unintuitive implicit conversion rules. Consider comparing against the actual value instead to avoid bugs. See https://smapi.io/buildmsg/avoid-implicit-net-field-cast for details.''"
+
Sample warning: "''This implicitly converts '{0}' from Net{1} to {2}, but Net{1} has unintuitive implicit conversion rules. Consider comparing against the actual value instead to avoid bugs. See https://smapi.io/package/avoid-implicit-net-field-cast for details.''"
   −
Your code is referencing a [[#Net fields|net field]], which can cause subtle bugs. The field you're referencing has an equivalent non-net property, like <tt>monster.Health</tt> (<tt>int</tt>) instead of <tt>monster.health</tt> (<tt>NetBool</tt>). Change your code to use the suggested property instead.
+
Your code is referencing a [[#Net fields|net field]], which can cause subtle bugs. The field you're referencing has an equivalent non-net property, like <samp>monster.Health</samp> (<samp>int</samp>) instead of <samp>monster.health</samp> (<samp>NetBool</samp>). Change your code to use the suggested property instead.
    
===FieldName is a Net* field...===
 
===FieldName is a Net* field...===
Sample warning: "'''{0}' is a Net{1} field; consider using the {2} property instead. See https://smapi.io/buildmsg/avoid-net-field for details.''"
+
Sample warning: "'''{0}' is a Net{1} field; consider using the {2} property instead. See https://smapi.io/package/avoid-net-field for details.''"
    
Your code is referencing a [[#Net fields|net field]], which can cause subtle bugs. You should access the underlying value instead:
 
Your code is referencing a [[#Net fields|net field]], which can cause subtle bugs. You should access the underlying value instead:
 
<ul>
 
<ul>
<li>For a reference type (i.e. one that can contain <tt>null</tt>), you can use the <tt>.Value</tt> property (or <tt>.FieldDict</tt> for a <tt>NetDictionary</tt>):
+
<li>For a reference type (''i.e.,'' one that can contain <samp>null</samp>), you can use the <samp>.Value</samp> property (or <samp>.FieldDict</samp> for a <samp>NetDictionary</samp>):
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
if (building.indoors.Value == null)
 
if (building.indoors.Value == null)
</source>
+
</syntaxhighlight>
    
Or convert the value before comparison:
 
Or convert the value before comparison:
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
GameLocation indoors = building.indoors.Value;
 
GameLocation indoors = building.indoors.Value;
 
if(indoors == null)
 
if(indoors == null)
 
   // ...
 
   // ...
</source></li>
+
</syntaxhighlight></li>
<li>For a value type (i.e. one that can't contain <tt>null</tt>), check if the parent is null (if needed) and compare with <tt>.Value</tt>:
+
<li>For a value type (''i.e.,'' one that can't contain <samp>null</samp>), check if the parent is null (if needed) and compare with <samp>.Value</samp>:
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
if (item != null && item.category.Value == 0)
 
if (item != null && item.category.Value == 0)
</source></li>
+
</syntaxhighlight></li>
 
</ul>
 
</ul>
    
===The FieldName field is obsolete...===
 
===The FieldName field is obsolete...===
Sample warning: "''The 'Character.friendships' field is obsolete and should be replaced with 'friendshipData'. See https://smapi.io/buildmsg/avoid-obsolete-field for details.''"
+
Sample warning: "''The 'Character.friendships' field is obsolete and should be replaced with 'friendshipData'. See https://smapi.io/package/avoid-obsolete-field for details.''"
    
You're referencing a field which should no longer be used. Use the suggested field name instead to fix it.
 
You're referencing a field which should no longer be used. Use the suggested field name instead to fix it.
    
===An instance of analyzer ... cannot be created===
 
===An instance of analyzer ... cannot be created===
Update to the latest [https://www.visualstudio.com/vs/community/ Visual Studio 2017]; the NuGet package uses a recent feature that isn't available in older versions.
+
Update to the latest [https://visualstudio.microsoft.com/vs/community/ Visual Studio]; the NuGet package uses a recent feature that isn't available in older versions.
    
==FAQs==
 
==FAQs==
Line 185: Line 205:     
===Why not use implicit net field conversion?===
 
===Why not use implicit net field conversion?===
The migration guide suggests [[#Net fields|avoiding net field implicit conversion]]. This may be the most tedious part of the migration for many mods, and the code compiles fine without doing that, so it's tempting to just skip this step. That's not recommended. Although net field conversions will work fine in most cases, their conversion rules can cause strange bugs in unexpected places. It's better to avoid it altogether, rather than learn all the different cases where they'll cause problems. If you really want, you can [https://github.com/Pathoschild/SMAPI/blob/develop/docs/mod-build-config.md#code-warnings disable the build warnings] and decide for yourself when to use the implicit conversion.
+
The migration guide suggests [[#Net fields|avoiding net field implicit conversion]]. This may be the most tedious part of the migration for many mods, and the code compiles fine without doing that, so it's tempting to just skip this step. That's not recommended. Although net field conversions will work fine in most cases, their conversion rules can cause strange bugs in unexpected places. It's better to avoid it altogether, rather than learn all the different cases where they'll cause problems. If you really want, you can [https://smapi.io/package/code-warnings disable the build warnings] and decide for yourself when to use the implicit conversion.
    
[[Category:Modding]]
 
[[Category:Modding]]
105,667

edits

Navigation menu