From 1cac3892848bef50a58b07c567f551974635b6d8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 17 Oct 2020 22:03:43 -0400 Subject: fix error in heuristic rewriting --- docs/release-notes.md | 4 ++++ src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index a11721d4..d6b5df7a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,6 +7,10 @@ * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). --> +## Upcoming release +* For modders: + * Fixed error when heuristically rewriting a property for a type that no longer exists. + ## 3.7.5 Released 16 October 2020 for Stardew Valley 1.4.1 or later. diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs index ca04205c..f59a6ab1 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs @@ -68,7 +68,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters private bool TryRewriteToProperty(ModuleDefinition module, Instruction instruction, FieldReference fieldRef, TypeDefinition declaringType, bool isRead) { // get equivalent property - PropertyDefinition property = declaringType.Properties.FirstOrDefault(p => p.Name == fieldRef.Name); + PropertyDefinition property = declaringType?.Properties.FirstOrDefault(p => p.Name == fieldRef.Name); MethodDefinition method = isRead ? property?.GetMethod : property?.SetMethod; if (method == null) return false; -- cgit From 70cf63c9075a126e7254b82d2d8109a451313920 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Oct 2020 15:33:27 -0400 Subject: use update URL from server instead of hardcoding it --- src/SMAPI/Framework/Logging/LogManager.cs | 15 +++++--- src/SMAPI/Framework/SCore.cs | 57 +++++++++++++++++-------------- 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index 094dd749..1e484709 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -195,9 +195,10 @@ namespace StardewModdingAPI.Framework.Logging /// Write an update alert marker file. /// The new version found. - public void WriteUpdateMarker(string version) + /// The download URL for the update. + public void WriteUpdateMarker(string version, string url) { - File.WriteAllText(Constants.UpdateMarker, version); + File.WriteAllText(Constants.UpdateMarker, $"{version}|{url}"); } /// Check whether SMAPI crashed or detected an update during the last session, and display them in the SMAPI console. @@ -206,13 +207,17 @@ namespace StardewModdingAPI.Framework.Logging // show update alert if (File.Exists(Constants.UpdateMarker)) { - string rawUpdateFound = File.ReadAllText(Constants.UpdateMarker); - if (SemanticVersion.TryParse(rawUpdateFound, out ISemanticVersion updateFound)) + string[] rawUpdateFound = File.ReadAllText(Constants.UpdateMarker).Split(new [] { '|' }, 2); + if (SemanticVersion.TryParse(rawUpdateFound[0], out ISemanticVersion updateFound)) { if (Constants.ApiVersion.IsPrerelease() && updateFound.IsNewerThan(Constants.ApiVersion)) { + string url = rawUpdateFound.Length > 1 + ? rawUpdateFound[1] + : Constants.HomePageUrl; + this.Monitor.Log("A new version of SMAPI was detected last time you played.", LogLevel.Error); - this.Monitor.Log($"You can update to {updateFound}: https://smapi.io.", LogLevel.Error); + this.Monitor.Log($"You can update to {updateFound}: {url}.", LogLevel.Error); this.Monitor.Log("Press any key to continue playing anyway. (This only appears when using a SMAPI beta.)", LogLevel.Info); Console.ReadKey(); } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index f07e41f0..1b4c32bb 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1195,37 +1195,42 @@ namespace StardewModdingAPI.Framework this.Monitor.Log("Checking for updates..."); // check SMAPI version - ISemanticVersion updateFound = null; - try { - // fetch update check - ModEntryModel response = client.GetModInfo(new[] { new ModSearchEntryModel("Pathoschild.SMAPI", Constants.ApiVersion, new[] { $"GitHub:{this.Settings.GitHubProjectName}" }) }, apiVersion: Constants.ApiVersion, gameVersion: Constants.GameVersion, platform: Constants.Platform).Single().Value; - if (response.SuggestedUpdate != null) - this.Monitor.Log($"You can update SMAPI to {response.SuggestedUpdate.Version}: {Constants.HomePageUrl}", LogLevel.Alert); - else - this.Monitor.Log(" SMAPI okay."); - - updateFound = response.SuggestedUpdate?.Version; + ISemanticVersion updateFound = null; + string updateUrl = null; + try + { + // fetch update check + ModEntryModel response = client.GetModInfo(new[] { new ModSearchEntryModel("Pathoschild.SMAPI", Constants.ApiVersion, new[] { $"GitHub:{this.Settings.GitHubProjectName}" }) }, apiVersion: Constants.ApiVersion, gameVersion: Constants.GameVersion, platform: Constants.Platform).Single().Value; + updateFound = response.SuggestedUpdate?.Version; + updateUrl = response.SuggestedUpdate?.Url ?? Constants.HomePageUrl; + + // log message + if (updateFound != null) + this.Monitor.Log($"You can update SMAPI to {updateFound}: {updateUrl}", LogLevel.Alert); + else + this.Monitor.Log(" SMAPI okay."); - // show errors - if (response.Errors.Any()) + // show errors + if (response.Errors.Any()) + { + this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.", LogLevel.Warn); + this.Monitor.Log($"Error: {string.Join("\n", response.Errors)}"); + } + } + catch (Exception ex) { - this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.", LogLevel.Warn); - this.Monitor.Log($"Error: {string.Join("\n", response.Errors)}"); + this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you won't be notified of new versions if this keeps happening.", LogLevel.Warn); + this.Monitor.Log(ex is WebException && ex.InnerException == null + ? $"Error: {ex.Message}" + : $"Error: {ex.GetLogSummary()}" + ); } - } - catch (Exception ex) - { - this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you won't be notified of new versions if this keeps happening.", LogLevel.Warn); - this.Monitor.Log(ex is WebException && ex.InnerException == null - ? $"Error: {ex.Message}" - : $"Error: {ex.GetLogSummary()}" - ); - } - // show update message on next launch - if (updateFound != null) - this.LogManager.WriteUpdateMarker(updateFound.ToString()); + // show update message on next launch + if (updateFound != null) + this.LogManager.WriteUpdateMarker(updateFound.ToString(), updateUrl); + } // check mod versions if (mods.Any()) -- cgit From 7c652b0924476cea8dc89faa30983e01c0c66fec Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 24 Oct 2020 18:26:41 -0400 Subject: update item repo to allow creating instances later --- .../Framework/ItemData/SearchableItem.cs | 12 ++- .../Framework/ItemRepository.cs | 91 ++++++++++++---------- 2 files changed, 60 insertions(+), 43 deletions(-) diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs index d9e63126..3675a963 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs @@ -12,9 +12,12 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData /// The item type. public ItemType Type { get; } - /// The item instance. + /// A sample item instance. public Item Item { get; } + /// Create an item instance. + public Func CreateItem { get; } + /// The item's unique ID for its type. public int ID { get; } @@ -31,12 +34,13 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData /// Construct an instance. /// The item type. /// The unique ID (if different from the item's parent sheet index). - /// The item instance. - public SearchableItem(ItemType type, int id, Item item) + /// Create an item instance. + public SearchableItem(ItemType type, int id, Func createItem) { this.Type = type; this.ID = id; - this.Item = item; + this.CreateItem = () => createItem(this); + this.Item = createItem(this); } /// Get whether the item name contains a case-insensitive substring. diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index 37f5f8d1..a96a842c 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -30,47 +30,60 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework [SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = "TryCreate invokes the lambda immediately.")] public IEnumerable GetAll() { + // + // + // Be careful about closure variable capture here! + // + // SearchableItem stores the Func to create new instances later. Loop variables passed into the + // function will be captured, so every func in the loop will use the value from the last iteration. Use the + // TryCreate(type, id, entity => item) form to avoid the issue, or create a local variable to pass in. + // + // + + IEnumerable GetAllRaw() { // get tools - for (int quality = Tool.stone; quality <= Tool.iridium; quality++) + for (int q = Tool.stone; q <= Tool.iridium; q++) { - yield return this.TryCreate(ItemType.Tool, ToolFactory.axe, () => ToolFactory.getToolFromDescription(ToolFactory.axe, quality)); - yield return this.TryCreate(ItemType.Tool, ToolFactory.hoe, () => ToolFactory.getToolFromDescription(ToolFactory.hoe, quality)); - yield return this.TryCreate(ItemType.Tool, ToolFactory.pickAxe, () => ToolFactory.getToolFromDescription(ToolFactory.pickAxe, quality)); - yield return this.TryCreate(ItemType.Tool, ToolFactory.wateringCan, () => ToolFactory.getToolFromDescription(ToolFactory.wateringCan, quality)); + int quality = q; + + yield return this.TryCreate(ItemType.Tool, ToolFactory.axe, _ => ToolFactory.getToolFromDescription(ToolFactory.axe, quality)); + yield return this.TryCreate(ItemType.Tool, ToolFactory.hoe, _ => ToolFactory.getToolFromDescription(ToolFactory.hoe, quality)); + yield return this.TryCreate(ItemType.Tool, ToolFactory.pickAxe, _ => ToolFactory.getToolFromDescription(ToolFactory.pickAxe, quality)); + yield return this.TryCreate(ItemType.Tool, ToolFactory.wateringCan, _ => ToolFactory.getToolFromDescription(ToolFactory.wateringCan, quality)); if (quality != Tool.iridium) - yield return this.TryCreate(ItemType.Tool, ToolFactory.fishingRod, () => ToolFactory.getToolFromDescription(ToolFactory.fishingRod, quality)); + yield return this.TryCreate(ItemType.Tool, ToolFactory.fishingRod, _ => ToolFactory.getToolFromDescription(ToolFactory.fishingRod, quality)); } - yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset, () => new MilkPail()); // these don't have any sort of ID, so we'll just assign some arbitrary ones - yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 1, () => new Shears()); - yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 2, () => new Pan()); - yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 3, () => new Wand()); + yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset, _ => new MilkPail()); // these don't have any sort of ID, so we'll just assign some arbitrary ones + yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 1, _ => new Shears()); + yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 2, _ => new Pan()); + yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 3, _ => new Wand()); // clothing foreach (int id in Game1.clothingInformation.Keys) - yield return this.TryCreate(ItemType.Clothing, id, () => new Clothing(id)); + yield return this.TryCreate(ItemType.Clothing, id, p => new Clothing(p.ID)); // wallpapers for (int id = 0; id < 112; id++) - yield return this.TryCreate(ItemType.Wallpaper, id, () => new Wallpaper(id) { Category = SObject.furnitureCategory }); + yield return this.TryCreate(ItemType.Wallpaper, id, p => new Wallpaper(p.ID) { Category = SObject.furnitureCategory }); // flooring for (int id = 0; id < 56; id++) - yield return this.TryCreate(ItemType.Flooring, id, () => new Wallpaper(id, isFloor: true) { Category = SObject.furnitureCategory }); + yield return this.TryCreate(ItemType.Flooring, id, p => new Wallpaper(p.ID, isFloor: true) { Category = SObject.furnitureCategory }); // equipment foreach (int id in this.TryLoad("Data\\Boots").Keys) - yield return this.TryCreate(ItemType.Boots, id, () => new Boots(id)); + yield return this.TryCreate(ItemType.Boots, id, p => new Boots(p.ID)); foreach (int id in this.TryLoad("Data\\hats").Keys) - yield return this.TryCreate(ItemType.Hat, id, () => new Hat(id)); + yield return this.TryCreate(ItemType.Hat, id, p => new Hat(p.ID)); // weapons foreach (int id in this.TryLoad("Data\\weapons").Keys) { - yield return this.TryCreate(ItemType.Weapon, id, () => (id >= 32 && id <= 34) - ? (Item)new Slingshot(id) - : new MeleeWeapon(id) + yield return this.TryCreate(ItemType.Weapon, id, p => (p.ID >= 32 && p.ID <= 34) + ? (Item)new Slingshot(p.ID) + : new MeleeWeapon(p.ID) ); } @@ -78,14 +91,14 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework foreach (int id in this.TryLoad("Data\\Furniture").Keys) { if (id == 1466 || id == 1468 || id == 1680) - yield return this.TryCreate(ItemType.Furniture, id, () => new TV(id, Vector2.Zero)); + yield return this.TryCreate(ItemType.Furniture, id, p => new TV(p.ID, Vector2.Zero)); else - yield return this.TryCreate(ItemType.Furniture, id, () => new Furniture(id, Vector2.Zero)); + yield return this.TryCreate(ItemType.Furniture, id, p => new Furniture(p.ID, Vector2.Zero)); } // craftables foreach (int id in Game1.bigCraftablesInformation.Keys) - yield return this.TryCreate(ItemType.BigCraftable, id, () => new SObject(Vector2.Zero, id)); + yield return this.TryCreate(ItemType.BigCraftable, id, p => new SObject(Vector2.Zero, p.ID)); // objects foreach (int id in Game1.objectInformation.Keys) @@ -97,7 +110,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework { foreach (int secretNoteId in this.TryLoad("Data\\SecretNotes").Keys) { - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset + secretNoteId, () => + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset + secretNoteId, _ => { SObject note = new SObject(79, 1); note.name = $"{note.name} #{secretNoteId}"; @@ -108,18 +121,18 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // ring else if (id != 801 && fields?.Length >= 4 && fields[3] == "Ring") // 801 = wedding ring, which isn't an equippable ring - yield return this.TryCreate(ItemType.Ring, id, () => new Ring(id)); + yield return this.TryCreate(ItemType.Ring, id, p => new Ring(p.ID)); // item else { // spawn main item SObject item = null; - yield return this.TryCreate(ItemType.Object, id, () => + yield return this.TryCreate(ItemType.Object, id, p => { - return item = (id == 812 // roe - ? new ColoredObject(id, 1, Color.White) - : new SObject(id, 1) + return item = (p.ID == 812 // roe + ? new ColoredObject(p.ID, 1, Color.White) + : new SObject(p.ID, 1) ); }); if (item == null) @@ -131,7 +144,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // fruit products case SObject.FruitsCategory: // wine - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 2 + id, () => new SObject(348, 1) + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 2 + id, _ => new SObject(348, 1) { Name = $"{item.Name} Wine", Price = item.Price * 3, @@ -140,7 +153,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework }); // jelly - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 3 + id, () => new SObject(344, 1) + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 3 + id, _ => new SObject(344, 1) { Name = $"{item.Name} Jelly", Price = 50 + item.Price * 2, @@ -152,7 +165,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // vegetable products case SObject.VegetableCategory: // juice - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 4 + id, () => new SObject(350, 1) + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 4 + id, _ => new SObject(350, 1) { Name = $"{item.Name} Juice", Price = (int)(item.Price * 2.25d), @@ -161,7 +174,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework }); // pickled - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + id, () => new SObject(342, 1) + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + id, _ => new SObject(342, 1) { Name = $"Pickled {item.Name}", Price = 50 + item.Price * 2, @@ -172,7 +185,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // flower honey case SObject.flowersCategory: - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + id, () => + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + id, _ => { SObject honey = new SObject(Vector2.Zero, 340, $"{item.Name} Honey", false, true, false, false) { @@ -189,14 +202,14 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework foreach (var pair in Game1.objectInformation) { // get input - SObject input = this.TryCreate(ItemType.Object, -1, () => new SObject(pair.Key, 1))?.Item as SObject; + SObject input = this.TryCreate(ItemType.Object, pair.Key, p => new SObject(p.ID, 1))?.Item as SObject; if (input == null || input.Category != SObject.FishCategory) continue; Color color = this.GetRoeColor(input); // yield roe SObject roe = null; - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + id, () => + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + id, _ => { roe = new ColoredObject(812, 1, color) { @@ -211,7 +224,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // aged roe if (roe != null && pair.Key != 698) // aged sturgeon roe is caviar, which is a separate item { - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + id, () => new ColoredObject(447, 1, color) + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + id, _ => new ColoredObject(447, 1, color) { name = $"Aged {input.Name} Roe", Category = -27, @@ -255,13 +268,13 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework /// The item type. /// The unique ID (if different from the item's parent sheet index). /// Create an item instance. - private SearchableItem TryCreate(ItemType type, int id, Func createItem) + private SearchableItem TryCreate(ItemType type, int id, Func createItem) { try { - var item = createItem(); - item.getDescription(); // force-load item data, so it crashes here if it's invalid - return new SearchableItem(type, id, item); + var item = new SearchableItem(type, id, createItem); + item.Item.getDescription(); // force-load item data, so it crashes here if it's invalid + return item; } catch { -- cgit From f9f3db7db03e969bde33f417d66f259a3d3e6006 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 24 Oct 2020 18:28:43 -0400 Subject: add character-customization-only shirts to item repo --- docs/release-notes.md | 3 +++ .../Framework/ItemRepository.cs | 21 +++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index d6b5df7a..86081c62 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,9 @@ * For modders: * Fixed error when heuristically rewriting a property for a type that no longer exists. +* For the Console Commands mod: + * `player_add` can now spawn shirts normally only available during character customization. + ## 3.7.5 Released 16 October 2020 for Stardew Valley 1.4.1 or later. diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index a96a842c..5884d28a 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -61,8 +61,25 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 3, _ => new Wand()); // clothing - foreach (int id in Game1.clothingInformation.Keys) - yield return this.TryCreate(ItemType.Clothing, id, p => new Clothing(p.ID)); + { + // items + HashSet clothingIds = new HashSet(); + foreach (int id in Game1.clothingInformation.Keys) + { + if (id < 0) + continue; // placeholder data for character customization clothing below + + clothingIds.Add(id); + yield return this.TryCreate(ItemType.Clothing, id, p => new Clothing(p.ID)); + } + + // character customization shirts (some shirts in this range have no data, but game has special logic to handle them) + for (int id = 1000; id <= 1111; id++) + { + if (!clothingIds.Contains(id)) + yield return this.TryCreate(ItemType.Clothing, id, p => new Clothing(p.ID)); + } + } // wallpapers for (int id = 0; id < 112; id++) -- cgit From 295c34d5cd03764099f2fb803113bd60361cb282 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 28 Oct 2020 18:20:41 -0400 Subject: fix a captured loop variable --- .../Framework/ItemRepository.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index 5884d28a..c5e2b89d 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -161,7 +161,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // fruit products case SObject.FruitsCategory: // wine - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 2 + id, _ => new SObject(348, 1) + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 2 + item.ParentSheetIndex, _ => new SObject(348, 1) { Name = $"{item.Name} Wine", Price = item.Price * 3, @@ -170,7 +170,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework }); // jelly - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 3 + id, _ => new SObject(344, 1) + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 3 + item.ParentSheetIndex, _ => new SObject(344, 1) { Name = $"{item.Name} Jelly", Price = 50 + item.Price * 2, @@ -182,7 +182,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // vegetable products case SObject.VegetableCategory: // juice - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 4 + id, _ => new SObject(350, 1) + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 4 + item.ParentSheetIndex, _ => new SObject(350, 1) { Name = $"{item.Name} Juice", Price = (int)(item.Price * 2.25d), @@ -191,7 +191,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework }); // pickled - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + id, _ => new SObject(342, 1) + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + item.ParentSheetIndex, _ => new SObject(342, 1) { Name = $"Pickled {item.Name}", Price = 50 + item.Price * 2, @@ -202,7 +202,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // flower honey case SObject.flowersCategory: - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + id, _ => + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + item.ParentSheetIndex, _ => { SObject honey = new SObject(Vector2.Zero, 340, $"{item.Name} Honey", false, true, false, false) { @@ -215,7 +215,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework break; // roe and aged roe (derived from FishPond.GetFishProduce) - case SObject.sellAtFishShopCategory when id == 812: + case SObject.sellAtFishShopCategory when item.ParentSheetIndex == 812: foreach (var pair in Game1.objectInformation) { // get input @@ -226,7 +226,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // yield roe SObject roe = null; - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + id, _ => + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + item.ParentSheetIndex, _ => { roe = new ColoredObject(812, 1, color) { @@ -241,7 +241,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // aged roe if (roe != null && pair.Key != 698) // aged sturgeon roe is caviar, which is a separate item { - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + id, _ => new ColoredObject(447, 1, color) + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + item.ParentSheetIndex, _ => new ColoredObject(447, 1, color) { name = $"Aged {input.Name} Roe", Category = -27, -- cgit From 2831b1e75a4634214fd9ba4e95edcac91d9cf321 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 28 Oct 2020 18:21:33 -0400 Subject: add SearchableItem copy constructor This is for convenience in mods which copy this code; SMAPI itself doesn't use it. --- .../Framework/ItemData/SearchableItem.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs index 3675a963..72d01eb7 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs @@ -43,6 +43,16 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData this.Item = createItem(this); } + /// Construct an instance. + /// The item metadata to copy. + public SearchableItem(SearchableItem item) + { + this.Type = item.Type; + this.ID = item.ID; + this.CreateItem = item.CreateItem; + this.Item = item.Item; + } + /// Get whether the item name contains a case-insensitive substring. /// The substring to find. public bool NameContains(string substring) -- cgit From 90f49af275a97a903ef98735d6c2c03cb3d578c0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 28 Oct 2020 18:21:48 -0400 Subject: reduce brace warning level --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 4b40c2f0..d600d602 100644 --- a/.editorconfig +++ b/.editorconfig @@ -68,7 +68,7 @@ csharp_style_expression_bodied_accessors = true:suggestion csharp_style_inlined_variable_declaration = true:warning # avoid superfluous braces -csharp_prefer_braces = false:suggestion +csharp_prefer_braces = false:hint ########## ## Column guidelines -- cgit From ec84ba07cc80c74ed0c997550a401def6ea24916 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 30 Oct 2020 20:46:46 -0400 Subject: apply fish pond rules for roe spawning --- docs/release-notes.md | 1 + .../Framework/ItemRepository.cs | 88 +++++++++++++++------- 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 86081c62..3d131d07 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -13,6 +13,7 @@ * For the Console Commands mod: * `player_add` can now spawn shirts normally only available during character customization. + * `player_add` now applies fish pond rules for roe items. (That mainly adds Clam Roe, Sea Urchin Roe, and custom roe from mods.) ## 3.7.5 Released 16 October 2020 for Stardew Valley 1.4.1 or later. diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index c5e2b89d..d1dd758b 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -6,6 +6,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData; using StardewValley; +using StardewValley.GameData.FishPond; using StardewValley.Menus; using StardewValley.Objects; using StardewValley.Tools; @@ -216,39 +217,48 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // roe and aged roe (derived from FishPond.GetFishProduce) case SObject.sellAtFishShopCategory when item.ParentSheetIndex == 812: - foreach (var pair in Game1.objectInformation) { - // get input - SObject input = this.TryCreate(ItemType.Object, pair.Key, p => new SObject(p.ID, 1))?.Item as SObject; - if (input == null || input.Category != SObject.FishCategory) - continue; - Color color = this.GetRoeColor(input); - - // yield roe - SObject roe = null; - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + item.ParentSheetIndex, _ => - { - roe = new ColoredObject(812, 1, color) - { - name = $"{input.Name} Roe", - preserve = { Value = SObject.PreserveType.Roe }, - preservedParentSheetIndex = { Value = input.ParentSheetIndex } - }; - roe.Price += input.Price / 2; - return roe; - }); - - // aged roe - if (roe != null && pair.Key != 698) // aged sturgeon roe is caviar, which is a separate item + this.GetRoeContextTagLookups(out HashSet simpleTags, out List> complexTags); + + foreach (var pair in Game1.objectInformation) { - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + item.ParentSheetIndex, _ => new ColoredObject(447, 1, color) + // get input + SObject input = this.TryCreate(ItemType.Object, pair.Key, p => new SObject(p.ID, 1))?.Item as SObject; + var inputTags = input?.GetContextTags(); + if (inputTags?.Any() != true) + continue; + + // check if roe-producing fish + if (!inputTags.Any(tag => simpleTags.Contains(tag)) && !complexTags.Any(set => set.All(tag => input.HasContextTag(tag)))) + continue; + + // yield roe + SObject roe = null; + Color color = this.GetRoeColor(input); + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + item.ParentSheetIndex, _ => { - name = $"Aged {input.Name} Roe", - Category = -27, - preserve = { Value = SObject.PreserveType.AgedRoe }, - preservedParentSheetIndex = { Value = input.ParentSheetIndex }, - Price = roe.Price * 2 + roe = new ColoredObject(812, 1, color) + { + name = $"{input.Name} Roe", + preserve = { Value = SObject.PreserveType.Roe }, + preservedParentSheetIndex = { Value = input.ParentSheetIndex } + }; + roe.Price += input.Price / 2; + return roe; }); + + // aged roe + if (roe != null && pair.Key != 698) // aged sturgeon roe is caviar, which is a separate item + { + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + item.ParentSheetIndex, _ => new ColoredObject(447, 1, color) + { + name = $"Aged {input.Name} Roe", + Category = -27, + preserve = { Value = SObject.PreserveType.AgedRoe }, + preservedParentSheetIndex = { Value = input.ParentSheetIndex }, + Price = roe.Price * 2 + }); + } } } break; @@ -264,6 +274,26 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework /********* ** Private methods *********/ + /// Get optimized lookups to match items which produce roe in a fish pond. + /// A lookup of simple singular tags which match a roe-producing fish. + /// A list of tag sets which match roe-producing fish. + private void GetRoeContextTagLookups(out HashSet simpleTags, out List> complexTags) + { + simpleTags = new HashSet(); + complexTags = new List>(); + + foreach (FishPondData data in Game1.content.Load>("Data\\FishPondData")) + { + if (data.ProducedItems.All(p => p.ItemID != 812)) + continue; // doesn't produce roe + + if (data.RequiredTags.Count == 1 && !data.RequiredTags[0].StartsWith("!")) + simpleTags.Add(data.RequiredTags[0]); + else + complexTags.Add(data.RequiredTags); + } + } + /// Try to load a data file, and return empty data if it's invalid. /// The asset key type. /// The asset value type. -- cgit From 947d4545b1326bf6afbfb970d979dafd8ff2eb97 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 10 Nov 2020 20:11:52 -0500 Subject: fix 'collection was modified' error when using 'harmony summary' command in rare cases --- docs/release-notes.md | 1 + src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 3d131d07..14e5e81b 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,7 @@ ## Upcoming release * For modders: * Fixed error when heuristically rewriting a property for a type that no longer exists. + * Fixed rare 'collection was modified' error when using 'harmony summary' console command in rare cases. * For the Console Commands mod: * `player_add` can now spawn shirts normally only available during character customization. diff --git a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs index 8fdd4282..f3731d16 100644 --- a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs +++ b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs @@ -112,9 +112,9 @@ namespace StardewModdingAPI.Framework.Commands private IEnumerable GetAllPatches() { #if HARMONY_2 - foreach (MethodBase method in Harmony.GetAllPatchedMethods()) + foreach (MethodBase method in Harmony.GetAllPatchedMethods().ToArray()) #else - foreach (MethodBase method in this.HarmonyInstance.GetPatchedMethods()) + foreach (MethodBase method in this.HarmonyInstance.GetPatchedMethods().ToArray()) #endif { // get metadata for method -- cgit From 03506fc72ab33705a5b6ecd768d653acaabcc4e5 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 17 Nov 2020 19:09:00 -0500 Subject: update to TMXTile 1.5.7 --- docs/release-notes.md | 7 +++++-- src/SMAPI/SMAPI.csproj | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 14e5e81b..f73374c7 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,9 +8,12 @@ --> ## Upcoming release +* For players: + * Fixed error when heuristically rewriting a mod in rare cases (i.e. when it accesses a property for a type that no longer exists). + * Fixed rare 'collection was modified' error when using `harmony summary` console command in rare cases. + * For modders: - * Fixed error when heuristically rewriting a property for a type that no longer exists. - * Fixed rare 'collection was modified' error when using 'harmony summary' console command in rare cases. + * Updated TMXTile 1.5.6 → 1.5.7 to fix exported `.tmx` files losing tile index properties. * For the Console Commands mod: * `player_add` can now spawn shirts normally only available during character customization. diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 969071cf..acb1ecdf 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -16,7 +16,7 @@ - + -- cgit From 91289de74f451f8aa1485802618e46b22e0e608a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 17 Nov 2020 19:09:55 -0500 Subject: update internal dependencies --- .../SMAPI.ModBuildConfig.Analyzer.Tests.csproj | 2 +- src/SMAPI.Tests/SMAPI.Tests.csproj | 2 +- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 2 +- src/SMAPI.Web/SMAPI.Web.csproj | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj index 93eb476e..d0123e93 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj @@ -7,7 +7,7 @@ - + all diff --git a/src/SMAPI.Tests/SMAPI.Tests.csproj b/src/SMAPI.Tests/SMAPI.Tests.csproj index 1e13edc3..51fe32bf 100644 --- a/src/SMAPI.Tests/SMAPI.Tests.csproj +++ b/src/SMAPI.Tests/SMAPI.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 9e9d824a..3fc9de58 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj index 72cbc8a9..6f9f50f0 100644 --- a/src/SMAPI.Web/SMAPI.Web.csproj +++ b/src/SMAPI.Web/SMAPI.Web.csproj @@ -12,13 +12,13 @@ - - + + - + - + -- cgit From 8a66532e7455298af6c004df2141c0c643919f3b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 21 Nov 2020 12:40:44 -0500 Subject: update to TMXTile 1.5.8 --- docs/release-notes.md | 2 +- src/SMAPI/SMAPI.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index f73374c7..04d37580 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -13,7 +13,7 @@ * Fixed rare 'collection was modified' error when using `harmony summary` console command in rare cases. * For modders: - * Updated TMXTile 1.5.6 → 1.5.7 to fix exported `.tmx` files losing tile index properties. + * Updated TMXTile 1.5.6 → 1.5.8 to fix exported `.tmx` files losing tile index properties. * For the Console Commands mod: * `player_add` can now spawn shirts normally only available during character customization. diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index acb1ecdf..6344cb2f 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -16,7 +16,7 @@ - + -- cgit From cfdf783c2dfe659ff4bc656f720ddbf5c16c871a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 21 Nov 2020 14:04:58 -0500 Subject: split older release notes into a separate file --- docs/release-notes-archived.md | 1128 ++++++++++++++++++++++++++++++++++++++++ docs/release-notes.md | 1126 +-------------------------------------- src/SMAPI.sln | 1 + 3 files changed, 1132 insertions(+), 1123 deletions(-) create mode 100644 docs/release-notes-archived.md diff --git a/docs/release-notes-archived.md b/docs/release-notes-archived.md new file mode 100644 index 00000000..9f8de3cb --- /dev/null +++ b/docs/release-notes-archived.md @@ -0,0 +1,1128 @@ +← [README](README.md) + +# Release notes +## 3.0 and later +See [newer release notes](release-notes.md). + +## 2.11.3 +Released 13 September 2019 for Stardew Valley 1.3.36. + +* For players: + * SMAPI now prevents invalid items from breaking menus on hover. + * SMAPI now prevents invalid event preconditions from crashing the game (thanks to berkayylmao!). + * SMAPI now prevents more invalid dialogue from crashing the game. + * Fixed errors during early startup not shown before exit. + * Fixed various error messages and inconsistent spelling. + +* For the web UI: + * When filtering the mod list, clicking a mod link now automatically adds it to the visible mods. + * Added log parser instructions for Android. + * Fixed log parser failing in some cases due to time format localization. + +* For modders: + * `this.Monitor.Log` now defaults to the `Trace` log level instead of `Debug`. The change will only take effect when you recompile the mod. + * Fixed 'location list changed' verbose log not correctly listing changes. + * Fixed mods able to directly load (and in some cases edit) a different mod's local assets using internal asset key forwarding. + * Fixed changes to a map loaded by a mod being persisted across content managers. + * Fixed `SDate.AddDays` incorrectly changing year when the result is exactly winter 28. + +## 2.11.2 +Released 23 April 2019 for Stardew Valley 1.3.36. + +* For players: + * Fixed error when a custom map references certain vanilla tilesheets on Linux/Mac. + * Fixed compatibility with some Linux distros. + +## 2.11.1 +Released 17 March 2019 for Stardew Valley 1.3.36. + +* For players: + * Added crops option to `world_clear` console command. + * Prepared compatibility check for Stardew Valley 1.4. + * Updated mod compatibility list. + * Fixed `world_clear` console command removing chests edited to have a debris name. + +* For modders: + * Added support for suppressing false-positive warnings in rare cases. + +* For the web UI: + * The log parser now collapses redundant sections by default. + * Fixed log parser column resize bug. + +## 2.11 +Released 01 March 2019 for Stardew Valley 1.3.36. + +* For players: + * Updated for Stardew Valley 1.3.36. + +* For modders: + * Bumped all deprecation levels to _pending removal_. + +* For the web UI: + * The log parser now shows available updates in a section at the top. + * The mod compatibility page now crosses out mod links if they're outdated to avoid confusion. + * Fixed smapi.io linking to an archived download in rare cases. + +## 2.10.2 +Released 09 January 2019 for Stardew Valley 1.3.32–33. + +* For players: + * SMAPI now keeps the first save backup created for the day, instead of the last one. + * Fixed save backup for some Linux/Mac players. (When compression isn't available, SMAPI will now create uncompressed backups instead.) + * Fixed some common dependencies not linking to the mod page in 'missing mod' errors. + * Fixed 'unknown mod' deprecation warnings showing a stack trace when developers mode not enabled. + * Fixed 'unknown mod' deprecation warnings when they occur in the Mod constructor. + * Fixed confusing error message when using SMAPI 2.10._x_ with Stardew Valley 1.3.35+. + * Tweaked XNB mod message for clarity. + * Updated compatibility list. + +* For the web UI: + * Added beta status filter to compatibility list. + * Fixed broken ModDrop links in the compatibility list. + +* For modders: + * Asset changes are now propagated into the parsed save being loaded if applicable. + * Added locale to context trace logs. + * Fixed error loading custom map tilesheets in some cases. + * Fixed error when swapping maps mid-session for a location with interior doors. + * Fixed `Constants.SaveFolderName` and `CurrentSavePath` not available during early load stages when using `Specialized.LoadStageChanged` event. + * Fixed `LoadStage.SaveParsed` raised before the parsed save data is available. + * Fixed 'unknown mod' deprecation warnings showing the wrong stack trace. + * Fixed `e.Cursor` in input events showing wrong grab tile when player using a controller moves without moving the viewpoint. + * Fixed incorrect 'bypassed safety checks' warning for mods using the new `Specialized.LoadStageChanged` event in 2.10. + * Deprecated `EntryDll` values whose capitalization don't match the actual file. (This works on Windows, but causes errors for Linux/Mac players.) + +## 2.10.1 +Released 30 December 2018 for Stardew Valley 1.3.32–33. + +* For players: + * Fixed some mod integrations not working correctly in SMAPI 2.10. + +## 2.10 +Released 29 December 2018 for Stardew Valley 1.3.32–33. + +* For players: + * Added `world_clear` console command to remove spawned or placed entities. + * Minor performance improvements. + * Tweaked installer to reduce antivirus false positives. + +* For modders: + * Added [events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events): `GameLoop.OneSecondUpdateTicking`, `GameLoop.OneSecondUpdateTicked`, and `Specialized.LoadStageChanged`. + * Added `e.IsCurrentLocation` event arg to `World` events. + * You can now use `helper.Data.Read/WriteSaveData` as soon as the save is loaded (instead of once the world is initialized). + * Increased deprecation levels to _info_ for the upcoming SMAPI 3.0. + +* For the web UI: + * Reduced mod compatibility list's cache time. + +## 2.9.3 +Released 16 December 2018 for Stardew Valley 1.3.32. + +* For players: + * Fixed errors hovering items in some cases with SMAPI 2.9.2. + * Fixed some multiplayer features broken when a farmhand returns to title and rejoins. + +## 2.9.2 +Released 16 December 2018 for Stardew Valley 1.3.32. + +* For players: + * SMAPI now prevents invalid items from crashing the game on hover. + * Fixed some multiplayer features broken when connecting via Steam friends. + * Fixed cryptic error message when the game isn't installed correctly. + * Fixed error when a mod makes invalid changes to an NPC schedule. + * Fixed game launch errors logged as `SMAPI` instead of `game`. + * Fixed Windows installer adding unneeded Unix launcher to game folder. + +* For modders: + * Moved content pack methods into a new [content pack API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Content_Packs). + * Fixed invalid NPC data propagated when a mod changes NPC dispositions. + * Fixed `Display.RenderedWorld` event broken in SMAPI 2.9.1. + * **Deprecations:** + * The `assetData.AsDictionary().Set` methods are now deprecated. Mods should access the `Data` property directly instead. + * The content pack methods directly on `helper` are now deprecated. Mods should use `helper.ContentPacks` instead. + +* For SMAPI developers: + * Added SMAPI 3.0 readiness to mod API data. + +## 2.9.1 +Released 07 December 2018 for Stardew Valley 1.3.32. + +* For players: + * Fixed crash in SMAPI 2.9 when constructing certain buildings. + * Fixed error when a map asset is reloaded in rare cases. + +## 2.9 +Released 07 December 2018 for Stardew Valley 1.3.32. + +* For players: + * Added support for ModDrop in update checks and the mod compatibility list. + * Added friendly error for Steam players when Steam isn't loaded. + * Fixed cryptic error when running the installer from inside a zip file on Windows. + * Fixed error when leaving and rejoining a multiplayer server in the same session. + * Fixed empty "mods with warnings" list in some cases due to hidden warnings. + * Fixed Console Commands' handling of tool upgrade levels for item commands. + +* For modders: + * Added ModDrop update keys (see [docs](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest#Update_checks)). + * Added `IsLocalPlayer` to new player events. + * Added `helper.CreateTemporaryContentPack` to replace the deprecated `CreateTransitionalContentPack`. + * Reloading a map asset will now update affected locations. + * Reloading the `Data\NPCDispositions` asset will now update affected NPCs. + * Disabled trace messages related to paranoid mode when it's disabled. + * Fixed world events like `ObjectListChanged` not working in the mines. + * Fixed some map tilesheets not editable if not playing in English. + * Fixed newlines in manifest fields not being ignored. + * Fixed `Display.RenderedWorld` event invoked after overlays are rendered. + * **Deprecations:** + * All static events are deprecated and will be removed in SMAPI 3.0. Mods should use the new event system available through `helper.Events` instead; see [_migrate to SMAPI 3.0_](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_3.0) for details. + +* For the web UI: + * Added stats to compatibility list. + * Fixed compatibility list showing beta header when there's no beta in progress. + +## 2.8.2 +Released 19 November 2018 for Stardew Valley 1.3.32. + +* Fixed game crash in MacOS with SMAPI 2.8. + +## 2.8.1 +Released 19 November 2018 for Stardew Valley 1.3.32. + +* Fixed installer error on Windows with SMAPI 2.8. + +## 2.8 +Released 19 November 2018 for Stardew Valley 1.3.32. + +* For players: + * Reorganised SMAPI files: + * Moved most SMAPI files into a `smapi-internal` subfolder (so your game folder is less messy). + * Moved save backups into a `save-backups` subfolder (so they're easier to find). + * Simplified the installer files to