diff options
| author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-07-09 22:30:13 -0400 |
|---|---|---|
| committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-07-09 22:30:13 -0400 |
| commit | 8f96a97f070d654764de3b138678d8f62707f485 (patch) | |
| tree | 9a59953e01bfc27d0f124579ae897fabec46092f | |
| parent | 7dd3e37dec6924b1acd3f3045aefe5ebc8f91e8f (diff) | |
| parent | 60af28760a6edc509b16e7c62aa8b00ba9798793 (diff) | |
| download | SMAPI-8f96a97f070d654764de3b138678d8f62707f485.tar.gz SMAPI-8f96a97f070d654764de3b138678d8f62707f485.tar.bz2 SMAPI-8f96a97f070d654764de3b138678d8f62707f485.zip | |
Merge branch 'develop' into stable
27 files changed, 583 insertions, 248 deletions
diff --git a/build/common.targets b/build/common.targets index d538cc53..ce805402 100644 --- a/build/common.targets +++ b/build/common.targets @@ -1,7 +1,7 @@ <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <!--set general build properties --> - <Version>3.10.1</Version> + <Version>3.11.0</Version> <Product>SMAPI</Product> <LangVersion>latest</LangVersion> <AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths> diff --git a/docs/release-notes.md b/docs/release-notes.md index caa90d19..d0aa2fbf 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,6 +7,30 @@ * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). --> +## 3.11.0 +Released 09 July 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/53514295). + +* For players: + * Updated for Stardew Valley 1.4.5 multiplayer hotfix on Linux/macOS. + * Fixed installer error on Windows when running as administrator (thanks to LostLogic!). + * Fixed installer error on some Windows systems (thanks to eddyballs!). + * Fixed error if SMAPI fails to dispose on game exit. + * Fixed `player_add` and `list_items` console commands not including some shirts _(in Console Commands)_. + +* For mod authors: + * Added `World.FurnitureListChanged` event (thanks to DiscipleOfEris!). + * Added asset propagation for building/house paint masks. + * Added log message for troubleshooting if Windows software which often causes issues is installed (currently MSI Afterburner and RivaTuner). + * Improved validation for the manifest `Dependencies` field. + * Fixed validation for mods with invalid version `0.0.0`. + * Fixed _loaded with custom settings_ trace log added when using default settings. + * Fixed `Constants.SaveFolderName` and `Constants.CurrentSavePath` not set correctly in rare cases. + +* For the web UI and JSON validator: + * Updated the JSON validator/schema for Content Patcher 1.23.0. + * Fixed [JSON schema](technical/web.md#using-a-schema-file-directly) in Visual Studio Code warning about comments and trailing commas. + * Fixed JSON schema for `i18n` files requiring the wrong value for the `$schema` field. + ## 3.10.1 Released 03 May 2021 for Stardew Valley 1.5.4 or later. diff --git a/docs/technical/mod-package.md b/docs/technical/mod-package.md index 6d04722c..91da971b 100644 --- a/docs/technical/mod-package.md +++ b/docs/technical/mod-package.md @@ -324,7 +324,7 @@ To do that: <GamePath>PATH_HERE</GamePath> </PropertyGroup> ``` -3. Replace `PATH_HERE` with your game's folder path. +3. Replace `PATH_HERE` with your game's folder path (don't add quotes). The configuration will check your custom path first, then fall back to the default paths (so it'll still compile on a different computer). diff --git a/src/SMAPI.Installer/assets/unix-launcher.sh b/src/SMAPI.Installer/assets/unix-launcher.sh index a33c0d7f..d309f750 100644 --- a/src/SMAPI.Installer/assets/unix-launcher.sh +++ b/src/SMAPI.Installer/assets/unix-launcher.sh @@ -43,8 +43,34 @@ if [ "$UNAME" == "Darwin" ]; then cp -p StardewValley.bin.osx StardewModdingAPI.bin.osx fi + # Make sure we're running in Terminal (so the user can see errors/warnings/update alerts). + # Previously we would just use `open -a Terminal` to launch the .bin.osx file, but that + # doesn't let us set environment variables. + if [ ! -t 1 ]; then # https://stackoverflow.com/q/911168/262123 + # sanity check to make sure we don't have an infinite loop of opening windows + SKIP_TERMINAL=false + for argument in "$@"; do + if [ "$argument" == "--no-reopen-terminal" ]; then + SKIP_TERMINAL=true + break + fi + done + + # reopen in Terminal if needed + # https://stackoverflow.com/a/29511052/262123 + if [ "$SKIP_TERMINAL" == "false" ]; then + echo "Reopening in the Terminal app..." + echo "\"$0\" $@ --no-reopen-terminal" > /tmp/open-smapi-terminal.sh + chmod +x /tmp/open-smapi-terminal.sh + cat /tmp/open-smapi-terminal.sh + open -W -a Terminal /tmp/open-smapi-terminal.sh + rm /tmp/open-smapi-terminal.sh + exit 0 + fi + fi + # launch SMAPI - open -a Terminal ./StardewModdingAPI.bin.osx "$@" + LC_ALL="C" ./StardewModdingAPI.bin.osx "$@" else # choose binary file to launch LAUNCH_FILE="" @@ -79,44 +105,44 @@ else terminal|termite) # consumes only one argument after -e # options containing space characters are unsupported - exec $TERMINAL_NAME -e "env TERM=xterm $LAUNCH_FILE $@" + exec $TERMINAL_NAME -e "env TERM=xterm LC_ALL=\"C\" $LAUNCH_FILE $@" ;; xterm|konsole|alacritty) # consumes all arguments after -e - exec $TERMINAL_NAME -e env TERM=xterm $LAUNCH_FILE "$@" + exec $TERMINAL_NAME -e env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@" ;; terminator|xfce4-terminal|mate-terminal) # consumes all arguments after -x - exec $TERMINAL_NAME -x env TERM=xterm $LAUNCH_FILE "$@" + exec $TERMINAL_NAME -x env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@" ;; gnome-terminal) # consumes all arguments after -- - exec $TERMINAL_NAME -- env TERM=xterm $LAUNCH_FILE "$@" + exec $TERMINAL_NAME -- env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@" ;; kitty) # consumes all trailing arguments - exec $TERMINAL_NAME env TERM=xterm $LAUNCH_FILE "$@" + exec $TERMINAL_NAME env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@" ;; *) # If we don't know the terminal, just try to run it in the current shell. # If THAT fails, launch with no output. - env TERM=xterm $LAUNCH_FILE "$@" + env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@" if [ $? -eq 127 ]; then - exec $LAUNCH_FILE --no-terminal "$@" + exec LC_ALL="C" $LAUNCH_FILE --no-terminal "$@" fi esac ## terminal isn't executable; fallback to current shell or no terminal else echo "The '$TERMINAL_NAME' terminal isn't executable. SMAPI might be running in a sandbox or the system might be misconfigured? Falling back to current shell." - env TERM=xterm $LAUNCH_FILE "$@" + env TERM=xterm LC_ALL="C" $LAUNCH_FILE "$@" if [ $? -eq 127 ]; then - exec $LAUNCH_FILE --no-terminal "$@" + exec LC_ALL="C" $LAUNCH_FILE --no-terminal "$@" fi fi fi diff --git a/src/SMAPI.Installer/assets/windows-install.bat b/src/SMAPI.Installer/assets/windows-install.bat index 2cc54e80..2cd98554 100644 --- a/src/SMAPI.Installer/assets/windows-install.bat +++ b/src/SMAPI.Installer/assets/windows-install.bat @@ -1,8 +1,8 @@ @echo off -echo %~dp0 | findstr /C:"%TEMP%" 1>nul +echo "%~dp0" | findstr /C:"%TEMP%" 1>nul if not errorlevel 1 ( echo Oops! It looks like you're running the installer from inside a zip file. Make sure you unzip the download first. pause ) else ( - start /WAIT /B ./internal/windows-install.exe + start /WAIT /B internal\windows-install.exe ) diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index 34149209..0357fe6b 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -28,8 +28,10 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework ** Public methods *********/ /// <summary>Get all spawnable items.</summary> + /// <param name="itemTypes">The item types to fetch (or null for any type).</param> + /// <param name="includeVariants">Whether to include flavored variants like "Sunflower Honey".</param> [SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = "TryCreate invokes the lambda immediately.")] - public IEnumerable<SearchableItem> GetAll() + public IEnumerable<SearchableItem> GetAll(ItemType[] itemTypes = null, bool includeVariants = true) { // // @@ -41,222 +43,246 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // // - IEnumerable<SearchableItem> GetAllRaw() { + HashSet<ItemType> types = itemTypes?.Any() == true ? new HashSet<ItemType>(itemTypes) : null; + bool ShouldGet(ItemType type) => types == null || types.Contains(type); + // get tools - for (int q = Tool.stone; q <= Tool.iridium; q++) + if (ShouldGet(ItemType.Tool)) { - 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)); + for (int q = Tool.stone; q <= Tool.iridium; q++) + { + 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, 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 + if (ShouldGet(ItemType.Clothing)) { - // 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); + foreach (int id in this.GetShirtIds()) 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, p => new Wallpaper(p.ID) { Category = SObject.furnitureCategory }); + if (ShouldGet(ItemType.Wallpaper)) + { + for (int id = 0; id < 112; id++) + 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, p => new Wallpaper(p.ID, isFloor: true) { Category = SObject.furnitureCategory }); + if (ShouldGet(ItemType.Flooring)) + { + for (int id = 0; id < 56; id++) + 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, p => new Boots(p.ID)); - foreach (int id in this.TryLoad<int, string>("Data\\hats").Keys) - yield return this.TryCreate(ItemType.Hat, id, p => new Hat(p.ID)); + if (ShouldGet(ItemType.Boots)) + { + foreach (int id in this.TryLoad<int, string>("Data\\Boots").Keys) + yield return this.TryCreate(ItemType.Boots, id, p => new Boots(p.ID)); + } + if (ShouldGet(ItemType.Hat)) + { + foreach (int id in this.TryLoad<int, string>("Data\\hats").Keys) + yield return this.TryCreate(ItemType.Hat, id, p => new Hat(p.ID)); + } // weapons - foreach (int id in this.TryLoad<int, string>("Data\\weapons").Keys) + if (ShouldGet(ItemType.Weapon)) { - yield return this.TryCreate(ItemType.Weapon, id, p => (p.ID >= 32 && p.ID <= 34) - ? (Item)new Slingshot(p.ID) - : new MeleeWeapon(p.ID) - ); + foreach (int id in this.TryLoad<int, string>("Data\\weapons").Keys) + { + yield return this.TryCreate(ItemType.Weapon, id, p => (p.ID >= 32 && p.ID <= 34) + ? (Item)new Slingshot(p.ID) + : new MeleeWeapon(p.ID) + ); + } } // furniture - foreach (int id in this.TryLoad<int, string>("Data\\Furniture").Keys) - yield return this.TryCreate(ItemType.Furniture, id, p => Furniture.GetFurnitureInstance(p.ID)); + if (ShouldGet(ItemType.Furniture)) + { + foreach (int id in this.TryLoad<int, string>("Data\\Furniture").Keys) + yield return this.TryCreate(ItemType.Furniture, id, p => Furniture.GetFurnitureInstance(p.ID)); + } // craftables - foreach (int id in Game1.bigCraftablesInformation.Keys) - yield return this.TryCreate(ItemType.BigCraftable, id, p => new SObject(Vector2.Zero, p.ID)); + if (ShouldGet(ItemType.BigCraftable)) + { + foreach (int id in Game1.bigCraftablesInformation.Keys) + yield return this.TryCreate(ItemType.BigCraftable, id, p => new SObject(Vector2.Zero, p.ID)); + } // objects - foreach (int id in Game1.objectInformation.Keys) + if (ShouldGet(ItemType.Object) || ShouldGet(ItemType.Ring)) { - string[] fields = Game1.objectInformation[id]?.Split('/'); - - // secret notes - if (id == 79) + foreach (int id in Game1.objectInformation.Keys) { - foreach (int secretNoteId in this.TryLoad<int, string>("Data\\SecretNotes").Keys) + string[] fields = Game1.objectInformation[id]?.Split('/'); + + // secret notes + if (id == 79) { - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset + secretNoteId, _ => + if (ShouldGet(ItemType.Object)) { - SObject note = new SObject(79, 1); - note.name = $"{note.name} #{secretNoteId}"; - return note; - }); + foreach (int secretNoteId in this.TryLoad<int, string>("Data\\SecretNotes").Keys) + { + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset + secretNoteId, _ => + { + SObject note = new SObject(79, 1); + note.name = $"{note.name} #{secretNoteId}"; + return note; + }); + } + } } - } - - // 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, p => new Ring(p.ID)); - // item - else - { - // spawn main item - SObject item = null; - yield return this.TryCreate(ItemType.Object, id, p => + // ring + else if (id != 801 && fields?.Length >= 4 && fields[3] == "Ring") // 801 = wedding ring, which isn't an equippable ring { - return item = (p.ID == 812 // roe - ? new ColoredObject(p.ID, 1, Color.White) - : new SObject(p.ID, 1) - ); - }); - if (item == null) - continue; - - // flavored items - switch (item.Category) + if (ShouldGet(ItemType.Ring)) + yield return this.TryCreate(ItemType.Ring, id, p => new Ring(p.ID)); + } + + // item + else if (ShouldGet(ItemType.Object)) { - // fruit products - case SObject.FruitsCategory: - // wine - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 2 + item.ParentSheetIndex, _ => new SObject(348, 1) - { - Name = $"{item.Name} Wine", - Price = item.Price * 3, - preserve = { SObject.PreserveType.Wine }, - preservedParentSheetIndex = { item.ParentSheetIndex } - }); - - // jelly - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 3 + item.ParentSheetIndex, _ => new SObject(344, 1) - { - Name = $"{item.Name} Jelly", - Price = 50 + item.Price * 2, - preserve = { SObject.PreserveType.Jelly }, - preservedParentSheetIndex = { item.ParentSheetIndex } - }); - break; - - // vegetable products - case SObject.VegetableCategory: - // juice - 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), - preserve = { SObject.PreserveType.Juice }, - preservedParentSheetIndex = { item.ParentSheetIndex } - }); - - // pickled - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + item.ParentSheetIndex, _ => new SObject(342, 1) - { - Name = $"Pickled {item.Name}", - Price = 50 + item.Price * 2, - preserve = { SObject.PreserveType.Pickle }, - preservedParentSheetIndex = { item.ParentSheetIndex } - }); - break; - - // flower honey - case SObject.flowersCategory: - 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) - { - Name = $"{item.Name} Honey", - preservedParentSheetIndex = { item.ParentSheetIndex } - }; - honey.Price += item.Price * 2; - return honey; - }); - break; - - // roe and aged roe (derived from FishPond.GetFishProduce) - case SObject.sellAtFishShopCategory when item.ParentSheetIndex == 812: + // spawn main item + SObject item = null; + yield return this.TryCreate(ItemType.Object, id, p => + { + return item = (p.ID == 812 // roe + ? new ColoredObject(p.ID, 1, Color.White) + : new SObject(p.ID, 1) + ); + }); + if (item == null) + continue; + + // flavored items + if (includeVariants) + { + switch (item.Category) { - this.GetRoeContextTagLookups(out HashSet<string> simpleTags, out List<List<string>> complexTags); + // fruit products + case SObject.FruitsCategory: + // wine + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 2 + item.ParentSheetIndex, _ => new SObject(348, 1) + { + Name = $"{item.Name} Wine", + Price = item.Price * 3, + preserve = { SObject.PreserveType.Wine }, + preservedParentSheetIndex = { item.ParentSheetIndex } + }); - 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; - 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, _ => + // jelly + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 3 + item.ParentSheetIndex, _ => new SObject(344, 1) + { + Name = $"{item.Name} Jelly", + Price = 50 + item.Price * 2, + preserve = { SObject.PreserveType.Jelly }, + preservedParentSheetIndex = { item.ParentSheetIndex } + }); + break; + + // vegetable products + case SObject.VegetableCategory: + // juice + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 4 + item.ParentSheetIndex, _ => new SObject(350, 1) { - roe = new ColoredObject(812, 1, color) + Name = $"{item.Name} Juice", + Price = (int)(item.Price * 2.25d), + preserve = { SObject.PreserveType.Juice }, + preservedParentSheetIndex = { item.ParentSheetIndex } + }); + + // pickled + yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + item.ParentSheetIndex, _ => new SObject(342, 1) + { + Name = $"Pickled {item.Name}", + Price = 50 + item.Price * 2, + preserve = { SObject.PreserveType.Pickle }, + preservedParentSheetIndex = { item.ParentSheetIndex } + }); + break; + + // flower honey + case SObject.flowersCategory: + 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) { - name = $"{input.Name} Roe", - preserve = { Value = SObject.PreserveType.Roe }, - preservedParentSheetIndex = { Value = input.ParentSheetIndex } + Name = $"{item.Name} Honey", + preservedParentSheetIndex = { item.ParentSheetIndex } }; - roe.Price += input.Price / 2; - return roe; + honey.Price += item.Price * 2; + return honey; }); + break; - // aged roe - if (roe != null && pair.Key != 698) // aged sturgeon roe is caviar, which is a separate item + // roe and aged roe (derived from FishPond.GetFishProduce) + case SObject.sellAtFishShopCategory when item.ParentSheetIndex == 812: { - yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + item.ParentSheetIndex, _ => new ColoredObject(447, 1, color) + this.GetRoeContextTagLookups(out HashSet<string> simpleTags, out List<List<string>> complexTags); + + foreach (var pair in Game1.objectInformation) { - name = $"Aged {input.Name} Roe", - Category = -27, - preserve = { Value = SObject.PreserveType.AgedRoe }, - preservedParentSheetIndex = { Value = input.ParentSheetIndex }, - Price = roe.Price * 2 - }); + // 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, _ => + |
