using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Xna.Framework; using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData; using StardewValley; using StardewValley.Menus; using StardewValley.Objects; using StardewValley.Tools; using SObject = StardewValley.Object; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework { /// Provides methods for searching and constructing items. internal class ItemRepository { /********* ** Fields *********/ /// The custom ID offset for items don't have a unique ID in the game. private readonly int CustomIDOffset = 1000; /********* ** Public methods *********/ /// Get all spawnable items. [SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = "TryCreate invokes the lambda immediately.")] public IEnumerable GetAll() { IEnumerable GetAllRaw() { // get tools for (int quality = Tool.stone; quality <= Tool.iridium; quality++) { 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, 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()); // wallpapers for (int id = 0; id < 112; id++) yield return this.TryCreate(ItemType.Wallpaper, id, () => new Wallpaper(id) { Category = SObject.furnitureCategory }); // flooring for (int id = 0; id < 40; id++) yield return this.TryCreate(ItemType.Flooring, id, () => new Wallpaper(id, isFloor: true) { Category = SObject.furnitureCategory }); // equipment foreach (int id in Game1.content.Load>("Data\\Boots").Keys) yield return this.TryCreate(ItemType.Boots, id, () => new Boots(id)); foreach (int id in Game1.content.Load>("Data\\hats").Keys) yield return this.TryCreate(ItemType.Hat, id, () => new Hat(id)); foreach (int id in Game1.objectInformation.Keys) { if (id >= Ring.ringLowerIndexRange && id <= Ring.ringUpperIndexRange) yield return this.TryCreate(ItemType.Ring, id, () => new Ring(id)); } // weapons foreach (int id in Game1.content.Load>("Data\\weapons").Keys) { yield return this.TryCreate(ItemType.Weapon, id, () => (id >= 32 && id <= 34) ? (Item)new Slingshot(id) : new MeleeWeapon(id) ); } // furniture foreach (int id in Game1.content.Load>("Data\\Furniture").Keys) { if (id == 1466 || id == 1468) yield return this.TryCreate(ItemType.Furniture, id, () => new TV(id, Vector2.Zero)); else yield return this.TryCreate(ItemType.Furniture, id, () => new Furniture(id, Vector2.Zero)); } // craftables foreach (int id in Game1.bigCraftablesInformation.Keys) yield return this.TryCreate(ItemType.BigCraftable, id, () => new SObject(Vector2.Zero, id)); // secret notes foreach (int id in Game1.content.Load>("Data\\SecretNotes").Keys) { yield return this.TryCreate(ItemType.Object, this.CustomIDOffset + id, () => { SObject note = new SObject(79, 1); note.name = $"{note.name} #{id}"; return note; }); } // objects foreach (int id in Game1.objectInformation.Keys) { if (id == 79) continue; // secret note handled above if (id >= Ring.ringLowerIndexRange && id <= Ring.ringUpperIndexRange) continue; // handled separated // spawn main item SObject item; { SearchableItem main = this.TryCreate(ItemType.Object, id, () => id == 812 ? new ColoredObject(id, 1, Color.White) : new SObject(id, 1) ); yield return main; item = main?.Item as SObject; } if (item == null) continue; // fruit products if (item.Category == SObject.FruitsCategory) { // wine yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 2 + id, () => { SObject wine = new SObject(348, 1) { Name = $"{item.Name} Wine", Price = item.Price * 3 }; wine.preserve.Value = SObject.PreserveType.Wine; wine.preservedParentSheetIndex.Value = item.ParentSheetIndex; return wine; }); // jelly yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 3 + id, () => { SObject jelly = new SObject(344, 1) { Name = $"{item.Name} Jelly", Price = 50 + item.Price * 2 }; jelly.preserve.Value = SObject.PreserveType.Jelly; jelly.preservedParentSheetIndex.Value = item.ParentSheetIndex; return jelly; }); } // vegetable products else if (item.Category == SObject.VegetableCategory) { // juice yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 4 + id, () => { SObject juice = new SObject(350, 1) { Name = $"{item.Name} Juice", Price = (int)(item.Price * 2.25d) }; juice.preserve.Value = SObject.PreserveType.Juice; juice.preservedParentSheetIndex.Value = item.ParentSheetIndex; return juice; }); // pickled yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + id, () => { SObject pickled = new SObject(342, 1) { Name = $"Pickled {item.Name}", Price = 50 + item.Price * 2 }; pickled.preserve.Value = SObject.PreserveType.Pickle; pickled.preservedParentSheetIndex.Value = item.ParentSheetIndex; return pickled; }); } // flower honey else if (item.Category == SObject.flowersCategory) { yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + id, () => { SObject honey = new SObject(Vector2.Zero, 340, $"{item.Name} Honey", false, true, false, false) { Name = $"{item.Name} Honey", preservedParentSheetIndex = { item.ParentSheetIndex } }; honey.Price += item.Price * 2; return honey; }); } // roe and aged roe (derived from FishPond.GetFishProduce) else if (id == 812) { foreach (var pair in Game1.objectInformation) { // get input SObject input = new SObject(pair.Key, 1); if (input.Category != SObject.FishCategory) continue; Color color = TailoringMenu.GetDyeColor(input) ?? Color.Orange; // yield roe SObject roe = new ColoredObject(812, 1, color) { name = $"{input.Name} Roe", preserve = { Value = SObject.PreserveType.Roe }, preservedParentSheetIndex = { Value = input.ParentSheetIndex } }; roe.Price += input.Price / 2; yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 6 + 1, roe); // aged roe if (pair.Key != 698) // aged sturgeon roe is caviar, which is a separate item { ColoredObject agedRoe = 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 }; yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 6 + 1, agedRoe); } } } } } return GetAllRaw().Where(p => p != null); } /********* ** Private methods *********/ /// Create a searchable item if valid. /// 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) { try { return new SearchableItem(type, id, createItem()); } catch { return null; // if some item data is invalid, just don't include it } } } }