Changes

Line 1: Line 1:  
←[[Modding:Index|Index]]
 
←[[Modding:Index|Index]]
<div style="border: 1px solid red; background: #FCC; padding: 1em;">'''This is an internal draft page. SMAPI 3.0 is not ready and modders don't need to do anything yet.</div>
     −
'''This page is for modders. Players: see [[Modding:SMAPI compatibility]] instead.'''
+
{{Modder compatibility header}}
 
+
This page explains how to update your mod code for compatibility with SMAPI 3.0. See also [[Modding:Migrate to Stardew Valley 1.4]].
This page explains how to update your mod code for compatibility with SMAPI 3.0 (release TBD).
      
==Overview==
 
==Overview==
Line 10: Line 8:  
[[File:SMAPI compatibility.png|thumb|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.]]
 
[[File:SMAPI compatibility.png|thumb|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 <tt>MenuEvents.MenuChanged</tt> 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.
+
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 <samp>MenuEvents.MenuChanged</samp> 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...
 
SMAPI 3.0 is the release that fixes all that. This release...
Line 18: Line 16:  
* Weird behaviours and overlapping events have been eliminated.
 
* Weird behaviours and overlapping events have been eliminated.
 
* Many new events have been added.
 
* Many new events have been added.
* Every event now has relevant event arguments.
+
* Events now have relevant event arguments.
 +
* Each mod now has its own event instances, to support features like mod message targeting.
 +
 
 +
SMAPI 3.0 also adds compatibility with Stardew Valley 1.4, drops all deprecated APIs, and makes a number of other changes listed below.
    
===Is this the modapocalypse?===
 
===Is this the modapocalypse?===
Nope. Although this is a major change, significant effort will be undertaken to minimise the impact:
+
Nope. Although this is a major change, significant efforts were undertaken to minimize 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;
+
* the old events were 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;
+
* pull requests were 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;
+
* unofficial updates were 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.
+
* the changes were 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.
 
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.
Line 36: Line 37:  
# You can refer to the following sections on how to replace specific interfaces.
 
# You can refer to the following sections on how to replace specific interfaces.
   −
==Major changes==
+
==Changes==
===Removed APIs===
+
===Mod build package updated===
The following APIs will be removed in SMAPI 3.0.
+
Update the [https://www.nuget.org/packages/Pathoschild.Stardew.ModBuildConfig mod build package] for compatibility with SMAPI 3.0.
 +
 
 +
===Mods are loaded earlier===
 +
Mods were previously loaded right before the first <samp>UpdateTicking</samp> event, which is too late for some changes like intercepting core assets. SMAPI 3.0 loads them much earlier, before the game is fully initialised. '''Do not depend on game fields like <samp>Game1.objectInformation</samp> having a valid value in your <samp>Entry</samp> method!''' You can use the <samp>GameLaunched</samp> event for that instead. See [[Modding:Modder Guide/APIs/Mod structure#Mod entry]] for more info.
   −
''TODO''
+
For example, let's say you have code like this:
 +
<syntaxhighlight lang="c#">
 +
public override void Entry(IModHelper helper)
 +
{
 +
  this.Config = helper.ReadConfig<ModConfig>();
   −
===Event changes===
+
  CommunityCenter communityCenter = (CommunityCenter)Game1.getLocationFromName("CommunityCenter");
The former events will be removed in SMAPI 3.0. Here's how to convert them to the new events under <tt>this.Helper.Events</tt>. This list does not cover new events in 3.0.
+
  this.VaultRoomID = communityCenter.getAreaNumberFromName("Vault");
 +
}
 +
</syntaxhighlight>
 +
 
 +
That code fails in SMAPI 3.0, because no locations are loaded yet. This code could be rewritten like this:
 +
<syntaxhighlight lang="c#">
 +
public override void Entry(IModHelper helper)
 +
{
 +
  this.Config = helper.ReadConfig<ModConfig>();
 +
 
 +
  helper.Events.GameLoop.GameLaunched += this.OnGameLaunched;
 +
}
 +
 
 +
private void OnGameLaunched(object sender, GameLaunchedEventArgs args)
 +
{
 +
  CommunityCenter communityCenter = (CommunityCenter)Game1.getLocationFromName("CommunityCenter");
 +
  this.VaultRoomID = communityCenter.getAreaNumberFromName("Vault");
 +
}
 +
</syntaxhighlight>
    +
===API changes===
 
{| class="wikitable"
 
{| class="wikitable"
 
|-
 
|-
! old event
+
! old API
 
! →
 
! →
! new event
+
! new API
 
! conversion notes
 
! conversion notes
 
|-
 
|-
| <tt>ContentEvents.AfterLocaleChanged</tt>
+
| <samp>helper.GetContentPacks</samp>
 
| →
 
| →
| ''TODO''
+
| <samp>helper.ContentPacks.GetOwned</samp>
|  
+
| Identical usage.
 
|-
 
|-
| <tt>ControlEvents.ControllerButtonPressed</tt>
+
| <samp>helper.CreateTransitionalContentPack</samp>
 
| →
 
| →
| <tt>Input.ButtonPressed</tt>
+
| <samp>helper.ContentPacks.CreateTemporary</samp>
| Mostly equivalent.
+
| Identical usage. (This was a temporary method for the transition to [[Modding:Content packs|SMAPI content packs]], but a few mods have permanent use cases for it.)
* Remove <tt>e.PlayerIndex</tt> (this was always <tt>PlayerIndex.One</tt>).
  −
* Change <tt>e.ButtonPressed</tt> to <tt>e.Button</tt>, which is now an [[Modding:Modder Guide/APIs/Utilities#Input|<tt>SButton</tt>]] value.
   
|-
 
|-
| <tt>ControlEvents.ControllerButtonReleased</tt>
+
| <samp>assetData.AsDictionary&lt;T, T&gt;().Set</samp>
 
| →
 
| →
| <tt>Input.ButtonReleased</tt>
+
| <samp>assetData.AsDictionary&lt;T, T&gt;().Data</samp>
| Mostly equivalent.
+
| The <samp>Set</samp> methods caused a lot of confusion and were of limited usefulness. You can access the dictionary directly using the <samp>Data</samp> field instead.
* Remove <tt>e.PlayerIndex</tt> (this was always <tt>PlayerIndex.One</tt>).
  −
* Change <tt>e.ButtonPressed</tt> to <tt>e.Button</tt>, which is now an [[Modding:Modder Guide/APIs/Utilities#Input|<tt>SButton</tt>]] value.
  −
 
   
|-
 
|-
| <tt>ControlEvents.ControllerTriggerPressed</tt>
+
| <samp>SemanticVersion.Build</samp>
 
| →
 
| →
| <tt>Input.ButtonPressed</tt>
+
| <samp>SemanticVersion.PrereleaseTag</samp>
| Mostly equivalent.
+
| Identical usage.
* Remove <tt>e.PlayerIndex</tt> (this was always <tt>PlayerIndex.One</tt>).
+
|}
* Remove <tt>e.Value</tt>.
+
 
* Change <tt>e.ButtonPressed</tt> to <tt>e.Button</tt>, which is now an [[Modding:Modder Guide/APIs/Utilities#Input|<tt>SButton</tt>]] value.
+
===Event changes===
 +
The former events are removed in SMAPI 3.0. Here's how to convert them to the new events under <samp>this.Helper.Events</samp>. This list only shows equivalent events in SMAPI 3.0; see [[Modding:Modder Guide/APIs/Events|''events'' in the modder guide]] for a full list.
 +
 
 +
{| class="wikitable"
 
|-
 
|-
| <tt>ControlEvents.ControllerTriggerReleased</tt>
+
! old event
|
+
! →
| <tt>Input.ButtonReleased</tt>
+
! new event
| Mostly equivalent.
+
! conversion notes
* Remove <tt>e.PlayerIndex</tt> (this was always <tt>PlayerIndex.One</tt>).
+
{{/event
* Remove <tt>e.Value</tt>.
+
|old events = ContentEvents.AfterLocaleChanged
* Change <tt>e.ButtonPressed</tt> to <tt>e.Button</tt>, which is now an [[Modding:Modder Guide/APIs/Utilities#Input|<tt>SButton</tt>]] value.
+
|new events = none
|-
+
|summary    = 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.
| <tt>ControlEvents.KeyboardChanged</tt>
+
}}
|
+
{{/event
| <tt>Input.ButtonPressed</tt><br /><tt>Input.ButtonReleased</tt>
+
|old events = ControlEvents.ControllerButtonPressed, ControlEvents.ControllerButtonReleased, ControlEvents.ControllerTriggerPressed, ControlEvents.ControllerTriggerReleased
| Not directly equivalent; may need to rewrite affected code.
+
|new events = Input.ButtonPressed, Input.ButtonReleased
|-
+
|summary    = Mostly equivalent.
| <tt>ControlEvents.KeyPressed</tt>
+
* Remove <samp>e.PlayerIndex</samp> (this was always <samp>PlayerIndex.One</samp>).
|
+
* Change <samp>e.ButtonPressed</samp> to <samp>e.Button</samp>, which is now an [[Modding:Modder Guide/APIs/Utilities#Input|<samp>SButton</samp>]] value.
| <tt>Input.ButtonPressed</tt>
+
* Remove <samp>e.Value</samp> for trigger events.
| Mostly equivalent.
+
}}
* Change <tt>e.KeyPressed</tt> to <tt>e.Button</tt>, which is now an [[Modding:Modder Guide/APIs/Utilities#Input|<tt>SButton</tt>]] value.
+
{{/event
|-
+
|old events = ControlEvents.KeyboardChanged
| <tt>ControlEvents.KeyReleased</tt>
+
|new events = Input.ButtonPressed, Input.ButtonReleased
|
+
|summary    = Not directly equivalent; may need to rewrite affected code.
| <tt>Input.ButtonReleased</tt>
+
}}
| Mostly equivalent.
+
{{/event
* Change <tt>e.KeyPressed</tt> to <tt>e.Button</tt>, which is now an [[Modding:Modder Guide/APIs/Utilities#Input|<tt>SButton</tt>]] value.
+
|old events = ControlEvents.KeyPressed, ControlEvents.KeyReleased
|-
+
|new events = Input.ButtonPressed, Input.ButtonReleased
| <tt>ControlEvents.MouseChanged</tt>
+
|summary    = Mostly equivalent.
| →
+
* Change <samp>e.KeyPressed</samp> to <samp>e.Button</samp>, which is now an [[Modding:Modder Guide/APIs/Utilities#Input|<samp>SButton</samp>]] value.
| <tt>Input.CursorMoved</tt>
+
}}
| Not directly equivalent; may need to rewrite affected code.
+
{{/event
|-
+
|old events = ControlEvents.MouseChanged
| <tt>GameEvents.EighthUpdateTick</tt>
+
|new events = Input.ButtonPressed, Input.ButtonReleased, Input.CursorMoved, Input.MouseWheelScrolled
| →
+
|summary    = Not directly equivalent; may need to rewrite affected code.
| ''TODO''
+
}}
|
+
{{/event
|-
+
|old events = GameEvents.EighthUpdateTick, GameEvents.FourthUpdateTick, GameEvents.HalfSecondTick, GameEvents.QuarterSecondTick, GameEvents.SecondUpdateTick
| <tt>GameEvents.FirstUpdateTick</tt>
+
|new events = GameLoop.UpdateTicked
|
+
|summary    = Mostly equivalent. You can use <code>e.IsMultipleOf</code> to choose an update rate (<samp>SecondUpdateTick</samp> = 2, <samp>FourthUpdateTick</samp> = 4, <samp>EighthUpdateTick</samp> = 8, <samp>QuarterSecondTick</samp> = 15, and <samp>HalfSecondTick</samp> = 30).
| ''TODO''
+
}}
|
+
{{/event
|-
+
|old events = GameEvents.FirstUpdateTick
| <tt>GameEvents.FourthUpdateTick</tt>
+
|new events = GameLoop.GameLaunched
| →
+
|summary    = 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 <samp>GameEvents.UpdateTicked</samp> with <samp>if (e.Ticks == 1)</samp>.
| ''TODO''
+
}}
|
+
{{/event
|-
+
|old events = GameEvents.UpdateTick
| <tt>GameEvents.HalfSecondTick</tt>
+
|new events = GameLoop.UpdateTicked
| →
+
|summary    = equivalent
| ''TODO''
+
}}
|  
+
{{/event
|-
+
|old events = GameEvents.OneSecondTick
| <tt>GameEvents.OneSecondTick</tt>
+
|new events = GameLoop.OneSecondUpdateTicked
|
+
|summary    = equivalent
| ''TODO''
+
}}
|
+
{{/event
|-
+
|old events = GraphicsEvents.OnPostRenderEvent
| <tt>GameEvents.QuarterSecondTick</tt>
+
|new events = Display.Rendered
|
+
|summary    = Equivalent, except the new event isn't triggered during certain special cutscenes and minigames (''e.g.,'' the [[Sebastian#Six Hearts|board game scene]]).
| ''TODO''
+
}}
|
+
{{/event
|-
+
|old events = GraphicsEvents.OnPostRenderGuiEvent
| <tt>GameEvents.SecondUpdateTick</tt>
+
|new events = Display.RenderedActiveMenu
|
+
|summary    = equivalent
| ''TODO''
+
}}
|
+
{{/event
|-
+
|old events = GraphicsEvents.OnPostRenderHudEvent
| <tt>GameEvents.UpdateTick</tt>
+
|new events = Display.RenderedHud
| →
+
|summary    = equivalent
| ''TODO''
+
}}
|  
+
{{/event
|-
+
|old events = GraphicsEvents.OnPreRenderEvent
| <tt>GraphicsEvents.OnPostRenderEvent</tt>
+
|new events = Display.RenderingWorld
| →
+
|summary    = equivalent
| ''TODO''
+
}}
|  
+
{{/event
|-
+
|old events = GraphicsEvents.OnPreRenderGuiEvent
| <tt>GraphicsEvents.OnPostRenderGuiEvent</tt>
+
|new events = Display.RenderingActiveMenu
|
+
|summary    = equivalent
| ''TODO''
+
}}
|
+
{{/event
|-
+
|old events = GraphicsEvents.OnPreRenderHudEvent
| <tt>GraphicsEvents.OnPostRenderHudEvent</tt>
+
|new events = Display.RenderingHud
|
+
|summary    = equivalent
| ''TODO''
+
}}
|
+
{{/event
|-
+
|old events = GraphicsEvents.Resize
| <tt>GraphicsEvents.OnPreRenderEvent</tt>
+
|new events = Display.WindowResized
|
+
|summary    = equivalent
| ''TODO''
+
}}
|
+
{{/event
|-
+
|old events = InputEvents.ButtonPressed, InputEvents.ButtonReleased
| <tt>GraphicsEvents.OnPreRenderGuiEvent</tt>
+
|new events = Input.ButtonPressed, Input.ButtonReleased
|
+
|summary    = Mostly equivalent.
| ''TODO''
+
* Change <samp>e.IsActionButton</samp> to <samp>e.Button.IsActionButton()</samp>.
|
+
* Change <samp>e.IsUseToolButton</samp> to <samp>e.Button.IsUseToolButton()</samp>.
|-
+
* Change <samp>e.SuppressButton</samp> to <samp>this.Helper.Input.Suppress(button)</samp>.
| <tt>GraphicsEvents.OnPreRenderHudEvent</tt>
+
}}
|
+
{{/event
| ''TODO''
+
|old events = LocationEvents.BuildingsChanged
|
+
|new events = World.BuildingListChanged
|-
+
|summary    = equivalent
| <tt>GraphicsEvents.Resize</tt>
+
}}
|
+
{{/event
| ''TODO''
+
|old events = LocationEvents.LocationsChanged
|
+
|new events = World.LocationListChanged
|-
+
|summary    = equivalent
| <tt>InputEvents.ButtonPressed</tt>
+
}}
|
+
{{/event
| <tt>Input.ButtonPressed</tt>
+
|old events = LocationEvents.ObjectsChanged
| Mostly equivalent.
+
|new events = World.ObjectListChanged
* Change <tt>e.IsActionButton</tt> to <tt>e.Button.IsActionButton()</tt>.
+
|summary    = equivalent
* Change <tt>e.IsUseToolButton</tt> to <tt>e.Button.IsUseToolButton()</tt>.
+
}}
|-
+
{{/event
| <tt>InputEvents.ButtonReleased</tt>
+
|old events = MenuEvents.MenuChanged, MenuEvents.MenuClosed
| →
+
|new events = Display.MenuChanged
| <tt>Input.ButtonReleased</tt>
+
|summary    = These caused a lot of confusion (''e.g.,'' <samp>MenuEvents.MenuClosed</samp> wasn't called when a menu was closed and immediately replaced), so they've been combined into one event which is called when a menu is opened, closed, or replaced. You can check the <samp>e.OldMenu</samp> and <samp>e.NewMenu</samp> event args to know what the change is (''e.g.,'' to match the old <samp>MenuEvents.MenuClosed</samp>, check <code>if (e.NewMenu == null)</code>).
| Mostly equivalent.
+
}}
* Change <tt>e.IsActionButton</tt> to <tt>e.Button.IsActionButton()</tt>.
+
{{/event
* Change <tt>e.IsUseToolButton</tt> to <tt>e.Button.IsUseToolButton()</tt>.
+
|old events = MineEvents.MineLevelChanged
|-
+
|new events = Player.Warped
| <tt>LocationEvents.BuildingsChanged</tt>
+
|summary    = Check <code>if (e.NewLocation is MineShaft mine)</code> to detect a mine level change (the new mine level will be <code>mine.mineLevel</code>). Although the new event is still only called for the current player, that may change in a future version; '''make sure to check <samp>e.IsLocalPlayer</samp> if you only want to handle the current player'''.
|
+
}}
| <tt>World.BuildingListChanged</tt>
+
{{/event
| Equivalent.
+
|old events = MultiplayerEvents.AfterMainBroadcast, MultiplayerEvents.AfterMainSync, MultiplayerEvents.BeforeMainBroadcast, MultiplayerEvents.BeforeMainSync
|-
+
|new events = none
| <tt>LocationEvents.LocationsChanged</tt>
+
|summary    = No known mods use these. If you need them, consider replacing <samp>Game1.multiplayer</samp> with a delegating subclass.
|
+
}}
| <tt>World.LocationListChanged</tt>
+
{{/event
| Equivalent.
+
|old events = PlayerEvents.InventoryChanged
|-
+
|new events = Player.InventoryChanged
| <tt>LocationEvents.ObjectsChanged</tt>
+
|summary    = 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 may change in a future version; '''make sure to check <samp>e.IsLocalPlayer</samp> if you only want to handle the current player'''.
|
+
}}
| <tt>World.ObjectListChanged</tt>
+
{{/event
| Equivalent.
+
|old events = PlayerEvents.LeveledUp
|-
+
|new events = Player.LevelChanged
| <tt>MenuEvents.MenuChanged</tt>
+
|summary    = 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 may change in a future version; '''make sure to check <samp>e.IsLocalPlayer</samp> if you only want to handle the current player'''.
|
+
}}
| ''TODO''
+
{{/event
|
+
|old events = PlayerEvents.Warped
|-
+
|new events = Player.Warped
| <tt>MenuEvents.MenuClosed</tt>
+
|summary    = 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 may change in a future version; '''make sure to check <samp>e.IsLocalPlayer</samp> if you only want to handle the current player'''.
| →
+
}}
| ''TODO''
+
{{/event
|
+
|old events = SaveEvents.AfterCreate
|-
+
|new events = GameLoop.SaveCreated
| <tt>MineEvents.MineLevelChanged</tt>
+
|summary    = equivalent
| →
+
}}
| ''TODO''
+
{{/event
|
+
|old events = SaveEvents.AfterLoad
|-
+
|new events = GameLoop.SaveLoaded
| <tt>MultiplayerEvents.AfterMainBroadcast</tt>
+
|summary    = equivalent
| →
+
}}
| ''TODO''
+
{{/event
|  
+
|old events = SaveEvents.AfterReturnToTitle
|-
+
|new events = GameLoop.ReturnedToTitle
| <tt>MultiplayerEvents.AfterMainSync</tt>
+
|summary    = equivalent
| →
+
}}
| ''TODO''
+
{{/event
|
+
|old events = SaveEvents.AfterSave
|-
+
|new events = GameLoop.Saved
| <tt>MultiplayerEvents.BeforeMainBroadcast</tt>
+
|summary    = equivalent
| →
+
}}
| ''TODO''
+
{{/event
|  
+
|old events = SaveEvents.BeforeCreate
|-
+
|new events = GameLoop.SaveCreating
| <tt>MultiplayerEvents.BeforeMainSync</tt>
+
|summary    = equivalent
| →
+
}}
| ''TODO''
+
{{/event
|  
+
|old events = SaveEvents.BeforeSave
|-
+
|new events = GameLoop.Saving
| <tt>PlayerEvents.InventoryChanged</tt>
+
|summary    = equivalent
| →
+
}}
| ''TODO''
+
{{/event
|  
+
|old events = SpecialisedEvents.UnvalidatedUpdateTick
|-
+
|new events = Specialised.UnvalidatedUpdateTicked
| <tt>PlayerEvents.LeveledUp</tt>
+
|summary    = equivalent
| →
+
}}
| ''TODO''
+
{{/event
|  
+
|old events = TimeEvents.AfterDayStarted
|-
+
|new events = GameLoop.DayStarted
| <tt>PlayerEvents.Warped</tt>
+
|summary    = equivalent
| →
+
}}
| ''TODO''
+
{{/event
|
+
|old events = TimeEvents.TimeOfDayChanged
|-
+
|new events = GameLoop.TimeChanged
| <tt>SaveEvents.AfterCreate</tt>
+
|summary    = Mostly equivalent. Change <samp>e.OldInt</samp> and <samp>e.NewInt</samp> to <samp>e.OldTime</samp> and <samp>e.NewTime</samp> respectively.
|
+
}}
| ''TODO''
  −
|
  −
|-
  −
| <tt>SaveEvents.AfterLoad</tt>
  −
|
  −
| ''TODO''
  −
|
  −
|-
  −
| <tt>SaveEvents.AfterReturnToTitle</tt>
  −
|
  −
| ''TODO''
  −
|
  −
|-
  −
| <tt>SaveEvents.AfterSave</tt>
  −
|
  −
| ''TODO''
  −
|
  −
|-
  −
| <tt>SaveEvents.BeforeCreate</tt>
  −
|
  −
| ''TODO''
  −
|
  −
|-
  −
| <tt>SaveEvents.BeforeSave</tt>
  −
|
  −
| ''TODO''
  −
|
  −
|-
  −
| <tt>SpecialisedEvents.UnvalidatedUpdateTick</tt>
  −
|
  −
| ''TODO''
  −
|
  −
|-
  −
| <tt>TimeEvents.AfterDayStarted</tt>
  −
|
  −
| ''TODO''
  −
|  
  −
|-
  −
| <tt>TimeEvents.TimeOfDayChanged</tt>
  −
| →
  −
| ''TODO''
  −
|
   
|}
 
|}
 +
 +
===Manifest version format===
 +
The [[Modding:Modder Guide/APIs/Manifest|<samp>manifest.json</samp>]] no longer allows versions in this form:
 +
<syntaxhighlight lang="js">
 +
"Version": {
 +
  "MajorVersion": 1,
 +
  "MinorVersion": 0,
 +
  "PatchVersion": 0,
 +
  "Build": "beta"
 +
}
 +
</syntaxhighlight>
 +
 +
Versions should be written in the standard string form instead:
 +
<syntaxhighlight lang="js">
 +
"Version": "1.0.0-beta"
 +
</syntaxhighlight>
 +
 +
===Version format===
 +
SMAPI no longer omits <code>.0</code> patch numbers when formatting versions. For example, the console now shows SMAPI 3.0.0 instead of 3.0. That's more [https://semver.org/ standard], improves compatibility with external tools, and reduces player confusion.
 +
 +
This doesn't affect most mod code. You may need to update your code if you use <samp>ISemanticVersion.ToString()</samp> and compare the output to a hardcoded two-part version string.
    
[[Category:Modding]]
 
[[Category:Modding]]
translators
8,404

edits