Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BossPotions v0.2.1
BepInEx/plugins/BossPotions/BossPotions.dll
Decompiled a month agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; 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(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("BossPotions")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.1.0")] [assembly: AssemblyInformationalVersion("0.2.1")] [assembly: AssemblyProduct("BossPotions")] [assembly: AssemblyTitle("BossPotions")] [assembly: AssemblyVersion("0.2.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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 BossPotions { internal enum CommitResult { Success, InvalidInventory, InvalidProgress, Limit } internal static class Consumption { internal static CommitResult Commit<T>(Dictionary<string, string> data, List<T> items, T item, ref int stack, Stat stat, int bonus, int cap, out Progress awarded) where T : class { awarded = default(Progress); if (items == null || item == null || stack < 1) { return CommitResult.InvalidInventory; } int num = -1; for (int i = 0; i < items.Count; i++) { if (items[i] == item) { if (num >= 0) { return CommitResult.InvalidInventory; } num = i; } } if (num < 0) { return CommitResult.InvalidInventory; } if (!Progress.TryRead(data, out var progress)) { return CommitResult.InvalidProgress; } if (!progress.TryAward(stat, bonus, cap, out var next)) { return CommitResult.Limit; } string value = next.ToString(); data["kalel.bosspotions.progress.v2"] = value; stack--; if (stack == 0) { items.RemoveAt(num); } awarded = next; return CommitResult.Success; } } [BepInPlugin("kalel.bosspotions", "Boss Potions", "0.2.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { internal sealed class Potion { internal string Id; internal Stat Stat; internal int Tier; internal int Bonus; internal string Recipe; } public const string Guid = "kalel.bosspotions"; internal static Plugin Instance; internal static FieldInfo CustomDataField; internal static MethodInfo FoodMethod; internal static MethodInfo UseMethod; internal static MethodInfo CarryMethod; private Harmony harmony; private FieldInfo inventoryField; private MethodInfo inventoryChanged; private bool runtimeReady; private bool consuming; private bool preservationMode; private bool warnedRuntime; private readonly HashSet<string> registered = new HashSet<string>(StringComparer.Ordinal); private bool warnedProgress; private ConfigEntry<string> station; private readonly Dictionary<Stat, ConfigEntry<int>> caps = new Dictionary<Stat, ConfigEntry<int>>(); internal readonly Dictionary<string, Potion> Potions = new Dictionary<string, Potion>(StringComparer.Ordinal); private void Awake() { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown //IL_0489: Unknown result type (might be due to invalid IL or missing references) //IL_0493: Expected O, but got Unknown Instance = this; station = ((BaseUnityPlugin)this).Config.Bind<string>("Crafting", "Station", "piece_cauldron", "Crafting station prefab. Restart game after edits."); preservationMode = ((BaseUnityPlugin)this).Config.Bind<bool>("Safety", "PreservationMode", false, "Keep item definitions, but disable recipes, consumption and bonuses. Restart after edits. Keep this plugin installed.").Value; foreach (Stat value in Enum.GetValues(typeof(Stat))) { caps[value] = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", value.ToString(), 0, new ConfigDescription("Maximum awarded " + StatLabel(value) + "; 0 = no gameplay cap. Existing points are retained.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16777216), Array.Empty<object>())); } string[] bosses = new string[7] { "TrophyEikthyr", "TrophyTheElder", "TrophyBonemass", "TrophyDragonQueen", "TrophyGoblinKing", "TrophySeekerQueen", "TrophyFader" }; Define(Stat.Stamina, 1, 10, Recipe(bosses, 2, 1)); Define(Stat.Health, 1, 5, Recipe(bosses, 3, 1)); Define(Stat.Stamina, 2, 20, Recipe(bosses, 4, 2)); Define(Stat.Health, 2, 10, Recipe(bosses, 5, 2)); Define(Stat.Stamina, 3, 30, Recipe(bosses, 7, 3) + ";FrozenKingDrop:3"); Define(Stat.Health, 3, 15, Recipe(bosses, 7, 4) + ";FrozenKingDrop:4"); Define(Stat.CarryWeight, 1, 10, Recipe(bosses, 2, 1)); Define(Stat.CarryWeight, 2, 20, Recipe(bosses, 4, 2)); Define(Stat.CarryWeight, 3, 50, Recipe(bosses, 7, 3) + ";FrozenKingDrop:3"); Define(Stat.Eitr, 1, 5, Recipe(bosses, 6, 1)); Define(Stat.Eitr, 2, 10, Recipe(bosses, 7, 2)); Define(Stat.Eitr, 3, 15, Recipe(bosses, 7, 4) + ";FrozenKingDrop:4"); PrefabManager.OnVanillaPrefabsAvailable += RegisterItems; if (preservationMode) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Preservation mode: item IDs remain registered; recipes, consumption and bonuses are disabled. Stored points are not edited."); return; } try { CustomDataField = AccessTools.Field(typeof(Player), "m_customData"); FoodMethod = AccessTools.Method(typeof(Player), "GetTotalFoodValue", new Type[3] { typeof(float).MakeByRefType(), typeof(float).MakeByRefType(), typeof(float).MakeByRefType() }, (Type[])null); UseMethod = AccessTools.Method(typeof(Humanoid), "UseItem", new Type[3] { typeof(Inventory), typeof(ItemData), typeof(bool) }, (Type[])null); CarryMethod = AccessTools.Method(typeof(Player), "GetMaxCarryWeight", Type.EmptyTypes, (Type[])null); inventoryField = AccessTools.Field(typeof(Inventory), "m_inventory"); inventoryChanged = AccessTools.Method(typeof(Inventory), "Changed", new Type[2] { typeof(bool), typeof(bool) }, (Type[])null); if (CustomDataField == null || CustomDataField.FieldType != typeof(Dictionary<string, string>) || FoodMethod == null || FoodMethod.ReturnType != typeof(void) || UseMethod == null || UseMethod.ReturnType != typeof(void) || CarryMethod == null || CarryMethod.ReturnType != typeof(float) || inventoryField == null || inventoryField.FieldType != typeof(List<ItemData>) || inventoryChanged == null || inventoryChanged.ReturnType != typeof(void)) { throw new NotSupportedException("Game API differs. Keeping inert item definitions; no crafting/consumption/bonuses. Send game references."); } harmony = new Harmony("kalel.bosspotions"); harmony.PatchAll(typeof(Plugin).Assembly); runtimeReady = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Boss Potions 0.2.1 loaded: 12 permanent potions."); } catch (Exception ex) { runtimeReady = false; try { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogError((object)ex2); } ((BaseUnityPlugin)this).Logger.LogError((object)ex); } } private static string Recipe(string[] bosses, int count, int quantity) { return string.Join(";", from x in bosses.Take(count) select x + ":" + quantity.ToString(CultureInfo.InvariantCulture)); } private void Define(Stat stat, int tier, int bonus, string recipe) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown string text = "BP_" + stat.ToString() + "_T" + tier; ConfigEntry<int> val = ((BaseUnityPlugin)this).Config.Bind<int>(text, "Bonus", bonus, new ConfigDescription("Permanent points per potion. Restart after edits.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 1000000), Array.Empty<object>())); ConfigEntry<string> val2 = ((BaseUnityPlugin)this).Config.Bind<string>(text, "Recipe", recipe, "Prefab:quantity;Prefab:quantity. All ingredients required. Restart after edits."); Potions.Add(text, new Potion { Id = text, Stat = stat, Tier = tier, Bonus = val.Value, Recipe = val2.Value }); } private void RegisterItems() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Expected O, but got Unknown //IL_03c2: Unknown result type (might be due to invalid IL or missing references) //IL_03cc: Expected O, but got Unknown PrefabManager.OnVanillaPrefabsAvailable -= RegisterItems; foreach (Potion value in Potions.Values) { try { string text = StatLabel(value.Stat); CustomItem val = new CustomItem(value.Id, BasePrefab(value.Stat)); SharedData shared = val.ItemDrop.m_itemData.m_shared; shared.m_name = "Permanent " + text + " Potion - Tier " + value.Tier; shared.m_description = "Permanently increases maximum " + text + " by " + value.Bonus + ". Stacks per character. Does not occupy a food slot.\nID: " + value.Id; shared.m_maxStackSize = 20; shared.m_weight = 1f; shared.m_itemType = (ItemType)((!runtimeReady) ? 1 : 2); shared.m_consumeStatusEffect = null; shared.m_food = 0f; shared.m_foodStamina = 0f; shared.m_foodEitr = 0f; shared.m_foodRegen = 0f; if (!ItemManager.Instance.AddItem(val)) { throw new InvalidOperationException("Item ID was not accepted by Jotunn: " + value.Id); } registered.Add(value.Id); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Registered item " + value.Id)); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("ITEM REGISTRATION FAILED: " + value.Id + ". Do not load/save an existing character or world containing these items until fixed. " + ex)); } } if (!runtimeReady || registered.Count != Potions.Count) { runtimeReady = false; return; } foreach (Potion value2 in Potions.Values) { try { GameObject prefab = PrefabManager.Instance.GetPrefab(station.Value); if ((Object)(object)prefab == (Object)null || (Object)(object)prefab.GetComponent<CraftingStation>() == (Object)null) { throw new InvalidOperationException("Crafting station missing: " + station.Value); } RecipeConfig val2 = new RecipeConfig { Name = "Recipe_" + value2.Id, Item = value2.Id, Amount = 1, CraftingStation = station.Value, MinStationLevel = 1 }; if (string.IsNullOrWhiteSpace(value2.Recipe) || value2.Recipe.Length > 4096) { throw new FormatException("Empty or oversized recipe."); } HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); string[] array = value2.Recipe.Split(';'); foreach (string text2 in array) { string[] array2 = text2.Trim().Split(':'); if (array2.Length != 2 || !int.TryParse(array2[1].Trim(), NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result < 1 || result > 1000000) { throw new FormatException("Invalid recipe entry: " + text2); } string text3 = array2[0].Trim(); GameObject prefab2 = PrefabManager.Instance.GetPrefab(text3); if (!hashSet.Add(text3) || (Object)(object)prefab2 == (Object)null || (Object)(object)prefab2.GetComponent<ItemDrop>() == (Object)null) { throw new InvalidOperationException("Missing/duplicate ingredient: " + text3); } val2.AddRequirement(text3, result, 0); } if (!ItemManager.Instance.AddRecipe(new CustomRecipe(val2))) { throw new InvalidOperationException("Recipe was not accepted by Jotunn."); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Registered recipe " + value2.Id)); } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogError((object)("Recipe disabled for " + value2.Id + "; saved item definition remains registered. " + ex2)); } } } internal bool TryProgress(Player player, out Dictionary<string, string> data, out Progress progress) { data = null; progress = default(Progress); try { if ((Object)(object)player != (Object)null && CustomDataField != null) { data = CustomDataField.GetValue(player) as Dictionary<string, string>; } if (Progress.TryRead(data, out progress)) { return true; } } catch (Exception error) { WarnRuntime(error); } if (!warnedProgress) { warnedProgress = true; ((BaseUnityPlugin)this).Logger.LogError((object)"Missing/invalid permanent-potion data. Existing bytes preserved; no automatic reset or repair."); } return false; } internal void WarnRuntime(Exception error) { if (!warnedRuntime) { warnedRuntime = true; ((BaseUnityPlugin)this).Logger.LogError((object)("Boss Potions runtime error (further repeated messages suppressed): " + error)); } } internal static bool IsPotion(ItemData item) { if (item == null || (Object)(object)item.m_dropPrefab == (Object)null) { return false; } switch (((Object)item.m_dropPrefab).name) { case "BP_Stamina_T1": case "BP_Stamina_T2": case "BP_Stamina_T3": case "BP_Health_T1": case "BP_Health_T2": case "BP_Health_T3": case "BP_CarryWeight_T1": case "BP_CarryWeight_T2": case "BP_CarryWeight_T3": case "BP_Eitr_T1": case "BP_Eitr_T2": case "BP_Eitr_T3": return true; default: return false; } } internal bool Consume(Humanoid humanoid, Inventory inventory, ItemData item) { if (!runtimeReady || preservationMode || consuming) { return false; } if (!Potions.TryGetValue(((Object)item.m_dropPrefab).name, out var value)) { return false; } Player val = (Player)(object)((humanoid is Player) ? humanoid : null); if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer || ((Character)val).IsDead() || ((Character)val).InAttack() || ((Character)val).InDodge()) { return false; } Inventory inventory2 = ((Humanoid)val).GetInventory(); if (inventory2 == null || (inventory != null && inventory != inventory2)) { return false; } consuming = true; try { if (!TryProgress(val, out var data, out var _)) { return false; } List<ItemData> items = inventoryField.GetValue(inventory2) as List<ItemData>; if (Consumption.Commit(data, items, item, ref item.m_stack, value.Stat, value.Bonus, caps[value.Stat].Value, out var awarded) != CommitResult.Success) { ((Character)val).Message((MessageType)2, "Potion not consumed: limit reached or invalid data.", 0, (Sprite)null, false); return false; } try { inventoryChanged.Invoke(inventory2, new object[2] { false, false }); } catch (Exception error) { WarnRuntime(error); } try { ((Character)val).Message((MessageType)2, ProgressDisplay.Consumed(awarded, value.Stat, value.Bonus), 0, (Sprite)null, false); } catch (Exception error2) { WarnRuntime(error2); } return false; } finally { consuming = false; } } internal void AddStats(Player player, ref float health, ref float stamina, ref float eitr) { if (runtimeReady && !preservationMode && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && TryProgress(player, out var _, out var progress)) { float num = health + (float)progress.Health; float num2 = stamina + (float)progress.Stamina; float num3 = eitr + (float)progress.Eitr; if (!float.IsNaN(num3) && !float.IsInfinity(num3) && !float.IsNaN(num) && !float.IsInfinity(num) && !float.IsNaN(num2) && !float.IsInfinity(num2)) { health = num; stamina = num2; eitr = num3; } } } internal void AddCarryWeight(Player player, ref float maximum) { if (runtimeReady && !preservationMode && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && TryProgress(player, out var _, out var progress)) { float num = maximum + (float)progress.CarryWeight; if (!float.IsNaN(num) && !float.IsInfinity(num)) { maximum = num; } } } private static string StatLabel(Stat stat) { if (stat == Stat.CarryWeight) { return "Carry Weight"; } return stat.ToString(); } private static string BasePrefab(Stat stat) { return stat switch { Stat.Stamina => "MeadStaminaMinor", Stat.Health => "MeadHealthMinor", Stat.Eitr => "MeadEitrMinor", Stat.CarryWeight => "MeadStrength", _ => throw new ArgumentOutOfRangeException("stat"), }; } private void OnDestroy() { PrefabManager.OnVanillaPrefabsAvailable -= RegisterItems; Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } } [HarmonyPatch] internal static class UsePotionPatch { private static MethodBase TargetMethod() { return Plugin.UseMethod; } private static bool Prefix(Humanoid __instance, Inventory __0, ItemData __1) { if (!Plugin.IsPotion(__1)) { return true; } Plugin instance = Plugin.Instance; if ((Object)(object)instance == (Object)null) { return false; } try { return instance.Consume(__instance, __0, __1); } catch (Exception error) { instance.WarnRuntime(error); return false; } } } [HarmonyPatch] internal static class PermanentStatsPatch { private static MethodBase TargetMethod() { return Plugin.FoodMethod; } private static void Postfix(Player __instance, ref float __0, ref float __1, ref float __2) { Plugin instance = Plugin.Instance; if ((Object)(object)instance == (Object)null) { return; } try { instance.AddStats(__instance, ref __0, ref __1, ref __2); } catch (Exception error) { instance.WarnRuntime(error); } } } [HarmonyPatch] internal static class PermanentCarryWeightPatch { private static MethodBase TargetMethod() { return Plugin.CarryMethod; } private static void Postfix(Player __instance, ref float __result) { Plugin instance = Plugin.Instance; if ((Object)(object)instance == (Object)null) { return; } try { instance.AddCarryWeight(__instance, ref __result); } catch (Exception error) { instance.WarnRuntime(error); } } } internal enum Stat { Health, Stamina, CarryWeight, Eitr } internal struct Progress { internal int Health; internal int Stamina; internal int CarryWeight; internal int Eitr; internal const string LegacyKey = "kalel.bosspotions.progress.v1"; internal const string Key = "kalel.bosspotions.progress.v2"; internal const int MaximumPoints = 16777216; internal static bool TryRead(Dictionary<string, string> data, out Progress progress) { progress = default(Progress); if (data == null) { return false; } foreach (string key in data.Keys) { if (key.StartsWith("kalel.bosspotions.progress.", StringComparison.Ordinal) && key != "kalel.bosspotions.progress.v2" && key != "kalel.bosspotions.progress.v1") { return false; } } if (data.TryGetValue("kalel.bosspotions.progress.v2", out var value)) { return TryParse(value, out progress); } if (data.TryGetValue("kalel.bosspotions.progress.v1", out value)) { return TryParseLegacy(value, out progress); } return true; } private static bool Number(string text, out int number) { if (int.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out number) && number >= 0) { return number <= 16777216; } return false; } internal static bool TryParseLegacy(string value, out Progress progress) { progress = default(Progress); if (value == null || value.Length > 32) { return false; } string[] array = value.Split('|'); if (array.Length != 3 || array[0] != "1" || !Number(array[1], out var number) || !Number(array[2], out var number2)) { return false; } progress.Health = number; progress.Stamina = number2; return true; } internal static bool TryParse(string value, out Progress progress) { progress = default(Progress); if (value == null || value.Length > 64) { return false; } string[] array = value.Split('|'); if (array.Length != 5 || array[0] != "2" || !Number(array[1], out var number) || !Number(array[2], out var number2) || !Number(array[3], out var number3) || !Number(array[4], out var number4)) { return false; } progress.Health = number; progress.Stamina = number2; progress.CarryWeight = number3; progress.Eitr = number4; return true; } internal int Get(Stat stat) { return stat switch { Stat.Health => Health, Stat.Stamina => Stamina, Stat.CarryWeight => CarryWeight, Stat.Eitr => Eitr, _ => throw new ArgumentOutOfRangeException("stat"), }; } internal bool TryAward(Stat stat, int amount, int cap, out Progress next) { next = this; if (!Enum.IsDefined(typeof(Stat), stat) || Health < 0 || Stamina < 0 || CarryWeight < 0 || Eitr < 0 || Health > 16777216 || Stamina > 16777216 || CarryWeight > 16777216 || Eitr > 16777216 || amount <= 0 || cap < 0) { return false; } long num = (long)Get(stat) + (long)amount; if (num > 16777216 || (cap > 0 && num > cap)) { return false; } switch (stat) { case Stat.Health: next.Health = (int)num; break; case Stat.Stamina: next.Stamina = (int)num; break; case Stat.CarryWeight: next.CarryWeight = (int)num; break; case Stat.Eitr: next.Eitr = (int)num; break; } return true; } public override string ToString() { return string.Join("|", "2", Health.ToString(CultureInfo.InvariantCulture), Stamina.ToString(CultureInfo.InvariantCulture), CarryWeight.ToString(CultureInfo.InvariantCulture), Eitr.ToString(CultureInfo.InvariantCulture)); } } internal static class ProgressDisplay { internal static string Totals(Progress progress) { return "Permanent potion bonuses\nHealth: +" + Points(progress.Health) + "\nStamina: +" + Points(progress.Stamina) + "\nCarry Weight: +" + Points(progress.CarryWeight) + "\nEitr: +" + Points(progress.Eitr); } internal static string Consumed(Progress progress, Stat stat, int amount) { string text = ((stat == Stat.CarryWeight) ? "Carry Weight" : stat.ToString()); return "+" + Points(amount) + " " + text + "\n\n" + Totals(progress); } private static string Points(int value) { return value.ToString(CultureInfo.InvariantCulture); } } }