Modding:Migrate to Stardew Valley 1.6

From Stardew Valley Wiki
Revision as of 02:35, 19 January 2022 by Pathoschild (talk | contribs) (create initial draft)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Index

This page is for mod authors. Players: see Modding:Mod compatibility instead.

The following describes the upcoming SMAPI 4.0.0, and may change before release.

This page explains how to update your mods for compatibility with the next major game version (tentatively Stardew Valley 1.6.0), and documents some of the changes and new functionality.

This describes an unreleased alpha version of the game. Things will change before it's released!

FAQs

What's changing?

Stardew Valley 1.6 makes fundamental changes the game code to make the code more extensible for mods.

Is this the modapocalypse?

Yes. The update includes major changes to fundamental parts of the game, and SMAPI and Content Patcher can't feasibly rewrite older mods for compatibility with these changes. This will break a large proportion of existing mods until they're updated for the changes. However per discussion between the game developers and modding community, we've agreed that this short-term pain is more than offset by the huge long-term improvements to modding.

Which changes are likely to break mods?

How to update your mod

  1. Update your mod code and assets for any breaking changes listed below.
  2. If SMAPI still says your mod is incompatible, check the TRACE messages in the log file for the reason why.
    If the logs say "marked 'assume broken' in SMAPI's internal compatibility list", you can increase the Version in your content pack's manifest.json file to bypass it.
  3. Test the mod in-game and make any other changes needed.

Item overhaul

Overview

Stardew Valley 1.6 makes three major changes to how items work in the game:

  1. Each item type now has an ItemDataDefinition class which tells the game how to handle it. SMAPI mods can add custom item type definitions or patch the vanilla logic. Each definition has a unique prefix (like (O) for objects) which is used in qualified item IDs. The vanilla types are bigcraftables ((BC)), boots ((B)), furniture ((F)), hats ((H)), objects ((O)), pants ((P)), shirts ((S)), and weapons ((W)).
  2. Each item now has a locally unique string ID (ItemID) and a globally unique string ID (QualifiedItemID). The ItemID is assumed to only contain alphanumeric/underscore/dot characters so they can be used in fields delimited with spaces/slashes/commas, in filenames, etc. The QualifiedItemID is auto-generated by prefixing the ItemID with the item type identifier.

    For legacy reasons, the ItemID for vanilla item isn't globally unique. For example, Pufferfish (object 128) and Mushroom Box (bigcraftable 128) both have ItemID: 128. They can be distinguished by their QualifiedItemID, which is (O)128 and (BC)128 respectively.

    For mod items, both IDs should be globally unique. By convention the ItemID should include your mod ID or author name, like Example.ModId_ItemName.

  3. Custom items can now provide their own item texture, specified in a new field in the item dat assets (see below). The item's ParentSheetIndex field is the index within that texture.

In other words, the three important fields for items are:

name type description
ItemID string An item key which is only unique within its item type, like 128 (vanilla item) or Example.ModId_Watermelon (custom item).
QualifiedItemID string A globally unique item key, like (O)128 (vanilla item) or (O)Example.ModId_Watermelon (custom item).
ParentSheetIndex int The item's image sprite within its spritesheet (whether it's a vanilla spritesheet or custom one).

Item references

Item references throughout the game code now use the ItemID (and sometimes QualifiedItemID) instead of the ParentSheetIndex. Since the ItemID is identical to the index for existing vanilla items, most data assets are unaffected by this change. For example, here's from Data/NPCDispositions with one custom item:

"Universal_Like": "-2 -7 -26 -75 -80 72 395 613 634 635 636 637 638 724 459 Example.ModID_watermelon"

Define a custom item

You can define custom items for most vanilla item types using only Content Patcher or SMAPI's content API. The data asset for each item type has two new fields:

field effect
texture name The asset name for the texture under the game's Content folder. Use \ (or \\ in JSON) to separate name segments if needed. For example, objects use Maps\springobjects by default.
sprite index The index of the sprite within the above texture, starting at 0 for the top-left sprite.

Supported item types:

item type data asset sprite index index texture name index default texture name
big craftables Data/BigCraftablesInformation 10 11 TileSheets/Craftables
boots Data/Boots item sprite: 8
shoe color: 5
item sprite: 9
shoe color: 7
item sprite: Maps/springobjects
shoe color: Characters/Farmer/shoeColors
crops Data/Crops 2 9 TileSheets/crops
furniture Data/Furniture 8 9 TileSheets/furniture
fruit trees Data/FruitTrees 0 4 TileSheets/fruitTrees
hats Data/Hats 6 7 Characters/Farmer/hats
objects Data/ObjectInformation 9 10 Maps/springobjects
pants Data/ClothingInformation male: 3
female: 4
10 Characters/Farmer/pants
shirts Data/ClothingInformation male: 3
female: 4
10 Characters/Farmer/shirts
weapons Data/Weapons 15 16 TileSheets/weapons

For example, this content pack adds a new Pufferchick item with a custom image, custom gift tastes, and a custom crop that produces it. Note that item references in other data assets like Data/Crops and Data/NPCGiftTastes use the item ID.

{
    "Format": "2.0.0",
    "Changes": [
        // add item
        {
            "Action": "EditData",
            "Target": "Data/ObjectInformation",
            "Entries": {
                "Example.ModId_Pufferchick": "Pufferchick/1200/100/Seeds -74/Pufferchick/An example object.////0/Mods\\Example.ModId\\Objects"
            }
        },

        // add gift tastes
        {
            "Action": "EditData",
            "Target": "Data/NPCGiftTastes",
            "TextOperations": [
                {
                    "Operation": "Append",
                    "Target": ["Entries", "Universal_Love"],
                    "Value": "Example.ModId_Pufferchick",
                    "Delimiter": " " // if there are already values, add a space between them and the new one
                }
            ]
        },

        // add crop (Pufferchick is both seed and produce, like coffee beans)
        {
            "Action": "EditData",
            "Target": "Data/Crops",
            "Entries": {
                "Example.ModId_Pufferchick": "1 1 1 1 1/spring summer fall/0/Example.ModId_Pufferchick/-1/0/false/false/false/Mods\\Example.ModId\\Crops"
            }
        },

        // add item + crop images
        {
            "Action": "Load",
            "Target": "Mods/Example.ModId/Crops, Mods/Example.ModId/Objects",
            "FromFile": "assets/{{TargetWithoutPath}}.png" // assets/Crops.png, assets/Objects.png
        },
    ]
}

Fruit trees are a bit different since they have three separate entities (the sapling item, the fruit tree itself, and the fruit item it produces). Note that the key in Data/FruitTrees is the sapling's item ID, and the value references the fruit's item ID.

{
    "Format": "2.0.0",
    "Changes": [
        // add fruit + sapling items
        // note: sapling must have an edibility under 0 (usually -300) to be plantable
        {
            "Action": "EditData",
            "Target": "Data/ObjectInformation",
            "Entries": {
                "Example.ModId_Pufferfruit": "Pufferfruit/1200/100/Basic -6/Pufferfruit/An example fruit item.////1/Mods\\Example.ModId\\Objects",
                "Example.ModId_Puffersapling": "Puffersapling/1200/-300/Basic -74/Puffersapling/An example tree sapling.////2/Mods\\Example.ModId\\Objects"
            }
        },

        // add fruit tree
        {
            "Action": "EditData",
            "Target": "Data/FruitTrees",
            "Entries": {
                "Example.ModId_Puffersapling": "0/spring/Example.ModId_Pufferfruit/0/Mods\\Example.ModId\\FruitTrees"
            }
        },

        // add images
        {
            "Action": "Load",
            "Target": "Mods/Example.ModId/FruitTrees, Mods/Example.ModId/Objects",
            "FromFile": "assets/{{TargetWithoutPath}}.png" // assets/FruitTrees.png, assets/Objects.png
        },
    ]
}

The other data assets work just like Data/ObjectInformation in the first example.

Error items

In-game items with no underlying data (e.g. because you removed the mod which adds them) would previously cause issues like invisible items, errors, and crashes. This was partly mitigated by the bundled ErrorHandler mod.

Stardew Valley 1.6 adds comprehensive handling for such items. They'll be shown with a 🛇 sprite in inventory UIs and in-game, with the name Error Item and a description which indicates the missing item ID for troubleshooting.

For C# mods

Compare items
Since Item.QualifiedItemID is globally unique, it can be used to simplify comparing items. For example:
old code new code
item.ParentSheetIndex == 848 item.QualifiedItemID == "(O)848"
IsNormalObjectAtParentSheetIndex(item, 74) item.QualifiedItemID == "(O)74"
!item.bigCraftable && item.ParentSheetIndex == 128 item.QualifiedItemID == "(O)128"
item is Boots && item.ParentSheetIndex == 505 item.QualifiedItemID == "(B)505"

Caveat: flavored item don't have their own ID. For example, Blueberry Wine and Wine are both (O)348. This affects flavored jellies, juices, pickles, and wines. In those cases you should still compare their separate fields like preservedParentSheetIndex (which actually contains the preserved item's ItemID, not its ParentSheetIndex).

Construct items
Creating items works just like before, except that you now specify the item's ItemID (_not_ QualifiedItemID) instead of its ParentSheetIndex. For example:
new Object("634", 1);                      // vanilla item
new Object("Example.ModId_Watermelon", 1); // custom item

You can use a new utility method to construct items from their QualifiedItemID:

Item item = Utility.CreateItemByID("(B)505"); // Rubber Boots
Define custom item types
You can subclass ItemDataDefinition for your own item type, and add an instance to the ItemDataDefinition.ItemTypes and IdentifierLookup lists. This provides all the logic needed by the game to handle the item type: where to get item data, how to draw them, etc. This is extremely specialized, and multiplayer compatibility is unknown. Most mods should add custom items within the existing types instead.

Buff overhaul

1.6 rewrites buffs to work more consistently and be more extensible.

Overview

  • Buff logic is unified into Game1.player.buffs, which is the single source of truth for buff data. This replaces the previous player.added* and player.appliedBuffs fields, BuffsDisplay logic, enchantment stat bonuses, and boots/ring attribute changes on (un)equip.
  • This also removes limitations on buff types (e.g. buffs can add weapon bonuses and weapons can add attribute buffs) and buffable equipment (e.g. equipped tools can have buffs too).
  • Buff effects are now fully recalculated when they change, to fix a range of longstanding bugs like attribute drift and double-debuffs. Just like before, the buffs are managed locally; only the buff IDs and aggregate attribute effects are synced.

For C# mods:

  • Each buff now has a unique string ID. You can apply a new buff with the same ID to replace it (so you no longer need to manually find and remove previous instances of the buff).
  • You can add standard buff effects to any equipment by overriding Item.AddEquipmentEffects, or add custom behaviour/buffs by overriding Item.onEquip and Item.onUnequip.
  • You can add custom food or drink buffs by overriding Item.GetFoodOrDrinkBuffs().
  • The Buff constructor now supports a custom icon texture, sprite index, display name, description, and millisecond duration to fully support custom buffs.
  • You can change how buff attributes are displayed (or add new attributes) by extending the BuffsDisplay.displayAttributes list.

For example, here's how to create a custom buff which adds +3 speed:

Buff buff = new Buff(
    buff_id: "Example.ModId/ZoomZoom",
    display_name: "Zoom Zoom", // can optionally specify description text too
    icon_texture: this.Helper.Content.Load<Texture2D>("assets/zoom.png"),
    icon_sheet_index: 0,
    duration: 30_000, // 30 seconds
    buff_effects: new BuffEffects()
    {
        speed = { 10 } // shortcut for buff.speed.Value = 10
    }
)

You can also implement your own custom effects in code by checking if the buff is active, like Game1.player.hasBuff("Example.ModId/ZoomZoom").

Standardized data fields

1.6 standardizes the number of fields in data assets, and fixes inconsistencies between English and localized files. This is a major breaking change for content packs.

Three examples illustrate the standardization:

  • Data/CookingRecipes had four fields in English, and a fifth field in other languages for the display name. The display name field is now required in English too.
  • Data/BigCraftables had an optional 9th field which indicates whether it's a lamp, and a 10th field for the display name. Even if it's empty, the 9th field is now required (note the extra / before the last field):
    // before
    "151": "Marble Brazier/500/-300/Crafting -9/Provides a moderate amount of light./true/true/0/Marble Brazier", // 9 fields
    
    // after
    "151": "Marble Brazier/500/-300/Crafting -9/Provides a moderate amount of light./true/true/0//Marble Brazier" // 10 fields
    
  • Data/ObjectInformation had several optional fields at the end. These are now required even if empty:
    // before
    "0": "Weeds/0/-1/Basic/Weeds/A bunch of obnoxious weeds."
    
    // after
    "0": "Weeds/0/-1/Basic/Weeds/A bunch of obnoxious weeds.///"
    

Existing mods which add entries without the now-required fields may cause errors and crashes. XNB mods which change data are likely universally broken.

Exception: for item data assets, the sprite index and texture name fields are optional and can be omitted.

String event IDs

TODO

Custom wild trees

TODO

Custom map areas

TODO

Custom shop entries

TODO

New C# utility methods

1.6 adds a new set of utilities (GetDataAtIndex, GetIntAtIndex, and GetFloatAtIndex) to replace common logic for parsing the game's data assets while accounting for optional field indexes.

For example, code like this:

string[] rawEffects = fields.Length > Object.objectInfoBuffTypesIndex && fields[Object.objectInfoBuffTypesIndex].Length > 0
    ? fields[Object.objectInfoBuffTypesIndex].Split(' ')
    : new string[0];

int farming = rawEffects.Length > Buffs.farming && int.TryParse(rawEffects[Buffs.farming], out int _farming)
    ? _farming
    : 0;
int fishing = rawEffects.Length > Buffs.fishing && int.TryParse(rawEffects[Buffs.fishing], out int _fishing)
    ? _fishing
    : 0;

Can now be rewritten like this:

string[] rawEffects = Utility.GetDataAtIndex(fields, Object.objectInfoBuffTypesIndex, "").Split(' ');
int farming = Utility.GetIntAtIndex(rawEffects, Buffs.farming);
int fishing = Utility.GetIntAtIndex(rawEffects, Buffs.fishing);

See also