Decompiled source of BossPotions v0.2.1

BepInEx/plugins/BossPotions/BossPotions.dll

Decompiled a month ago
using 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);
		}
	}
}