using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("MGDev.ConfigurableCraftingRecipes")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("MGDev.ConfigurableCraftingRecipes")] [assembly: AssemblyTitle("MGDev.ConfigurableCraftingRecipes")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace MGDev.ConfigurableCraftingRecipes { [BepInPlugin("mgdev.configurablecraftingrecipes", "MGDev Configurable Crafting Recipes", "1.1.3")] [BepInDependency("com.jotunn.jotunn", "2.20.0")] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class ConfigurableCraftingRecipesPlugin : BaseUnityPlugin { private sealed class KnownRecipeConfig { public string ItemPrefab { get; } public ConfigEntry Enabled { get; } public ConfigEntry Amount { get; } public ConfigEntry Station { get; } public ConfigEntry StationLevel { get; } public ConfigEntry Requirements { get; } public KnownRecipeConfig(string itemPrefab, ConfigEntry enabled, ConfigEntry amount, ConfigEntry station, ConfigEntry stationLevel, ConfigEntry requirements) { ItemPrefab = itemPrefab; Enabled = enabled; Amount = amount; Station = station; StationLevel = stationLevel; Requirements = requirements; } } private sealed class TrophyRecipeConfig { public string DisplayName { get; } public string ItemPrefab { get; } public ConfigEntry Enabled { get; } public ConfigEntry Station { get; } public ConfigEntry Requirements { get; } public TrophyRecipeConfig(string displayName, string itemPrefab, ConfigEntry enabled, ConfigEntry station, ConfigEntry requirements) { DisplayName = displayName; ItemPrefab = itemPrefab; Enabled = enabled; Station = station; Requirements = requirements; } } private sealed class CustomRecipeConfig { public ConfigEntry Enabled { get; } public ConfigEntry Item { get; } public ConfigEntry Amount { get; } public ConfigEntry Station { get; } public ConfigEntry StationLevel { get; } public ConfigEntry Requirements { get; } public CustomRecipeConfig(ConfigEntry enabled, ConfigEntry item, ConfigEntry amount, ConfigEntry station, ConfigEntry stationLevel, ConfigEntry requirements) { Enabled = enabled; Item = item; Amount = amount; Station = station; StationLevel = stationLevel; Requirements = requirements; } } private sealed class RecipeDefinition { public string ItemPrefab { get; } public int OutputAmount { get; } public string Station { get; } public int StationLevel { get; } public List Ingredients { get; } public RecipeDefinition(string itemPrefab, int outputAmount, string station, int stationLevel, List ingredients) { ItemPrefab = itemPrefab; OutputAmount = outputAmount; Station = station; StationLevel = stationLevel; Ingredients = ingredients; } } private sealed class IngredientDefinition { public string ItemPrefab { get; } public int Amount { get; } public IngredientDefinition(string itemPrefab, int amount) { ItemPrefab = itemPrefab; Amount = amount; } } public const string ModGuid = "mgdev.configurablecraftingrecipes"; public const string ModName = "MGDev Configurable Crafting Recipes"; public const string ModVersion = "1.1.3"; private static readonly Dictionary StationAliases = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["None"] = "", ["NoStation"] = "", ["Workbench"] = "piece_workbench", ["WorkBench"] = "piece_workbench", ["Forge"] = "forge", ["Cauldron"] = "piece_cauldron", ["Stonecutter"] = "piece_stonecutter", ["StoneCutter"] = "piece_stonecutter", ["ArtisanTable"] = "piece_artisanstation", ["Artisan"] = "piece_artisanstation", ["BlackForge"] = "blackforge", ["GaldrTable"] = "piece_magetable", ["FoodPreparationTable"] = "piece_preptable", ["PrepTable"] = "piece_preptable" }; private static readonly List KnownRecipes = new List(); private static readonly List TrophyRecipes = new List(); private static readonly List CustomRecipes = new List(); private static ConfigEntry EnableTrophyRecipes; private static ManualLogSource Log; private static ObjectDB RegisteredObjectDB; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; BindConfig(); ItemManager.OnItemsRegistered += RegisterConfiguredRecipes; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Loaded. Recipes will register when Jotunn finishes loading items."); } private void OnDestroy() { ItemManager.OnItemsRegistered -= RegisterConfiguredRecipes; } private void BindConfig() { KnownRecipes.Clear(); TrophyRecipes.Clear(); CustomRecipes.Clear(); AddKnownRecipe("1 - Custom Recipes", "Chain", "Chain", enabled: true, 1, "Forge", 3, "Iron:2"); AddKnownRecipe("1 - Custom Recipes", "Fishing Rod", "FishingRod", enabled: true, 1, "Workbench", 3, "FineWood:10,Iron:2"); AddKnownRecipe("1 - Custom Recipes", "Fishing Bait", "FishingBait", enabled: true, 20, "FoodPreparationTable", 1, "Entrails:5"); AddKnownRecipe("1 - Custom Recipes", "Thunderstone", "Thunderstone", enabled: true, 1, "Forge", 2, "SurtlingCore:10,Iron:1"); AddKnownRecipe("1 - Custom Recipes", "Barrel Rings", "BarrelRings", enabled: true, 1, "Forge", 1, "Iron:2"); AddKnownRecipe("1 - Custom Recipes", "Surtling Core", "SurtlingCore", enabled: true, 1, "Forge", 1, "Coal:25,Stone:10,Resin:5"); AddKnownRecipe("2 - Valuables Recipes", "Amber from Coins", "Amber", enabled: true, 1, "None", 1, "Coins:5"); AddKnownRecipe("2 - Valuables Recipes", "Amber Pearl from Coins", "AmberPearl", enabled: true, 1, "None", 1, "Coins:10"); AddKnownRecipe("2 - Valuables Recipes", "Ruby from Coins", "Ruby", enabled: true, 1, "None", 1, "Coins:20"); AddKnownRecipe("2 - Valuables Recipes", "Silver Necklace from Coins", "SilverNecklace", enabled: true, 1, "None", 1, "Coins:30"); AddKnownRecipe("2 - Valuables Recipes", "Coins from Amber", "Coins", enabled: true, 5, "None", 1, "Amber:1"); AddKnownRecipe("2 - Valuables Recipes", "Coins from Amber Pearl", "Coins", enabled: true, 10, "None", 1, "AmberPearl:1"); AddKnownRecipe("2 - Valuables Recipes", "Coins from Ruby", "Coins", enabled: true, 20, "None", 1, "Ruby:1"); AddKnownRecipe("2 - Valuables Recipes", "Coins from Silver Necklace", "Coins", enabled: true, 30, "None", 1, "SilverNecklace:1"); EnableTrophyRecipes = ((BaseUnityPlugin)this).Config.Bind("3 - Trophy Recipes", "Enable All Trophy Recipes", true, "Master toggle for all trophy crafting recipes."); AddTrophyRecipe("Boar", "TrophyBoar", enabled: true, "None", "LeatherScraps:10"); AddTrophyRecipe("Deer", "TrophyDeer", enabled: true, "None", "DeerHide:10"); AddTrophyRecipe("Neck", "TrophyNeck", enabled: true, "None", "NeckTail:10"); AddTrophyRecipe("Eikthyr", "TrophyEikthyr", enabled: true, "None", "HardAntler:10"); AddTrophyRecipe("Greydwarf", "TrophyGreydwarf", enabled: true, "None", "GreydwarfEye:10"); AddTrophyRecipe("Skeleton", "TrophySkeleton", enabled: true, "None", "BoneFragments:10"); AddTrophyRecipe("Troll", "TrophyFrostTroll", enabled: true, "None", "TrollHide:10"); AddTrophyRecipe("Elder", "TrophyTheElder", enabled: true, "None", "AncientSeed:10"); AddTrophyRecipe("Serpent", "TrophySerpent", enabled: true, "None", "SerpentScale:50"); AddTrophyRecipe("Leech", "TrophyLeech", enabled: true, "None", "Bloodbag:20"); AddTrophyRecipe("Surtling", "TrophySurtling", enabled: true, "None", "SurtlingCore:2"); AddTrophyRecipe("Abomination", "TrophyAbomination", enabled: true, "None", "Root:10"); AddTrophyRecipe("Bonemass", "TrophyBonemass", enabled: true, "None", "WitheredBone:20"); AddTrophyRecipe("Wolf", "TrophyWolf", enabled: true, "None", "WolfPelt:10"); AddTrophyRecipe("Drake", "TrophyHatchling", enabled: true, "None", "FreezeGland:3"); AddTrophyRecipe("Fenring", "TrophyFenring", enabled: true, "None", "WolfClaw:10"); AddTrophyRecipe("Cultist", "TrophyCultist", enabled: true, "None", "WolfHairBundle:10"); AddTrophyRecipe("Stone Golem", "TrophySGolem", enabled: true, "None", "Crystal:100"); AddTrophyRecipe("Moder", "TrophyDragonQueen", enabled: true, "None", "DragonTear:10"); AddTrophyRecipe("Deathsquito", "TrophyDeathsquito", enabled: true, "None", "Needle:50"); AddTrophyRecipe("Fuling Shaman", "TrophyGoblinShaman", enabled: true, "None", "GoblinTotem:2"); AddTrophyRecipe("Yagluth", "TrophyGoblinKing", enabled: true, "None", "TornSpirit:10"); AddTrophyRecipe("Growth", "TrophyGrowth", enabled: true, "None", "Tar:20"); AddTrophyRecipe("Lox", "TrophyLox", enabled: true, "None", "LoxPelt:10"); AddTrophyRecipe("Hare", "TrophyHare", enabled: true, "None", "ScaleHide:10"); AddTrophyRecipe("Tick", "TrophyTick", enabled: true, "None", "GiantBloodSack:10"); AddTrophyRecipe("Seeker", "TrophySeeker", enabled: true, "None", "Carapace:10"); AddTrophyRecipe("Gjall", "TrophyGjall", enabled: true, "None", "Bilebag:10"); AddTrophyRecipe("Queen", "TrophySeekerQueen", enabled: true, "None", "QueenDrop:10"); AddTrophyRecipe("Asksvin", "TrophyAsksvin", enabled: true, "None", "AskHide:10"); AddTrophyRecipe("Morgen", "TrophyMorgen", enabled: true, "None", "MorgenSinew:10"); AddTrophyRecipe("Fallen Valkyrie", "TrophyFallenValkyrie", enabled: true, "None", "CelestialFeather:10"); AddTrophyRecipe("Bonemaw Serpent", "TrophyBonemawSerpent", enabled: true, "None", "BonemawMeat:10"); AddTrophyRecipe("Fader", "TrophyFader", enabled: true, "None", "FaderDrop:10"); for (int i = 1; i <= 10; i++) { AddCustomRecipe(i); } } private void AddKnownRecipe(string section, string name, string item, bool enabled, int amount, string station, int stationLevel, string requirements) { KnownRecipes.Add(new KnownRecipeConfig(item, ((BaseUnityPlugin)this).Config.Bind(section, name + " - Enabled", enabled, "Enable the " + name + " recipe."), ((BaseUnityPlugin)this).Config.Bind(section, name + " - Amount", amount, "How many " + item + " this recipe creates."), ((BaseUnityPlugin)this).Config.Bind(section, name + " - Station", station, "Crafting station. Use None, Workbench, Forge, Cauldron, StoneCutter, ArtisanTable, BlackForge, GaldrTable, FoodPreparationTable, or an exact station prefab name."), ((BaseUnityPlugin)this).Config.Bind(section, name + " - Station Level", stationLevel, "Required crafting station level. Use 1 for no upgrade requirement."), ((BaseUnityPlugin)this).Config.Bind(section, name + " - Requirements", requirements, "Required items as ItemPrefab:Amount pairs separated by commas."))); } private void AddTrophyRecipe(string name, string item, bool enabled, string station, string requirements) { TrophyRecipes.Add(new TrophyRecipeConfig(name, item, ((BaseUnityPlugin)this).Config.Bind("3 - Trophy Recipes", name + " - Enabled", enabled, "Enable crafting for " + name + " trophy."), ((BaseUnityPlugin)this).Config.Bind("3 - Trophy Recipes", name + " - Station", station, "Crafting station. Use None, Workbench, Forge, Cauldron, StoneCutter, ArtisanTable, BlackForge, GaldrTable, FoodPreparationTable, or an exact station prefab name."), ((BaseUnityPlugin)this).Config.Bind("3 - Trophy Recipes", name + " - Requirements", requirements, "Required items as ItemPrefab:Amount pairs separated by commas."))); } private void AddCustomRecipe(int index) { string text = $"Slot {index:00}"; CustomRecipes.Add(new CustomRecipeConfig(((BaseUnityPlugin)this).Config.Bind("4 - Extra Recipes", text + " - Enabled", false, "Enable this custom recipe slot."), ((BaseUnityPlugin)this).Config.Bind("4 - Extra Recipes", text + " - Item", "", "Item prefab name to craft. Example: TrophyDeer, Chain, SurtlingCore"), ((BaseUnityPlugin)this).Config.Bind("4 - Extra Recipes", text + " - Amount", 1, "How many items this recipe creates."), ((BaseUnityPlugin)this).Config.Bind("4 - Extra Recipes", text + " - Station", "Workbench", "Crafting station. Use None, Workbench, Forge, Cauldron, StoneCutter, ArtisanTable, BlackForge, GaldrTable, FoodPreparationTable, or an exact station prefab name."), ((BaseUnityPlugin)this).Config.Bind("4 - Extra Recipes", text + " - Station Level", 1, "Required crafting station level. Use 1 for no upgrade requirement."), ((BaseUnityPlugin)this).Config.Bind("4 - Extra Recipes", text + " - Requirements", "", "Required items as ItemPrefab:Amount pairs separated by commas. Example: Wood:5,LeatherScraps:2"))); } private static void RegisterConfiguredRecipes() { if ((Object)(object)ObjectDB.instance == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"ObjectDB is not available yet. Recipes were not registered."); } } else { if ((Object)(object)RegisteredObjectDB == (Object)(object)ObjectDB.instance) { return; } RegisteredObjectDB = ObjectDB.instance; int num = 0; foreach (RecipeDefinition configuredRecipe in GetConfiguredRecipes()) { if (!ValidateRecipe(configuredRecipe)) { continue; } Recipe valheimRecipe = CreateValheimRecipe(configuredRecipe); if (!((Object)(object)valheimRecipe == (Object)null)) { ObjectDB.instance.m_recipes.RemoveAll((Recipe existing) => (Object)(object)existing != (Object)null && ((Object)existing).name == ((Object)valheimRecipe).name); ObjectDB.instance.m_recipes.Add(valheimRecipe); num++; ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)$"Registered recipe: {configuredRecipe.ItemPrefab} x{configuredRecipe.OutputAmount} at {configuredRecipe.Station}."); } } } ManualLogSource log3 = Log; if (log3 != null) { log3.LogInfo((object)$"Registered {num} configurable crafting recipe(s)."); } } } private static IEnumerable GetConfiguredRecipes() { foreach (KnownRecipeConfig knownRecipe in KnownRecipes) { if (knownRecipe.Enabled.Value) { yield return CreateRecipeDefinition(knownRecipe.ItemPrefab, knownRecipe.Amount.Value, knownRecipe.Station.Value, knownRecipe.StationLevel.Value, knownRecipe.Requirements.Value); } } if (EnableTrophyRecipes.Value) { foreach (TrophyRecipeConfig trophyRecipe in TrophyRecipes) { if (trophyRecipe.Enabled.Value) { yield return CreateRecipeDefinition(trophyRecipe.ItemPrefab, 1, trophyRecipe.Station.Value, 1, trophyRecipe.Requirements.Value); } } } foreach (CustomRecipeConfig customRecipe in CustomRecipes) { if (customRecipe.Enabled.Value) { yield return CreateRecipeDefinition(customRecipe.Item.Value.Trim(), customRecipe.Amount.Value, customRecipe.Station.Value, customRecipe.StationLevel.Value, customRecipe.Requirements.Value); } } } private static RecipeDefinition CreateRecipeDefinition(string itemPrefab, int amount, string station, int stationLevel, string requirements) { return new RecipeDefinition(itemPrefab, amount, station.Trim(), Math.Max(1, stationLevel), ParseIngredients(requirements, itemPrefab)); } private static List ParseIngredients(string value, string recipeText) { List list = new List(); string[] array = value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); string[] array2 = text.Split(new char[1] { ':' }); if (array2.Length != 2 || !int.TryParse(array2[1].Trim(), out var result) || result <= 0) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("Ignored invalid ingredient '" + text + "' in recipe '" + recipeText + "'.")); } continue; } string text2 = array2[0].Trim(); if (text2.Length == 0) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("Ignored invalid ingredient '" + text + "' in recipe '" + recipeText + "'.")); } } else { list.Add(new IngredientDefinition(text2, result)); } } return list; } private static bool ValidateRecipe(RecipeDefinition recipe) { if (string.IsNullOrWhiteSpace(recipe.ItemPrefab)) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"Ignored recipe with empty item prefab."); } return false; } if (recipe.OutputAmount <= 0) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("Ignored recipe '" + recipe.ItemPrefab + "'. Amount must be positive.")); } return false; } if (recipe.Ingredients.Count == 0) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)("Ignored recipe '" + recipe.ItemPrefab + "'. At least one ingredient is required.")); } return false; } if (!ItemExists(recipe.ItemPrefab)) { ManualLogSource log4 = Log; if (log4 != null) { log4.LogWarning((object)("Ignored recipe for missing item prefab: " + recipe.ItemPrefab)); } return false; } string text = ResolveStation(recipe.Station); if (text.Length > 0 && !StationExists(text)) { ManualLogSource log5 = Log; if (log5 != null) { log5.LogWarning((object)("Ignored recipe '" + recipe.ItemPrefab + "'. Crafting station not found: " + recipe.Station + " -> " + text)); } return false; } foreach (IngredientDefinition ingredient in recipe.Ingredients) { if (!ItemExists(ingredient.ItemPrefab)) { ManualLogSource log6 = Log; if (log6 != null) { log6.LogWarning((object)("Ignored recipe '" + recipe.ItemPrefab + "'. Ingredient prefab not found: " + ingredient.ItemPrefab)); } return false; } } return true; } private static Recipe CreateValheimRecipe(RecipeDefinition recipe) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(recipe.ItemPrefab); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null); if ((Object)(object)val == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("Ignored recipe '" + recipe.ItemPrefab + "'. Output item has no ItemDrop.")); } return null; } string text = ResolveStation(recipe.Station); CraftingStation val2 = null; if (text.Length > 0) { GameObject prefab = ZNetScene.instance.GetPrefab(text); val2 = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent() : null); if ((Object)(object)val2 == (Object)null) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("Ignored recipe '" + recipe.ItemPrefab + "'. Crafting station has no CraftingStation component: " + recipe.Station + " -> " + text)); } return null; } } Recipe obj = ScriptableObject.CreateInstance(); ((Object)obj).name = string.Format("MGDev_{0}_{1}_Recipe", recipe.ItemPrefab, Math.Abs(string.Join("_", recipe.Ingredients.Select((IngredientDefinition ingredient) => ingredient.ItemPrefab)).GetHashCode())); obj.m_item = val; obj.m_amount = recipe.OutputAmount; obj.m_enabled = true; obj.m_craftingStation = val2; obj.m_minStationLevel = Math.Max(1, recipe.StationLevel); obj.m_resources = ((IEnumerable)recipe.Ingredients).Select((Func)delegate(IngredientDefinition ingredient) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown GameObject itemPrefab2 = ObjectDB.instance.GetItemPrefab(ingredient.ItemPrefab); return new Requirement { m_resItem = itemPrefab2.GetComponent(), m_amount = ingredient.Amount, m_amountPerLevel = 0, m_recover = true }; }).ToArray(); return obj; } private static string ResolveStation(string station) { if (string.IsNullOrWhiteSpace(station)) { return ""; } string text = station.Trim(); if (!StationAliases.TryGetValue(text, out var value)) { return text; } return value; } private static bool ItemExists(string prefabName) { if ((Object)(object)ObjectDB.instance != (Object)null) { return (Object)(object)ObjectDB.instance.GetItemPrefab(prefabName) != (Object)null; } return false; } private static bool StationExists(string prefabName) { GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(prefabName) : null); if ((Object)(object)val != (Object)null) { return (Object)(object)val.GetComponent() != (Object)null; } return false; } } }