Modding:Migrate to SMAPI 3.0

From Stardew Valley Wiki
Jump to: navigation, search

Index

This page is for modders. Players: see Modding:SMAPI compatibility instead.

This page explains how to update your mod code for compatibility with SMAPI 3.0, which will release in Q1 2019.

Overview

What's changing?

SMAPI compatibility over time. The SMAPI 2.0 release in October 2017 appears as a small bump. The Stardew Valley 1.3 release in May 2018 appears as a sudden cliff.

Events are arguably the most important and most visible part of SMAPI, but they've stayed essentially unchanged since SMAPI 0.40. New events have been added organically since then, but the event system has always been separate from the rest of SMAPI — they're inconsistent, have some weird behaviours (like MenuEvents.MenuChanged not being raised when a menu is closed), aren't available through the same helper as every other API, and there are some glaring omissions in the available events.

SMAPI 3.0 is the release that fixes all that. This release...

  • Completely rewrites the event engine to make events more efficient and enable events that weren't possible before.
  • Makes events much more consistent: they're now fully compliant with the .NET design guidelines, have predictable and descriptive names, have consistent event handler signatures, and have clear documentation.
  • All events are now accessible through helper.Events, so they're discoverable like any other SMAPI API.
  • Weird behaviours and overlapping events have been eliminated.
  • Many new events have been added.
  • Events now have relevant event arguments.

Is this the modapocalypse?

Nope. Although this is a major change, significant effort will be undertaken to minimise the impact:

  • the old events will be supported for a long time with increasingly prominent warnings in the SMAPI console about their deprecation and removal;
  • pull requests will be submitted to update affected open-source mods;
  • unofficial updates will be created for mods which haven't updated officially by the time SMAPI 3.0 is released;
  • the changes will be actively communicated and documented to modders.

In addition, the current target is at least 95% compatibility for open-source mods before SMAPI 3.0 is released. All of this means that the 3.0 release should have minimal impact on mod compatibility, despite the scope of the changes.

How to update your mod

You don't need to comb through your code manually. SMAPI can tell you if you're using a deprecated interface:

  1. Use the latest SMAPI for developers download. This will show deprecation messages in the console:
    Modding - updating deprecated SMAPI code - deprecation warnings.png
  2. When you look at the code, you'll see a deprecation warning with a hint of how to fix it:
    Modding - updating deprecated SMAPI code - deprecation intellisense.png
  3. You can refer to the following sections on how to replace specific interfaces.

Changes

API changes

old API new API conversion notes
helper.CreateTransitionalContentPack helper.CreateTemporaryContentPack Identical usage. (This was a temporary method for the transition to SMAPI content packs, but a few mods have permanent use cases for it.)
SemanticVersion.Build SemanticVersion.PrereleaseTag Identical usage.

Event changes

The former events will be removed in SMAPI 3.0. Here's how to convert them to the new events under this.Helper.Events. This list only shows equivalent events in SMAPI 3.0; see events in the modder guide for a full list.

old event new event conversion notes
#ContentEvents.AfterLocaleChanged none Mods very rarely need to handle this, since SMAPI's translation and content APIs do. Since the locale can only change on the title screen, mods that used this can check once the save is loaded instead.
#ControlEvents.ControllerButtonPressed
ControlEvents.ControllerButtonReleased
ControlEvents.ControllerTriggerPressed
ControlEvents.ControllerTriggerReleased
Input.ButtonPressed
Input.ButtonReleased
Mostly equivalent.
  • Remove e.PlayerIndex (this was always PlayerIndex.One).
  • Change e.ButtonPressed to e.Button, which is now an SButton value.
  • Remove e.Value for trigger events.
#ControlEvents.KeyboardChanged Input.ButtonPressed
Input.ButtonReleased
Not directly equivalent; may need to rewrite affected code.
#ControlEvents.KeyPressed
ControlEvents.KeyReleased
Input.ButtonPressed
Input.ButtonReleased
Mostly equivalent.
  • Change e.KeyPressed to e.Button, which is now an SButton value.
#ControlEvents.MouseChanged Input.ButtonPressed
Input.ButtonReleased
Input.CursorMoved
Input.MouseWheelScrolled
Not directly equivalent; may need to rewrite affected code.
#GameEvents.EighthUpdateTick
GameEvents.FourthUpdateTick
GameEvents.HalfSecondTick
GameEvents.OneSecondTick
GameEvents.QuarterSecondTick
GameEvents.SecondUpdateTick
GameLoop.UpdateTicked Mostly equivalent. You can match the previous update ticks using e.IsMultipleOf (SecondUpdateTick = 2, FourthUpdateTick = 4, EighthUpdateTick = 8, QuarterSecondTick = 15, and HalfSecondTick = 30) and e.IsOneSecond.
#GameEvents.FirstUpdateTick GameLoop.GameLaunched The new event is raised before the first update tick (the old one happened after it). To do something after the first update tick, use GameEvents.UpdateTicked with if (e.Ticks == 1).
#GameEvents.UpdateTick GameLoop.UpdateTicked Equivalent.
#GraphicsEvents.OnPostRenderEvent Display.Rendered Equivalent, except the new event isn't triggered during certain special cutscenes and minigames (e.g. the board game scene).
#GraphicsEvents.OnPostRenderGuiEvent Display.RenderedActiveMenu Equivalent.
#GraphicsEvents.OnPostRenderGuiEvent Display.RenderedActiveMenu Equivalent.
#GraphicsEvents.OnPostRenderHudEvent Display.RenderedHud Equivalent.
#GraphicsEvents.OnPreRenderEvent Display.RenderingWorld Equivalent.
#GraphicsEvents.OnPreRenderGuiEvent Display.RenderingActiveMenu Equivalent.
#GraphicsEvents.OnPreRenderHudEvent Display.RenderingHud Equivalent.
#GraphicsEvents.Resize Display.WindowResized Equivalent.
#InputEvents.ButtonPressed
InputEvents.ButtonReleased
Input.ButtonPressed
Input.ButtonReleased
Mostly equivalent.
  • Change e.IsActionButton to e.Button.IsActionButton().
  • Change e.IsUseToolButton to e.Button.IsUseToolButton().
  • Change e.SuppressButton to this.Helper.Input.Suppress(button).
#LocationEvents.BuildingsChanged World.BuildingListChanged Equivalent.
#LocationEvents.LocationsChanged World.LocationListChanged Equivalent.
#LocationEvents.ObjectsChanged World.ObjectListChanged Equivalent.
#MineEvents.MineLevelChanged PlayerEvents.Warped To know whether the player changed mine level, you can check if (e.NewLocation is MineShaft mine); the new mine level will be mine.mineLevel.
#MultiplayerEvents.AfterMainBroadcast
MultiplayerEvents.AfterMainSync
MultiplayerEvents.BeforeMainBroadcast
MultiplayerEvents.BeforeMainSync
none No known mods use these. If you need them, consider replacing Game1.multiplayer with a delegating subclass.
#PlayerEvents.InventoryChanged Player.InventoryChanged Mostly equivalent. The event arguments changed type, but should be straightforward to migrate. Although the new event is still only called for the current player, that will likely change in a future version; make sure to check e.IsLocalPlayer if you only want to handle the current player.
#PlayerEvents.LeveledUp Player.LevelChanged Mostly equivalent. The event arguments changed type, but should be straightforward to migrate. Although the new event is still only called for the current player, that will likely change in a future version; make sure to check e.IsLocalPlayer if you only want to handle the current player.
#PlayerEvents.Warped Player.Warped Mostly equivalent. The event arguments changed type, but should be straightforward to migrate. Although the new event is still only called for the current player, that will likely change in a future version; make sure to check e.IsLocalPlayer if you only want to handle the current player.
#SaveEvents.AfterCreate GameLoop.SaveCreated Equivalent.
#SaveEvents.AfterLoad GameLoop.SaveLoaded Equivalent.
#SaveEvents.AfterReturnToTitle GameLoop.ReturnedToTitle Equivalent.
#SaveEvents.AfterSave GameLoop.Saved Equivalent.
#SaveEvents.BeforeCreate GameLoop.SaveCreating Equivalent.
#SaveEvents.BeforeSave GameLoop.Saving Equivalent.
#SpecialisedEvents.UnvalidatedUpdateTick Specialised.UnvalidatedUpdateTicked Equivalent.
#TimeEvents.AfterDayStarted GameLoop.DayStarted Equivalent.
#TimeEvents.TimeOfDayChanged GameLoop.TimeChanged Mostly equivalent. Change e.OldInt and e.NewInt to e.OldTime and e.NewTime respectively.

Manifest version format

The manifest.json no longer allows versions in this form:

"Version": {
   "MajorVersion": 1,
   "MinorVersion": 0,
   "PatchVersion": 0,
   "Build": "beta"
}

Versions should be written in the standard string form instead:

"Version": "1.0.0-beta"