summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj2
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs22
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs194
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/manifest.json4
-rw-r--r--src/SMAPI.Mods.SaveBackup/manifest.json4
-rw-r--r--src/SMAPI.Tests/SMAPI.Tests.csproj2
-rw-r--r--src/SMAPI.Toolkit/SMAPI.Toolkit.csproj2
-rw-r--r--src/SMAPI.Web/SMAPI.Web.csproj8
-rw-r--r--src/SMAPI.sln1
-rw-r--r--src/SMAPI/Constants.cs2
-rw-r--r--src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs4
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs15
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs2
-rw-r--r--src/SMAPI/Framework/SCore.cs57
-rw-r--r--src/SMAPI/SMAPI.csproj2
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>