From 7c652b0924476cea8dc89faa30983e01c0c66fec Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 24 Oct 2020 18:26:41 -0400 Subject: [PATCH] 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 {