diff --git a/.gitignore b/.gitignore index 22320aacd..7c74fd54e 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,7 @@ ValheimMods.sln /EpicLoot/EpicLoot.csproj /EpicLoot/EpicLoot.sln /EpicLoot/shardstone_testing_matrix.csv +.env + +# mod-tools staging directories, created during packaging and never committed +*/Package/plugins/ diff --git a/AdvancedPortals/.gitignore b/AdvancedPortals/.gitignore new file mode 100644 index 000000000..4c49bd78f --- /dev/null +++ b/AdvancedPortals/.gitignore @@ -0,0 +1 @@ +.env diff --git a/AdvancedPortals/AdvancedPortals.csproj b/AdvancedPortals/AdvancedPortals.csproj index 78b66c99b..f1c51e95c 100644 --- a/AdvancedPortals/AdvancedPortals.csproj +++ b/AdvancedPortals/AdvancedPortals.csproj @@ -54,4 +54,44 @@ + + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/')) + + mod-tools + + + + + + + + + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AdvancedPortals/CHANGELOG.md b/AdvancedPortals/Package/CHANGELOG.md similarity index 90% rename from AdvancedPortals/CHANGELOG.md rename to AdvancedPortals/Package/CHANGELOG.md index 924474ba5..fe29e7b94 100644 --- a/AdvancedPortals/CHANGELOG.md +++ b/AdvancedPortals/Package/CHANGELOG.md @@ -1,52 +1,52 @@ -## 1.1.3 +**1.1.3** * Fixed an issue with the AllowEverything config option no longer being used in the teleport check. -## 1.1.2 +**1.1.2** * Changed portals metal check patch to better respect other mods including World Advancement & Progression. -## 1.1.1 +**1.1.1** * Fixed a small typo issue that only manifested when TargetPortal was installed. -## 1.1.0 +**1.1.0** * Overhauled portal appearances to better match vanilla styles! * Now requires Jotunn to run, please install this new dependency! -## 1.0.11 +**1.0.11** * Update for Valheim version 0.219.13 Bog Witch. -## 1.0.10 +**1.0.10** * ServerSync Update fixing a multiplayer issue -## 1.0.9 +**1.0.9** * Update for 0.217.24 - Hildr's Request -## 1.0.8 +**1.0.8** * Fixing the Portal Saving issue with the changes that were presented in a recent update. * This now adds the Custom Portal Prefabs to the PortalPrefab list during Game.Awake reducing the need to patch any ZDOMan stuff. -## 1.0.7 +**1.0.7** * Hildir's Request Update 0.217.14 -## 1.0.6 +**1.0.6** * Restored Portal Connections on Dedicated Servers. -## 1.0.5 +**1.0.5** * Updated Portal Connection logic which was preventing Advanced Portals from connecting -## 1.0.4 +**1.0.4** * Updates for 0.216.9 Valheim -## 1.0.3 +**1.0.3** * Vapok fixed a bug that makes Adventure Backpacks work with Advanced Portals -## 1.0.2 +**1.0.2** * Added bronze to Ancient portal transport list (how could I forget?) * Updated to support other mods that extend the inventory (Thanks Vapok) -## 1.0.1 +**1.0.1** * Added compatibility with AnyPortal and TargetPortal * Fixed a bug with Obsidian and Black Marble portal recipes * Added 5 BlackMetal to the default recipe for Black Marble portals (delete your config to automatically use the new recipe) -## 1.0.0 +**1.0.0** * Initial Release \ No newline at end of file diff --git a/AdvancedPortals/Package/README.md b/AdvancedPortals/Package/README.md new file mode 100644 index 000000000..d4c3fbd9b --- /dev/null +++ b/AdvancedPortals/Package/README.md @@ -0,0 +1,36 @@ +# Advanced Portals + +Author: [RandyKnapp](https://discord.gg/ZNhYeavv3C) +Source: [Github](https://github.com/RandyKnapp/ValheimMods/tree/main/AdvancedPortals) +Patreon: [patreon.com/randyknapp](https://www.patreon.com/randyknapp) +Discord: [RandyKnapp's Mod Community](https://discord.gg/ZNhYeavv3C) + +Adds three new portals to provide a lore-friendly and balanced way to reduce the item-transport slog! + + * **Ancient Portal:** Allows teleporting Copper and Tin + * *Requires:* 20 Ancient Bark, 5 Iron, 2 Surtling Cores + * **Obsidian Portal:** Allows teleporting Iron + * *Requires:* 20 Obsidian, 5 Silver, 2 Surtling Cores + * **Black Marble Portal:** Allows teleporting anything + * *Requires:* 20 Black Marble, BlackMetal 5, 2 Refined Eitr + +## Version 1.1.0! + +As of the 1.1.0 update Jotunn is required to run this mod. A version check will be performed on server connection to ensure all players have the mod installed properly. + +Configurations should sync on servers and live update on changing the randyknapp.mods.advancedportals.cfg file. + +## Configuration: + +Each portal can be configured: + + * Enabled: Enable building the portal. Existing portals of this type will not be removed. + * Recipe: Items needed to build the portal in the format "ITEM1:QUANTITY,ITEM2:QUANTITY,..." where each ITEM is the item ID ([found here](https://valheim-modding.github.io/Jotunn/data/objects/item-list.html)), and QUANTITY is an integer. + * Allowed Items: Items allowed to teleport through the portal in the format: "ITEM1,ITEM2,ITEM3,..." where ITEM is the item ID. + * Allow Everything: Allow all items through the portal. + * Use All Previous: For the Obsidian portal also include the Allowed Items from the Ancient portal. For the Black Marble portal also include the Allowed Items from both the Ancient and Obsidian portals. + +## Installation: + + * Manual: Drop the AdvancedPortals.dll into your BepInEx/plugins folder. Download Jotunn and install similarly. + * ThunderStore: When using a thunderstore mod manager the files should be placed in the correct directory for you. Dependencies should install automatically. diff --git a/AdvancedPortals/thunderstore/icon.png b/AdvancedPortals/Package/icon.png similarity index 100% rename from AdvancedPortals/thunderstore/icon.png rename to AdvancedPortals/Package/icon.png diff --git a/AdvancedPortals/thunderstore/manifest.json b/AdvancedPortals/Package/manifest.json similarity index 90% rename from AdvancedPortals/thunderstore/manifest.json rename to AdvancedPortals/Package/manifest.json index 23109c053..fdc4b0f05 100644 --- a/AdvancedPortals/thunderstore/manifest.json +++ b/AdvancedPortals/Package/manifest.json @@ -1,6 +1,6 @@ { "name": "AdvancedPortals", - "version_number": "1.1.1", + "version_number": "1.1.3", "website_url": "https://discord.gg/ZNhYeavv3C", "description": "Add new, lore-friendly and balanced portals to allow some items to be teleported.", "dependencies": [ diff --git a/AdvancedPortals/README.md b/AdvancedPortals/README.md deleted file mode 100644 index d4c3fbd9b..000000000 --- a/AdvancedPortals/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Advanced Portals - -Author: [RandyKnapp](https://discord.gg/ZNhYeavv3C) -Source: [Github](https://github.com/RandyKnapp/ValheimMods/tree/main/AdvancedPortals) -Patreon: [patreon.com/randyknapp](https://www.patreon.com/randyknapp) -Discord: [RandyKnapp's Mod Community](https://discord.gg/ZNhYeavv3C) - -Adds three new portals to provide a lore-friendly and balanced way to reduce the item-transport slog! - - * **Ancient Portal:** Allows teleporting Copper and Tin - * *Requires:* 20 Ancient Bark, 5 Iron, 2 Surtling Cores - * **Obsidian Portal:** Allows teleporting Iron - * *Requires:* 20 Obsidian, 5 Silver, 2 Surtling Cores - * **Black Marble Portal:** Allows teleporting anything - * *Requires:* 20 Black Marble, BlackMetal 5, 2 Refined Eitr - -## Version 1.1.0! - -As of the 1.1.0 update Jotunn is required to run this mod. A version check will be performed on server connection to ensure all players have the mod installed properly. - -Configurations should sync on servers and live update on changing the randyknapp.mods.advancedportals.cfg file. - -## Configuration: - -Each portal can be configured: - - * Enabled: Enable building the portal. Existing portals of this type will not be removed. - * Recipe: Items needed to build the portal in the format "ITEM1:QUANTITY,ITEM2:QUANTITY,..." where each ITEM is the item ID ([found here](https://valheim-modding.github.io/Jotunn/data/objects/item-list.html)), and QUANTITY is an integer. - * Allowed Items: Items allowed to teleport through the portal in the format: "ITEM1,ITEM2,ITEM3,..." where ITEM is the item ID. - * Allow Everything: Allow all items through the portal. - * Use All Previous: For the Obsidian portal also include the Allowed Items from the Ancient portal. For the Black Marble portal also include the Allowed Items from both the Ancient and Obsidian portals. - -## Installation: - - * Manual: Drop the AdvancedPortals.dll into your BepInEx/plugins folder. Download Jotunn and install similarly. - * ThunderStore: When using a thunderstore mod manager the files should be placed in the correct directory for you. Dependencies should install automatically. diff --git a/AdvancedPortals/README.md b/AdvancedPortals/README.md new file mode 120000 index 000000000..d11c97e84 --- /dev/null +++ b/AdvancedPortals/README.md @@ -0,0 +1 @@ +Package/README.md \ No newline at end of file diff --git a/AdvancedPortals/release.yml b/AdvancedPortals/release.yml new file mode 100644 index 000000000..6ac971c7e --- /dev/null +++ b/AdvancedPortals/release.yml @@ -0,0 +1,34 @@ +# Release configuration for mod-tools. +# Shared settings come from the preset, which ships inside the gem. +# +# Inspect what this actually resolves to: +# mod-tools print-config + +extends: jotunn-mono +assembly: AdvancedPortals + +# This repository predates the tool and spells the version constant its own way. +version_const: Version + +# Copy the build into this machine's mod manager profiles after every build. +deploy: + enabled: true + + # The Thunderstore/Hexium team these publish under. +author_name: RandyKnapp + +# Copy the build into this machine's mod manager profiles after every build +deploy: + enabled: true + +publish: + thunderstore: + enabled: true + nexus: + enabled: true + mod_id: 2187 + file_id: 3852925 + github: + enabled: true + discord: + enabled: true diff --git a/DvergerColor/.gitignore b/DvergerColor/.gitignore new file mode 100644 index 000000000..4c49bd78f --- /dev/null +++ b/DvergerColor/.gitignore @@ -0,0 +1 @@ +.env diff --git a/DvergerColor/DvergerColor.cs b/DvergerColor/DvergerColor.cs index 295327c37..494e76442 100644 --- a/DvergerColor/DvergerColor.cs +++ b/DvergerColor/DvergerColor.cs @@ -6,11 +6,12 @@ namespace DvergerColor { - [BepInPlugin(PluginId, "Dverger Color", "1.0.6")] + [BepInPlugin(PluginId, "Dverger Color", Version)] [BepInProcess("valheim.exe")] public class DvergerColor : BaseUnityPlugin { public const string PluginId = "randyknapp.mods.dvergercolor"; + public const string Version = "1.0.7"; public static ConfigEntry Color; public static ConfigEntry MaxSteps; diff --git a/DvergerColor/ImprovedDvergerCirclet.csproj b/DvergerColor/ImprovedDvergerCirclet.csproj index 55b29ef9a..604735abe 100644 --- a/DvergerColor/ImprovedDvergerCirclet.csproj +++ b/DvergerColor/ImprovedDvergerCirclet.csproj @@ -45,4 +45,44 @@ + + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/')) + + mod-tools + + + + + + + + + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DvergerColor/Package/CHANGELOG.md b/DvergerColor/Package/CHANGELOG.md new file mode 100644 index 000000000..a99053b21 --- /dev/null +++ b/DvergerColor/Package/CHANGELOG.md @@ -0,0 +1,3 @@ +**1.0.7** + +* Existing release; changelog begins here. diff --git a/DvergerColor/Package/README.md b/DvergerColor/Package/README.md new file mode 100644 index 000000000..3ac64bd1f --- /dev/null +++ b/DvergerColor/Package/README.md @@ -0,0 +1,14 @@ +# Improved Dverger Circlet +##### by RandyKnapp +Source: [Github](https://github.com/RandyKnapp/ValheimMods/tree/main/DvergerColor) +Discord: [RandyKnapp's Mod Community](https://discord.gg/randyknappmods) +Patreon: [Randy's Patreon](https://www.patreon.com/randyknapp) + +Changes the color of the light emitted by the Dverger circlet to white. Adds several beam width options, and a pool of radiance (point light) option. + +## Configuration +Press "[" (left bracket) to narrow the beam and "]" (right bracket) to widen the beam. At maximum width, the beam turns into a point light centered on the player. Hotkeys, beam color, and beam values are all configurable. + +## Installation: +Install with [BepInEx](https://valheim.thunderstore.io/package/denikson/BepInExPack_Valheim/) +Copy the ImprovedDvergerCirclet.dll file in the "files" folder to your BepInEx plugins folder. \ No newline at end of file diff --git a/DvergerColor/thunderstore/icon.png b/DvergerColor/Package/icon.png similarity index 100% rename from DvergerColor/thunderstore/icon.png rename to DvergerColor/Package/icon.png diff --git a/DvergerColor/thunderstore/manifest.json b/DvergerColor/Package/manifest.json similarity index 100% rename from DvergerColor/thunderstore/manifest.json rename to DvergerColor/Package/manifest.json diff --git a/DvergerColor/README.md b/DvergerColor/README.md deleted file mode 100644 index 3ac64bd1f..000000000 --- a/DvergerColor/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Improved Dverger Circlet -##### by RandyKnapp -Source: [Github](https://github.com/RandyKnapp/ValheimMods/tree/main/DvergerColor) -Discord: [RandyKnapp's Mod Community](https://discord.gg/randyknappmods) -Patreon: [Randy's Patreon](https://www.patreon.com/randyknapp) - -Changes the color of the light emitted by the Dverger circlet to white. Adds several beam width options, and a pool of radiance (point light) option. - -## Configuration -Press "[" (left bracket) to narrow the beam and "]" (right bracket) to widen the beam. At maximum width, the beam turns into a point light centered on the player. Hotkeys, beam color, and beam values are all configurable. - -## Installation: -Install with [BepInEx](https://valheim.thunderstore.io/package/denikson/BepInExPack_Valheim/) -Copy the ImprovedDvergerCirclet.dll file in the "files" folder to your BepInEx plugins folder. \ No newline at end of file diff --git a/DvergerColor/README.md b/DvergerColor/README.md new file mode 120000 index 000000000..d11c97e84 --- /dev/null +++ b/DvergerColor/README.md @@ -0,0 +1 @@ +Package/README.md \ No newline at end of file diff --git a/DvergerColor/release.yml b/DvergerColor/release.yml new file mode 100644 index 000000000..01b293c14 --- /dev/null +++ b/DvergerColor/release.yml @@ -0,0 +1,31 @@ +# Release configuration for mod-tools. +# Shared settings come from the preset, which ships inside the gem. +# +# Inspect what this actually resolves to: +# mod-tools print-config + +extends: jotunn-mono +assembly: ImprovedDvergerCirclet + +# This repository predates the tool and spells the version constant its own way. +version_const: Version + +# The directory and the plugin source say DvergerColor; the assembly (and the +# Thunderstore package) say ImprovedDvergerCirclet. +plugin_source: DvergerColor.cs + +# Copy the build into this machine's mod manager profiles after every build. +deploy: + enabled: true + +publish: + thunderstore: + enabled: true + nexus: + enabled: true + mod_id: 79 + file_id: 490738 + github: + enabled: true + discord: + enabled: true \ No newline at end of file diff --git a/EpicLoot/.gitignore b/EpicLoot/.gitignore new file mode 100644 index 000000000..4c49bd78f --- /dev/null +++ b/EpicLoot/.gitignore @@ -0,0 +1 @@ +.env diff --git a/EpicLoot/API/Version.cs b/EpicLoot/API/Version.cs index 101f34f94..23810ac7f 100644 --- a/EpicLoot/API/Version.cs +++ b/EpicLoot/API/Version.cs @@ -1,4 +1,4 @@ -using JetBrains.Annotations; +using JetBrains.Annotations; using System.Collections.Generic; using System.Linq; using System.Reflection; diff --git a/EpicLoot/EpicLoot.cs b/EpicLoot/EpicLoot.cs index 216aa5163..6729a01a2 100644 --- a/EpicLoot/EpicLoot.cs +++ b/EpicLoot/EpicLoot.cs @@ -37,7 +37,7 @@ namespace EpicLoot; public sealed class EpicLoot : BaseUnityPlugin { public const string PluginId = "randyknapp.mods.epicloot"; public const string DisplayName = "Epic Loot"; - public const string Version = "0.13.0"; + public const string Version = "0.13.2"; private static string ConfigFileName = PluginId + ".cfg"; private static string ConfigFileFullPath = BepInEx.Paths.ConfigPath + Path.DirectorySeparatorChar + ConfigFileName; diff --git a/EpicLoot/EpicLoot.csproj b/EpicLoot/EpicLoot.csproj index 5dd1e27ef..dd3840149 100755 --- a/EpicLoot/EpicLoot.csproj +++ b/EpicLoot/EpicLoot.csproj @@ -46,8 +46,8 @@ - - + + @@ -57,13 +57,6 @@ - - - - - - - + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/')) + + mod-tools + + + + + + + + + true + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EpicLoot/CHANGELOG.md b/EpicLoot/Package/CHANGELOG.md similarity index 97% rename from EpicLoot/CHANGELOG.md rename to EpicLoot/Package/CHANGELOG.md index 56e1af92f..d44ed8d4b 100644 --- a/EpicLoot/CHANGELOG.md +++ b/EpicLoot/Package/CHANGELOG.md @@ -1,4 +1,11 @@ -## Version 0.13.0 +**0.13.2** +* Fixes Loot generation recursively happening with certain mods +* Fixes terrain generation memory leak, noticable with excessive bounties + +**0.13.1** +* Fix uploaded version + +**0.13.0** New Content: * Shardstones Added! Shardstones are a new item that can be attached to weapons with available shardsockets @@ -96,23 +103,23 @@ API: * Mod authors: `API.AddRecipe`, `API.AddRecipes`, `API.UpdateRecipes` and the shim's `CustomRecipe` are now deprecated. -## Version 0.12.15 +**0.12.15** * Fixed an issue with bounties not spawning correctly if not using the Star Level System mod. * Fixed an issue with bounty and treasure map pins not displaying until toggled. * If you experienced loot not dropping from previous version upgrades (12.12, 12.13) please refresh your BepInEx\config\EpicLoot\baseconfig\loottables.json file to remove null entries. -## Version 0.12.14 +**0.12.14** * Fixed an issue with auto-adding loot tables causing mobs to not drop loot. * All items from the game can now be added to loot tables from patches and work with the "Auto Add ..." configuration settings. -## Version 0.12.13 +**0.12.13** * Added back PlayerExtensions.GetEquipment for mods that patch that method. * If you are a mod author please remove your patches to this method! We refactored it to use vanilla methods in version 12.7. -## Version 0.12.12 +**0.12.12** * Bug fix for Mead Cooldown reducing all SE_Stats mead times, should now work as intended. * Bug fix for Gambling loot tables not saving values correctly when auto-assigned causing large coin values. @@ -124,7 +131,7 @@ API: * Disabling adventure mode in the main configuration file will now also remove the minimap toggles. * Star Level System API support. -## Version 0.12.11 +**0.12.11** * NEW FEATURE: map pins from bounties and treasure maps can now be toggled in the minimap UI in the bottom left corner! Thanks Rusty! * Removed a debugging feature that accidentally made it to production. This would cause a new folder called "dumps" to be generated in your game directory. It is safe to delete that folder and its contents. Apologies for the mess! @@ -137,7 +144,7 @@ API: * Fixed a null reference exception issue when generating treasure maps if configurations were missing for any biomes. * Fixed Multishot effects CostScale setting not applying to stamina and eitr costs correctly (this setting does not work for draw stamina). -## Version 0.12.10 +**0.12.10** * Fixed an issue with enchanting table identifying unknown magic items changing the stack size of the original item (coins and other costs). * Fixed formatting error with minimal configuration that would not load the mod properly when used. @@ -147,12 +154,12 @@ API: * Added missing localizations for ModifyFireRate magic effect for all other languages (existed for English). * Added missing localization for key mod_epicloot_me_modifydamage_desc. -## Version 0.12.9 +**0.12.9** * Fixed issue with empty IdentifyCosts throwing an error. * Added compatibility with Guilds mod that broke buying things in the custom trader menu. -## Version 0.12.8 +**0.12.8** * Many configuration tweaks. Delete and regenerate the json files specified to get the changes for each or grab the fixes manually if you have made changes: * adventuredata.json, enchantcosts.json: Added default configurations for "None" and "Misc" item types. This will fix support for some modded items like "Bows Before Hoes" quivers. @@ -167,7 +174,7 @@ API: * Removed throwable bombs showing up as a possible unidentifiable item roll. * Added mod compatibility for Steady Regeneration (broke in 0.12.6). -## Version 0.12.7 +**0.12.7** * Reworked magic effects OffSet Attack and DodgeBuff to fix bugs including missing assets (sound and visuals). * Normalized OffSet Attack values in magiceffects.json, this value represents the percentage of damage reduction (15-40%). @@ -176,14 +183,14 @@ API: * Bug fix for spam accepting bounties at the merchant and fixed errors when closing the store UI before completed. * More edge case safeguards for misconfigured files and unexpected game items. -## Version 0.12.6 +**0.12.6** * Fixed AddHealthRegen not applying after the 12.5 update. * Fixed typo for defeated_eikthyr key in new itemsorter.json file. * More edge case detection for the automatic item sorting logic. * Added some null reference exceptions safeguards in different places in code. -## Version 0.12.5 +**0.12.5** * NEW itemsorter.json configuration file to configure the auto-add behaviors! * Tooltips now display on hover in the enchanting table menus. @@ -203,11 +210,11 @@ API: * Note: If you used version 0.12.0+ please delete your old BepInEx\config\EpicLoot\baseconfig and let them regenerate! Or, if you have made changes, grab the changes from github to manually update your files. * Note: If you are NOT making edits to the baseconfig files we highly recommend setting "Debug - Always Refresh Core Configs" to true in your randyknapp.mods.epicloot.cfg file. Then you will not need to manually delete to refresh them. -## Version 0.12.4 +**0.12.4** * Fixed another an issue with MultiShot not resetting weapon attack values properly due to the percent chance to trigger config. -## Version 0.12.3 +**0.12.3** * Changed how patches are applied - they will no longer override your baseconfig files on startup! You should be able to generate your baseconfig files once then make changes without them being reverted. See the [wiki for more information](https://thunderstore.io/c/valheim/p/RandyKnapp/EpicLoot/wiki/358-6-config-patching/). We apologize for the confusion around this new feature. * Fixed an issue with MultiShot breaking enemy projectile damage (someday we will get this effect right on the first try). @@ -216,7 +223,7 @@ API: * Added missing configurations for ModifyMagicFireRate and ModifyFireRate in the magiceffects.json files. * Reenabled Weightless rolls for the balanced config. -## Version 0.12.2 +**0.12.2** * Fixed some display issues with the in game compendium. * Added some localizations for the compendium menus. @@ -233,7 +240,7 @@ API: * Fixed an issue with magic items not initializing correctly after the 11.7 update (attempt to fix another bug made more, fun times). * Fixed particle effects on dropped Andvaranaut. -## Version 0.12.1 +**0.12.1** * Tweaked the Rune costs config values. * If you used version 0.12.0 please delete your old BepInEx\config\EpicLoot\baseconfig and let them regenerate! @@ -242,7 +249,7 @@ API: * Fixed magic items not stacking when using the features from the Recycle & Reclaim mod. * Fixed an issue with Rune configurations not syncing properly on a server. -## Version 0.12.0 +**0.12.0** * NEW Unidentified items: * Unidentified items are a new item type that can be dropped as normal loot. @@ -314,14 +321,14 @@ API: * Reduced Augment Upgrade costs of Thunderstone and YmirRemains from 10 to 5. We felt it was still too high. * Other bugs fixes we forgot to write down (sorry! enjoy!) -## Version 0.11.7 +**0.11.7** * Updates for the Russian translation file. * Removed extended item data framework support (this has been deprecated for over two years now). * Bug fix for item stands not working correctly with magic items. * Added missing trinket information in tooltips. -## Version 0.11.6 +**0.11.6** * Removed the NoRoll from Modify Summon Damage and Modify Summon Health. Can now roll these effects. @@ -352,7 +359,7 @@ API: * Modify Attack Health bug fix for restoring correct value state after performing an attack. * Stagger no longer applies to players with PVP disabled. -## Version 0.11.4 +**0.11.4** * Fixed some patches not applying properly on servers (could use more user verification). * Fully fixed an issue with gamble costs applying to multiple of the same item incorrectly. @@ -366,25 +373,25 @@ API: * Added in world text to indicate when Feint was applied. * Fixed some display issues with Enchanting Table unlocks/star levels. -## Version 0.11.3 +**0.11.3** * Fixed issue with loot rolling lowest tier when using fallbacks from iteminfo config. * Fixed commands like "magicitemset" not dropping the correct items. * Multijump reworked to make successive jumps feel like a normal jump from the ground. * Fixed Andvaranaut not appearing. -## Version 0.11.2 +**0.11.2** * Fixed issue with loot rolling higher tiers than should have been allowed for loottable config. * Small bug fixes for various null reference exception errors. * Fixed tooltip display bug with gamble menu. * Fixed recipes not being added for singleplayer (and hosts). -## Version 0.11.1 +**0.11.1** * Hotpatch to re-add deprecated Items field in iteminfo.json file. This field does nothing, please remove from your patch files. -## Version 0.11.0 +**0.11.0** * New external dependency for Jotunn. You must install this for the mod to function! * Added version requirement check such that server AND clients must have the mod installed and running for multiplayer. @@ -423,7 +430,7 @@ API: * Augment symbol changed to a filled in triangle rather than an empty diamond. * Bounties now ping when using the Andvaranaut until they take first damage. -## Version 0.10.7 +**0.10.7** * Update for game version 0.220.3. * Upgraded unity project to version 2022.3.50. @@ -433,17 +440,17 @@ API: * Updated ServerSync to version 1.18. * New external dependency for JsonDotNET 13.0.3. You must install this for the mod to function! -## Version 0.10.6 +**0.10.6** * Fixed Coin Hoarder not working unless on a weapon. * Fixed ranged damage from non-players not applying correctly. -## Version 0.10.5 +**0.10.5** * Fixed magiceffects ItemUsesStaminaOnAttack, ItemUsesEitrOnAttack, ItemUsesHealthOnAttack, and ItemUsesDrawStaminaOnAttack not resolving correctly when set to true. * Changed Warmth effect to only roll on Epic and above. -## Version 0.10.4 +**0.10.4** * New configuration GatedFreebuildMode to control when items are unlocked when using the free build enchantment. Defaults to BossKillUnlocksCurrentBiomePieces. * DoubleMagicShot now uses twice the eitr plus tweaks to fix projectile amount on some staffs. @@ -472,12 +479,12 @@ API: * Added feature to allow some requirements in the MagicItemEffects json accept a value of false (such as ItemUsesEitrOnAttack). * Removed Bows from ModifyAttackStaminaUse and ModifyAttackSpeed (It does not actually work). -## Version 0.10.3 +**0.10.3** * Hot fix for Sacrificing item selecting the wrong item in the inventory. * Increased minimum required version to 10.0. -## Version 0.10.2 +**0.10.2** * Set fallback gambling Coins from 1 to 10000 when misconfigured. * Tweaked default fallback items for the item info configuration. @@ -493,7 +500,7 @@ API: * Re-enabled gotomerchant command to teleport the player again. * Set the default value of the "Config Sync - Lock Config" configuration to true. -## Version 0.10.1 +**0.10.1** * Fixed internal mod version and updated logo. @@ -710,32 +717,32 @@ API: * Changed the Location of Where Adventure Saved Data is stored. * Cleaned up KnownTexts and now saves in Player Custom Data -## Version 0.9.10 +**0.9.10** * Udpated for Valheim 0.214.305 * Added Merchant Fix for Auga 1.2.0 * Added Changelog.md for Thunderstore -## Version 0.9.9 +**0.9.9** * Removed butcher knife and stone axe from the allowed to enchant list * New Feature: Disenchant - remove the enchant from an item at the cost of iron bounty tokens * Merge Patching: Enabled "Merge" patch action, adds or overwrites all named properties in the target object (Thanks @nelson-saldanha) * Updating for Valheim 0.214.2 Patch -## Version 0.9.8 +**0.9.8** * Patch Config File location moved. Patch files have been moved outside of the Epic Loot Plugin Folder to prevent mod managers from deleting patch files. * Patch files are now located in BepInEx\config\EpicLoot\patches folder. * This folder will automatically be created upon first run of Epic Loot. * Debug Merged Output Files (if set to output) will be located in BepInEx\config\EpicLoot * Fix for "craftable legendary with no effects" bug -## Version 0.9.7 +**0.9.7** * Fixed an unfortunate amount of bugs: * Feather Falling, Indestructible, and Free Build (among others) not loading correctly. * Old Items Created in an extremely old version of EIDF were not converting to Custom Data. -## Version 0.9.6 +**0.9.6** * Removed dependency on EIDF in the Thunderstore manifest -## Version 0.9.5 +**0.9.5** * Fixed a bug where two enchanting tables next to each other could not be used * Removed accidentally included "_patched.json" files -## Version 0.9.4 +**0.9.4** * **New Features:** * Enchanting Table * New build piece with custom crafting UI just for Epic Loot! @@ -761,7 +768,7 @@ API: * Fixed errors with Stagger effect. * Items that have many Available Effects/Enchants were clipping when reviewing them in the Augment tab. Fixed to scale the font to a smaller size. * This is not an issue in Auga. -## Version 0.9.3 +**0.9.3** * Introduction of the JSON Configuration Patching System. * Please reference https://github.com/RandyKnapp/ValheimMods/wiki/Config-Patching for information. * Example Patch Configs and Additional Information to be made available Soon(tm) @@ -785,7 +792,7 @@ API: * Adding in Dvergers and DvergerMage's to Loot Table, whom were left out. * Removing Timescale which was not working and preventing vanilla timescale from functioning. * Adding Blood Magic and Elemental Magic Skills to available Skill Effect options. -## Version 0.9.2 +**0.9.2** * When using CLLC, creatures now correctly drop Epic Loot items * Gambles won't drop mats when the ItemsToMaterialsRatio is set to materials * Fixing a bunch of Object.Destroy calls that should be ZNetScene.instance.Destroy calls @@ -794,7 +801,7 @@ API: * Added an asset for each of the crafting materials * Added Biome/Boss/BossDefeatKey configuration to adventuredata.json * Fixing a bug that was causing Auga's compendium to display incorrectly -## Version 0.9.1 +**0.9.1** * Drop rates can now be globally scaled by using the "Global Drop Rate Modifier" config value * Magic Materials can be dropped instead of enchanted items by using the "Items To Materials Drop Ratio" config value. * The value goes from 0-1 where 0 is all items and 1 is all materials and any values in between is a random percentage of drops that will be materials instead of items. @@ -810,7 +817,7 @@ API: * Fixed tooltips missing stamina/health/eitr use * Updated data to handle new item type TwoHandedWeaponLeft * Fixed various nullrefs -## Version 0.9.0 +**0.9.0** * Updated for Valheim version 0.212.7 (Mistlands Update) * Augmenter no longer needed for augmenting (engage with augmenting far earlier in your playthrough) * Updated loot tables, gambles, treasure maps, and bounties for Mistlands and H&H @@ -832,26 +839,26 @@ API: * Fixed a bug with Indestructible items * Bounty targets are sometimes named special names (join the Patreon to add your name!) * Fixed a bug with magic effect requirements for or excluding Tower Shields -## Version 0.8.10 +**0.8.10** * Update for Valheim 0.211.11 and ServerSync 1.13 -## Version 0.8.9 +**0.8.9** * Compatibility fix for the Auga version of the merchant panel. -## Version 0.8.8 +**0.8.8** * Fixed an issue with multiplayer that was causing too much data to be sent across the wire. Lag and desync issues should be minimal now. -## Version 0.8.7 +**0.8.7** * Fixed Auga integration for crafting materials * Fixed Auga integration for showing the full tooltip in the sacrifice panel -## Version 0.8.6 +**0.8.6** * Disabled water walking (won't fix), but made it free to augment off of existing gear. -## Version 0.8.5 +**0.8.5** * Updated for H&H -## Version 0.8.4 +**0.8.4** * Added support for Project Auga * Completed localization for the entire mod (thanks Anya77!) -## Version 0.8.3 +**0.8.3** * ServerSync updated to latest * Fix for bounty completion bug -## Version 0.8.2 +**0.8.2** * Better item name localization * Fix for explosive arrow friendly fire * Fix for Armor +% magic effect @@ -859,9 +866,9 @@ API: * Fix for bounty target NRE and player known sync ([sbtoonz](https://github.com/sbtoonz)) * Fix for getting encumbered incorrectly ([M3TO](https://github.com/M3TO)) * New legendary weapons from LitanyOfFire: Lævateinn, Strength of the Valkyrie, Skofnung, Gram, Angurvadal, The Shattering, Atgier of Sagas, Skaði's Hunt, Ullr's Favor, The Endless Hunt, Message of Lindisfame, Höfuð, Njǫrd's Favor, Life-Drinker -## Version 0.8.1 +**0.8.1** * Fixed an exploit in enchanting -## Version 0.8.0 +**0.8.0** * Magic Effect lookup is now optimized * Removed debug function that was eating up frame-time * Legendary effects work in multiplayer @@ -874,7 +881,7 @@ API: * Added legendary set system * Added item ability system * New Legendary Sets: Heimdall's Legacy, Ragnar's Fury -## Version 0.7.10 +**0.7.10** * Fix for Server Side Character crash bug * Increased epic and legendary drop rates at low tiers * Wishbone is now Epic rarity @@ -885,12 +892,12 @@ API: * Magic Effect: Luck, increase chance for higher rarity items * Magic Effect names shortened, detailed descriptions added to compendium * Fix not loading initial known items in singleplayer and for server host. ([jsza](https://github.com/jsza)) -## Version 0.7.9 +**0.7.9** * Reduced Opportunist chance to proc * Allowed AddSkill to exceed 100 points (and fixed vanilla bug around damage ranges not going over 100%) * Excluded pickaxes from a bunch of magic effects that are useless for them * Added increased chop and pickaxe damage for axes and pickaxes when they have bonus slash/pierce damage (by [M3TO](https://github.com/M3TO)) -## Version 0.7.8 +**0.7.8** * ConfigSync from blaxxun added. MCE and the MCE addon are NO LONGER NEEDED! * Config json files are now editable while the game is running and will automatically hot reload! (thanks blaxxun!) * New Magic Item Effects: @@ -920,36 +927,36 @@ API: * Known items and recipes (for item gating) are now synced between all players on a server * Added "Sacrifice All" button to Sacrifice tab which sacrifices the whole stack of whatever you have selected * Fixed a bug where no boss trophies would drop in single player -## Version 0.7.7 +**0.7.7** * Added config to drop trophies based only on the number of nearby players, not just total players on the server * Fixed unlocalized minimap icon text for treasure maps * Embedded fastJSON and epicloot asset bundle to DLL (this fixes several linux server issues including one that was causing problems with bounties) * Merged fix from [maxrd2](https://github.com/maxrd2) fixing a crash when hitting enemies on Linux * Added a toggle to disable adventure mode features: secret stash, gambling, treasure maps, and bounties -## Version 0.7.6 +**0.7.6** * Updated loottables to cover up to 5-star enemies for all enemy types * Bounties are now completed correctly if another player kills your bounty target while you are offline (bounty completes on next login) -## Version 0.7.5 +**0.7.5** * Correctly spawning and checking bounty targets slain (this breaks all current old-version bounties) -## Version 0.7.4 +**0.7.4** * Removed freeze time, as it totally breaks multiplayer -## Version 0.7.3 +**0.7.3** * Added config value to hide equipped and hotbar items in the sacrifice tab * Also added freeze time keybind (RCtrl+Backspace) and made free fly camera fixed update time (so you can fly around during freeze time) * Integrated LifeSteal magic effect (submitted by [nanonull](https://github.com/nanonull)) * Added ModifyAttackSpeed, Waterproof, Paralyze * Localization part 1 * Hotfix for bug where buying treasuremaps or bounties can disconnect you from the server -## Version 0.7.2 +**0.7.2** * Bosses drop one trophy per player (configurable) * Fixed a bug where you could complete a bounty just by killing the minions if they were the same type as the bounty target * Changed bounty generation so you don't have identical bounties to other players on the server, to prevent confusion * Can now abandon bounties (please report bugs if the monsters actually don't spawn though) -## Version 0.7.1 +**0.7.1** * Fixed bug with disappearing bounty/treasuremap pins on logout * Fixed but with items purchased from Haldor's Secret Stash disappearing on logout * Added some junk to trophy recipes -## Version 0.7.0 +**0.7.0** * Added adventure panel to merchant * Added purchasing crafting mats from merchant * Added gambling for magic items from merchant @@ -957,23 +964,23 @@ API: * Added bounty hunting * Fixed some bugs with item gating * Fixed some bugs with augmenting legendary items -## Version 0.6.4 +**0.6.4** * Item Names Update! * Adding logs to Augmenting * Show all magical effects in compendium * Show currently equipped item in tooltip by holding LeftControl * Adding support for Mod Config Enforcer (see addon mod) -## Version 0.6.3 +**0.6.3** * Fixed a bug where augmenting an equipped item would apply the augment to the first item in the augment list * Fixed a bug where selecting a different item while one was already augmenting would not cancel the craft * Moved the restricted item names list to config (fan request, to allow Dyrnwyn as a drop on their server) -## Version 0.6.2 +**0.6.2** * Fixed a bug where augmented items would not save when logging out * Fixed a bug where augmenting some items would result in UI errors * Changed the set item icon again, shieldknot -## Version 0.6.1 +**0.6.1** * (reserved version number for Thunderstore rollback to 0.5.16) -## Version 0.6.0 +**0.6.0** * Added Augmenting at forge with augmenter or at artisan table * Sacrifice, Enchant, and Augment recipes are now configurable in `enchantcosts.json` * Added options for alternate crafting tab layout (compatibility with SimpleRecycling) @@ -981,39 +988,39 @@ API: * Added option to `magicitem` console command to specify the number of effects to roll * Added console command `cheatgating` which toggles item gating on or off * Enabled logging toggle for all of EpicLoot logging -## Version 0.5.16 +**0.5.16** * Three new craftable, enchantable utility items * All conversion and upgrade recipes for crafting materials in configurable json file * Small coin amounts added to conversion and upgrade recipes for crafting materials * Magic effect changes: * Parry now only rolls on two-handed weapons and shields * Block now only rolls on shields -## Version 0.5.15 +**0.5.15** * Loot tables now use a leveled format that can be extended beyond level 3 * DLC Stuff allowed to enchant * Can limit magic effects by SkillType * Can limit magic effects by exclusions, see info.md * Drastically reduced Movement Speed bonus (for new drops or enchanted) * Added gating for dropped item types by known recipe or known item (or unlimited) -## Version 0.5.14 +**0.5.14** * Updating the console command with a few more exclusions * Modified a UI display to better support multiple hotkeybars and multiple inventory grids -## Version 0.5.13 +**0.5.13** * Made it slightly easier for other modders to access the enchanting and disenchanting information * Made the tooltip text lookup for set items more defensive to prevent some mod conflicts -## Version 0.5.12 +**0.5.12** * Enchanting an item maintains its current durability percentage * Enchanting uses a new UI flow and shows the item after you enchant it * Magic Item Effects now load from a config file * Magic Item Effects now use a string ID instead of an enum * Changing TreasureChest_plains_stone loottable to use the TreasureChest_heath table * Fixed mod conflict with PlantingPlus -## Version 0.5.11 +**0.5.11** * Fixed a bug where crafter name would be applied to upgraded objects * Upgraded objects automatically repaired to full durability * Changing the default rarity of Dverger Circlet, Megingjord, and Wishbone to Rare * Fixing a bug where Eikthyr (or some other mob, like Troll lvl 3) dies repeatedly -## Version 0.5.10 +**0.5.10** * Removing all cheat and dlc items from the random loot generation cheat * Fixed a bug that showed 0% chance for all magic effect counts while enchanting * Fixed a bug that caused some chests to spawn non-magical items @@ -1027,46 +1034,46 @@ API: * Reduced loot tiers, increased drop chance counts for Serpent * Fixed treasure chests so they properly reflected their biome (`plains_stone` is actually Black Forest, `heath` is Plains) * Reduced loot tiers for `meadows_buried` and `shipwreck_karve` chests (they still have higher rarity chances) -## Version 0.5.9 +**0.5.9** * Fixed bug where sacrificing with nearly full inventory resulted in lost items (items that do not fit in the inventory now fall to the ground) * Loot2 and Loot3 in the loot table are now exclusive (e.g. if the mob is level 2, and Loot2 is present, then only the loot from Loot2 is used, otherwise it falls back to Loot) * ItemSets have been added to the loot table schema (If the "Item" field of the Loot list is in the item set, roll on that loot table for that item instead) * Can now reference other loot tables in the loot table item config using "Item": "`object`.`level`" where `object` is the name of a loot table entry and `level` is an integer between 1 and 3 that refers to Loot, Loot2, or Loot3. * Number of magic effects per rarity is now configurable in loottables.json in the "MagicEffectsCount" object * Completed loottables with updates from feedback and using the new system -## Version 0.5.8 +**0.5.8** * Hiding console commands behind the cheat flag * Removing log spam -## Version 0.5.7 +**0.5.7** * Removing accidentally added debug object -## Version 0.5.6 +**0.5.6** * Fixing crafting tabs showing magic items * Changing crafting tab item description to scrolling (can turn off in config) * Can set display name of rarity types in config * Can put non-magic items in the loot table by omitting the Rarity chance array * Loot beam sounds now respect the in-game SFX volume setting -## Version 0.5.5 +**0.5.5** * Fixed yet another couple of crafting tab bugs * Reduced mats upgrade recipe to 5:1 * Added shard to same rarity dust/essence/reagent recipe at 2:1 * Increased drop chance on The Elder and Bonemass * Fixed crafting recipe list selection exploit/bug * Added special recipe to sacrifice Swamp Key (who needs more than one?) -## Version 0.5.4 +**0.5.4** * Fixed bug with viewing effect ranges * Added troll trophy to rare disenchant list * Moved greydwarf brute and shaman trophy to rare disenchant list * Fixed an icon bug where the new material message showed the red material icon * Fixed some bugs with the crafting tabs -## Version 0.5.3 +**0.5.3** * Fixed stamina regen and health regen * Added holding shift to see ranges in tooltips * Fixed Elder/Bonemass runestone rarity mixup -## Version 0.5.2 +**0.5.2** * Fix for an enchant exploit * Updated swamp loot tables * Fixed resistances not working at all -## Version 0.5.1 +**0.5.1** * Fixed never respawning after dying * Fixed a bug where a whole stack of trophies would be disenchanted for a single crafting material * Updated correct ## Version number everywhere diff --git a/EpicLoot/thunderstore/README.md b/EpicLoot/Package/README.md similarity index 100% rename from EpicLoot/thunderstore/README.md rename to EpicLoot/Package/README.md diff --git a/EpicLoot/thunderstore/icon.png b/EpicLoot/Package/icon.png similarity index 100% rename from EpicLoot/thunderstore/icon.png rename to EpicLoot/Package/icon.png diff --git a/EpicLoot/thunderstore/manifest.json b/EpicLoot/Package/manifest.json similarity index 91% rename from EpicLoot/thunderstore/manifest.json rename to EpicLoot/Package/manifest.json index b13a0f3e5..ede69247d 100644 --- a/EpicLoot/thunderstore/manifest.json +++ b/EpicLoot/Package/manifest.json @@ -1,6 +1,6 @@ { "name": "EpicLoot", - "version_number": "0.13.0", + "version_number": "0.13.2", "website_url": "https://discord.gg/ZNhYeavv3C", "description": "Adds loot drops, magic items, and enchanting to Valheim.", "dependencies": [ diff --git a/EpicLoot/Properties/AssemblyInfo.cs b/EpicLoot/Properties/AssemblyInfo.cs index 9b0fdc9c1..8376ad7e5 100644 --- a/EpicLoot/Properties/AssemblyInfo.cs +++ b/EpicLoot/Properties/AssemblyInfo.cs @@ -32,7 +32,7 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] // -// KEEP IN SYNC with EpicLoot.Version (EpicLoot.cs) and thunderstore/manifest.json. The csproj sets +// KEEP IN SYNC with EpicLoot.Version (EpicLoot.cs) and Package/manifest.json. The csproj sets // GenerateAssemblyInfo=false, so these are hand-maintained; anything that version-binds against the // assembly rather than the BepInEx plugin metadata reads these. [assembly: AssemblyVersion("0.13.0")] diff --git a/EpicLoot/README.md b/EpicLoot/README.md index 568b102e2..d11c97e84 120000 --- a/EpicLoot/README.md +++ b/EpicLoot/README.md @@ -1 +1 @@ -thunderstore/README.md \ No newline at end of file +Package/README.md \ No newline at end of file diff --git a/EpicLoot/release.yml b/EpicLoot/release.yml new file mode 100644 index 000000000..3de363ed3 --- /dev/null +++ b/EpicLoot/release.yml @@ -0,0 +1,30 @@ +# Release configuration for mod-tools. +# Shared settings come from the preset, which ships inside the gem. +# +# Inspect what this actually resolves to: +# mod-tools print-config + +extends: jotunn-mono +assembly: EpicLoot + +# This repository predates the tool and spells the version constant its own way. +version_const: Version + +# The Thunderstore/Hexium team these publish under. +author_name: RandyKnapp + +# Copy the build into this machine's mod manager profiles after every build +deploy: + enabled: true + +publish: + thunderstore: + enabled: true + nexus: + enabled: true + mod_id: 387 + file_id: 7405772 + github: + enabled: true + discord: + enabled: true diff --git a/EpicLoot/src/Adventure/AdventureDataManager.cs b/EpicLoot/src/Adventure/AdventureDataManager.cs index 8db2cee90..ae160a0b1 100755 --- a/EpicLoot/src/Adventure/AdventureDataManager.cs +++ b/EpicLoot/src/Adventure/AdventureDataManager.cs @@ -127,6 +127,11 @@ public static void OnZNetDestroyed() Gamble.OnZNetDestroyed(); TreasureMaps.OnZNetDestroyed(); Bounties.OnZNetDestroyed(); + + // The spawn-point cache lives for the client's session rather than the merchant panel's, so + // this is the only thing that empties it. Its contents are world positions -- carrying them + // into the next world would hand out points from the wrong map. + BountyLocationEarlyCache.Reset(); } public static void OnWorldSave() diff --git a/EpicLoot/src/Adventure/MerchantPanel.cs b/EpicLoot/src/Adventure/MerchantPanel.cs index 0e3ad2487..380eae7dc 100755 --- a/EpicLoot/src/Adventure/MerchantPanel.cs +++ b/EpicLoot/src/Adventure/MerchantPanel.cs @@ -193,8 +193,10 @@ public void Awake() IronBountyTokensCount = transform.Find("Currencies/BountyTokensIronCount").GetComponent(); GoldBountyTokensCount = transform.Find("Currencies/BountyTokensGoldCount").GetComponent(); - // Initialize the cache of of bounty positions, starting from the central part of the map. - StartCoroutine(BountyLocationEarlyCache.PopulateCacheFromStart()); + // Top up the cache of bounty positions. Runs on the cache's own driver rather than this + // panel: StoreGui deactivates the panel on close, which would abandon the fill partway. + // It is a no-op once every biome is stocked, so reopening the merchant costs nothing. + BountyLocationEarlyCache.RequestRefill(ignoreThreshold: true); if (EpicLoot.HasAuga) { diff --git a/EpicLoot/src/Adventure/TreasureMapChest.cs b/EpicLoot/src/Adventure/TreasureMapChest.cs index 378364d2e..4f4dddefd 100755 --- a/EpicLoot/src/Adventure/TreasureMapChest.cs +++ b/EpicLoot/src/Adventure/TreasureMapChest.cs @@ -112,7 +112,7 @@ public static class Container_Awake_Patch { public static void Postfix(Container __instance) { - var zdo = __instance.m_nview.GetZDO(); + var zdo = __instance.m_nview == null ? null : __instance.m_nview.GetZDO(); if (zdo != null) { var biomeString = zdo.GetString($"{nameof(TreasureMapChest)}.{nameof(TreasureMapChest.Biome)}"); @@ -140,7 +140,7 @@ public static class Container_RPC_OpenRespons_Patch { public static void Postfix(Container __instance, long uid, bool granted) { - var zdo = __instance.m_nview.GetZDO(); + var zdo = __instance.m_nview == null ? null : __instance.m_nview.GetZDO(); if (zdo == null || !zdo.IsValid() || zdo.GetBool("TreasureMapChest.HasBeenFound")) { return; diff --git a/EpicLoot/src/Adventure/bounties/AdventureSpawnController.cs b/EpicLoot/src/Adventure/bounties/AdventureSpawnController.cs index 3ce2e72f9..65221ede6 100644 --- a/EpicLoot/src/Adventure/bounties/AdventureSpawnController.cs +++ b/EpicLoot/src/Adventure/bounties/AdventureSpawnController.cs @@ -27,6 +27,9 @@ internal class AdventureSpawnController : MonoBehaviour private const int SpawnAttemptsPerBand = 100; + /// Set once a missing creature prefab has been reported, so it is logged one time. + private bool reportedMissingPrefab = false; + private int currentUpdates = 0; private int updatesRequired = InitialSearchDelayFrames; private bool startedPlacement = false; @@ -136,8 +139,7 @@ private void SpawnBountyTargets(BountyInfo bounty) var mainPrefab = ZNetScene.instance.GetPrefab(bounty.Target.MonsterID); if (mainPrefab == null) { - EpicLoot.LogWarning($"Could not find prefab for bounty target! BountyID: " + - $"{bounty.ID}, MonsterID: {bounty.Target.MonsterID}"); + ReportMissingPrefab("target", bounty.ID, bounty.Target.MonsterID); return; } @@ -149,8 +151,7 @@ private void SpawnBountyTargets(BountyInfo bounty) var prefab = ZNetScene.instance.GetPrefab(addConfig.MonsterID); if (prefab == null) { - EpicLoot.LogError($"Could not find prefab for bounty add! BountyID: " + - $"{bounty.ID}, MonsterID: {addConfig.MonsterID}"); + ReportMissingPrefab("add", bounty.ID, addConfig.MonsterID); return; } prefabs.Add(prefab); @@ -368,6 +369,35 @@ private void ParkAndRetry() updatesRequired = RetrySearchDelayFrames; } + /// + /// Handles a bounty creature prefab that ZNetScene does not have -- typically a creature from a + /// mod that has since been removed. + /// + /// Spawning bails without setting placed, and Update re-enters it on the very next frame, + /// so before this the spawner retried a lookup that cannot succeed **every frame for as long as + /// the player stayed near the bounty**. Back off to the same delay a failed location search uses, + /// and report it once at Error rather than per-frame at Warning -- Warning is invisible at the + /// default Log Level, which is why this failed silently. + /// + /// The spawner is deliberately not marked placed: the player has already paid for this bounty, + /// and re-adding the missing mod should let it spawn normally. + /// + private void ReportMissingPrefab(string role, string bountyId, string monsterId) + { + if (!reportedMissingPrefab) + { + reportedMissingPrefab = true; + EpicLoot.LogError($"Could not find prefab for bounty {role}! BountyID: {bountyId}, " + + $"MonsterID: {monsterId}. This bounty cannot spawn until that prefab exists again " + + "(is the mod that adds it still installed?). Retrying occasionally."); + } + + // Delay the next attempt without clearing startedPlacement -- the spawn point is fine, it is + // only the prefab that is missing, so there is nothing to re-search for. + currentUpdates = 0; + updatesRequired = RetrySearchDelayFrames; + } + /// /// True when the chosen point falls outside the circle drawn on the map. The pin's /// m_worldSize is a diameter (vanilla sets it to range * 2), and MinimapController diff --git a/EpicLoot/src/Adventure/feature/AdventureFeature.cs b/EpicLoot/src/Adventure/feature/AdventureFeature.cs index 260af0aef..51156e588 100755 --- a/EpicLoot/src/Adventure/feature/AdventureFeature.cs +++ b/EpicLoot/src/Adventure/feature/AdventureFeature.cs @@ -115,11 +115,20 @@ public static ItemDrop CreateItemDrop(string prefabName) return null; } - ZNetView.m_forceDisableInit = true; - var item = Object.Instantiate(itemDropPrefab); - ZNetView.m_forceDisableInit = false; - - return item; + // Save and restore rather than assigning false. Instantiate runs the new object's whole Awake + // chain, and a throw in there would strand this global true -- after which every ZNetView in + // the session destroys itself in Awake instead of registering a ZDO, which corrupts + // ZNetScene for the rest of the run. Same guard as LootRoller.SpawnLootForDrop. + var priorForceDisableInit = ZNetView.m_forceDisableInit; + try + { + ZNetView.m_forceDisableInit = true; + return Object.Instantiate(itemDropPrefab); + } + finally + { + ZNetView.m_forceDisableInit = priorForceDisableInit; + } } public static List CollectItems(List itemList) diff --git a/EpicLoot/src/Adventure/feature/BountyLocationEarlyCache.cs b/EpicLoot/src/Adventure/feature/BountyLocationEarlyCache.cs index 8889808b8..1b07dc390 100755 --- a/EpicLoot/src/Adventure/feature/BountyLocationEarlyCache.cs +++ b/EpicLoot/src/Adventure/feature/BountyLocationEarlyCache.cs @@ -8,13 +8,29 @@ namespace EpicLoot.Adventure.Feature { internal static class BountyLocationEarlyCache { - // This could be shifted to multiple variable zsynced lists to preserve the generated values for future use. + // Per-client and deliberately unsynchronized: two players picking different spots for their own + // bounties is fine, and the alternative (a shared list) would need an anchor to live on -- + // Haldor has none, since vanilla Trader carries no ZNetView and no networked state at all. + // + // Lifetime is the client's session, not the merchant panel's. Reset() clears it on world change + // (points are world-specific); nothing else empties it, so reopening the merchant is free. public static Dictionary> PotentialBiomeLocations = new Dictionary> { }; - private static int _minimumLocationKeys = 3; - private static int _cacheTriesPerBiome = 10; - private static int _maximumTries = 100; + // Target and low-water mark are separate. The old single value of 3 was both the goal and the + // cap, and SelectSpawnPoint consumes one per bounty or treasure map, so a biome ran dry after + // two of them -- and every bounty after that paid for a full blocking search. + private const int TargetPointsPerBiome = 12; + private const int RefillThreshold = 4; + + private const int MaxTriesPerBiomeFill = 60; + private const int MaxTriesOnDemand = 100; + + // A candidate whose zone will not come up is not worth waiting on forever; move to the next one. + private const int MaxZoneWaitFrames = 120; + + // Only one top-up coroutine at a time, so repeated merchant visits cannot stack them. + private static bool _refilling; private static Dictionary> GetRadiusRanges() { @@ -35,35 +51,63 @@ internal static class BountyLocationEarlyCache return radiusRanges; } + /// + /// Drops the cache. Called on world change -- every point is a world position, so carrying them + /// into another world would hand out spawn points from the wrong map. + /// + public static void Reset() + { + // Stop first: a top-up still in flight would otherwise finish against the new world and file + // points it picked from the old one. + AdventureCacheDriver.StopAll(); + PotentialBiomeLocations.Clear(); + _refilling = false; + } + + internal static int CachedPointCount(Heightmap.Biome biome) + { + return PotentialBiomeLocations.TryGetValue(biome, out var points) ? points.Count : 0; + } + internal static void TryAddBiomePoint(Heightmap.Biome biome, Vector3 point) { - if (!PotentialBiomeLocations.ContainsKey(biome)) + if (!PotentialBiomeLocations.TryGetValue(biome, out var points)) { - PotentialBiomeLocations.Add(biome, new List() { }); + points = new List(); + PotentialBiomeLocations.Add(biome, points); } - else if (PotentialBiomeLocations[biome].Count >= _minimumLocationKeys) + + if (points.Count >= TargetPointsPerBiome) { return; } - PotentialBiomeLocations[biome].Add(point); + points.Add(point); } public static IEnumerator TryGetBiomePoint( Heightmap.Biome biome, AdventureSaveData saveData, Action onComplete) { - Dictionary> radiusRanges = GetRadiusRanges(); - - // Check if any valid cached - if (PotentialBiomeLocations.ContainsKey(biome) && PotentialBiomeLocations[biome].Count > 1) + // Anything cached is served immediately -- the player is standing at the merchant waiting on + // a bounty they have already paid for, so searching here is the one thing worth avoiding. + if (CachedPointCount(biome) > 0) { SelectSpawnPoint(biome, onComplete); + RequestRefill(); yield break; } - yield return AddBiomePointLazyCache(radiusRanges, biome, true, onComplete); + // Nothing banked for this biome, so there is no choice but to search now. + yield return AddBiomePointLazyCache(GetRadiusRanges(), biome, true, onComplete); + RequestRefill(); } + /// + /// Tops the cache up to . Called from MerchantPanel.Awake, so + /// it runs again every time the panel is rebuilt -- it deliberately does not clear anything, and + /// returns immediately when every biome is already stocked. Regenerating on each merchant visit + /// is what made accepting a bounty pay for hundreds of world-point tests. + /// public static IEnumerator PopulateCacheFromStart() { // Can't setup the cache without a player @@ -72,44 +116,78 @@ public static IEnumerator PopulateCacheFromStart() yield break; } - // Clear old data - PotentialBiomeLocations = new Dictionary> { }; - Dictionary> radiusRanges = GetRadiusRanges(); - - int index = 0; - Heightmap.Biome[] biomeList = AdventureDataManager.Config.TreasureMap.GetBiomeList(); + yield return FillBiomesBelowTarget(); + } - while (index < biomeList.Length) + /// + /// Kicks off a background top-up when any biome has fallen below the low-water mark. Fire and + /// forget: the caller never waits on it, so consuming a point never blocks on a search. + /// + public static void RequestRefill(bool ignoreThreshold = false) + { + if (_refilling || Player.m_localPlayer == null) { - Heightmap.Biome targetBiome = biomeList[index]; + return; + } - Tuple radiusRange = radiusRanges[targetBiome]; + if (!ignoreThreshold && !AnyBiomeBelowThreshold()) + { + return; + } - yield return AddBiomePointLazyCache(radiusRanges, targetBiome); + AdventureCacheDriver.Run(RefillCache()); + } - index++; + private static bool AnyBiomeBelowThreshold() + { + foreach (var biome in AdventureDataManager.Config.TreasureMap.GetBiomeList()) + { + if (biome == Heightmap.Biome.None || biome == Heightmap.Biome.All) + { + continue; + } - // Check if all biomes have the required number of keys - bool isReady = true; - foreach (var biome in biomeList) + if (CachedPointCount(biome) < RefillThreshold) { - if (biome == Heightmap.Biome.None || biome == Heightmap.Biome.All) - { - continue; - } + return true; + } + } - if (!PotentialBiomeLocations.ContainsKey(biome) || - PotentialBiomeLocations[biome].Count < _minimumLocationKeys) - { - isReady = false; - break; - } + return false; + } + + private static IEnumerator RefillCache() + { + _refilling = true; + try + { + yield return FillBiomesBelowTarget(); + } + finally + { + _refilling = false; + } + } + + private static IEnumerator FillBiomesBelowTarget() + { + Dictionary> radiusRanges = GetRadiusRanges(); + + foreach (var biome in AdventureDataManager.Config.TreasureMap.GetBiomeList()) + { + if (biome == Heightmap.Biome.None || biome == Heightmap.Biome.All) + { + continue; } - if (isReady) + // Already stocked. Points do not go stale -- the world does not change -- so there is + // nothing to refresh and this is the path a repeat merchant visit takes. + if (CachedPointCount(biome) >= TargetPointsPerBiome) { - break; + continue; } + + yield return AddBiomePointLazyCache(radiusRanges, biome); } } @@ -118,32 +196,83 @@ public static IEnumerator AddBiomePointLazyCache(Dictionary onComplete = null) { int tries = 0; + int maxTries = requireSelection ? MaxTriesOnDemand : MaxTriesPerBiomeFill; while (true) { // Fail safe, exit coroutine. - if ((!requireSelection && tries > _cacheTriesPerBiome) || tries > _maximumTries) + if (tries > maxTries) { onComplete?.Invoke(false, Vector3.zero); yield break; } - // Prevent locking main thread. - if (tries % 20 == 0 && tries > 1) + // Prevent locking main thread. Every path below increments tries, so this is guaranteed + // to come around -- it previously could not, because the radius-reject path used to + // `continue` without incrementing, leaving the loop spinning with nothing to yield on. + if (tries % 10 == 0 && tries > 1) { yield return new WaitForSeconds(1f); } + + // Stop filling the moment the biome is stocked, rather than burning the full try budget. + if (!requireSelection && CachedPointCount(biome) >= TargetPointsPerBiome) + { + yield break; + } + var range = radiusRanges.ContainsKey(biome) ? radiusRanges[biome] : new Tuple(0f, WorldGenerator.waterEdge); var spawnPoint = SelectWorldPoint(range, tries, biome); + + // Cheap rejection first. WorldGenerator answers biome and height from the world seed with + // no instantiation at all, and it is the same data the Heightmap is built from -- so any + // point it rejects would have failed the full check below too. This is what keeps the + // zone spawn underneath from running on nearly every candidate. + if (!PassesWorldGenPreFilter(spawnPoint)) + { + tries++; + continue; + } + + // The full check needs a real Heightmap (GetGroundData raycasts terrain colliders, and + // the lava test reads the vegetation mask), so the zone has to exist for it. var zoneId = ZoneSystem.GetZone(spawnPoint); - while (!ZoneSystem.instance.SpawnZone(zoneId, ZoneSystem.SpawnMode.Client, out _)) + GameObject zoneRoot = null; + int zoneWaitFrames = 0; + while (!ZoneSystem.instance.SpawnZone(zoneId, ZoneSystem.SpawnMode.Client, out zoneRoot)) { - // Wait until the zone is spawned. + // A zone whose location prefab has not finished loading can stay unavailable for an + // open-ended time. Give up on this candidate rather than waiting on it forever. + if (++zoneWaitFrames > MaxZoneWaitFrames) + { + break; + } + yield return new WaitForEndOfFrame(); } - if (!IsSpawnLocationValid(spawnPoint, out Heightmap.Biome spawnLocationBiome)) + if (zoneRoot == null) + { + tries++; + continue; + } + + Heightmap.Biome spawnLocationBiome; + bool valid; + try + { + valid = IsSpawnLocationValid(spawnPoint, out spawnLocationBiome); + } + finally + { + // SpawnZone always instantiates a fresh root and, unlike PokeLocalZone, we never + // register it in ZoneSystem.m_zones -- so UpdateTTL will never destroy it. Left alone + // this leaked a zone and its terrain mesh on every single try. + UnityEngine.Object.Destroy(zoneRoot); + } + + if (!valid) { tries++; continue; @@ -156,26 +285,53 @@ public static IEnumerator AddBiomePointLazyCache(Dictionary max) { - var min = radiusRanges[spawnLocationBiome].Item1; - var max = radiusRanges[spawnLocationBiome].Item2; - var mag = new Vector2(spawnPoint.x, spawnPoint.z).magnitude; - if (mag < min || mag > max) - { - continue; - } + tries++; + continue; } - - TryAddBiomePoint(spawnLocationBiome, spawnPoint); } + TryAddBiomePoint(spawnLocationBiome, spawnPoint); tries++; } } + /// + /// Seed-level rejection of a candidate point, with nothing instantiated. Mirrors the biome and + /// water tests in using WorldGenerator, which is the source + /// the Heightmap is generated from -- so this only ever rejects points the full check rejects. + /// + private static bool PassesWorldGenPreFilter(Vector3 point) + { + var worldGenerator = WorldGenerator.instance; + if (worldGenerator == null) + { + // No generator to consult yet; let the full check decide. + return true; + } + + var biome = worldGenerator.GetBiome(point.x, point.z); + if (biome == Heightmap.Biome.None) + { + return false; + } + + if (biome != Heightmap.Biome.Ocean && + ZoneSystem.instance.m_waterLevel > worldGenerator.GetHeight(point.x, point.z) + 2f) + { + return false; + } + + return true; + } + internal static void SelectSpawnPoint(Heightmap.Biome biome, Action onComplete) { List locations = PotentialBiomeLocations[biome]; @@ -302,4 +458,34 @@ private static TreasureMapBiomeInfoConfig GetBiomeInfoConfig(Heightmap.Biome bio return AdventureDataManager.Config.TreasureMap.BiomeInfo.Find(x => x.Biome == biome); } } + + /// + /// Host for the background cache top-up. The merchant panel cannot run it -- StoreGui deactivates + /// that object on close, which would stop the coroutine partway -- and a refill is normally + /// requested at the moment a point is consumed, i.e. while the panel is on its way out. + /// + internal class AdventureCacheDriver : MonoBehaviour + { + private static AdventureCacheDriver _instance; + + public static void Run(IEnumerator routine) + { + if (_instance == null) + { + var go = new GameObject("EpicLoot_AdventureCacheDriver"); + UnityEngine.Object.DontDestroyOnLoad(go); + _instance = go.AddComponent(); + } + + _instance.StartCoroutine(routine); + } + + public static void StopAll() + { + if (_instance != null) + { + _instance.StopAllCoroutines(); + } + } + } } diff --git a/EpicLoot/src/Config/ConfigVersionManager.cs b/EpicLoot/src/Config/ConfigVersionManager.cs index fc5fb5981..c5f0a025f 100644 --- a/EpicLoot/src/Config/ConfigVersionManager.cs +++ b/EpicLoot/src/Config/ConfigVersionManager.cs @@ -1,4 +1,4 @@ -using BepInEx; +using BepInEx; using EpicLoot.Patching; using System; using System.Collections.Generic; diff --git a/EpicLoot/src/Config/ConfigVersionState.cs b/EpicLoot/src/Config/ConfigVersionState.cs index df7f84168..a0a14d101 100644 --- a/EpicLoot/src/Config/ConfigVersionState.cs +++ b/EpicLoot/src/Config/ConfigVersionState.cs @@ -1,4 +1,4 @@ -using BepInEx; +using BepInEx; using Newtonsoft.Json; using System; using System.Collections.Generic; diff --git a/EpicLoot/src/GamePatches/Container_Patch.cs b/EpicLoot/src/GamePatches/Container_Patch.cs index acfe9b19a..a4c2c9a53 100644 --- a/EpicLoot/src/GamePatches/Container_Patch.cs +++ b/EpicLoot/src/GamePatches/Container_Patch.cs @@ -1,5 +1,6 @@ using EpicLoot.Config; using HarmonyLib; +using UnityEngine; namespace EpicLoot { @@ -13,6 +14,13 @@ public static void Postfix(Container __instance) return; } + // CLLC's DropTable.GetDropListItems postfix multiplies a drop by appending the *same* + // ItemDrop.ItemData reference N times rather than cloning it, and vanilla AddDefaultItems + // feeds each of those to Inventory.AddItem. The result is one object occupying several + // inventory slots, which every later pass over the inventory — ours included — then + // treats as distinct items. Split them before EpicLoot touches this container. + PendingChestLoot.DeAliasInventory(__instance, containerName); + var zdo = __instance.m_nview == null ? null : __instance.m_nview.GetZDO(); // CheckForChanges destroys an m_autoDestroyEmpty container as soon as it is owned, empty @@ -21,7 +29,19 @@ public static void Postfix(Container __instance) if (!ELConfig.DeferChestLootRoll.Value || __instance.m_autoDestroyEmpty || zdo == null || !zdo.IsValid()) { - PendingChestLoot.Roll(__instance, containerName, lootTables); + // ZNetView.m_useInitZDO is true for exactly as long as ZNetScene.CreateObject is + // mid-Instantiate of this container, and this postfix runs inside Container.Awake, + // inside that Instantiate. Rolling here would nest a second Instantiate inside the + // first while ZNetScene still has m_initZDO checked out — so hand it to LateUpdate. + if (ZNetView.m_useInitZDO) + { + PendingChestLoot.RequestDirectRoll(__instance); + } + else + { + PendingChestLoot.Roll(__instance, containerName, lootTables); + } + return; } @@ -48,8 +68,12 @@ public static void Postfix(Container __instance) /// /// The universal "contents are being read" accessor — InventoryGui, Container.CanBeRemoved and - /// quick-loot mods all route through it. Rolling in a prefix hands the caller an inventory that - /// already holds the loot, so nothing needs a second pass to notice it. + /// quick-loot mods all route through it. It is a plain getter, though: container-scanning mods + /// (AzuCraftyBoxes' EpicLoot inventory provider among them, via our own API) call it over every + /// container in range, several times per frame. Rolling inline here meant one crafting-station + /// query force-rolled every unopened chest in the dungeon in a single frame. Queue instead; the + /// loot lands on the next LateUpdate, which is still well before a player can act on it, since + /// hovering a chest fires the trigger below first. /// [HarmonyPatch(typeof(Container), nameof(Container.GetInventory))] public static class Container_GetInventory_Patch @@ -58,7 +82,7 @@ public static void Prefix(Container __instance) { if (PendingChestLoot.AnyPending) { - PendingChestLoot.TryRoll(__instance); + PendingChestLoot.RequestRoll(__instance); } } } @@ -66,7 +90,8 @@ public static void Prefix(Container __instance) /// /// GetHoverText reads m_inventory directly, so it bypasses the GetInventory hook; without this a /// pending chest whose vanilla drop table rolled nothing would read "( empty )". Hovering is a - /// short local raycast, so it is a fair "the player is at the chest" signal. + /// short local raycast, so it is a fair "the player is at the chest" signal — but it also runs + /// every frame the player looks at the chest, so it queues rather than rolling inline. /// [HarmonyPatch(typeof(Container), nameof(Container.GetHoverText))] public static class Container_GetHoverText_Patch @@ -74,6 +99,23 @@ public static class Container_GetHoverText_Patch public static void Prefix(Container __instance) { if (PendingChestLoot.AnyPending) + { + PendingChestLoot.RequestRoll(__instance); + } + } + } + + /// + /// Opening a chest is a deliberate player action on one specific container, so it rolls + /// synchronously — there is no bulk-call risk here, and the contents must be present by the time + /// the open RPC resolves. + /// + [HarmonyPatch(typeof(Container), nameof(Container.Interact))] + public static class Container_Interact_Patch + { + public static void Prefix(Container __instance, bool hold) + { + if (!hold && PendingChestLoot.AnyPending) { PendingChestLoot.TryRoll(__instance); } @@ -82,7 +124,8 @@ public static void Prefix(Container __instance) /// /// DropAllItems reads m_inventory directly, so smashing a chest nobody ever opened would - /// otherwise silently lose its EpicLoot contents. + /// otherwise silently lose its EpicLoot contents. Synchronous: the inventory is emptied inside + /// this same call, so a deferred roll would arrive after the loot had already been dropped. /// [HarmonyPatch(typeof(Container), nameof(Container.OnDestroyed))] public static class Container_OnDestroyed_Patch @@ -98,7 +141,8 @@ public static void Prefix(Container __instance) /// /// Take-all moves out of m_inventory directly. Unreachable for chests in vanilla (Interact bails - /// on hold), but Container.TakeAll is public and quick-loot mods call it. + /// on hold), but Container.TakeAll is public and quick-loot mods call it. Synchronous for the + /// same reason as OnDestroyed. /// [HarmonyPatch(typeof(Container), nameof(Container.RPC_TakeAllRespons))] public static class Container_RPC_TakeAllRespons_Patch diff --git a/EpicLoot/src/General/ConfigUpdatePrompt.cs b/EpicLoot/src/General/ConfigUpdatePrompt.cs index 6bef51c49..58e8099fb 100644 --- a/EpicLoot/src/General/ConfigUpdatePrompt.cs +++ b/EpicLoot/src/General/ConfigUpdatePrompt.cs @@ -1,4 +1,4 @@ -using EpicLoot.Config; +using EpicLoot.Config; using HarmonyLib; using Jotunn.Managers; using System; diff --git a/EpicLoot/src/Loot/LootRoller.cs b/EpicLoot/src/Loot/LootRoller.cs index d5d0a831c..dcd9744c0 100644 --- a/EpicLoot/src/Loot/LootRoller.cs +++ b/EpicLoot/src/Loot/LootRoller.cs @@ -40,7 +40,7 @@ public static class LootRoller // Biome shard drops name their item set "ShardStone_{Biome}" using the Heightmap.Biome enum name, the // same convention as TreasureMapChest_{Biome} and {Biome}_{Rarity}_Unidentified. This one covers a biome - // with no set of its own -- a modded biome, or Heightmap.Biome.None. + // with no set of its own — a modded biome, or Heightmap.Biome.None. private const string DefaultShardStoneSet = "ShardStone_None"; // Ceiling on how many sockets a SocketCounts entry may ask for. SocketsUI builds its inventory @@ -221,7 +221,19 @@ public static List RollLootTableAndSpawnObjects(LootTable lootTable, foreach (var itemObject in gameObjects) { results.Add(itemObject.GetComponent().m_itemData.Clone()); - ZNetScene.instance.Destroy(itemObject); + + // ZNetScene.Destroy is the right call either way: it no-ops the ZDO half when the + // ZNetView never registered (the normal case here, since these are spawned with + // m_forceDisableInit), and unregisters properly if it did. Plain Object.Destroy would + // strand a live entry in ZNetScene.m_instances in that second case. + if (ZNetScene.instance != null) + { + ZNetScene.instance.Destroy(itemObject); + } + else + { + Object.Destroy(itemObject); + } } return results; @@ -532,8 +544,8 @@ private static List RollLootTableInternal(LootTable lootTable, var rarityLength = lootDrop?.Rarity?.Length != null ? lootDrop.Rarity.Length : -1; EpicLoot.Log($"Item: {itemName} - Rarity Count: {rarityLength} - Weight: {lootDrop.Weight}"); - // A drop that is already a shard -- rolled from an elite creature's bonus shard set or from a - // boss's shard table -- must not be re-rolled into a biome shard, nor sacrificed for + // A drop that is already a shard — rolled from an elite creature's bonus shard set or from a + // boss's shard table — must not be re-rolled into a biome shard, nor sacrificed for // materials. The unidentified category needs no such guard: IsAllowedMagicItemType rejects // a Material. var isShardDrop = lootDrop.Item != null && @@ -556,8 +568,8 @@ private static List RollLootTableInternal(LootTable lootTable, break; } - // Every substitute category can still fail late -- a missing prefab, a rarity with no - // sacrifice products -- and each one warns before it does. Falling back to the item the + // Every substitute category can still fail late — a missing prefab, a rarity with no + // sacrifice products — and each one warns before it does. Falling back to the item the // loot table actually named is what keeps a failure from silently eating the drop. if (!spawned) { @@ -571,7 +583,7 @@ private static List RollLootTableInternal(LootTable lootTable, // Rolls what a single drop becomes. The four Balance drop ratios are relative weights, not // independent chances, so only their proportions matter and any of them may be zeroed to remove // that category. Categories this particular drop cannot become are left out of the roll entirely - // rather than rolled and then rejected -- an ineligible category in the pool would silently eat + // rather than rolled and then rejected — an ineligible category in the pool would silently eat // the drop's chance of becoming any of the others. private static LootDropType SelectDropType(LootDrop lootDrop, bool isShardDrop, bool cheatsActive) { @@ -730,27 +742,41 @@ private static bool TrySpawnUnidentified(LootDrop lootDrop, ref Vector3 dropPoin EpicLoot.Log($"Adding {rarity} unidentified item"); var randomRotation = Quaternion.Euler(0.0f, Random.Range(0.0f, 360.0f), 0.0f); - ZNetView.m_forceDisableInit = !initializeObject; - var lootdrop = Object.Instantiate(prefab, dropPoint, randomRotation); - // Ensure that the unidentified item has the correct magic item data for the rarity - var id = lootdrop.GetComponent(); - var mic = id.m_itemData.Data().GetOrCreate(); - mic.SetMagicItem(new MagicItem - { - Rarity = rarity, - IsUnidentified = true, - }); - // Persist the rarity/unidentified state into the ZDO so a real world drop survives reload. - // No-op for the container path where the ZNetView was disabled (Save early-returns on - // invalid nview). - id.Save(); - ZNetView.m_forceDisableInit = false; + + // m_forceDisableInit is a global that ZNetView.Awake reads to decide whether to register a + // ZDO at all. Restore whatever it was, in a finally: leaving it stuck true makes every + // later ZNetView awake unregistered, which strands null-ZDO entries in ZNetScene.m_instances + // and NREs ZNetScene.RemoveObjects every frame for the rest of the session. + var priorForceDisableInit = ZNetView.m_forceDisableInit; + GameObject lootdrop; + try + { + ZNetView.m_forceDisableInit = !initializeObject; + lootdrop = Object.Instantiate(prefab, dropPoint, randomRotation); + // Ensure that the unidentified item has the correct magic item data for the rarity + var id = lootdrop.GetComponent(); + var mic = id.m_itemData.Data().GetOrCreate(); + mic.SetMagicItem(new MagicItem + { + Rarity = rarity, + IsUnidentified = true, + }); + // Persist the rarity/unidentified state into the ZDO so a real world drop survives reload. + // No-op for the container path where the ZNetView was disabled (Save early-returns on + // invalid nview). + id.Save(); + } + finally + { + ZNetView.m_forceDisableInit = priorForceDisableInit; + } + results.Add(lootdrop); return true; } // Replaces the drop with the magic crafting materials that item would yield if sacrificed. Returns - // false when nothing could be spawned -- an item with no sacrifice products for the rolled rarity + // false when nothing could be spawned — an item with no sacrifice products for the rolled rarity // drops as itself rather than as nothing at all. private static bool TrySpawnMaterials(LootDrop lootDrop, Vector3 dropPoint, float luckFactor, List results) @@ -857,10 +883,21 @@ private static void SpawnNormalItem(LootDrop lootDrop, string objectName, Vector public static GameObject SpawnLootForDrop(GameObject itemPrefab, Vector3 dropPoint, bool initializeObject) { Quaternion randomRotation = Quaternion.Euler(0.0f, Random.Range(0.0f, 360.0f), 0.0f); - ZNetView.m_forceDisableInit = !initializeObject; - GameObject item = Object.Instantiate(itemPrefab, dropPoint, randomRotation); - ZNetView.m_forceDisableInit = false; - return item; + + // Save and restore rather than assigning false: Instantiate runs the new object's Awake + // chain (ItemDrop.Awake, ItemDataManager, our own postfixes), any of which can throw, and + // the chest path can run nested inside another instantiate. A stranded true here breaks + // ZNetScene for the rest of the session — see the comment in TrySpawnUnidentified. + var priorForceDisableInit = ZNetView.m_forceDisableInit; + try + { + ZNetView.m_forceDisableInit = !initializeObject; + return Object.Instantiate(itemPrefab, dropPoint, randomRotation); + } + finally + { + ZNetView.m_forceDisableInit = priorForceDisableInit; + } } // Resolves a loot entry down to a name that ObjectDB can look up, following per-rarity maps, @@ -871,7 +908,7 @@ public static GameObject SpawnLootForDrop(GameObject itemPrefab, Vector3 dropPoi // console commands resolve entries outside of any drop and have no luck to apply. // // consumeRarityItems: false stops resolution at the first entry carrying a per-rarity map, leaving - // its authored Rarity spread intact. Only the luck-test command wants that -- rolling a rarity is + // its authored Rarity spread intact. Only the luck-test command wants that — rolling a rarity is // exactly what it is trying to report on rather than perform. public static LootDrop ResolveLootDrop(LootDrop lootDrop, float luckFactor = 0f, bool consumeRarityItems = true) { @@ -883,8 +920,26 @@ public static LootDrop ResolveLootDrop(LootDrop lootDrop, float luckFactor = 0f, RarityItems = lootDrop.RarityItems }; var needsResolve = true; + + // Every branch below can hand the loop another name to resolve, so a cyclic config (set A + // -> set B -> set A, or a loot table referencing itself) spins here forever on the main + // thread with nothing logged. No legitimate chain is anywhere near this deep; trip the cap + // and name the trail instead of hanging the game. + const int maxResolveSteps = 32; + var resolveSteps = 0; + var resolveTrail = new List(); + while (needsResolve) { + resolveTrail.Add(result.Item); + if (++resolveSteps > maxResolveSteps) + { + EpicLoot.LogError($"ResolveLootDrop exceeded {maxResolveSteps} steps resolving " + + $"'{lootDrop.Item}' -- the loot config almost certainly has a cycle. " + + $"Chain: {string.Join(" -> ", resolveTrail)}"); + break; + } + // Checked first, and before any name lookup: the map is what decides which name this entry // even has. Whatever it names is then resolved by the branches below, so a rarity may point // at an ItemSet or another table just as Item may. @@ -908,7 +963,7 @@ public static LootDrop ResolveLootDrop(LootDrop lootDrop, float luckFactor = 0f, result.Item = itemSetResult.Item; result.Weight = itemSetResult.Weight; // A rarity map belongs to the name it was authored next to, so unlike Rarity it always - // replaces what came in -- the entry we just rolled is the one that knows its prefabs. + // replaces what came in — the entry we just rolled is the one that knows its prefabs. result.RarityItems = itemSetResult.RarityItems; if (ArrayUtils.IsNullOrEmpty(result.Rarity)) { @@ -1095,7 +1150,7 @@ public static MagicItem RollMagicItem(ItemRarity rarity, ItemDrop.ItemData baseI // plugins, and randomized wear is part of that flow. internal static void InitializeMagicItem(ItemDrop.ItemData baseItem) { - // Callers run SetMagicItem first, which already synced Indestructible -- so an + // Callers run SetMagicItem first, which already synced Indestructible — so an // indestructible drop reads m_useDurability == false here and skips the wear roll. if (baseItem.m_shared.m_useDurability) { @@ -1297,13 +1352,13 @@ public static List RollEffects(List // nothing) when the entry carries no map, which is the common case. // // Pinning is what makes the feature safe to use for anything other than shards. Every later stage - // -- the magic item roll in SpawnNormalItem, and the Unidentified and Materials substitutions -- + // — the magic item roll in SpawnNormalItem, and the Unidentified and Materials substitutions -- // re-reads Rarity, so leaving the original spread in place would let a drop be selected as one // rarity and then rolled as another. Shards do not care (they are Materials and carry their rarity // in their own prefab's shared data), but a rarity map pointing at gear would. // // The map is cleared as it is consumed so the caller's while-loop can keep resolving whatever was - // substituted -- an ItemSet or an "Object.Level" reference -- without re-entering here. + // substituted — an ItemSet or an "Object.Level" reference — without re-entering here. private static bool ResolveRarityItem(LootDrop lootDrop, float luckFactor) { if (lootDrop?.RarityItems == null || lootDrop.RarityItems.Count == 0) diff --git a/EpicLoot/src/Loot/PendingChestLoot.cs b/EpicLoot/src/Loot/PendingChestLoot.cs index 237221f67..44ede4ea1 100644 --- a/EpicLoot/src/Loot/PendingChestLoot.cs +++ b/EpicLoot/src/Loot/PendingChestLoot.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using UnityEngine; namespace EpicLoot { @@ -26,6 +27,27 @@ public static class PendingChestLoot // player standing above it, but an overworld ruin's chests are at ordinary distances). private static readonly HashSet Pending = new HashSet(); + // Chests whose roll was asked for from somewhere it must not run synchronously — a plain + // accessor that other mods call in bulk, or inside ZNetScene's own object creation. Drained + // from LateUpdate, a few per frame, by PendingChestLootDriver. + private static readonly Queue DeferredRolls = new Queue(); + private static readonly HashSet Queued = new HashSet(); + + // Chests queued by the *non-deferred* AddDefaultItems path, which never sets the ZDO flag and so + // is invisible to TryRoll. Kept apart from the flag-gated queue on purpose: a chest reaches the + // queue below from plain accessors too, and rolling those unconditionally would re-roll a chest + // every time anything read it. + private static readonly HashSet DirectRollRequests = new HashSet(); + + // Rolling instantiates and destroys objects and touches ZNetView's global init flag. Nothing + // in that sequence is safe to re-enter, and a chest's own Awake/Load postfixes can reach back + // into any of the trigger patches, so a roll is never allowed to start inside another one. + private static bool _rolling; + + // A single container sweep from a crafting-station mod can ask for every chest in range at + // once. Spreading them keeps one frame from instantiating hundreds of loot objects. + private const int MaxRollsPerFrame = 2; + public static bool AnyPending => Pending.Count > 0; public static void Register(Container container) @@ -64,6 +86,28 @@ public static bool TryGetLootTables(Container container, out string containerNam /// the owner. /// public static void Roll(Container container, string containerName, List lootTables) + { + if (_rolling) + { + EpicLoot.LogWarning($"Re-entrant chest roll blocked for {containerName}; " + + "deferring it instead."); + RequestDirectRoll(container); + return; + } + + _rolling = true; + try + { + RollInternal(container, containerName, lootTables); + } + finally + { + _rolling = false; + } + } + + private static void RollInternal(Container container, string containerName, + List lootTables) { var items = LootRoller.RollLootTable(lootTables, 1, container.m_piece.name, container.transform.position); @@ -126,16 +170,164 @@ public static void TryRoll(Container container) nview.ClaimOwnership(); } - // Container polls its ZDO once a second, so m_inventory can be a second stale. - container.Load(); - - // Clear before rolling. A failed roll then loses the loot rather than duplicating it, - // the helper is reentrant-safe, and no peer can observe another peer's items without - // also observing the cleared flag, since both live in this same ZDO. + // Clear before anything that can re-enter, not merely before Roll. container.Load() below + // runs every mod's Container.Load postfix, and any of those that reads the contents back + // through Container.GetInventory lands in this method again — with the container still + // pending and the flag still set, that recursed without bound. Clearing first also means a + // failed roll loses the loot rather than duplicating it, and no peer can observe another + // peer's items without also observing the cleared flag, since both live in this same ZDO. Pending.Remove(container); zdo.Set(PendingKey, false); + // Container polls its ZDO once a second, so m_inventory can be a second stale. + container.Load(); + Roll(container, containerName, lootTables); } + + /// + /// Splits duplicate ItemData references out of a container's inventory. + /// + /// CreatureLevelAndLootControl's DropTable.GetDropListItems postfix implements its loot + /// multiplier by appending the same ItemDrop.ItemData reference N times instead of cloning it. + /// Container.AddDefaultItems then hands each of those to Inventory.AddItem, so one object ends + /// up in m_inventory several times: for a stackable item AddItem's own loop bound + /// (index < item.m_stack) is the field FindFreeStackItem then increments, and for a + /// non-stackable one the entries simply pile up sharing a single m_gridPos. Everything that + /// later walks the inventory — our custom-data layer keys an ItemInfo off the instance, so one + /// backs every slot — sees items that are not actually separate, and a save/reload turns them + /// into real duplicates. + /// + /// Cloning all but the first occurrence costs one pass over a list that was just built, and + /// only ever does anything when another mod produced the aliasing. + /// + public static void DeAliasInventory(Container container, string containerName) + { + var items = container == null || container.m_inventory == null + ? null + : container.m_inventory.m_inventory; + if (items == null || items.Count < 2) + { + return; + } + + var seen = new HashSet(); + var cloned = 0; + for (var i = 0; i < items.Count; ++i) + { + var item = items[i]; + if (item == null || seen.Add(item)) + { + continue; + } + + var copy = item.Clone(); + copy.m_gridPos = item.m_gridPos; + items[i] = copy; + seen.Add(copy); + ++cloned; + } + + if (cloned > 0) + { + EpicLoot.LogWarning($"Container '{containerName}' was given {cloned} duplicate " + + "ItemData reference(s) by another mod's drop-table multiplier; cloned them so the " + + "slots are independent items."); + } + } + + /// + /// Queues a chest to roll on the next LateUpdate instead of right now. Used by every trigger + /// that is not a deliberate player action: Container.GetInventory and GetHoverText are plain + /// reads that other mods call over every container in range, many times per frame, and rolling + /// inline there turned one crafting-station sweep into hundreds of instantiates in a frame. + /// + public static void RequestRoll(Container container) + { + if (container == null || !Queued.Add(container)) + { + return; + } + + DeferredRolls.Enqueue(container); + PendingChestLootDriver.Ensure(); + } + + /// + /// Queues a chest that must roll exactly once without going through the ZDO pending flag — the + /// non-deferred AddDefaultItems path, when it cannot roll inline because it is running inside + /// ZNetScene object creation. + /// + public static void RequestDirectRoll(Container container) + { + if (container == null) + { + return; + } + + DirectRollRequests.Add(container); + RequestRoll(container); + } + + /// + /// Drains the deferred queue. Called from LateUpdate, so it always runs off whatever call + /// stack asked for the roll — in particular outside ZNetScene's object creation, where a + /// nested Instantiate would corrupt the ZDO it is in the middle of handing out. + /// + public static void PumpDeferredRolls() + { + for (var i = 0; i < MaxRollsPerFrame && DeferredRolls.Count > 0; ++i) + { + var container = DeferredRolls.Dequeue(); + Queued.Remove(container); + var wasDirect = DirectRollRequests.Remove(container); + + // Unity fake-null: the zone can unload a chest between queueing and draining. Both sets + // are cleared above rather than after this check so a destroyed chest cannot leak one. + if (container == null) + { + continue; + } + + if (wasDirect) + { + if (TryGetLootTables(container, out var containerName, out var lootTables)) + { + Roll(container, containerName, lootTables); + } + } + else if (Pending.Contains(container)) + { + TryRoll(container); + } + } + } + } + + /// + /// Drives . LateUpdate rather than Update so the + /// roll cannot land inside another component's Update — ZNetScene.Update in particular, which + /// creates and destroys the very objects a roll would be instantiating. + /// + public class PendingChestLootDriver : MonoBehaviour + { + private static PendingChestLootDriver _instance; + + public static void Ensure() + { + if (_instance != null) + { + return; + } + + var go = new GameObject("EpicLoot_PendingChestLootDriver"); + Object.DontDestroyOnLoad(go); + _instance = go.AddComponent(); + } + + public void LateUpdate() + { + PendingChestLoot.PumpDeferredRolls(); + } } } diff --git a/EpicLoot/src/Magic/MagicItemEffects/Slow.cs b/EpicLoot/src/Magic/MagicItemEffects/Slow.cs index 8209ff87e..b2a10c7bd 100755 --- a/EpicLoot/src/Magic/MagicItemEffects/Slow.cs +++ b/EpicLoot/src/Magic/MagicItemEffects/Slow.cs @@ -48,7 +48,7 @@ public static class SlowAddRPC_Character_Awake_Patch [UsedImplicitly] private static void Postfix(Character __instance) { - __instance.m_nview.Register(Slow.RPCKey, (s, multiplier) => RPC_Slow(__instance, multiplier)); + __instance.m_nview?.Register(Slow.RPCKey, (s, multiplier) => RPC_Slow(__instance, multiplier)); } private static void RPC_Slow(Character character, float multiplier) diff --git a/EpicLoot/src/data/CustomDataManager.cs b/EpicLoot/src/data/CustomDataManager.cs index d54fc72ba..bc9b3085f 100644 --- a/EpicLoot/src/data/CustomDataManager.cs +++ b/EpicLoot/src/data/CustomDataManager.cs @@ -194,7 +194,7 @@ public T Get(string key = "") where T : class // Fast path: no custom data on this item means no CustomItemData of any type can exist // (Add always writes an m_customData entry). Skips the classKey/dataKey string building - // below for every mundane item -- this is the common case on hot lookup paths. + // below for every mundane item — this is the common case on hot lookup paths. if (ItemData.m_customData.Count == 0) { return null; @@ -442,45 +442,130 @@ private static void SaveCheckingForStackableItemData(ItemDrop.ItemData item) => private static Dictionary newValuesOnStackable; + private static readonly Dictionary StoreToLoadLocal = new Dictionary + { + { OpCodes.Stloc_0, OpCodes.Ldloc_0 }, + { OpCodes.Stloc_1, OpCodes.Ldloc_1 }, + { OpCodes.Stloc_2, OpCodes.Ldloc_2 }, + { OpCodes.Stloc_3, OpCodes.Ldloc_3 }, + { OpCodes.Stloc_S, OpCodes.Ldloc_S }, + { OpCodes.Stloc, OpCodes.Ldloc } + }; + + // Injects "&& CheckItemDataIsStackableFindFree(loopItem, checkingForStackableItemData)" onto the + // end of the match condition inside Inventory.FindFreeStackItem / FindFreeStackSpace, so custom + // item data gets a say in whether two items stack. + // + // Every step here is a guess about the shape of compiled vanilla IL — which instruction is the + // loop-continue branch, which local holds the loop variable — and both methods are co-patched by + // other mods (EquipmentAndQuickSlots prefixes them). Any mismatch used to throw out of a yield + // iterator, i.e. from inside Harmony IL emission, called from a static constructor, which + // surfaces as a TypeInitializationException on every single ItemDrop.Awake. Build the list + // eagerly instead and bail to the unmodified instructions with one log line: losing custom-data + // stack checks in one method is a small bug, taking the type initializer down with it is not. private static IEnumerable CheckStackableInFindFreeStackMethods( - IEnumerable instructionsEnumerable) + IEnumerable instructionsEnumerable, MethodBase original) { CodeInstruction[] instructions = instructionsEnumerable.ToArray(); - Label target = (Label)instructions.First(i => - i.opcode == OpCodes.Br || i.opcode == OpCodes.Br_S).operand; - CodeInstruction targetedInstr = instructions.First(i => i.labels.Contains(target)); - CodeInstruction lastBranch = instructions.Reverse().First(i => - i.Branches(out Label? label) && targetedInstr.labels.Contains(label!.Value)); - CodeInstruction loadingInstruction = null; - for (int i = 0; i < instructions.Length; ++i) + void Bail(string why) + { + string name = original == null + ? "" + : original.DeclaringType?.Name + "." + original.Name; + EpicLoot.LogError($"ItemDataManager: could not patch {name} ({why}). Custom item data " + + "will not affect stacking in this method; leaving it unmodified."); + } + + CodeInstruction loopBranch = instructions.FirstOrDefault(i => + i.opcode == OpCodes.Br || i.opcode == OpCodes.Br_S); + if (loopBranch == null || !(loopBranch.operand is Label target)) + { + Bail("no loop branch found"); + return instructions; + } + + CodeInstruction targetedInstr = instructions.FirstOrDefault(i => i.labels.Contains(target)); + if (targetedInstr == null) + { + Bail("loop branch target is not in the instruction list"); + return instructions; + } + + int lastBranchIndex = -1; + for (int i = instructions.Length - 1; i >= 0; --i) { - yield return instructions[i]; - // get hold of the loop variable store (the itemdata we want to compare against) - if (loadingInstruction == null && instructions[i].opcode == OpCodes.Call && - ((MethodInfo)instructions[i].operand).Name == "get_Current") + if (instructions[i].Branches(out Label? label) && label.HasValue && + targetedInstr.labels.Contains(label.Value)) { - loadingInstruction = instructions[i + 1].Clone(); - loadingInstruction.opcode = new Dictionary - { - { OpCodes.Stloc_0, OpCodes.Ldloc_0 }, - { OpCodes.Stloc_1, OpCodes.Ldloc_1 }, - { OpCodes.Stloc_2, OpCodes.Ldloc_2 }, - { OpCodes.Stloc_3, OpCodes.Ldloc_3 }, - { OpCodes.Stloc_S, OpCodes.Ldloc_S } - }[loadingInstruction.opcode]; + lastBranchIndex = i; + break; } + } - if (instructions[i] == lastBranch) + if (lastBranchIndex < 0) + { + Bail("no conditional branch back to the loop head"); + return instructions; + } + + // The loop variable store immediately follows the enumerator get_Current call. Mirror it + // into the matching load so the injected check can push the same item. + CodeInstruction loadingInstruction = null; + for (int i = 0; i < instructions.Length - 1 && i < lastBranchIndex; ++i) + { + if (instructions[i].opcode != OpCodes.Call || + !(instructions[i].operand is MethodInfo method) || method.Name != "get_Current") { - yield return loadingInstruction!; - yield return new CodeInstruction(OpCodes.Ldsfld, - AccessTools.DeclaredField(typeof(ItemInfo), nameof(checkingForStackableItemData))); - yield return new CodeInstruction(OpCodes.Call, - AccessTools.DeclaredMethod(typeof(ItemInfo), nameof(CheckItemDataIsStackableFindFree))); - yield return new CodeInstruction(OpCodes.Brfalse, target); + continue; + } + + CodeInstruction store = instructions[i + 1]; + if (!StoreToLoadLocal.TryGetValue(store.opcode, out OpCode loadOpCode)) + { + Bail($"unexpected loop variable store '{store.opcode}'"); + return instructions; + } + + loadingInstruction = store.Clone(); + loadingInstruction.opcode = loadOpCode; + loadingInstruction.labels.Clear(); + loadingInstruction.blocks.Clear(); + break; + } + + if (loadingInstruction == null) + { + Bail("loop variable store not found before the loop branch"); + return instructions; + } + + FieldInfo checkingField = + AccessTools.DeclaredField(typeof(ItemInfo), nameof(checkingForStackableItemData)); + MethodInfo checkMethod = + AccessTools.DeclaredMethod(typeof(ItemInfo), nameof(CheckItemDataIsStackableFindFree)); + if (checkingField == null || checkMethod == null) + { + Bail("could not resolve the injected field or method"); + return instructions; + } + + List result = new List(instructions.Length + 4); + for (int i = 0; i < instructions.Length; ++i) + { + result.Add(instructions[i]); + if (i != lastBranchIndex) + { + continue; } + + result.Add(loadingInstruction); + result.Add(new CodeInstruction(OpCodes.Ldsfld, checkingField)); + result.Add(new CodeInstruction(OpCodes.Call, checkMethod)); + result.Add(new CodeInstruction(OpCodes.Brfalse, target)); } + + return result; } private static bool CheckItemDataIsStackableFindFree(ItemDrop.ItemData item, ItemDrop.ItemData target) @@ -576,6 +661,18 @@ private static IEnumerable HandleAutostackableItems( List instructions = instructionList.ToList(); FieldInfo stack = AccessTools.DeclaredField(typeof(ItemDrop.ItemData), nameof(ItemDrop.ItemData.m_stack)); FieldInfo itemData = AccessTools.DeclaredField(typeof(ItemDrop), nameof(ItemDrop.m_itemData)); + MethodInfo applyMethod = + AccessTools.DeclaredMethod(typeof(ItemInfo), nameof(ApplyCustomItemDataStackableAutoStack)); + MethodInfo isStackableMethod = + AccessTools.DeclaredMethod(typeof(ItemInfo), nameof(IsStackableItemDrop)); + + if (stack == null || itemData == null || applyMethod == null || isStackableMethod == null) + { + EpicLoot.LogError("ItemDataManager: could not resolve the members needed to patch " + + "ItemDrop.AutoStackItems; leaving it unmodified."); + return instructions; + } + for (int i = 0; i < instructions.Count; ++i) { if (!instructions[i].StoresField(stack)) @@ -585,7 +682,7 @@ private static IEnumerable HandleAutostackableItems( for (int j = i; j > 0; --j) { - if (!instructions[j].Branches(out Label? skipTarget)) + if (!instructions[j].Branches(out Label? skipTarget) || !skipTarget.HasValue) { continue; } @@ -599,28 +696,34 @@ private static IEnumerable HandleAutostackableItems( LocalBuilder dict = ilg.DeclareLocal(typeof(Dictionary)); LocalBuilder itemDataVar = ilg.DeclareLocal(typeof(ItemDrop.ItemData)); - instructions.Insert(i + 1, new CodeInstruction(OpCodes.Call, - AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(ApplyCustomItemDataStackableAutoStack)))); - instructions.Insert(i + 1, new CodeInstruction(OpCodes.Ldloc, dict.LocalIndex)); + instructions.Insert(i + 1, new CodeInstruction(OpCodes.Call, applyMethod)); + instructions.Insert(i + 1, new CodeInstruction(OpCodes.Ldloc, dict)); instructions.Insert(i + 1, new CodeInstruction(OpCodes.Ldarg_0)); - instructions.Insert(j + 1, new CodeInstruction(OpCodes.Brfalse, skipTarget)); - instructions.Insert(j + 1, new CodeInstruction(OpCodes.Stloc, dict.LocalIndex)); - instructions.Insert(j + 1, new CodeInstruction(OpCodes.Dup, dict.LocalIndex)); - instructions.Insert(j + 1, new CodeInstruction(OpCodes.Call, - AccessTools.DeclaredMethod(typeof(ItemInfo), nameof(IsStackableItemDrop)))); - instructions.Insert(j + 1, new CodeInstruction(OpCodes.Ldloc, itemDataVar.LocalIndex)); + instructions.Insert(j + 1, new CodeInstruction(OpCodes.Brfalse, skipTarget.Value)); + instructions.Insert(j + 1, new CodeInstruction(OpCodes.Stloc, dict)); + // Dup takes no operand. It was previously handed dict.LocalIndex, which only + // survived because Harmony ignores an operand it cannot use for this opcode. + instructions.Insert(j + 1, new CodeInstruction(OpCodes.Dup)); + instructions.Insert(j + 1, new CodeInstruction(OpCodes.Call, isStackableMethod)); + instructions.Insert(j + 1, new CodeInstruction(OpCodes.Ldloc, itemDataVar)); instructions.Insert(j + 1, new CodeInstruction(OpCodes.Ldarg_0)); - instructions.Insert(k + 1, new CodeInstruction(OpCodes.Stloc, itemDataVar.LocalIndex)); + instructions.Insert(k + 1, new CodeInstruction(OpCodes.Stloc, itemDataVar)); instructions.Insert(k + 1, new CodeInstruction(OpCodes.Dup)); return instructions; } } } - throw new Exception("Found no stack store in a branch"); + + // Was a bare throw. This runs from ItemInfo's static constructor, so throwing here takes the + // whole type initializer down and every ItemDrop.Awake with it — for a feature whose only + // job is to preserve custom data across an auto-stack. + EpicLoot.LogError("ItemDataManager: found no stack store in a branch in " + + "ItemDrop.AutoStackItems; leaving it unmodified. Custom item data may be lost when " + + "dropped items auto-stack."); + return instructions; } private static ItemDrop.ItemData currentlyUpgradingItem; @@ -713,90 +816,138 @@ private static IEnumerable ImportCustomDataOnUpgrade(IEnumerabl } } + // Applies one patch in isolation. This runs from a static constructor, so a single failing + // AccessTools lookup or transpiler used to surface as a TypeInitializationException on the first + // touch of ItemInfo — which is every ItemDrop.Awake, i.e. every item in the world. Degrading one + // feature is a far better outcome than that. + private static void ApplyPatch(string what, Action patch) + { + try + { + patch(); + } + catch (Exception e) + { + EpicLoot.LogError($"ItemDataManager: failed to patch {what}: {e.Message}. " + + "That feature is disabled for this session."); + } + } + static ItemInfo() { Harmony harmony = new("org.bepinex.helpers.ItemDataManager"); - harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.Save)), - prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(SaveInventoryPrefix)), Priority.First)); - foreach (MethodInfo method in typeof(ItemDrop.ItemData) - .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) - .Where(m => m.Name == nameof(ItemDrop.SaveToZDO))) - { - harmony.Patch(method, prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(SavePrefix)), Priority.First)); - } - - harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.AddItem), - new[] { typeof(ItemDrop.ItemData), typeof(int), typeof(int), typeof(int) }), - prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(CheckItemDataStackableAddItem))), - postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(ApplyCustomItemDataStackableAddItem)))); - - harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.CanAddItem), - new[] { typeof(ItemDrop.ItemData), typeof(int) }), - prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(SaveCheckingForStackableItemData))), - finalizer: new HarmonyMethod(typeof(ItemInfo), nameof(ResetCheckingForStackableItemData))); - harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.AddItem), - new[] { typeof(ItemDrop.ItemData) }), - prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(SaveCheckingForStackableItemData))), - finalizer: new HarmonyMethod(typeof(ItemInfo), nameof(ResetCheckingForStackableItemData))); - - harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.FindFreeStackSpace)), - transpiler: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(CheckStackableInFindFreeStackMethods)))); - harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.FindFreeStackItem)), - transpiler: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(CheckStackableInFindFreeStackMethods))), - prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(ResetNewValuesOnStackable))), - postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), nameof(ApplyNewValuesOnStackable)))); - - harmony.Patch(AccessTools.DeclaredMethod(typeof(ItemDrop), nameof(ItemDrop.AutoStackItems)), - transpiler: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(HandleAutostackableItems)))); - - harmony.Patch(AccessTools.DeclaredMethod(typeof(InventoryGui), nameof(InventoryGui.DoCrafting)), - transpiler: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(TransferCustomItemDataOnUpgrade))), - finalizer: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(ResetCurrentlyUpgradingItem)))); + + ApplyPatch("Inventory.Save", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.Save)), + prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(SaveInventoryPrefix)), Priority.First))); + + ApplyPatch("ItemData.SaveToZDO", () => + { + foreach (MethodInfo method in typeof(ItemDrop.ItemData) + .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + .Where(m => m.Name == nameof(ItemDrop.SaveToZDO))) + { + harmony.Patch(method, prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(SavePrefix)), Priority.First)); + } + }); + + ApplyPatch("Inventory.AddItem(ItemData,int,int,int)", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.AddItem), + new[] { typeof(ItemDrop.ItemData), typeof(int), typeof(int), typeof(int) }), + prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(CheckItemDataStackableAddItem))), + postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(ApplyCustomItemDataStackableAddItem))))); + + ApplyPatch("Inventory.CanAddItem", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.CanAddItem), + new[] { typeof(ItemDrop.ItemData), typeof(int) }), + prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(SaveCheckingForStackableItemData))), + finalizer: new HarmonyMethod(typeof(ItemInfo), nameof(ResetCheckingForStackableItemData)))); + + ApplyPatch("Inventory.AddItem(ItemData)", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.AddItem), + new[] { typeof(ItemDrop.ItemData) }), + prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(SaveCheckingForStackableItemData))), + finalizer: new HarmonyMethod(typeof(ItemInfo), nameof(ResetCheckingForStackableItemData)))); + + ApplyPatch("Inventory.FindFreeStackSpace", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.FindFreeStackSpace)), + transpiler: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(CheckStackableInFindFreeStackMethods))))); + + ApplyPatch("Inventory.FindFreeStackItem", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.FindFreeStackItem)), + transpiler: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(CheckStackableInFindFreeStackMethods))), + prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(ResetNewValuesOnStackable))), + postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), nameof(ApplyNewValuesOnStackable))))); + + ApplyPatch("ItemDrop.AutoStackItems", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(ItemDrop), nameof(ItemDrop.AutoStackItems)), + transpiler: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(HandleAutostackableItems))))); + + ApplyPatch("InventoryGui.DoCrafting", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(InventoryGui), nameof(InventoryGui.DoCrafting)), + transpiler: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(TransferCustomItemDataOnUpgrade))), + finalizer: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(ResetCurrentlyUpgradingItem))))); // Force loads - foreach (MethodInfo method in typeof(ItemDrop.ItemData) - .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) - .Where(m => m.Name == nameof(ItemDrop.LoadFromZDO))) + ApplyPatch("ItemData.LoadFromZDO", () => { - harmony.Patch(method, postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(RegisterForceLoadedTypes)))); - } + foreach (MethodInfo method in typeof(ItemDrop.ItemData) + .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + .Where(m => m.Name == nameof(ItemDrop.LoadFromZDO))) + { + harmony.Patch(method, postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(RegisterForceLoadedTypes)))); + } + }); + // Note: Inventory load implicitly handled by CustomItemData.Clone() handling within AddItem - harmony.Patch(AccessTools.DeclaredMethod(typeof(Player), nameof(Player.Load)), - postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(RegisterForceLoadedTypesOnPlayerLoaded)), Priority.VeryHigh)); - harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.AddItem), new[] { - typeof(string), typeof(int), typeof(int), typeof(int), typeof(long), typeof(string), typeof(bool) }), - postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(RegisterForceLoadedTypesAddItem)), Priority.First)); - harmony.Patch(AccessTools.DeclaredMethod(typeof(ItemDrop), nameof(ItemDrop.Awake)), - transpiler: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(ImportCustomDataOnUpgrade)), Priority.First), - postfix: new HarmonyMethod(AccessTools.DeclaredMethod( - typeof(ItemInfo), nameof(ItemDropAwake)), Priority.First)); - harmony.Patch(AccessTools.DeclaredMethod(typeof(ItemDrop), nameof(ItemDrop.Awake)), - postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(ItemDropAwakeDelayed)), Priority.First - 1)); - harmony.Patch(AccessTools.DeclaredMethod(typeof(ItemDrop.ItemData), nameof(ItemDrop.ItemData.Clone)), - prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), nameof(ItemDataClonePrefix))), - postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), nameof(ItemDataClonePostfix)), - Priority.HigherThanNormal)); - harmony.Patch(AccessTools.DeclaredMethod(typeof(ItemDrop.ItemData), nameof(ItemDrop.ItemData.Clone)), - postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), - nameof(ItemDataClonePostfixDelayed)), Priority.HigherThanNormal - 1)); + ApplyPatch("Player.Load", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(Player), nameof(Player.Load)), + postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(RegisterForceLoadedTypesOnPlayerLoaded)), Priority.VeryHigh))); + + ApplyPatch("Inventory.AddItem(string,...)", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(Inventory), nameof(Inventory.AddItem), new[] { + typeof(string), typeof(int), typeof(int), typeof(int), typeof(long), typeof(string), typeof(bool) }), + postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(RegisterForceLoadedTypesAddItem)), Priority.First))); + + ApplyPatch("ItemDrop.Awake", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(ItemDrop), nameof(ItemDrop.Awake)), + transpiler: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(ImportCustomDataOnUpgrade)), Priority.First), + postfix: new HarmonyMethod(AccessTools.DeclaredMethod( + typeof(ItemInfo), nameof(ItemDropAwake)), Priority.First))); + + ApplyPatch("ItemDrop.Awake (delayed)", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(ItemDrop), nameof(ItemDrop.Awake)), + postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(ItemDropAwakeDelayed)), Priority.First - 1))); + + ApplyPatch("ItemData.Clone", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(ItemDrop.ItemData), nameof(ItemDrop.ItemData.Clone)), + prefix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), nameof(ItemDataClonePrefix))), + postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), nameof(ItemDataClonePostfix)), + Priority.HigherThanNormal))); + + ApplyPatch("ItemData.Clone (delayed)", () => + harmony.Patch(AccessTools.DeclaredMethod(typeof(ItemDrop.ItemData), nameof(ItemDrop.ItemData.Clone)), + postfix: new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ItemInfo), + nameof(ItemDataClonePostfixDelayed)), Priority.HigherThanNormal - 1))); } + } [PublicAPI] diff --git a/EpicLoot/thunderstore/plugins/EpicLoot.dll b/EpicLoot/thunderstore/plugins/EpicLoot.dll deleted file mode 100644 index 8b5f455ea..000000000 Binary files a/EpicLoot/thunderstore/plugins/EpicLoot.dll and /dev/null differ diff --git a/EquipmentAndQuickSlots/.gitignore b/EquipmentAndQuickSlots/.gitignore new file mode 100644 index 000000000..4c49bd78f --- /dev/null +++ b/EquipmentAndQuickSlots/.gitignore @@ -0,0 +1 @@ +.env diff --git a/EquipmentAndQuickSlots/EquipmentAndQuickSlots.cs b/EquipmentAndQuickSlots/EquipmentAndQuickSlots.cs index ae5c9c423..261bbfea9 100644 --- a/EquipmentAndQuickSlots/EquipmentAndQuickSlots.cs +++ b/EquipmentAndQuickSlots/EquipmentAndQuickSlots.cs @@ -10,12 +10,13 @@ namespace EquipmentAndQuickSlots { - [BepInPlugin(PluginId, "Equipment and Quick Slots", "2.1.14")] + [BepInPlugin(PluginId, "Equipment and Quick Slots", Version)] [BepInDependency("moreslots", BepInDependency.DependencyFlags.SoftDependency)] [BepInDependency("randyknapp.mods.auga", BepInDependency.DependencyFlags.SoftDependency)] public class EquipmentAndQuickSlots : BaseUnityPlugin { public const string PluginId = "randyknapp.mods.equipmentandquickslots"; + public const string Version = "2.1.14"; public const int QuickSlotCount = 3; public static int EquipSlotCount => EquipSlotTypes.Count; diff --git a/EquipmentAndQuickSlots/EquipmentAndQuickSlots.csproj b/EquipmentAndQuickSlots/EquipmentAndQuickSlots.csproj index d985763c1..077f626db 100644 --- a/EquipmentAndQuickSlots/EquipmentAndQuickSlots.csproj +++ b/EquipmentAndQuickSlots/EquipmentAndQuickSlots.csproj @@ -63,4 +63,44 @@ + + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/')) + + mod-tools + + + + + + + + + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EquipmentAndQuickSlots/CHANGELOG.md b/EquipmentAndQuickSlots/Package/CHANGELOG.md similarity index 94% rename from EquipmentAndQuickSlots/CHANGELOG.md rename to EquipmentAndQuickSlots/Package/CHANGELOG.md index 090119f67..1d0f75d44 100644 --- a/EquipmentAndQuickSlots/CHANGELOG.md +++ b/EquipmentAndQuickSlots/Package/CHANGELOG.md @@ -1,36 +1,36 @@ -## Release 2.1.14 +**2.1.14** * Updated for Valheim 0.219.13 Patch (Bog Witch)
Changelog History (click to expand) -## Release 2.1.13 +**2.1.13** * Updated for Valheim 0.217.38 Patch -## Release 2.1.12 +**2.1.12** * Updated for Valheim 0.217.27 * Updated Auga and CLLC API's * If Auga is loaded, set the default position of Quick Slot Bar accordingly * Updated for World Modifiers and Hard/Hardcore Settings -## Release 2.1.11 +**2.1.11** * Adjusting InventoryGrid Initialization to prevent Awake from happening before variables are set. * This has fixed a compatibility issue that was found with Jewelcrafting allowing EAQS to now be used with Smoothbrain's Jewelcrafting -## Release 2.1.10 +**2.1.10** * Fixing Hotkey Bar Binding Texts ## Release 2.1.8 & 2.1.9 * Hildir's Request Updates 0.217.24 * Updated version from 2.1.7 to 2.1.9 because I forgot to change it. -## Release 2.1.7 +**2.1.7** * Hildir's Request Updates 0.217.14 -## Release 2.1.6 +**2.1.6** * Updates needed for Valheim 0.216.7 -## Release 2.1.5 +**2.1.5** * Fixing Keybinds to defaults if config is messed up and showing None. * This was caused by a change in how keybinds are stored. * Tooltips when using Controllers are now visible and not hiding behind the Equipment Slots -## Release 2.1.4 +**2.1.4** * DLL packaged with 2.1.3 was incorrectly built as 2.1.2 and might not have had all the changes in it. * Bumping version by 1 and reuploading correct version. -## Release 2.1.3 +**2.1.3** * Improved Controller Support Between Hotbars/Inventories * Known Bug: The weight calculation is still not working when using controller to transfer items. * Updated Keybindings to Support Controllers @@ -38,11 +38,11 @@ * No longer a Prefix that blocks UpdateIcons * Allows other mods to affect item icons in the Hotkeybar (like EpicLoot) * Potential for Performance Improvement -## Release 2.1.2 +**2.1.2** * Updated for Valheim 0.214.2 Patch -## Release 2.1.1 +**2.1.1** * Fixed compatibility issues with JewelCrafting and MultiUserChest -## Release 2.1.0 +**2.1.0** * Updated for Mistlands! * Now uses player.m_customData instead of knownTexts * On death, drops equipment in second gravestone diff --git a/EquipmentAndQuickSlots/Package/README.md b/EquipmentAndQuickSlots/Package/README.md new file mode 100644 index 000000000..eece5a24a --- /dev/null +++ b/EquipmentAndQuickSlots/Package/README.md @@ -0,0 +1,69 @@ +# New for Mistlands +The new, v2.1 of Equipment and Quick Slots does not need to be installed on servers. + +Please report bugs on our discord: https://discord.gg/ZNhYeavv3C + +# Equipment and Quick Slots v2.1.3 +##### by RandyKnapp +Give equipped items their own dedicated inventory slots, plus three more hotkeyable quick slots. + +Hotkeys must be assigned according to the conventions listed here: https://docs.unity3d.com/Manual/class-InputManager.html + +Source: [Github](https://github.com/RandyKnapp/ValheimMods/EquipmentAndQuickSlots/) + +Install with [BepInEx](https://valheim.thunderstore.io/package/denikson/BepInExPack_Valheim/) + +Copy EquipmentAndQuickSlots.dll into the BepInEx/plugins folder + +### Changelist: + * 1.0.3 + * Integrated fix for larger containers (this mod was not allowing the same row to be used in containers as it uses in the Inventory) + * 1.0.4 + * Fixed issue where gamepad could not use quick slots + * 1.0.5 + * Fixed issue where the previous fix broke the #8 hotkey... + * 2.0.0 Stability Update + * Items are saved even if accidentally uninstalling or having an error + * UIs work better with controller + * Never drop or lose items on death + * 2.0.1 + * Hotfix for not being able to craft when fully equipped + * 2.0.2 + * Fixed an issue where some equipment was lost on death + * Re-added the toggles to disable and enable features + * 2.0.3 + * Fixed a bug where players could teleport with items they normally can't + * 2.0.4 + * Fixed gamepad navigation for the crafting recipe list + * Fixed bug preventing new characters from being created + * 2.0.5 + * Put in a potential fix for "losing items" on tombstone pickup (they're in your inventory, just outside the grid. This version should fix that.) + * 2.0.6 + * Fix for double upgrade when using EpicLoot with EAQS + * Fix for items lost in tombstone by [jsza](https://github.com/jsza). + * 2.0.7 + * (This entire update provided by [jsza](https://github.com/jsza)) + * Fix a variety of equipment bugs + * Fix a variety of pickup/stacking bugs + * 2.0.8 + * Had to update the number to re-upload to ThunderStore + * 2.0.9 + * Quick slots position is now configurable + * 2.0.10 + * Fixed an encumberance bug + * 2.0.11 + * Added support for Project Auga + * 2.0.12 + * Better Valheim+ and Auga positioning for the inventory + * 2.0.14 + * Updated for H&H + * 2.0.15 + * Yet Another Attempt at fixing the lost-equipment-on-death bug + * 2.1.0 + * Updated for Mistlands! + * Now uses player.m_customData instead of knownTexts + * On death, drops equipment in second gravestone + * Fixed bug where you couldn't move items out of your quickslots + * Added new config features: DontDropEquipmentOnDeath, DontDropQuickslotsOnDeath, InstantlyReequipArmorOnPickup, InstantlyReequipQuickslotsOnPickup + * 2.1.1 + * Fixed compatibility issues with JewelCrafting and MultiUserChest \ No newline at end of file diff --git a/EquipmentAndQuickSlots/thunderstore/icon.png b/EquipmentAndQuickSlots/Package/icon.png similarity index 100% rename from EquipmentAndQuickSlots/thunderstore/icon.png rename to EquipmentAndQuickSlots/Package/icon.png diff --git a/EquipmentAndQuickSlots/thunderstore/manifest.json b/EquipmentAndQuickSlots/Package/manifest.json similarity index 91% rename from EquipmentAndQuickSlots/thunderstore/manifest.json rename to EquipmentAndQuickSlots/Package/manifest.json index 6be4c8886..2bc3b9bac 100644 --- a/EquipmentAndQuickSlots/thunderstore/manifest.json +++ b/EquipmentAndQuickSlots/Package/manifest.json @@ -1,6 +1,6 @@ { "name": "EquipmentAndQuickSlots", - "version_number": "2.1.12", + "version_number": "2.1.14", "website_url": "https://github.com/RandyKnapp/ValheimMods/tree/main/EquipmentAndQuickSlots", "description": "Give equipped items their own dedicated inventory slots, plus three more hotkeyable quick slots.", "dependencies": [ diff --git a/EquipmentAndQuickSlots/README.md b/EquipmentAndQuickSlots/README.md deleted file mode 100644 index eece5a24a..000000000 --- a/EquipmentAndQuickSlots/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# New for Mistlands -The new, v2.1 of Equipment and Quick Slots does not need to be installed on servers. - -Please report bugs on our discord: https://discord.gg/ZNhYeavv3C - -# Equipment and Quick Slots v2.1.3 -##### by RandyKnapp -Give equipped items their own dedicated inventory slots, plus three more hotkeyable quick slots. - -Hotkeys must be assigned according to the conventions listed here: https://docs.unity3d.com/Manual/class-InputManager.html - -Source: [Github](https://github.com/RandyKnapp/ValheimMods/EquipmentAndQuickSlots/) - -Install with [BepInEx](https://valheim.thunderstore.io/package/denikson/BepInExPack_Valheim/) - -Copy EquipmentAndQuickSlots.dll into the BepInEx/plugins folder - -### Changelist: - * 1.0.3 - * Integrated fix for larger containers (this mod was not allowing the same row to be used in containers as it uses in the Inventory) - * 1.0.4 - * Fixed issue where gamepad could not use quick slots - * 1.0.5 - * Fixed issue where the previous fix broke the #8 hotkey... - * 2.0.0 Stability Update - * Items are saved even if accidentally uninstalling or having an error - * UIs work better with controller - * Never drop or lose items on death - * 2.0.1 - * Hotfix for not being able to craft when fully equipped - * 2.0.2 - * Fixed an issue where some equipment was lost on death - * Re-added the toggles to disable and enable features - * 2.0.3 - * Fixed a bug where players could teleport with items they normally can't - * 2.0.4 - * Fixed gamepad navigation for the crafting recipe list - * Fixed bug preventing new characters from being created - * 2.0.5 - * Put in a potential fix for "losing items" on tombstone pickup (they're in your inventory, just outside the grid. This version should fix that.) - * 2.0.6 - * Fix for double upgrade when using EpicLoot with EAQS - * Fix for items lost in tombstone by [jsza](https://github.com/jsza). - * 2.0.7 - * (This entire update provided by [jsza](https://github.com/jsza)) - * Fix a variety of equipment bugs - * Fix a variety of pickup/stacking bugs - * 2.0.8 - * Had to update the number to re-upload to ThunderStore - * 2.0.9 - * Quick slots position is now configurable - * 2.0.10 - * Fixed an encumberance bug - * 2.0.11 - * Added support for Project Auga - * 2.0.12 - * Better Valheim+ and Auga positioning for the inventory - * 2.0.14 - * Updated for H&H - * 2.0.15 - * Yet Another Attempt at fixing the lost-equipment-on-death bug - * 2.1.0 - * Updated for Mistlands! - * Now uses player.m_customData instead of knownTexts - * On death, drops equipment in second gravestone - * Fixed bug where you couldn't move items out of your quickslots - * Added new config features: DontDropEquipmentOnDeath, DontDropQuickslotsOnDeath, InstantlyReequipArmorOnPickup, InstantlyReequipQuickslotsOnPickup - * 2.1.1 - * Fixed compatibility issues with JewelCrafting and MultiUserChest \ No newline at end of file diff --git a/EquipmentAndQuickSlots/README.md b/EquipmentAndQuickSlots/README.md new file mode 120000 index 000000000..d11c97e84 --- /dev/null +++ b/EquipmentAndQuickSlots/README.md @@ -0,0 +1 @@ +Package/README.md \ No newline at end of file diff --git a/EquipmentAndQuickSlots/release.yml b/EquipmentAndQuickSlots/release.yml new file mode 100644 index 000000000..b18dc6790 --- /dev/null +++ b/EquipmentAndQuickSlots/release.yml @@ -0,0 +1,27 @@ +# Release configuration for mod-tools. +# Shared settings come from the preset, which ships inside the gem. +# +# Inspect what this actually resolves to: +# mod-tools print-config + +extends: jotunn-mono +assembly: EquipmentAndQuickSlots + +# This repository predates the tool and spells the version constant its own way. +version_const: Version + +# Copy the build into this machine's mod manager profiles after every build. +deploy: + enabled: true + +publish: + thunderstore: + enabled: true + nexus: + enabled: true + mod_id: 92 + file_id: 543565 + github: + enabled: true + discord: + enabled: true \ No newline at end of file diff --git a/ImprovedBuildHud/.gitignore b/ImprovedBuildHud/.gitignore new file mode 100644 index 000000000..4c49bd78f --- /dev/null +++ b/ImprovedBuildHud/.gitignore @@ -0,0 +1 @@ +.env diff --git a/ImprovedBuildHud/ImprovedBuildHud.cs b/ImprovedBuildHud/ImprovedBuildHud.cs index 2f031791d..63a04bf8e 100644 --- a/ImprovedBuildHud/ImprovedBuildHud.cs +++ b/ImprovedBuildHud/ImprovedBuildHud.cs @@ -14,12 +14,13 @@ public static class ImprovedBuildHudConfig public static ConfigEntry CanBuildAmountColor; } - [BepInPlugin(PluginId, "Improved Build HUD", "1.0.8")] + [BepInPlugin(PluginId, "Improved Build HUD", Version)] [BepInProcess("valheim.exe")] [BepInDependency("aedenthorn.CraftFromContainers", BepInDependency.DependencyFlags.SoftDependency)] public class ImprovedBuildHud : BaseUnityPlugin { public const string PluginId = "randyknapp.mods.improvedbuildhud"; + public const string Version = "1.0.8"; private Harmony _harmony; private static List _cachedContainers; diff --git a/ImprovedBuildHud/ImprovedBuildHud.csproj b/ImprovedBuildHud/ImprovedBuildHud.csproj index 59ee1e7d0..9f62f04a2 100644 --- a/ImprovedBuildHud/ImprovedBuildHud.csproj +++ b/ImprovedBuildHud/ImprovedBuildHud.csproj @@ -42,4 +42,44 @@ + + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/')) + + mod-tools + + + + + + + + + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ImprovedBuildHud/CHANGELOG.md b/ImprovedBuildHud/Package/CHANGELOG.md similarity index 81% rename from ImprovedBuildHud/CHANGELOG.md rename to ImprovedBuildHud/Package/CHANGELOG.md index 23872c34c..caa9701d5 100644 --- a/ImprovedBuildHud/CHANGELOG.md +++ b/ImprovedBuildHud/Package/CHANGELOG.md @@ -1,7 +1,7 @@ -## 1.0.8 +**1.0.8** * Update for Valheim version 0.219.13 Bog Witch. -## 1.0.7 +**1.0.7** * Pre-Bog Witch compatible version. \ No newline at end of file diff --git a/ImprovedBuildHud/Package/README.md b/ImprovedBuildHud/Package/README.md new file mode 100644 index 000000000..939c6d0fb --- /dev/null +++ b/ImprovedBuildHud/Package/README.md @@ -0,0 +1,14 @@ +# Improved Build Hud + +Author: [RandyKnapp](https://discord.gg/ZNhYeavv3C) +Source: [Github](https://github.com/RandyKnapp/ValheimMods/tree/main/ImprovedBuildHud/) +Patreon: [patreon.com/randyknapp](https://www.patreon.com/randyknapp) +Discord: [RandyKnapp's Mod Community](https://discord.gg/ZNhYeavv3C) + +Adds the total number you can build and your current inventory amount after the required amount in the build hud. + +Can optionally set the color and custom format. + +## Installation + +Install with [BepInEx](https://valheim.thunderstore.io/package/denikson/BepInExPack_Valheim/). Copy the ImprovedBuildHud.dll file to your BepInEx plugins folder. \ No newline at end of file diff --git a/ImprovedBuildHud/thunderstore/icon.png b/ImprovedBuildHud/Package/icon.png similarity index 100% rename from ImprovedBuildHud/thunderstore/icon.png rename to ImprovedBuildHud/Package/icon.png diff --git a/ImprovedBuildHud/thunderstore/manifest.json b/ImprovedBuildHud/Package/manifest.json similarity index 91% rename from ImprovedBuildHud/thunderstore/manifest.json rename to ImprovedBuildHud/Package/manifest.json index 60f60b555..29e8fb569 100644 --- a/ImprovedBuildHud/thunderstore/manifest.json +++ b/ImprovedBuildHud/Package/manifest.json @@ -1,6 +1,6 @@ { "name": "ImprovedBuildHud", - "version_number": "1.0.7", + "version_number": "1.0.8", "website_url": "https://github.com/RandyKnapp/ValheimMods/tree/main/ImprovedBuildHud", "description": "Add the total can-build amount plus the player's current inventory count to the requirements.", "dependencies": [ diff --git a/ImprovedBuildHud/README.md b/ImprovedBuildHud/README.md deleted file mode 100644 index 939c6d0fb..000000000 --- a/ImprovedBuildHud/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Improved Build Hud - -Author: [RandyKnapp](https://discord.gg/ZNhYeavv3C) -Source: [Github](https://github.com/RandyKnapp/ValheimMods/tree/main/ImprovedBuildHud/) -Patreon: [patreon.com/randyknapp](https://www.patreon.com/randyknapp) -Discord: [RandyKnapp's Mod Community](https://discord.gg/ZNhYeavv3C) - -Adds the total number you can build and your current inventory amount after the required amount in the build hud. - -Can optionally set the color and custom format. - -## Installation - -Install with [BepInEx](https://valheim.thunderstore.io/package/denikson/BepInExPack_Valheim/). Copy the ImprovedBuildHud.dll file to your BepInEx plugins folder. \ No newline at end of file diff --git a/ImprovedBuildHud/README.md b/ImprovedBuildHud/README.md new file mode 120000 index 000000000..d11c97e84 --- /dev/null +++ b/ImprovedBuildHud/README.md @@ -0,0 +1 @@ +Package/README.md \ No newline at end of file diff --git a/ImprovedBuildHud/release.yml b/ImprovedBuildHud/release.yml new file mode 100644 index 000000000..10ab9a0d8 --- /dev/null +++ b/ImprovedBuildHud/release.yml @@ -0,0 +1,27 @@ +# Release configuration for mod-tools. +# Shared settings come from the preset, which ships inside the gem. +# +# Inspect what this actually resolves to: +# mod-tools print-config + +extends: jotunn-mono +assembly: ImprovedBuildHud + +# This repository predates the tool and spells the version constant its own way. +version_const: Version + +# Copy the build into this machine's mod manager profiles after every build. +deploy: + enabled: true + +publish: + thunderstore: + enabled: true + nexus: + enabled: true + mod_id: 72 + file_id: 459295 + github: + enabled: true + discord: + enabled: true \ No newline at end of file diff --git a/ItsJustWood/.gitignore b/ItsJustWood/.gitignore new file mode 100644 index 000000000..4c49bd78f --- /dev/null +++ b/ItsJustWood/.gitignore @@ -0,0 +1 @@ +.env diff --git a/ItsJustWood/ItsJustWood.csproj b/ItsJustWood/ItsJustWood.csproj index f08a80679..f3319a18e 100644 --- a/ItsJustWood/ItsJustWood.csproj +++ b/ItsJustWood/ItsJustWood.csproj @@ -45,4 +45,44 @@ + + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/')) + + mod-tools + + + + + + + + + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ItsJustWood/Package/CHANGELOG.md b/ItsJustWood/Package/CHANGELOG.md new file mode 100644 index 000000000..1603f1b3f --- /dev/null +++ b/ItsJustWood/Package/CHANGELOG.md @@ -0,0 +1,3 @@ +**1.1.0** + +* Existing release; changelog begins here. diff --git a/ItsJustWood/Package/README.md b/ItsJustWood/Package/README.md new file mode 100644 index 000000000..458385ebd --- /dev/null +++ b/ItsJustWood/Package/README.md @@ -0,0 +1,11 @@ +# It's Just Wood! v1.1.0 +##### by RandyKnapp +> Ever get frustrated that you can't fuel a fire with that stack of nice, fine wood you have in your inventory? Wonder why ancient bark can't make coal? Worry no more! + +**Shout-out to Github user @shudnal** for providing a big update for Mistlands wood + +Convert uncommon wood types to regular wood. Use all wood types to fuel fires and the charcoal kiln. + +Source: [Github](https://github.com/RandyKnapp/ValheimMods) + +Install with [BepInEx](https://valheim.thunderstore.io/package/denikson/BepInExPack_Valheim/) \ No newline at end of file diff --git a/ItsJustWood/thunderstore/icon.png b/ItsJustWood/Package/icon.png similarity index 100% rename from ItsJustWood/thunderstore/icon.png rename to ItsJustWood/Package/icon.png diff --git a/ItsJustWood/thunderstore/manifest.json b/ItsJustWood/Package/manifest.json similarity index 100% rename from ItsJustWood/thunderstore/manifest.json rename to ItsJustWood/Package/manifest.json diff --git a/ItsJustWood/README.md b/ItsJustWood/README.md deleted file mode 100644 index 458385ebd..000000000 --- a/ItsJustWood/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# It's Just Wood! v1.1.0 -##### by RandyKnapp -> Ever get frustrated that you can't fuel a fire with that stack of nice, fine wood you have in your inventory? Wonder why ancient bark can't make coal? Worry no more! - -**Shout-out to Github user @shudnal** for providing a big update for Mistlands wood - -Convert uncommon wood types to regular wood. Use all wood types to fuel fires and the charcoal kiln. - -Source: [Github](https://github.com/RandyKnapp/ValheimMods) - -Install with [BepInEx](https://valheim.thunderstore.io/package/denikson/BepInExPack_Valheim/) \ No newline at end of file diff --git a/ItsJustWood/README.md b/ItsJustWood/README.md new file mode 120000 index 000000000..d11c97e84 --- /dev/null +++ b/ItsJustWood/README.md @@ -0,0 +1 @@ +Package/README.md \ No newline at end of file diff --git a/ItsJustWood/release.yml b/ItsJustWood/release.yml new file mode 100644 index 000000000..2a921d6ab --- /dev/null +++ b/ItsJustWood/release.yml @@ -0,0 +1,27 @@ +# Release configuration for mod-tools. +# Shared settings come from the preset, which ships inside the gem. +# +# Inspect what this actually resolves to: +# mod-tools print-config + +extends: jotunn-mono +assembly: ItsJustWood + +# This repository predates the tool and spells the version constant its own way. +version_const: pluginVersion + +# Copy the build into this machine's mod manager profiles after every build. +deploy: + enabled: true + +publish: + thunderstore: + enabled: true + nexus: + enabled: true + mod_id: 151 + file_id: 775840 + github: + enabled: true + discord: + enabled: true \ No newline at end of file diff --git a/Jam/.gitignore b/Jam/.gitignore new file mode 100644 index 000000000..4c49bd78f --- /dev/null +++ b/Jam/.gitignore @@ -0,0 +1 @@ +.env diff --git a/Jam/Jam.csproj b/Jam/Jam.csproj index c738dca4c..bf390fc7c 100644 --- a/Jam/Jam.csproj +++ b/Jam/Jam.csproj @@ -55,4 +55,44 @@ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. + + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/')) + + mod-tools + + + + + + + + + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Jam/Package/CHANGELOG.md b/Jam/Package/CHANGELOG.md new file mode 100644 index 000000000..1603f1b3f --- /dev/null +++ b/Jam/Package/CHANGELOG.md @@ -0,0 +1,3 @@ +**1.1.0** + +* Existing release; changelog begins here. diff --git a/Jam/Package/README.md b/Jam/Package/README.md new file mode 100644 index 000000000..2a980f168 --- /dev/null +++ b/Jam/Package/README.md @@ -0,0 +1,94 @@ +# Jam + +Author: [RandyKnapp](https://discord.gg/ZNhYeavv3C) +Source: [Github](https://github.com/RandyKnapp/ValheimMods/tree/main/Jam) +Patreon: [patreon.com/randyknapp](https://www.patreon.com/randyknapp) +Discord: [RandyKnapp's Mod Community](https://discord.gg/ZNhYeavv3C) + +## Version 1.1.0! + +As of the 1.1.0 update Jotunn is required to run this mod. A version check will be performed on server connection to ensure all players have the mod installed properly. + +Configurations should sync on servers and live update on changing the randyknapp.mods.jam.cfg file. The old json file is no longer supported, please use the new bepinex configurations! + +Please note that any jams crafted will retain their values on a live configuration change until they are reloaded! + +## Jams + +The following lists the jams with default configurations and prefab names: + +### Raspberry Jam **x4**: +* Recipe: 14 Raspberries +* Effect: Lasts 20 minutes, 14 HP, 30 stamina, 1HP regen per second +* Prefab Name: RaspberryJam + +### Sweetened Raspberry Jam **x4**: +* Recipe: 8 Raspberries, 4 Honey +* Effect: Lasts 20 minutes, 18 HP, 35 stamina, 2HP regen per second +* Prefab Name: HoneyRaspberryJam + +### Blueberry Jam **x4**: +* Recipe: 14 Blueberries +* Effect: Lasts 20 minutes, 14 HP, 35 stamina, 1HP regen per second +* Prefab Name: BlueberryJam + +### Sweetened Blueberry Jam **x4**: +* Recipe: 8 Blueberries, 4 Honey +* Effect: Lasts 20 minutes, 18 HP, 40 stamina, 2HP regen per second +* Prefab Name: HoneyBlueberryJam + +### Cloudberry Jam **x4**: +* Recipe: 14 Cloudberries +* Effect: Lasts 20 minutes, 26 HP, 45 stamina, 1HP regen per second +* Prefab Name: CloudberryJam + +### Sweetened Cloudberry Jam **x4**: +* Recipe: 8 Cloudberries, 4 Honey +* Effect: Lasts 20 minutes, 30 HP, 50 stamina, 2HP regen per second +* Prefab Name: HoneyCloudberryJam + +### Kings Jam **x4**: +* Recipe: 8 Raspberries, 6 Cloudberries +* Effect: Lasts 20 minutes, 26 HP, 50 stamina, 2HP regen per second +* Prefab Name: KingsJam + +### Nordic Jam **x4**: +* Recipe: 8 Blueberries, 6 Cloudberries +* Effect: Lasts 20 minutes, 26 HP, 55 stamina, 2HP regen per second +* Prefab Name: NordicJam + +### Savory Eitrian Jam **x4**: +* Recipe: 4 Magecap, 4 Jotunn Puffs, 2 Onions, 2 Honey +* Effect: Lasts 25 minutes, 35 HP, 60 stamina, 30 Eitr, 3HP regen per second +* Prefab Name: MushroomJam + +### Rich Eitrian Jam **x4**: +* Recipe: 4 Vineberry Cluster, 4 Smoke Puff, 2 Onions, 2 Honey +* Effect: Lasts 25 minutes, 40 HP, 60 stamina, 35 Eitr, 4HP regen per second +* Prefab Name: AshlandsJam + +## Installation: + +* Manual: Drop the Jam.dll into your BepInEx/plugins folder. Download Jotunn and install similarly. +* ThunderStore: When using a thunderstore mod manager the files should be placed in the correct directory for you. Dependencies should install automatically. + +## Changelog + +### 1.1.0 +* Now requires Jotunn to run, please install this new dependency! +* New ashlands level jam: Rich Eitrian Jam! +* Rebalanced existing jams to be more in line with vanilla values. (If something doesn't feel right let us know!) +* Stack size of jams is now 10. If upgrading from an older version of this mod split your larger stacks first to prevent item loss. +* Jams can now be placed with the Serving Tray. + +### 1.0.9 +* Update for Valheim version 0.219.13 Bog Witch. + +### 1.0.8 +* Hildr's Request - Updated for Valheim 0.217.27 + +### 1.0.7 +* Hildr's Request - Updated for Valheim BepInEx 5.4.2022 + +### 1.0.6 +* Hildr's Request - 0.217.24 diff --git a/Jam/thunderstore/icon.png b/Jam/Package/icon.png similarity index 100% rename from Jam/thunderstore/icon.png rename to Jam/Package/icon.png diff --git a/Jam/thunderstore/manifest.json b/Jam/Package/manifest.json similarity index 100% rename from Jam/thunderstore/manifest.json rename to Jam/Package/manifest.json diff --git a/Jam/README.md b/Jam/README.md deleted file mode 100644 index 2a980f168..000000000 --- a/Jam/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# Jam - -Author: [RandyKnapp](https://discord.gg/ZNhYeavv3C) -Source: [Github](https://github.com/RandyKnapp/ValheimMods/tree/main/Jam) -Patreon: [patreon.com/randyknapp](https://www.patreon.com/randyknapp) -Discord: [RandyKnapp's Mod Community](https://discord.gg/ZNhYeavv3C) - -## Version 1.1.0! - -As of the 1.1.0 update Jotunn is required to run this mod. A version check will be performed on server connection to ensure all players have the mod installed properly. - -Configurations should sync on servers and live update on changing the randyknapp.mods.jam.cfg file. The old json file is no longer supported, please use the new bepinex configurations! - -Please note that any jams crafted will retain their values on a live configuration change until they are reloaded! - -## Jams - -The following lists the jams with default configurations and prefab names: - -### Raspberry Jam **x4**: -* Recipe: 14 Raspberries -* Effect: Lasts 20 minutes, 14 HP, 30 stamina, 1HP regen per second -* Prefab Name: RaspberryJam - -### Sweetened Raspberry Jam **x4**: -* Recipe: 8 Raspberries, 4 Honey -* Effect: Lasts 20 minutes, 18 HP, 35 stamina, 2HP regen per second -* Prefab Name: HoneyRaspberryJam - -### Blueberry Jam **x4**: -* Recipe: 14 Blueberries -* Effect: Lasts 20 minutes, 14 HP, 35 stamina, 1HP regen per second -* Prefab Name: BlueberryJam - -### Sweetened Blueberry Jam **x4**: -* Recipe: 8 Blueberries, 4 Honey -* Effect: Lasts 20 minutes, 18 HP, 40 stamina, 2HP regen per second -* Prefab Name: HoneyBlueberryJam - -### Cloudberry Jam **x4**: -* Recipe: 14 Cloudberries -* Effect: Lasts 20 minutes, 26 HP, 45 stamina, 1HP regen per second -* Prefab Name: CloudberryJam - -### Sweetened Cloudberry Jam **x4**: -* Recipe: 8 Cloudberries, 4 Honey -* Effect: Lasts 20 minutes, 30 HP, 50 stamina, 2HP regen per second -* Prefab Name: HoneyCloudberryJam - -### Kings Jam **x4**: -* Recipe: 8 Raspberries, 6 Cloudberries -* Effect: Lasts 20 minutes, 26 HP, 50 stamina, 2HP regen per second -* Prefab Name: KingsJam - -### Nordic Jam **x4**: -* Recipe: 8 Blueberries, 6 Cloudberries -* Effect: Lasts 20 minutes, 26 HP, 55 stamina, 2HP regen per second -* Prefab Name: NordicJam - -### Savory Eitrian Jam **x4**: -* Recipe: 4 Magecap, 4 Jotunn Puffs, 2 Onions, 2 Honey -* Effect: Lasts 25 minutes, 35 HP, 60 stamina, 30 Eitr, 3HP regen per second -* Prefab Name: MushroomJam - -### Rich Eitrian Jam **x4**: -* Recipe: 4 Vineberry Cluster, 4 Smoke Puff, 2 Onions, 2 Honey -* Effect: Lasts 25 minutes, 40 HP, 60 stamina, 35 Eitr, 4HP regen per second -* Prefab Name: AshlandsJam - -## Installation: - -* Manual: Drop the Jam.dll into your BepInEx/plugins folder. Download Jotunn and install similarly. -* ThunderStore: When using a thunderstore mod manager the files should be placed in the correct directory for you. Dependencies should install automatically. - -## Changelog - -### 1.1.0 -* Now requires Jotunn to run, please install this new dependency! -* New ashlands level jam: Rich Eitrian Jam! -* Rebalanced existing jams to be more in line with vanilla values. (If something doesn't feel right let us know!) -* Stack size of jams is now 10. If upgrading from an older version of this mod split your larger stacks first to prevent item loss. -* Jams can now be placed with the Serving Tray. - -### 1.0.9 -* Update for Valheim version 0.219.13 Bog Witch. - -### 1.0.8 -* Hildr's Request - Updated for Valheim 0.217.27 - -### 1.0.7 -* Hildr's Request - Updated for Valheim BepInEx 5.4.2022 - -### 1.0.6 -* Hildr's Request - 0.217.24 diff --git a/Jam/README.md b/Jam/README.md new file mode 120000 index 000000000..d11c97e84 --- /dev/null +++ b/Jam/README.md @@ -0,0 +1 @@ +Package/README.md \ No newline at end of file diff --git a/Jam/release.yml b/Jam/release.yml new file mode 100644 index 000000000..8988350e3 --- /dev/null +++ b/Jam/release.yml @@ -0,0 +1,27 @@ +# Release configuration for mod-tools. +# Shared settings come from the preset, which ships inside the gem. +# +# Inspect what this actually resolves to: +# mod-tools print-config + +extends: jotunn-mono +assembly: Jam + +# This repository predates the tool and spells the version constant its own way. +version_const: Version + +# Copy the build into this machine's mod manager profiles after every build. +deploy: + enabled: true + +publish: + thunderstore: + enabled: true + nexus: + enabled: true + mod_id: 75 + file_id: 467302 + github: + enabled: true + discord: + enabled: true \ No newline at end of file diff --git a/MinimalStatusEffects/.gitignore b/MinimalStatusEffects/.gitignore new file mode 100644 index 000000000..4c49bd78f --- /dev/null +++ b/MinimalStatusEffects/.gitignore @@ -0,0 +1 @@ +.env diff --git a/MinimalStatusEffects/MinimalStatusEffects.csproj b/MinimalStatusEffects/MinimalStatusEffects.csproj index 48f4c26d1..43e4d4a8b 100644 --- a/MinimalStatusEffects/MinimalStatusEffects.csproj +++ b/MinimalStatusEffects/MinimalStatusEffects.csproj @@ -43,4 +43,44 @@ + + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/')) + + mod-tools + + + + + + + + + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MinimalStatusEffects/Package/CHANGELOG.md b/MinimalStatusEffects/Package/CHANGELOG.md new file mode 100644 index 000000000..a99053b21 --- /dev/null +++ b/MinimalStatusEffects/Package/CHANGELOG.md @@ -0,0 +1,3 @@ +**1.0.7** + +* Existing release; changelog begins here. diff --git a/MinimalStatusEffects/Package/README.md b/MinimalStatusEffects/Package/README.md new file mode 100644 index 000000000..615332993 --- /dev/null +++ b/MinimalStatusEffects/Package/README.md @@ -0,0 +1,11 @@ +# Minimal Status Effects +##### by RandyKnapp +Moves the status effect list down below the minimap, shrinks the icons, and compacts the text information. + +**Shout-out to Github user @shudnal** for providing a refersh to this mod and adding some config options to support the nomap World options that have been introduced. + +Source: [Github](https://github.com/RandyKnapp/ValheimMods) + +Install with [BepInEx](https://valheim.thunderstore.io/package/denikson/BepInExPack_Valheim/) + +Copy MinimalStatusEffects.dll into the BepInEx/plugins folder \ No newline at end of file diff --git a/MinimalStatusEffects/thunderstore/icon.png b/MinimalStatusEffects/Package/icon.png similarity index 100% rename from MinimalStatusEffects/thunderstore/icon.png rename to MinimalStatusEffects/Package/icon.png diff --git a/MinimalStatusEffects/thunderstore/manifest.json b/MinimalStatusEffects/Package/manifest.json similarity index 100% rename from MinimalStatusEffects/thunderstore/manifest.json rename to MinimalStatusEffects/Package/manifest.json diff --git a/MinimalStatusEffects/README.md b/MinimalStatusEffects/README.md deleted file mode 100644 index 615332993..000000000 --- a/MinimalStatusEffects/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Minimal Status Effects -##### by RandyKnapp -Moves the status effect list down below the minimap, shrinks the icons, and compacts the text information. - -**Shout-out to Github user @shudnal** for providing a refersh to this mod and adding some config options to support the nomap World options that have been introduced. - -Source: [Github](https://github.com/RandyKnapp/ValheimMods) - -Install with [BepInEx](https://valheim.thunderstore.io/package/denikson/BepInExPack_Valheim/) - -Copy MinimalStatusEffects.dll into the BepInEx/plugins folder \ No newline at end of file diff --git a/MinimalStatusEffects/README.md b/MinimalStatusEffects/README.md new file mode 120000 index 000000000..d11c97e84 --- /dev/null +++ b/MinimalStatusEffects/README.md @@ -0,0 +1 @@ +Package/README.md \ No newline at end of file diff --git a/MinimalStatusEffects/release.yml b/MinimalStatusEffects/release.yml new file mode 100644 index 000000000..4d798991e --- /dev/null +++ b/MinimalStatusEffects/release.yml @@ -0,0 +1,15 @@ +# Release configuration for mod-tools. +# Shared settings come from the preset, which ships inside the gem. +# +# Inspect what this actually resolves to: +# mod-tools print-config + +extends: jotunn-mono +assembly: MinimalStatusEffects + +# This repository predates the tool and spells the version constant its own way. +version_const: Version + +# Copy the build into this machine's mod manager profiles after every build. +deploy: + enabled: true diff --git a/StationsAreContainers/.gitignore b/StationsAreContainers/.gitignore new file mode 100644 index 000000000..4c49bd78f --- /dev/null +++ b/StationsAreContainers/.gitignore @@ -0,0 +1 @@ +.env diff --git a/StationsAreContainers/CHANGELOG.md b/StationsAreContainers/Package/CHANGELOG.md similarity index 88% rename from StationsAreContainers/CHANGELOG.md rename to StationsAreContainers/Package/CHANGELOG.md index 2f3ad7718..e52df4532 100644 --- a/StationsAreContainers/CHANGELOG.md +++ b/StationsAreContainers/Package/CHANGELOG.md @@ -1,23 +1,23 @@ -## 1.0.7 +**1.0.7** * Update for Valheim version 0.219.13 Bog Witch. -## 1.0.6 +**1.0.6** * Updated for Hildir's Request - 0.217.27 -## 1.0.5 +**1.0.5** * ServerSync update -## 1.0.4 +**1.0.4** * Updated for Hildir's Request - 0.217.24 -## 1.0.3 +**1.0.3** * Updated for Hildir's Request. * Big thanks to probablykory for the pull request. -## 1.0.2 +**1.0.2** * Updated for Mistlands crafting stations * Allows for modded crafting stations (automatically creates a 3x6 container for modded stations, once you have placed the station, it will appear with its own section in the config file) * Some minor bugfixes -## 1.0.1 +**1.0.1** * Minor changes to be more compatible with EpicLoot \ No newline at end of file diff --git a/StationsAreContainers/Package/README.md b/StationsAreContainers/Package/README.md new file mode 100644 index 000000000..b1c759d2f --- /dev/null +++ b/StationsAreContainers/Package/README.md @@ -0,0 +1,23 @@ +# Stations Are Containers + +Author: [RandyKnapp](https://discord.gg/ZNhYeavv3C) +Source: [Github](https://github.com/RandyKnapp/ValheimMods/tree/main/StationsAreContainers/) +Patreon: [patreon.com/randyknapp](https://www.patreon.com/randyknapp) +Discord: [RandyKnapp's Mod Community](https://discord.gg/ZNhYeavv3C) + +Crafting stations now have a built in storage container and can craft from it. + +Uses ServerSync. + +## Compatible with: + * Auga + * ImprovedBuildHud + * QuickStack + +## Configuration + * Ability to turn off built-in container on a per-station basis + * Ability to configure the size of the container's inventory on a per-station basis + +## Installation + * Nexus: Drop the StationsAreContainers.dll right into your BepInEx/plugins folder + * ThunderStore: Use r2modman to install, or manually drop the dll into your BepInEx/plugins folder \ No newline at end of file diff --git a/StationsAreContainers/thunderstore/icon.png b/StationsAreContainers/Package/icon.png similarity index 100% rename from StationsAreContainers/thunderstore/icon.png rename to StationsAreContainers/Package/icon.png diff --git a/StationsAreContainers/thunderstore/manifest.json b/StationsAreContainers/Package/manifest.json similarity index 91% rename from StationsAreContainers/thunderstore/manifest.json rename to StationsAreContainers/Package/manifest.json index c7946b99a..3f1b4c2d3 100644 --- a/StationsAreContainers/thunderstore/manifest.json +++ b/StationsAreContainers/Package/manifest.json @@ -1,6 +1,6 @@ { "name": "StationsAreContainers", - "version_number": "1.0.6", + "version_number": "1.0.7", "website_url": "https://github.com/RandyKnapp/ValheimMods/tree/main/StationsAreContainers", "description": "Crafting stations now have a built in storage containers and can build from it.", "dependencies": [ diff --git a/StationsAreContainers/README.md b/StationsAreContainers/README.md deleted file mode 100644 index b1c759d2f..000000000 --- a/StationsAreContainers/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Stations Are Containers - -Author: [RandyKnapp](https://discord.gg/ZNhYeavv3C) -Source: [Github](https://github.com/RandyKnapp/ValheimMods/tree/main/StationsAreContainers/) -Patreon: [patreon.com/randyknapp](https://www.patreon.com/randyknapp) -Discord: [RandyKnapp's Mod Community](https://discord.gg/ZNhYeavv3C) - -Crafting stations now have a built in storage container and can craft from it. - -Uses ServerSync. - -## Compatible with: - * Auga - * ImprovedBuildHud - * QuickStack - -## Configuration - * Ability to turn off built-in container on a per-station basis - * Ability to configure the size of the container's inventory on a per-station basis - -## Installation - * Nexus: Drop the StationsAreContainers.dll right into your BepInEx/plugins folder - * ThunderStore: Use r2modman to install, or manually drop the dll into your BepInEx/plugins folder \ No newline at end of file diff --git a/StationsAreContainers/README.md b/StationsAreContainers/README.md new file mode 120000 index 000000000..d11c97e84 --- /dev/null +++ b/StationsAreContainers/README.md @@ -0,0 +1 @@ +Package/README.md \ No newline at end of file diff --git a/StationsAreContainers/StationsAreContainers.csproj b/StationsAreContainers/StationsAreContainers.csproj index 001209c5a..ebf0dad79 100644 --- a/StationsAreContainers/StationsAreContainers.csproj +++ b/StationsAreContainers/StationsAreContainers.csproj @@ -55,4 +55,44 @@ + + + $([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/')) + + mod-tools + + + + + + + + + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/StationsAreContainers/release.yml b/StationsAreContainers/release.yml new file mode 100644 index 000000000..b09609da9 --- /dev/null +++ b/StationsAreContainers/release.yml @@ -0,0 +1,27 @@ +# Release configuration for mod-tools. +# Shared settings come from the preset, which ships inside the gem. +# +# Inspect what this actually resolves to: +# mod-tools print-config + +extends: jotunn-mono +assembly: StationsAreContainers + +# This repository predates the tool and spells the version constant its own way. +version_const: Version + +# Copy the build into this machine's mod manager profiles after every build. +deploy: + enabled: true + +publish: + thunderstore: + enabled: true + nexus: + enabled: true + mod_id: 1716 + file_id: 2374114 + github: + enabled: true + discord: + enabled: true \ No newline at end of file