diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj | 2 | ||||
-rw-r--r-- | src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs | 22 | ||||
-rw-r--r-- | src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs | 194 | ||||
-rw-r--r-- | src/SMAPI.Mods.ConsoleCommands/manifest.json | 4 | ||||
-rw-r--r-- | src/SMAPI.Mods.SaveBackup/manifest.json | 4 | ||||
-rw-r--r-- | src/SMAPI.Tests/SMAPI.Tests.csproj | 2 | ||||
-rw-r--r-- | src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 2 | ||||
-rw-r--r-- | src/SMAPI.Web/SMAPI.Web.csproj | 8 | ||||
-rw-r--r-- | src/SMAPI.sln | 1 | ||||
-rw-r--r-- | src/SMAPI/Constants.cs | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs | 4 | ||||
-rw-r--r-- | src/SMAPI/Framework/Logging/LogManager.cs | 15 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/SCore.cs | 57 | ||||
-rw-r--r-- | src/SMAPI/SMAPI.csproj | 2 |
15 files changed, 203 insertions, 118 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 @@ <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.10.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" /> <PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0"> <PrivateAssets>all</PrivateAssets> diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs index d9e63126..72d01eb7 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 /// <summary>The item type.</summary> public ItemType Type { get; } - /// <summary>The item instance.</summary> + /// <summary>A sample item instance.</summary> public Item Item { get; } + /// <summary>Create an item instance.</summary> + public Func<Item> CreateItem { get; } + /// <summary>The item's unique ID for its type.</summary> public int ID { get; } @@ -31,12 +34,23 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData /// <summary>Construct an instance.</summary> /// <param name="type">The item type.</param> /// <param name="id">The unique ID (if different from the item's parent sheet index).</param> - /// <param name="item">The item instance.</param> - public SearchableItem(ItemType type, int id, Item item) + /// <param name="createItem">Create an item instance.</param> + public SearchableItem(ItemType type, int id, Func<SearchableItem, Item> createItem) { this.Type = type; this.ID = id; - this.Item = item; + this.CreateItem = () => createItem(this); + this.Item = createItem(this); + } + + /// <summary>Construct an instance.</summary> + /// <param name="item">The item metadata to copy.</param> + public SearchableItem(SearchableItem item) + { + this.Type = item.Type; + this.ID = item.ID; + this.CreateItem = item.CreateItem; + this.Item = item.Item; } /// <summary>Get whether the item name contains a case-insensitive substring.</summary> diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index 37f5f8d1..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; @@ -30,47 +31,77 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework [SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = "TryCreate invokes the lambda immediately.")] public IEnumerable<SearchableItem> GetAll() { + // + // + // Be careful about closure variable capture here! + // + // SearchableItem stores the Func<Item> 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<SearchableItem> 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)); + { + // items + HashSet<int> clothingIds = new HashSet<int>(); + 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++) - 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<int, string>("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<int, string>("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<int, string>("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 +109,14 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework foreach (int id in this.TryLoad<int, string>("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 +128,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework { foreach (int secretNoteId in this.TryLoad<int, string>("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 +139,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 +162,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, @@ -140,7 +171,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, @@ -152,7 +183,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), @@ -161,7 +192,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, @@ -172,7 +203,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) { @@ -185,40 +216,49 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework break; // roe and aged roe (derived from FishPond.GetFishProduce) - case SObject.sellAtFishShopCategory when id == 812: - foreach (var pair in Game1.objectInformation) + case SObject.sellAtFishShopCategory when item.ParentSheetIndex == 812: { - // get input - SObject input = this.TryCreate(ItemType.Object, -1, () => new SObject(pair.Key, 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, () => - { - 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<string> simpleTags, out List<List<string>> complexTags); + + foreach (var pair in Game1.objectInformation) { - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + id, () => 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; @@ -234,6 +274,26 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework /********* ** Private methods *********/ + /// <summary>Get optimized lookups to match items which produce roe in a fish pond.</summary> + /// <param name="simpleTags">A lookup of simple singular tags which match a roe-producing fish.</param> + /// <param name="complexTags">A list of tag sets which match roe-producing fish.</param> + private void GetRoeContextTagLookups(out HashSet<string> simpleTags, out List<List<string>> complexTags) + { + simpleTags = new HashSet<string>(); + complexTags = new List<List<string>>(); + + foreach (FishPondData data in Game1.content.Load<List<FishPondData>>("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); + } + } + /// <summary>Try to load a data file, and return empty data if it's invalid.</summary> /// <typeparam name="TKey">The asset key type.</typeparam> /// <typeparam name="TValue">The asset value type.</typeparam> @@ -255,13 +315,13 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework /// <param name="type">The item type.</param> /// <param name="id">The unique ID (if different from the item's parent sheet index).</param> /// <param name="createItem">Create an item instance.</param> - private SearchableItem TryCreate(ItemType type, int id, Func<Item> createItem) + private SearchableItem TryCreate(ItemType type, int id, Func<SearchableItem, Item> 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 { diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 300350e4..ddc55a73 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "3.7.5", + "Version": "3.7.6", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.7.5" + "MinimumApiVersion": "3.7.6" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index d1d8ae32..0fe98909 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "3.7.5", + "Version": "3.7.6", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.7.5" + "MinimumApiVersion": "3.7.6" } 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 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Moq" Version="4.14.6" /> + <PackageReference Include="Moq" Version="4.15.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="NUnit" Version="3.12.0" /> </ItemGroup> 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 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="HtmlAgilityPack" Version="1.11.24" /> + <PackageReference Include="HtmlAgilityPack" Version="1.11.28" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Pathoschild.Http.FluentClient" Version="4.0.0" /> <PackageReference Include="System.Management" Version="4.5.0" Condition="'$(OS)' == 'Windows_NT'" /> 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 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Azure.Storage.Blobs" Version="12.6.0" /> - <PackageReference Include="Hangfire.AspNetCore" Version="1.7.14" /> + <PackageReference Include="Azure.Storage.Blobs" Version="12.7.0" /> + <PackageReference Include="Hangfire.AspNetCore" Version="1.7.17" /> <PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" /> - <PackageReference Include="HtmlAgilityPack" Version="1.11.24" /> + <PackageReference Include="HtmlAgilityPack" Version="1.11.28" /> <PackageReference Include="Humanizer.Core" Version="2.8.26" /> <PackageReference Include="JetBrains.Annotations" Version="2020.1.0" /> - <PackageReference Include="Markdig" Version="0.21.1" /> + <PackageReference Include="Markdig" Version="0.22.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.8" /> <PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" /> <PackageReference Include="Pathoschild.FluentNexus" Version="1.0.1" /> diff --git a/src/SMAPI.sln b/src/SMAPI.sln index aaf4d374..bc74e6b9 100644 --- a/src/SMAPI.sln +++ b/src/SMAPI.sln @@ -35,6 +35,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{EB35A917-6 ProjectSection(SolutionItems) = preProject ..\docs\mod-build-config.md = ..\docs\mod-build-config.md ..\docs\README.md = ..\docs\README.md + ..\docs\release-notes-archived.md = ..\docs\release-notes-archived.md ..\docs\release-notes.md = ..\docs\release-notes.md EndProjectSection EndProject diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 754ab295..88f79811 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -51,7 +51,7 @@ namespace StardewModdingAPI ** Public ****/ /// <summary>SMAPI's current semantic version.</summary> - public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.7.5"); + public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.7.6"); /// <summary>The minimum supported version of Stardew Valley.</summary> public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.4.1"); 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<SearchResult> 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 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 /// <summary>Write an update alert marker file.</summary> /// <param name="version">The new version found.</param> - public void WriteUpdateMarker(string version) + /// <param name="url">The download URL for the update.</param> + public void WriteUpdateMarker(string version, string url) { - File.WriteAllText(Constants.UpdateMarker, version); + File.WriteAllText(Constants.UpdateMarker, $"{version}|{url}"); } /// <summary>Check whether SMAPI crashed or detected an update during the last session, and display them in the SMAPI console.</summary> @@ -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/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; 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()) diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 969071cf..6344cb2f 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -16,7 +16,7 @@ <PackageReference Include="LargeAddressAware" Version="1.0.5" /> <PackageReference Include="Mono.Cecil" Version="0.11.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> - <PackageReference Include="Platonymous.TMXTile" Version="1.5.6" /> + <PackageReference Include="Platonymous.TMXTile" Version="1.5.8" /> </ItemGroup> <ItemGroup> |