Modding:Migrate to Stardew Valley 1.3

From Stardew Valley Wiki
Revision as of 22:16, 24 June 2018 by Pathoschild (talk | contribs) (→‎Multiplayer: update link)
Jump to navigation Jump to search

Index

This page is for modders. Players: see Modding:SMAPI compatibility instead. For updating Content Patcher or XNB mods, see 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.

Overview

Single player

At a high level, here's how to update a SMAPI mod:

  1. Update the mod build NuGet package to 2.1-beta. (You may need to enable the 'include prerelease' checkbox to see the beta.)
    This adds support for Stardew Valley 1.3, and adds code analysis which will report common problems in Stardew Valley 1.3 as build warnings.
  2. Restart Visual Studio to fully install the package.
  3. Rebuild your solution.
  4. Fix any compiler errors and warnings you see in the Error List pane in Visual Studio (or equivalent for other editors).
  5. See below for help with specific changes and warnings.
  6. Test all mod features to make sure they work.

If you need help updating your code, feel free to ask questions on the Stardew Valley Discord.

Multiplayer

The instructions for single player apply for multiplayer too. Stardew Valley 1.3 automatically synchronises most world changes to other players (see net fields), so many mods will work in multiplayer too. Some mods may need further changes, but that can only be decided case-by-case.

There are three common approaches to multiplayer compatibility:

  • Let anyone install the mod. If your mod makes changes to the world, make sure it'll work fine if different players have different versions or configuration, or only some of the players have it installed.
  • Only let the main player install the mod, which avoids complications from other players changing the same data. To do this, check SMAPI's Context.IsMainPlayer in your code.
  • Only enable in single-player mode. This eliminates all multiplayer and sync complications, though players may be disappointed. To do this, check SMAPI's Context.IsMultiplayer in your code.

Common issues:

  • If your mod adds custom buildings or items, the game may crash trying to sync them to other players.

Major changes

⚠ 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 Net prefix in their name. A net type can represent a simple value like NetBool, or complex values like NetFieldDictionary. Many existing fields have been converted to net types (called 'net fields'), each wrapping the underlying value:

NetString str = new NetString("bar");
if (str.Value == "bar") // true

Impact on mods:

  • The game will regularly collect all the net fields reachable from Game1.netWorldState 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 bool x = new NetBool(true)), but their conversion rules can be counterintuitive and error-prone. For example, item?.category == null && item?.category != null can both be true at once. Always avoid implicit casts to minimise bugs.

Suggested fix:

  • With the latest mod build package installed, rebuild your project. The package will detect net field references you need to update, and show an appropriate warning. See fix common build warnings below.

⚠ Location changes for farmhands

In multiplayer, if the current player isn't the main player:

  • Game1.locations 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 Utility.getAllCharacters(), which searches the in-game locations. There's no fix for this yet. You can use SMAPI's helper.Multiplayer.GetActiveLocations() 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.
  • Game1.currentLocation 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

The Game1.player.friendships field is always null in Stardew Valley 1.3. Use the new Game1.player.friendshipData field instead, which wraps the raw data with a more useful model and more data.

Texture constructor arguments

Many constructors which previously accepted Texture2D texture arguments now take a string textureName 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 reflection), but don't change the texture name to avoid multiplayer sync issues.

Reserved key bindings

Mods won't receive input sent to the chatbox, and they won't receive the toggle-chatbox key (T by default).

Reflection

If you use reflection to access private game code, double-check that the fields/properties/methods you're accessing still match. In particular, watch out for these changes:

  • fields/properties changing return type;
  • fields replaced by properties.

Overlay objects

Stardew Valley 1.3 adds an overlayObjects field to GameLocation 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 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.)

SMAPI deprecated APIs removed

Since nearly all SMAPI mods broke in Stardew Valley 1.2, SMAPI 2.6 also drops support for deprecated APIs:

since interfaces replacement
2.3 IReflectionHelper.GetPrivateField
IReflectionHelper.GetPrivateMethod
IReflectionHelper.GetPrivateProperty
renamed to GetField, GetMethod, and GetProperty respectively; their return values have also been renamed (IPrivateFieldIReflectedField, IPrivatePropertyIReflectedProperty, and IPrivateMethodIReflectedMethod).
2.3 IReflectionHelper.GetPrivateValue use GetPrivateField(...).GetValue() instead.

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:

old event new event migration notes
LocationEvents.CurrentLocationChanged PlayerEvents.Warped
  • Change EventArgsCurrentLocationChanged to EventArgsPlayerWarped.
LocationEvents.LocationsChanged (same name)
  • Change EventArgsGameLocationsChanged to EventArgsLocationsChanged.
  • The event is now raised when any location is added/removed (including building interiors), not just the main world locations in Game1.locations. If you need to handle only main world locations, you can check if (Game1.locations.Contains(e.NewLocation)).
  • 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 e.NewLocations, you can replace it with Game1.locations.
LocationEvents.LocationObjectsChanged LocationEvents.ObjectsChanged
  • 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 if (e.Location == Game1.player.currentLocation).
  • 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 e.NewObjects, you can use e.Location.netObjects.FieldDict instead.

Beneficial changes

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.
  • Game1.WorldDate 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 SDate class.
  • Many type checks now allow subclasses (like item.GetType() == typeof(Axe)item is Axe).
  • Any GameLocation can now set IsGreenhouse = true, and crops will grow there all year.
  • Any NPC can now set IsSocial 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.
  • 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.
  • Several changes to support upcoming SMAPI features and fixes.

Fix common build warnings

Make sure you check your Error List pane in Visual Studio (or equivalent in other IDEs) and fix any warnings. Here are some common ones:

Mismatch between the processor architecture...

Sample warning: "There was a mismatch between the processor architecture of the project being built "{0}" and the processor architecture of the reference "{1}". This mismatch may cause runtime failures."

That warning is normal. The error is saying that your build is set to 'Any CPU', but Stardew Valley is x86-only so it'll only work in x86 anyway. You can either ignore it, or change your platform target to x86.

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."

Your code is referencing a net field, which can cause subtle bugs. The field you're referencing has an equivalent non-net property, like monster.Health (int) instead of monster.health (NetBool). Change your code to use the suggested property instead.

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."

Your code is referencing a net field, which can cause subtle bugs. You should access the underlying value instead:

  • For a reference type (i.e. one that can contain null), you can use the .Value property (or .FieldDict for a NetDictionary):
    if (building.indoors.Value == null)
    

    Or convert the value before comparison:

    GameLocation indoors = building.indoors.Value;
    if(indoors == null)
       // ...
    
  • For a value type (i.e. one that can't contain null), check if the parent is null (if needed) and compare with .Value:
    if (item != null && item.category.Value == 0)
    

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."

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

Update to the latest Visual Studio 2017; the NuGet package uses a recent feature that isn't available in older versions.

FAQs

How do I test my code in multiplayer?

You can test mods in multiplayer on the same computer, by launching two instances of the game:

  1. Prepare player one:
    1. Launch SMAPI like usual.
    2. From the title screen: click co-op, then host.
    3. Start a new save slot (unless you've already created one). Make sure to set 'starting cabins' to at least one (you'll need one cabin per extra player).
  2. Prepare player two:
    1. Launch SMAPI again. (This will automatically create a separate log file.)
    2. From the title screen: click co-op, then join LAN game.
    3. Leave the 'Enter IP...' box empty and click OK.

Why not use implicit net field conversion?

The migration guide suggests 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 disable the build warnings and decide for yourself when to use the implicit conversion.