Merge branch 'develop' into stable
This commit is contained in:
commit
04c6733ada
|
@ -4,7 +4,7 @@
|
|||
|
||||
<!--set properties -->
|
||||
<PropertyGroup>
|
||||
<Version>3.8.1</Version>
|
||||
<Version>3.8.2</Version>
|
||||
<Product>SMAPI</Product>
|
||||
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/GOG Games/Stardew Valley/game</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.steam/steam/steamapps/common/Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.local/share/Steam/steamapps/common/Stardew Valley</GamePath>
|
||||
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.var/app/com.valvesoftware.Steam/data/Steam/steamapps/common/Stardew Valley</GamePath>
|
||||
|
||||
<!-- Mac (may be 'Unix' or 'OSX') -->
|
||||
<GamePath Condition="!Exists('$(GamePath)')">/Applications/Stardew Valley.app/Contents/MacOS</GamePath>
|
||||
|
|
|
@ -7,6 +7,23 @@
|
|||
* Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
|
||||
-->
|
||||
|
||||
## 3.8.2
|
||||
Released 03 January 2021 for Stardew Valley 1.5.1 or later.
|
||||
|
||||
* For players:
|
||||
* SMAPI now blocks farm map replacements that would crash the game in Stardew Valley 1.5.
|
||||
* On Linux, the SMAPI installer now auto-detects Flatpak Steam paths.
|
||||
* Updated compatibility list.
|
||||
* Fixed errors when multiple players join in split-screen mode.
|
||||
* Fixed 'skipped mods' section repeating mods in some cases.
|
||||
* Fixed out-of-date error text.
|
||||
|
||||
* For modders:
|
||||
* Added warning when a map replacement changes the order/IDs of the original tilesheets, which may cause errors and crashes. Doing so for a farm map is blocked outright since that causes a consistent crash in Stardew Valley 1.5.
|
||||
* Message data from the `ModMessageReceived` event now uses the same serializer settings as the rest of SMAPI. That mainly adds support for sending crossplatform `Color`, `Point`, `Vector2`, `Rectangle`, and `SemanticVersion` fields through network messages.
|
||||
* When a mod is blocked by SMAPI's compatibility override list, the `TRACE` messages while loading it now say so and indicate why.
|
||||
* Fixed how the input API handles UI scaling. This mainly affects `ICursorPosition` values returned by the API; see [the wiki docs](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Input#ICursorPosition) for how to account for UI scaling.
|
||||
|
||||
## 3.8.1
|
||||
Released 26 December 2020 for Stardew Valley 1.5.1 or later.
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Name": "Console Commands",
|
||||
"Author": "SMAPI",
|
||||
"Version": "3.8.1",
|
||||
"Version": "3.8.2",
|
||||
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
||||
"UniqueID": "SMAPI.ConsoleCommands",
|
||||
"EntryDll": "ConsoleCommands.dll",
|
||||
"MinimumApiVersion": "3.8.1"
|
||||
"MinimumApiVersion": "3.8.2"
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Name": "Save Backup",
|
||||
"Author": "SMAPI",
|
||||
"Version": "3.8.1",
|
||||
"Version": "3.8.2",
|
||||
"Description": "Automatically backs up all your saves once per day into its folder.",
|
||||
"UniqueID": "SMAPI.SaveBackup",
|
||||
"EntryDll": "SaveBackup.dll",
|
||||
"MinimumApiVersion": "3.8.1"
|
||||
"MinimumApiVersion": "3.8.2"
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
|||
// non-manifest fields
|
||||
case ModDataFieldKey.AlternativeUrl:
|
||||
case ModDataFieldKey.StatusReasonPhrase:
|
||||
case ModDataFieldKey.StatusReasonDetails:
|
||||
case ModDataFieldKey.Status:
|
||||
return false;
|
||||
|
||||
|
|
|
@ -13,6 +13,9 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
|||
Status,
|
||||
|
||||
/// <summary>A reason phrase for the <see cref="Status"/>, or <c>null</c> to use the default reason.</summary>
|
||||
StatusReasonPhrase
|
||||
StatusReasonPhrase,
|
||||
|
||||
/// <summary>Technical details shown in TRACE logs for the <see cref="Status"/>, or <c>null</c> to omit it.</summary>
|
||||
StatusReasonDetails
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,6 +107,11 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
|||
case ModDataFieldKey.StatusReasonPhrase:
|
||||
parsed.StatusReasonPhrase = field.Value;
|
||||
break;
|
||||
|
||||
// status technical reason
|
||||
case ModDataFieldKey.StatusReasonDetails:
|
||||
parsed.StatusReasonDetails = field.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,9 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
|||
/// <summary>A reason phrase for the <see cref="Status"/>, or <c>null</c> to use the default reason.</summary>
|
||||
public string StatusReasonPhrase { get; set; }
|
||||
|
||||
/// <summary>Technical details shown in TRACE logs for the <see cref="Status"/>, or <c>null</c> to omit it.</summary>
|
||||
public string StatusReasonDetails { get; set; }
|
||||
|
||||
/// <summary>The upper version for which the <see cref="Status"/> applies (if any).</summary>
|
||||
public ISemanticVersion StatusUpperVersion { get; set; }
|
||||
}
|
||||
|
|
|
@ -132,5 +132,11 @@ namespace StardewModdingAPI.Toolkit.Serialization
|
|||
{
|
||||
return JsonConvert.SerializeObject(model, formatting, this.JsonSettings);
|
||||
}
|
||||
|
||||
/// <summary>Get a low-level JSON serializer matching the <see cref="JsonSettings"/>.</summary>
|
||||
public JsonSerializer GetSerializer()
|
||||
{
|
||||
return JsonSerializer.CreateDefault(this.JsonSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,9 @@
|
|||
* - StatusReasonPhrase: a message to show to the player explaining why the mod can't be loaded
|
||||
* (if applicable). If blank, will default to a generic not-compatible message.
|
||||
*
|
||||
* - StatusReasonDetails: a technical reason shown in TRACE logs, indicating why the status
|
||||
* was overridden. If not provided, it defaults to the StatusReasonPhrase or 'no reason given'.
|
||||
*
|
||||
* - AlternativeUrl: a URL where the player can find an unofficial update or alternative if the
|
||||
* mod is no longer compatible.
|
||||
*/
|
||||
|
@ -76,8 +79,7 @@
|
|||
|
||||
"JSON Assets": {
|
||||
"ID": "spacechase0.JsonAssets",
|
||||
"Default | UpdateKey": "Nexus:1720",
|
||||
"1.3.1 | Status": "AssumeBroken" // causes runtime crashes
|
||||
"Default | UpdateKey": "Nexus:1720"
|
||||
},
|
||||
|
||||
"Mail Framework": {
|
||||
|
@ -87,8 +89,7 @@
|
|||
|
||||
"MTN": {
|
||||
"ID": "SgtPickles.MTN",
|
||||
"Default | UpdateKey": "Nexus:2256",
|
||||
"~1.2.6 | Status": "AssumeBroken" // replaces Game1.multiplayer, which breaks SMAPI's multiplayer API.
|
||||
"Default | UpdateKey": "Nexus:2256"
|
||||
},
|
||||
|
||||
"PyTK": {
|
||||
|
@ -157,42 +158,76 @@
|
|||
},
|
||||
|
||||
/*********
|
||||
** Broke in SDV 1.5
|
||||
** Broke in SDV 1.5 (Content Patcher packs)
|
||||
*********/
|
||||
"mi.Mermaids": {
|
||||
"ID": "mi.Mermaids",
|
||||
"~1.0.0 | Status": "AssumeBroken",
|
||||
"~1.0.0 | StatusReasonDetails": "causes errors due to removed Stardew Valley 1.5 content"
|
||||
},
|
||||
|
||||
/*********
|
||||
** Broke in SDV 1.5 (SMAPI mods)
|
||||
*********/
|
||||
"Audio Devices": {
|
||||
"ID": "maxvollmer.audiodevices",
|
||||
"~2.0.0 | Status": "AssumeBroken" // causes crash to desktop when starting the game
|
||||
"~2.0.0 | Status": "AssumeBroken",
|
||||
"~2.0.0 | StatusReasonDetails": "causes crash to desktop when starting the game"
|
||||
},
|
||||
|
||||
"ChestEx": {
|
||||
"ID": "berkayylmao.ChestEx",
|
||||
"~1.3.4 | Status": "AssumeBroken",
|
||||
"~1.3.4 | StatusReasonDetails": "has no effect due to changes in Stardew Valley 1.5, causes crashes in other mods like Chests Anywhere"
|
||||
},
|
||||
|
||||
"Custom Furniture": {
|
||||
"ID": "Platonymous.CustomFurniture",
|
||||
"~0.11.2 | Status": "AssumeBroken",
|
||||
"~0.11.2 | StatusReasonDetails": "causes errors and custom furniture no longer work in Stardew Valley 1.5"
|
||||
},
|
||||
|
||||
"Custom Localization": {
|
||||
"ID": "ZaneYork.CustomLocalization",
|
||||
"FormerIDs": "SMAPI.CustomLocalization", // changed in 1.0.1
|
||||
"~1.1 | Status": "AssumeBroken" // reflection error for _localizedAssets field
|
||||
"~1.1 | Status": "AssumeBroken",
|
||||
"~1.1 | StatusReasonDetails": "reflection error due to renamed _localizedAssets field"
|
||||
},
|
||||
|
||||
"Geode Info Menu": {
|
||||
"ID": "cat.geodeinfomenu",
|
||||
"~1.5.2 | Status": "AssumeBroken",
|
||||
"~1.5.2 | StatusReasonDetails": "shows no info, freezes game if you try to search"
|
||||
},
|
||||
|
||||
"Mod Settings Tab": {
|
||||
"ID": "GilarF.ModSettingsTab",
|
||||
"~0.2.1 | Status": "AssumeBroken" // fails extending title menu
|
||||
"~0.2.1 | Status": "AssumeBroken",
|
||||
"~0.2.1 | StatusReasonDetails": "fails extending title menu"
|
||||
},
|
||||
|
||||
"More Grass": {
|
||||
"ID": "EpicBellyFlop45.MoreGrass",
|
||||
"~1.0.8 | Status": "AssumeBroken" // crashes save load
|
||||
"~1.0.8 | Status": "AssumeBroken",
|
||||
"~1.0.8 | StatusReasonDetails": "crashes on save load"
|
||||
},
|
||||
|
||||
"Movement Speed": {
|
||||
"ID": "bcmpinc.MovementSpeed",
|
||||
"~3.0.0 | Status": "AssumeBroken" // transpiler errors
|
||||
"~3.0.0 | Status": "AssumeBroken",
|
||||
"~3.0.0 | StatusReasonDetails": "broken due to transpiler errors"
|
||||
},
|
||||
|
||||
"Tree Spread": {
|
||||
"ID": "bcmpinc.TreeSpread",
|
||||
"~3.0.0 | Status": "AssumeBroken" // transpiler errors
|
||||
"~3.0.0 | Status": "AssumeBroken",
|
||||
"~3.0.0 | StatusReasonDetails": "broken due to transpiler errors"
|
||||
},
|
||||
|
||||
"TreeTransplant": {
|
||||
"ID": "TreeTransplant",
|
||||
"~1.0.9 | Status": "AssumeBroken" // causes AccessViolationException which prevents game launch
|
||||
"~1.0.9 | Status": "AssumeBroken",
|
||||
"~3.0.0 | StatusReasonDetails": "breaks game launch due to AccessViolationException"
|
||||
},
|
||||
|
||||
/*********
|
||||
|
@ -200,17 +235,14 @@
|
|||
*********/
|
||||
"Auto Quality Patch": {
|
||||
"ID": "SilentOak.AutoQualityPatch",
|
||||
"~2.1.3-unofficial.7-mizzion | Status": "AssumeBroken" // runtime errors
|
||||
"~2.1.3-unofficial.7-mizzion | Status": "AssumeBroken",
|
||||
"~2.1.3-unofficial.7-mizzion | StatusReasonDetails": "broken due to runtime errors"
|
||||
},
|
||||
|
||||
"Fix Dice": {
|
||||
"ID": "ashley.fixdice",
|
||||
"~1.1.2 | Status": "AssumeBroken" // crashes game on startup
|
||||
},
|
||||
|
||||
"Grass Growth": {
|
||||
"ID": "bcmpinc.GrassGrowth",
|
||||
"~1.0 | Status": "AssumeBroken"
|
||||
"~1.1.2 | Status": "AssumeBroken",
|
||||
"~1.1.2 | StatusReasonDetails": "crashes game on startup"
|
||||
},
|
||||
|
||||
"Invite Code Mod": {
|
||||
|
@ -238,11 +270,6 @@
|
|||
"~2.15 | Status": "AssumeBroken"
|
||||
},
|
||||
|
||||
"Yet Another Harvest With Scythe Mod": {
|
||||
"ID": "bcmpinc.HarvestWithScythe",
|
||||
"~1.1 | Status": "AssumeBroken"
|
||||
},
|
||||
|
||||
/*********
|
||||
** Broke in SMAPI 3.0 (runtime errors due to lifecycle changes)
|
||||
*********/
|
||||
|
@ -251,16 +278,6 @@
|
|||
"~1.0.0 | Status": "AssumeBroken"
|
||||
},
|
||||
|
||||
"Arcade 2048": {
|
||||
"ID": "Platonymous.2048",
|
||||
"~1.0.6 | Status": "AssumeBroken" // possibly due to PyTK
|
||||
},
|
||||
|
||||
"Arcade Snake": {
|
||||
"ID": "Platonymous.Snake",
|
||||
"~1.1.0 | Status": "AssumeBroken" // possibly due to PyTK
|
||||
},
|
||||
|
||||
"Better Sprinklers": {
|
||||
"ID": "Speeder.BetterSprinklers",
|
||||
"~2.3.1-unofficial.7-pathoschild | Status": "AssumeBroken"
|
||||
|
@ -278,12 +295,8 @@
|
|||
|
||||
"Decrafting Mod": {
|
||||
"ID": "MSCFC.DecraftingMod",
|
||||
"~1.0 | Status": "AssumeBroken" // NRE in ModEntry
|
||||
},
|
||||
|
||||
"JoJaBan - Arcade Sokoban": {
|
||||
"ID": "Platonymous.JoJaBan",
|
||||
"~0.4.3 | Status": "AssumeBroken" // possibly due to PyTK
|
||||
"~1.0 | Status": "AssumeBroken",
|
||||
"~1.0 | StatusReasonDetails": "fails due to NullReferenceException in ModEntry"
|
||||
},
|
||||
|
||||
"Level Extender": {
|
||||
|
@ -301,11 +314,6 @@
|
|||
"~1.5 | Status": "AssumeBroken"
|
||||
},
|
||||
|
||||
"Seed Bag": {
|
||||
"ID": "Platonymous.SeedBag",
|
||||
"~1.2.7 | Status": "AssumeBroken" // possibly due to PyTK
|
||||
},
|
||||
|
||||
"Stardew Valley ESP": {
|
||||
"ID": "reimu.sdv-helper",
|
||||
"~1.1 | Status": "AssumeBroken"
|
||||
|
@ -313,12 +321,14 @@
|
|||
|
||||
"Underdark Krobus": {
|
||||
"ID": "melnoelle.underdarkkrobus",
|
||||
"~1.0.0 | Status": "AssumeBroken" // NRE in ModEntry
|
||||
"~1.0 | Status": "AssumeBroken",
|
||||
"~1.0 | StatusReasonDetails": "fails due to NullReferenceException in ModEntry"
|
||||
},
|
||||
|
||||
"Underdark Sewer": {
|
||||
"ID": "melnoelle.underdarksewer",
|
||||
"~1.1.0 | Status": "AssumeBroken" // NRE in ModEntry
|
||||
"~1.1.0 | Status": "AssumeBroken",
|
||||
"~1.1.0 | StatusReasonDetails": "fails due to NullReferenceException in ModEntry"
|
||||
},
|
||||
|
||||
/*********
|
||||
|
@ -327,60 +337,70 @@
|
|||
"2cute FarmCave": {
|
||||
"ID": "taintedwheat.2CuteFarmCave",
|
||||
"Default | UpdateKey": "Nexus:843",
|
||||
"~2.0 | Status": "AssumeBroken" // references deleted Content/Mine.xnb
|
||||
"~2.0 | Status": "AssumeBroken",
|
||||
"~2.0 | StatusReasonDetails": "references the deleted Content/Mine asset"
|
||||
},
|
||||
|
||||
"Ace's Expanded Caves - Default Cave": {
|
||||
"ID": "Acerbicon.AECdefault",
|
||||
"Default | UpdateKey": "Nexus:2131",
|
||||
"~1.2.2 | Status": "AssumeBroken" // references deleted Content/Mine.xnb
|
||||
"~1.2.2 | Status": "AssumeBroken",
|
||||
"~1.2.2 | StatusReasonDetails": "references the deleted Content/Mine asset"
|
||||
},
|
||||
|
||||
"Ace's Expanded Caves - Desert Cave": {
|
||||
"ID": "Acerbicon.AECdesert",
|
||||
"Default | UpdateKey": "Nexus:2131",
|
||||
"~1.2.2 | Status": "AssumeBroken" // references deleted Content/Mine.xnb
|
||||
"~1.2.2 | Status": "AssumeBroken",
|
||||
"~1.2.2 | StatusReasonDetails": "references the deleted Content/Mine asset"
|
||||
},
|
||||
|
||||
"Ace's Expanded Caves - Ice Cave": {
|
||||
"ID": "Acerbicon.AECice",
|
||||
"Default | UpdateKey": "Nexus:2131",
|
||||
"~1.2.2 | Status": "AssumeBroken" // references deleted Content/Mine.xnb
|
||||
"~1.2.2 | Status": "AssumeBroken",
|
||||
"~1.2.2 | StatusReasonDetails": "references the deleted Content/Mine asset"
|
||||
},
|
||||
|
||||
"Ace's Expanded Caves - Lava Cave": {
|
||||
"ID": "Acerbicon.AEClava",
|
||||
"Default | UpdateKey": "Nexus:2131",
|
||||
"~1.2.2 | Status": "AssumeBroken" // references deleted Content/Mine.xnb
|
||||
"~1.2.2 | Status": "AssumeBroken",
|
||||
"~1.2.2 | StatusReasonDetails": "references the deleted Content/Mine asset"
|
||||
},
|
||||
|
||||
"Ace's Expanded Caves - Slime Cave": {
|
||||
"ID": "Acerbicon.AECslime",
|
||||
"Default | UpdateKey": "Nexus:2131",
|
||||
"~1.2.2 | Status": "AssumeBroken" // references deleted Content/Mine.xnb
|
||||
"~1.2.2 | Status": "AssumeBroken",
|
||||
"~1.2.2 | StatusReasonDetails": "references the deleted Content/Mine asset"
|
||||
},
|
||||
|
||||
"Green Pastures Farm": {
|
||||
"ID": "bugbuddy.GreenPasturesFarm",
|
||||
"Default | UpdateKey": "Nexus:2326",
|
||||
"~1.0 | Status": "AssumeBroken" // references deleted Content/weapons.xnb
|
||||
"~1.0 | Status": "AssumeBroken",
|
||||
"~1.0 | StatusReasonDetails": "references the deleted Content/weapons asset"
|
||||
},
|
||||
|
||||
"Immersive Farm 2": {
|
||||
"ID": "zander.immersivefarm2",
|
||||
"~2.0.1 | Status": "AssumeBroken" // references deleted Content/Mine.xnb
|
||||
"~2.0.1 | Status": "AssumeBroken",
|
||||
"~2.0.1 | StatusReasonDetails": "references the deleted Content/Mine asset"
|
||||
},
|
||||
|
||||
"Karmylla's Immersive Map Edits": {
|
||||
"ID": "Karmylla.ImmersiveMapEdits",
|
||||
"Default | UpdateKey": "Nexus:1149",
|
||||
"~2.4 | Status": "AssumeBroken" // references deleted Content/weapons.xnb
|
||||
"~2.4 | Status": "AssumeBroken",
|
||||
"~2.4 | StatusReasonDetails": "references the deleted Content/weapons asset"
|
||||
},
|
||||
|
||||
"Secret Gardens Greenhouse": {
|
||||
"ID": "jessebot.secretgardens",
|
||||
"Default | UpdateKey": "Nexus:3067",
|
||||
"~2.0.1 | Status": "AssumeBroken" // references deleted Content/Mine.xnb
|
||||
"~2.0.1 | Status": "AssumeBroken",
|
||||
"~2.0.1 | StatusReasonDetails": "references the deleted Content/Mine asset"
|
||||
},
|
||||
|
||||
/*********
|
||||
|
@ -388,102 +408,55 @@
|
|||
*********/
|
||||
"Canon-Friendly Dialogue Expansion": {
|
||||
"ID": "gizzymo.canonfriendlyexpansion",
|
||||
"~1.1.1 | Status": "AssumeBroken" // causes a save crash on certain dates
|
||||
"~1.1.1 | Status": "AssumeBroken",
|
||||
"~1.1.1 | StatusReasonDetails": "causes a save crash on certain dates"
|
||||
},
|
||||
|
||||
"Everytime Submarine": {
|
||||
"ID": "MustafaDemirel.EverytimeSubmarine",
|
||||
"~1.0.0 | Status": "AssumeBroken" // breaks player saves if their beach bridge is fixed
|
||||
},
|
||||
|
||||
"Always Scroll Map": {
|
||||
"ID": "bcmpinc.AlwaysScrollMap",
|
||||
"~0.6 | Status": "AssumeBroken" // breaks newer versions of bcmpinc mods (per bcmpinc's request)
|
||||
},
|
||||
|
||||
"Arcade Pong": {
|
||||
"ID": "Platonymous.ArcadePong",
|
||||
"~1.0.2 | Status": "AssumeBroken" // broke in SMAPI 2.6-beta.16 due to reflection into SMAPI internals
|
||||
"~1.0.0 | Status": "AssumeBroken",
|
||||
"~1.1.1 | StatusReasonDetails": "breaks player saves if their beach bridge is fixed"
|
||||
},
|
||||
|
||||
"BJS Night Sounds": {
|
||||
"ID": "BunnyJumps.BJSNightSounds",
|
||||
"~1.0.0 | Status": "AssumeBroken" // runtime errors with Harmony 1.2.0.1 in SMAPI 2.8+
|
||||
},
|
||||
|
||||
"Craft Counter": {
|
||||
"ID": "bcmpinc.CraftCounter",
|
||||
"~0.6 | Status": "AssumeBroken" // breaks newer versions of bcmpinc mods (per bcmpinc's request)
|
||||
"~1.0.0 | Status": "AssumeBroken",
|
||||
"~1.0.0 | StatusReasonDetails": "runtime errors with Harmony 1.2.0.1 in SMAPI 2.8+"
|
||||
},
|
||||
|
||||
"Fishing Adjust": {
|
||||
"ID": "shuaiz.FishingAdjustMod",
|
||||
"~2.0.1 | Status": "AssumeBroken" // Method not found: 'Void Harmony.HarmonyInstance.Patch(System.Reflection.MethodBase, Harmony.HarmonyMethod, Harmony.HarmonyMethod, Harmony.HarmonyMethod)'
|
||||
"~2.0.1 | Status": "AssumeBroken",
|
||||
"~2.0.1 | StatusReasonDetails": "fails with 'method not found' error for 'Void Harmony.HarmonyInstance.Patch(System.Reflection.MethodBase, Harmony.HarmonyMethod, Harmony.HarmonyMethod, Harmony.HarmonyMethod)'"
|
||||
},
|
||||
|
||||
"Fishing Automaton": {
|
||||
"ID": "Drynwynn.FishingAutomaton",
|
||||
"~1.1 | Status": "AssumeBroken" // runtime errors with Harmony 1.2.0.1 in SMAPI 2.8+
|
||||
},
|
||||
|
||||
"Fix Animal Tools": {
|
||||
"ID": "bcmpinc.FixAnimalTools",
|
||||
"~0.6 | Status": "AssumeBroken" // breaks newer versions of bcmpinc mods (per bcmpinc's request)
|
||||
},
|
||||
|
||||
"Fix Scythe Exp": {
|
||||
"ID": "bcmpinc.FixScytheExp",
|
||||
"~0.3 | Status": "AssumeBroken" // broke in 1.3: Exception from HarmonyInstance "bcmpinc.FixScytheExp" [...] Bad label content in ILGenerator.
|
||||
"~1.1 | Status": "AssumeBroken",
|
||||
"~1.1 | StatusReasonDetails": "runtime errors with Harmony 1.2.0.1 in SMAPI 2.8+"
|
||||
},
|
||||
|
||||
"More Silo Storage": {
|
||||
"ID": "OrneryWalrus.MoreSiloStorage",
|
||||
"~1.0.1 | Status": "AssumeBroken" // broke in SDV 1.3
|
||||
"~1.0.1 | Status": "AssumeBroken"
|
||||
},
|
||||
|
||||
"No Added Flying Mine Monsters": {
|
||||
"ID": "Drynwynn.NoAddedFlyingMineMonsters",
|
||||
"~1.1 | Status": "AssumeBroken" // runtime errors with Harmony 1.2.0.1 in SMAPI 2.8+
|
||||
"~1.1 | Status": "AssumeBroken",
|
||||
"~1.1 | StatusReasonDetails": "runtime errors with Harmony 1.2.0.1 in SMAPI 2.8+"
|
||||
},
|
||||
|
||||
"Server Bookmarker": {
|
||||
"ID": "Ilyaki.ServerBookmarker",
|
||||
"~1.0.0 | Status": "AssumeBroken" // broke in Stardew Valley 1.3.29 (runtime errors)
|
||||
},
|
||||
|
||||
"Skull Cave Saver": {
|
||||
"ID": "cantorsdust.SkullCaveSaver",
|
||||
"FormerIDs": "8ac06349-26f7-4394-806c-95d48fd35774 | community.SkullCaveSaver", // changed in 1.1 and 1.2.2
|
||||
"1.3-beta | Status": "AssumeBroken" // doesn't work in multiplayer, no longer maintained
|
||||
},
|
||||
|
||||
"Stardew Hack": {
|
||||
"ID": "bcmpinc.StardewHack",
|
||||
"~0.6 | Status": "AssumeBroken" // breaks newer versions of bcmpinc mods (per bcmpinc's request)
|
||||
"~1.0.0 | Status": "AssumeBroken",
|
||||
"~1.0.0 | StatusReasonDetails": "runtime errors in Stardew Valley 1.3.29"
|
||||
},
|
||||
|
||||
"Stephan's Lots of Crops": {
|
||||
"ID": "stephansstardewcrops",
|
||||
"~1.1 | Status": "AssumeBroken" // broke in SDV 1.3 (overwrites vanilla items)
|
||||
},
|
||||
|
||||
"Summit Reborn": {
|
||||
"ID": "KoihimeNakamura.summitreborn",
|
||||
"FormerIDs": "emissaryofinfinity.summitreborn", // changed in 1.0.2
|
||||
"~1.0.2 | Status": "AssumeBroken" // broke in SDV 1.3 (runtime errors)
|
||||
},
|
||||
|
||||
"Tilled Soil Decay": {
|
||||
"ID": "bcmpinc.TilledSoilDecay",
|
||||
"~0.6 | Status": "AssumeBroken" // breaks newer versions of bcmpinc mods (per bcmpinc's request)
|
||||
},
|
||||
|
||||
/*********
|
||||
** Broke circa SDV 1.2
|
||||
*********/
|
||||
"Move Faster": {
|
||||
"ID": "shuaiz.MoveFasterMod",
|
||||
"~1.0.1 | Status": "AssumeBroken" // doesn't do anything as of SDV 1.2.33 (bad Harmony patch?)
|
||||
"~1.1 | Status": "AssumeBroken",
|
||||
"~1.1 | StatusReasonDetails": "causes errors due to overwritten Stardew Valley 1.3 items"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace StardewModdingAPI
|
|||
** Public
|
||||
****/
|
||||
/// <summary>SMAPI's current semantic version.</summary>
|
||||
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.8.1");
|
||||
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.8.2");
|
||||
|
||||
/// <summary>The minimum supported version of Stardew Valley.</summary>
|
||||
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.1");
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using StardewModdingAPI.Framework.Networking;
|
||||
using StardewModdingAPI.Toolkit.Serialization;
|
||||
|
||||
namespace StardewModdingAPI.Events
|
||||
{
|
||||
|
@ -12,6 +13,9 @@ namespace StardewModdingAPI.Events
|
|||
/// <summary>The underlying message model.</summary>
|
||||
private readonly ModMessageModel Message;
|
||||
|
||||
/// <summary>The JSON helper used to deserialize models.</summary>
|
||||
private readonly JsonHelper JsonHelper;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
|
@ -31,16 +35,18 @@ namespace StardewModdingAPI.Events
|
|||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="message">The received message.</param>
|
||||
internal ModMessageReceivedEventArgs(ModMessageModel message)
|
||||
/// <param name="jsonHelper">The JSON helper used to deserialize models.</param>
|
||||
internal ModMessageReceivedEventArgs(ModMessageModel message, JsonHelper jsonHelper)
|
||||
{
|
||||
this.Message = message;
|
||||
this.JsonHelper = jsonHelper;
|
||||
}
|
||||
|
||||
/// <summary>Read the message data into the given model type.</summary>
|
||||
/// <typeparam name="TModel">The message model type.</typeparam>
|
||||
public TModel ReadAs<TModel>()
|
||||
{
|
||||
return this.Message.Data.ToObject<TModel>();
|
||||
return this.Message.Data.ToObject<TModel>(this.JsonHelper.GetSerializer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,9 @@ namespace StardewModdingAPI.Framework
|
|||
/// <remarks>The game may adds content managers in asynchronous threads (e.g. when populating the load screen).</remarks>
|
||||
private readonly ReaderWriterLockSlim ContentManagerLock = new ReaderWriterLockSlim();
|
||||
|
||||
/// <summary>An unmodified content manager which doesn't intercept assets, used to compare asset data.</summary>
|
||||
private readonly LocalizedContentManager VanillaContentManager;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
|
@ -95,6 +98,7 @@ namespace StardewModdingAPI.Framework
|
|||
this.ContentManagers.Add(
|
||||
this.MainContentManager = new GameContentManager("Game1.content", serviceProvider, rootDirectory, currentCulture, this, monitor, reflection, this.OnDisposing, onLoadingFirstAsset)
|
||||
);
|
||||
this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory);
|
||||
this.CoreAssets = new CoreAssetPropagator(this.MainContentManager.AssertAndNormalizeAssetName, reflection);
|
||||
}
|
||||
|
||||
|
@ -150,6 +154,8 @@ namespace StardewModdingAPI.Framework
|
|||
{
|
||||
foreach (IContentManager contentManager in this.ContentManagers)
|
||||
contentManager.OnLocaleChanged();
|
||||
|
||||
this.VanillaContentManager.Unload();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -287,6 +293,23 @@ namespace StardewModdingAPI.Framework
|
|||
});
|
||||
}
|
||||
|
||||
/// <summary>Get a vanilla asset without interception.</summary>
|
||||
/// <typeparam name="T">The type of asset to load.</typeparam>
|
||||
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
|
||||
public bool TryLoadVanillaAsset<T>(string assetName, out T asset)
|
||||
{
|
||||
try
|
||||
{
|
||||
asset = this.VanillaContentManager.Load<T>(assetName);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
asset = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Dispose held resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@ using StardewModdingAPI.Framework.Reflection;
|
|||
using StardewModdingAPI.Framework.Utilities;
|
||||
using StardewValley;
|
||||
using xTile;
|
||||
using xTile.Tiles;
|
||||
|
||||
namespace StardewModdingAPI.Framework.ContentManagers
|
||||
{
|
||||
|
@ -308,15 +309,10 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
return null;
|
||||
}
|
||||
|
||||
// validate asset
|
||||
if (data == null)
|
||||
{
|
||||
mod.LogAsMod($"Mod incorrectly set asset '{info.AssetName}' to a null value; ignoring override.", LogLevel.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
// return matched asset
|
||||
return new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName);
|
||||
return this.TryValidateLoadedAsset(info, data, mod)
|
||||
? new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName)
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>Apply any <see cref="Editors"/> to a loaded asset.</summary>
|
||||
|
@ -386,5 +382,77 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
// return result
|
||||
return asset;
|
||||
}
|
||||
|
||||
/// <summary>Validate that an asset loaded by a mod is valid and won't cause issues.</summary>
|
||||
/// <typeparam name="T">The asset type.</typeparam>
|
||||
/// <param name="info">The basic asset metadata.</param>
|
||||
/// <param name="data">The loaded asset data.</param>
|
||||
/// <param name="mod">The mod which loaded the asset.</param>
|
||||
private bool TryValidateLoadedAsset<T>(IAssetInfo info, T data, IModMetadata mod)
|
||||
{
|
||||
// can't load a null asset
|
||||
if (data == null)
|
||||
{
|
||||
mod.LogAsMod($"SMAPI blocked asset replacement for '{info.AssetName}': mod incorrectly set asset to a null value.", LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// when replacing a map, the vanilla tilesheets must have the same order and IDs
|
||||
if (data is Map loadedMap && this.Coordinator.TryLoadVanillaAsset(info.AssetName, out Map vanillaMap))
|
||||
{
|
||||
for (int i = 0; i < vanillaMap.TileSheets.Count; i++)
|
||||
{
|
||||
// check for match
|
||||
TileSheet vanillaSheet = vanillaMap.TileSheets[i];
|
||||
bool found = this.TryFindTilesheet(loadedMap, vanillaSheet.Id, out int loadedIndex, out TileSheet loadedSheet);
|
||||
if (found && loadedIndex == i)
|
||||
continue;
|
||||
|
||||
// handle mismatch
|
||||
{
|
||||
// only show warning if not farm map
|
||||
// This is temporary: mods shouldn't do this for any vanilla map, but these are the ones we know will crash. Showing a warning for others instead gives modders time to update their mods, while still simplifying troubleshooting.
|
||||
bool isFarmMap = info.AssetNameEquals("Maps/Farm") || info.AssetNameEquals("Maps/Farm_Combat") || info.AssetNameEquals("Maps/Farm_Fishing") || info.AssetNameEquals("Maps/Farm_Foraging") || info.AssetNameEquals("Maps/Farm_FourCorners") || info.AssetNameEquals("Maps/Farm_Island") || info.AssetNameEquals("Maps/Farm_Mining");
|
||||
|
||||
|
||||
string reason = found
|
||||
? $"mod reordered the original tilesheets, which {(isFarmMap ? "would cause a crash" : "often causes crashes")}.\n\nTechnical details for mod author:\nExpected order [{string.Join(", ", vanillaMap.TileSheets.Select(p => $"'{p.ImageSource}' (id: {p.Id})"))}], but found tilesheet '{vanillaSheet.Id}' at index {loadedIndex} instead of {i}. Make sure custom tilesheet IDs are prefixed with 'z_' to avoid reordering tilesheets."
|
||||
: $"mod has no tilesheet with ID '{vanillaSheet.Id}'. Map replacements must keep the original tilesheets to avoid errors or crashes.";
|
||||
|
||||
SCore.DeprecationManager.PlaceholderWarn("3.8.2", DeprecationLevel.PendingRemoval);
|
||||
if (isFarmMap)
|
||||
{
|
||||
mod.LogAsMod($"SMAPI blocked asset replacement for '{info.AssetName}': {reason}", LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
mod.LogAsMod($"SMAPI detected a potential issue with asset replacement for '{info.AssetName}' map: {reason}", LogLevel.Warn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Find a map tilesheet by ID.</summary>
|
||||
/// <param name="map">The map whose tilesheets to search.</param>
|
||||
/// <param name="id">The tilesheet ID to match.</param>
|
||||
/// <param name="index">The matched tilesheet index, if any.</param>
|
||||
/// <param name="tilesheet">The matched tilesheet, if any.</param>
|
||||
private bool TryFindTilesheet(Map map, string id, out int index, out TileSheet tilesheet)
|
||||
{
|
||||
for (int i = 0; i < map.TileSheets.Count; i++)
|
||||
{
|
||||
if (map.TileSheets[i].Id == id)
|
||||
{
|
||||
index = i;
|
||||
tilesheet = map.TileSheets[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
index = -1;
|
||||
tilesheet = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Framework
|
||||
{
|
||||
|
@ -25,8 +26,8 @@ namespace StardewModdingAPI.Framework
|
|||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="absolutePixels">The pixel position relative to the top-left corner of the in-game map, adjusted for pixel zoom.</param>
|
||||
/// <param name="screenPixels">The pixel position relative to the top-left corner of the visible screen, adjusted for pixel zoom.</param>
|
||||
/// <param name="absolutePixels">The pixel position relative to the top-left corner of the in-game map, adjusted for zoom but not UI scaling.</param>
|
||||
/// <param name="screenPixels">The pixel position relative to the top-left corner of the visible screen, adjusted for zoom but not UI scaling.</param>
|
||||
/// <param name="tile">The tile position relative to the top-left corner of the map.</param>
|
||||
/// <param name="grabTile">The tile position that the game considers under the cursor for purposes of clicking actions.</param>
|
||||
public CursorPosition(Vector2 absolutePixels, Vector2 screenPixels, Vector2 tile, Vector2 grabTile)
|
||||
|
@ -42,5 +43,21 @@ namespace StardewModdingAPI.Framework
|
|||
{
|
||||
return other != null && this.AbsolutePixels == other.AbsolutePixels;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2 GetScaledAbsolutePixels()
|
||||
{
|
||||
return Game1.uiMode
|
||||
? Utility.ModifyCoordinatesForUIScale(this.AbsolutePixels)
|
||||
: this.AbsolutePixels;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2 GetScaledScreenPixels()
|
||||
{
|
||||
return Game1.uiMode
|
||||
? Utility.ModifyCoordinatesForUIScale(this.ScreenPixels)
|
||||
: this.ScreenPixels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,11 @@ namespace StardewModdingAPI.Framework
|
|||
this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity, Environment.StackTrace));
|
||||
}
|
||||
|
||||
/// <summary>A placeholder method used to track deprecated code for which a separate warning will be shown.</summary>
|
||||
/// <param name="version">The SMAPI version which deprecated it.</param>
|
||||
/// <param name="severity">How deprecated the code is.</param>
|
||||
public void PlaceholderWarn(string version, DeprecationLevel severity) { }
|
||||
|
||||
/// <summary>Print any queued messages.</summary>
|
||||
public void PrintQueued()
|
||||
{
|
||||
|
|
|
@ -63,18 +63,16 @@ namespace StardewModdingAPI.Framework.Input
|
|||
base.Update();
|
||||
|
||||
// update SMAPI extended data
|
||||
// note: Stardew Valley is *not* in UI mode when this code runs
|
||||
try
|
||||
{
|
||||
float scale = Game1.options.uiScale;
|
||||
float zoomMultiplier = (1f / Game1.options.zoomLevel);
|
||||
|
||||
// get real values
|
||||
var controller = new GamePadStateBuilder(base.GetGamePadState());
|
||||
var keyboard = new KeyboardStateBuilder(base.GetKeyboardState());
|
||||
var mouse = new MouseStateBuilder(base.GetMouseState());
|
||||
Vector2 cursorAbsolutePos = new Vector2(
|
||||
x: (mouse.X / scale) + Game1.uiViewport.X,
|
||||
y: (mouse.Y / scale) + Game1.uiViewport.Y
|
||||
);
|
||||
Vector2 cursorAbsolutePos = new Vector2((mouse.X * zoomMultiplier) + Game1.viewport.X, (mouse.Y * zoomMultiplier) + Game1.viewport.Y);
|
||||
Vector2? playerTilePos = Context.IsPlayerFree ? Game1.player.getTileLocation() : (Vector2?)null;
|
||||
HashSet<SButton> reallyDown = new HashSet<SButton>(this.GetPressedButtons(keyboard, mouse, controller));
|
||||
|
||||
|
@ -109,7 +107,7 @@ namespace StardewModdingAPI.Framework.Input
|
|||
if (cursorAbsolutePos != this.CursorPositionImpl?.AbsolutePixels || playerTilePos != this.LastPlayerTile)
|
||||
{
|
||||
this.LastPlayerTile = playerTilePos;
|
||||
this.CursorPositionImpl = this.GetCursorPosition(this.MouseState, cursorAbsolutePos, scale);
|
||||
this.CursorPositionImpl = this.GetCursorPosition(this.MouseState, cursorAbsolutePos, zoomMultiplier);
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
|
@ -202,11 +200,11 @@ namespace StardewModdingAPI.Framework.Input
|
|||
/// <summary>Get the current cursor position.</summary>
|
||||
/// <param name="mouseState">The current mouse state.</param>
|
||||
/// <param name="absolutePixels">The absolute pixel position relative to the map, adjusted for pixel zoom.</param>
|
||||
/// <param name="scale">The UI scale applied to pixel coordinates.</param>
|
||||
private CursorPosition GetCursorPosition(MouseState mouseState, Vector2 absolutePixels, float scale)
|
||||
/// <param name="zoomMultiplier">The multiplier applied to pixel coordinates to adjust them for pixel zoom.</param>
|
||||
private CursorPosition GetCursorPosition(MouseState mouseState, Vector2 absolutePixels, float zoomMultiplier)
|
||||
{
|
||||
Vector2 screenPixels = new Vector2(mouseState.X / scale, mouseState.Y / scale);
|
||||
Vector2 tile = new Vector2((int)((Game1.uiViewport.X + screenPixels.X) / Game1.tileSize), (int)((Game1.uiViewport.Y + screenPixels.Y) / Game1.tileSize));
|
||||
Vector2 screenPixels = new Vector2(mouseState.X * zoomMultiplier, mouseState.Y * zoomMultiplier);
|
||||
Vector2 tile = new Vector2((int)((Game1.viewport.X + screenPixels.X) / Game1.tileSize), (int)((Game1.viewport.Y + screenPixels.Y) / Game1.tileSize));
|
||||
Vector2 grabTile = (Game1.mouseCursorTransparency > 0 && Utility.tileWithinRadiusOfPlayer((int)tile.X, (int)tile.Y, 1, Game1.player)) // derived from Game1.pressActionButton
|
||||
? tile
|
||||
: Game1.player.GetGrabTile();
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace StardewModdingAPI.Framework.Logging
|
|||
search: new Regex(@"^System\.InvalidOperationException: Steamworks is not initialized\.[\s\S]+$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
||||
replacement:
|
||||
#if SMAPI_FOR_WINDOWS
|
||||
"Oops! Steam achievements won't work because Steam isn't loaded. You can launch the game through Steam to fix that (see 'Part 2: Configure Steam' in the install guide for more info: https://smapi.io/install).",
|
||||
"Oops! Steam achievements won't work because Steam isn't loaded. See 'Launch SMAPI through Steam or GOG Galaxy' in the install guide for more info: https://smapi.io/install.",
|
||||
#else
|
||||
"Oops! Steam achievements won't work because Steam isn't loaded. You can launch the game through Steam to fix that.",
|
||||
#endif
|
||||
|
@ -425,9 +425,11 @@ namespace StardewModdingAPI.Framework.Logging
|
|||
this.Monitor.Log($" ({mod.ErrorDetails})");
|
||||
}
|
||||
|
||||
// find skipped dependencies
|
||||
IModMetadata[] skippedDependencies;
|
||||
// group mods
|
||||
List<IModMetadata> skippedDependencies = new List<IModMetadata>();
|
||||
List<IModMetadata> otherSkippedMods = new List<IModMetadata>();
|
||||
{
|
||||
// track broken dependencies
|
||||
HashSet<string> skippedDependencyIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> skippedModIds = new HashSet<string>(from mod in skippedMods where mod.HasID() select mod.Manifest.UniqueID, StringComparer.OrdinalIgnoreCase);
|
||||
foreach (IModMetadata mod in skippedMods)
|
||||
|
@ -435,7 +437,15 @@ namespace StardewModdingAPI.Framework.Logging
|
|||
foreach (string requiredId in skippedModIds.Intersect(mod.GetRequiredModIds()))
|
||||
skippedDependencyIds.Add(requiredId);
|
||||
}
|
||||
skippedDependencies = skippedMods.Where(p => p.HasID() && skippedDependencyIds.Contains(p.Manifest.UniqueID)).ToArray();
|
||||
|
||||
// collect mod groups
|
||||
foreach (IModMetadata mod in skippedMods)
|
||||
{
|
||||
if (mod.HasID() && skippedDependencyIds.Contains(mod.Manifest.UniqueID))
|
||||
skippedDependencies.Add(mod);
|
||||
else
|
||||
otherSkippedMods.Add(mod);
|
||||
}
|
||||
}
|
||||
|
||||
// log skipped mods
|
||||
|
@ -451,7 +461,7 @@ namespace StardewModdingAPI.Framework.Logging
|
|||
this.Monitor.Newline();
|
||||
}
|
||||
|
||||
foreach (IModMetadata mod in skippedMods.OrderBy(p => p.DisplayName))
|
||||
foreach (IModMetadata mod in otherSkippedMods.OrderBy(p => p.DisplayName))
|
||||
LogSkippedMod(mod);
|
||||
this.Monitor.Newline();
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
switch (mod.DataRecord?.Status)
|
||||
{
|
||||
case ModStatus.Obsolete:
|
||||
mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.Obsolete, $"it's obsolete: {mod.DataRecord.StatusReasonPhrase}");
|
||||
mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.Obsolete, $"it's obsolete: {mod.DataRecord.StatusReasonPhrase}", this.GetTechnicalReasonForStatusOverride(mod));
|
||||
continue;
|
||||
|
||||
case ModStatus.AssumeBroken:
|
||||
|
@ -102,7 +102,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
error += $"version newer than {mod.DataRecord.StatusUpperVersion}";
|
||||
error += " at " + string.Join(" or ", updateUrls);
|
||||
|
||||
mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.Incompatible, error);
|
||||
mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.Incompatible, error, this.GetTechnicalReasonForStatusOverride(mod));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
@ -409,6 +409,38 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
yield return new ModDependency(manifest.ContentPackFor.UniqueID, manifest.ContentPackFor.MinimumVersion, FindMod(manifest.ContentPackFor.UniqueID), isRequired: true);
|
||||
}
|
||||
|
||||
/// <summary>Get a technical message indicating why a mod's compatibility status was overridden, if applicable.</summary>
|
||||
/// <param name="mod">The mod metadata.</param>
|
||||
private string GetTechnicalReasonForStatusOverride(IModMetadata mod)
|
||||
{
|
||||
// get compatibility list record
|
||||
var data = mod.DataRecord;
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
// get status label
|
||||
string statusLabel = data.Status switch
|
||||
{
|
||||
ModStatus.AssumeBroken => "'assume broken'",
|
||||
ModStatus.AssumeCompatible => "'assume compatible'",
|
||||
ModStatus.Obsolete => "obsolete",
|
||||
_ => data.Status.ToString()
|
||||
};
|
||||
|
||||
// get reason
|
||||
string[] reasons = new[] { mod.DataRecord.StatusReasonPhrase, mod.DataRecord.StatusReasonDetails }
|
||||
.Where(p => !string.IsNullOrWhiteSpace(p))
|
||||
.ToArray();
|
||||
|
||||
// build message
|
||||
return
|
||||
$"marked {statusLabel} in SMAPI's internal compatibility list for "
|
||||
+ (mod.DataRecord.StatusUpperVersion != null ? $"versions up to {mod.DataRecord.StatusUpperVersion}" : "all versions")
|
||||
+ ": "
|
||||
+ (reasons.Any() ? string.Join(": ", reasons) : "no reason given")
|
||||
+ ".";
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private models
|
||||
|
|
|
@ -1128,7 +1128,7 @@ namespace StardewModdingAPI.Framework
|
|||
modIDs.Remove(message.FromModID); // don't send a broadcast back to the sender
|
||||
|
||||
// raise events
|
||||
this.EventManager.ModMessageReceived.Raise(new ModMessageReceivedEventArgs(message), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID));
|
||||
this.EventManager.ModMessageReceived.Raise(new ModMessageReceivedEventArgs(message, this.Toolkit.JsonHelper), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID));
|
||||
}
|
||||
|
||||
/// <summary>Constructor a content manager to read game content files.</summary>
|
||||
|
@ -1532,7 +1532,7 @@ namespace StardewModdingAPI.Framework
|
|||
// validate status
|
||||
if (mod.Status == ModMetadataStatus.Failed)
|
||||
{
|
||||
this.Monitor.Log($" Failed: {mod.Error}");
|
||||
this.Monitor.Log($" Failed: {mod.ErrorDetails ?? mod.Error}");
|
||||
failReason = mod.FailReason;
|
||||
errorReasonPhrase = mod.Error;
|
||||
return false;
|
||||
|
|
|
@ -10,6 +10,7 @@ using StardewModdingAPI.Framework.Events;
|
|||
using StardewModdingAPI.Framework.Networking;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Toolkit.Serialization;
|
||||
using StardewModdingAPI.Utilities;
|
||||
using StardewValley;
|
||||
using StardewValley.Network;
|
||||
using StardewValley.SDKs;
|
||||
|
@ -54,15 +55,25 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>Whether to log network traffic.</summary>
|
||||
private readonly bool LogNetworkTraffic;
|
||||
|
||||
/// <summary>The backing field for <see cref="Peers"/>.</summary>
|
||||
private readonly PerScreen<IDictionary<long, MultiplayerPeer>> PeersImpl = new(() => new Dictionary<long, MultiplayerPeer>());
|
||||
|
||||
/// <summary>The backing field for <see cref="HostPeer"/>.</summary>
|
||||
private readonly PerScreen<MultiplayerPeer> HostPeerImpl = new();
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The metadata for each connected peer.</summary>
|
||||
public IDictionary<long, MultiplayerPeer> Peers { get; } = new Dictionary<long, MultiplayerPeer>();
|
||||
public IDictionary<long, MultiplayerPeer> Peers => this.PeersImpl.Value;
|
||||
|
||||
/// <summary>The metadata for the host player, if the current player is a farmhand.</summary>
|
||||
public MultiplayerPeer HostPeer;
|
||||
public MultiplayerPeer HostPeer
|
||||
{
|
||||
get => this.HostPeerImpl.Value;
|
||||
private set => this.HostPeerImpl.Value = value;
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
/// <summary>Represents a cursor position in the different coordinate systems.</summary>
|
||||
public interface ICursorPosition : IEquatable<ICursorPosition>
|
||||
{
|
||||
/// <summary>The pixel position relative to the top-left corner of the in-game map.</summary>
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The pixel position relative to the top-left corner of the in-game map, adjusted for zoom but not UI scaling. See also <see cref="GetScaledAbsolutePixels"/>.</summary>
|
||||
Vector2 AbsolutePixels { get; }
|
||||
|
||||
/// <summary>The pixel position relative to the top-left corner of the visible screen.</summary>
|
||||
/// <summary>The pixel position relative to the top-left corner of the visible screen, adjusted for zoom but not UI scaling. See also <see cref="GetScaledScreenPixels"/>.</summary>
|
||||
Vector2 ScreenPixels { get; }
|
||||
|
||||
/// <summary>The tile position under the cursor relative to the top-left corner of the map.</summary>
|
||||
|
@ -17,5 +21,15 @@ namespace StardewModdingAPI
|
|||
|
||||
/// <summary>The tile position that the game considers under the cursor for purposes of clicking actions. This may be different than <see cref="Tile"/> if that's too far from the player.</summary>
|
||||
Vector2 GrabTile { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get the <see cref="AbsolutePixels"/>, adjusted for UI scaling if needed. This is only different if <see cref="Game1.uiMode"/> is true.</summary>
|
||||
Vector2 GetScaledAbsolutePixels();
|
||||
|
||||
/// <summary>Get the <see cref="ScreenPixels"/>, adjusted for UI scaling if needed. This is only different if <see cref="Game1.uiMode"/> is true.</summary>
|
||||
Vector2 GetScaledScreenPixels();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue