Decompiled source of HearthPantry v1.0.4

plugins/HearthPantry.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn;
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: AssemblyCompany("blackhearthx")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Renews your manually chosen active meals, pauses food timers at the workbench or on a boat, and handles fight meads.")]
[assembly: AssemblyFileVersion("1.0.4.0")]
[assembly: AssemblyInformationalVersion("1.0.4+710d9fa2fcda5022c6fdd04e1168ca335ee15905")]
[assembly: AssemblyProduct("HearthPantry")]
[assembly: AssemblyTitle("HearthPantry")]
[assembly: AssemblyVersion("1.0.4.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 HearthPantry
{
	internal static class FoodManager
	{
		private const float WorkbenchRange = 20f;

		private static readonly HashSet<string> _expiryShown = new HashSet<string>();

		private static readonly HashSet<string> _lowSupplyShown = new HashSet<string>();

		private static readonly HashSet<string> _seenThisTick = new HashSet<string>();

		private static bool _nearWorkbench;

		private static bool _onBoat;

		private static Player _trackedPlayer;

		private static bool _foodRateResolved;

		private static FieldRef<float> _foodRateRef;

		private static bool _tcResolved;

		private static MethodInfo _tcMultiplierGetter;

		private const string TimeControlGuid = "drummercraig.time_control";

		public static bool NearWorkbench => _nearWorkbench;

		public static bool OnBoat => _onBoat;

		public static void Tick()
		{
			Player localPlayer = Player.m_localPlayer;
			if (localPlayer != _trackedPlayer)
			{
				_trackedPlayer = localPlayer;
				_expiryShown.Clear();
				_lowSupplyShown.Clear();
			}
			_nearWorkbench = PluginConfig.ModEnabled.Value && (Object)(object)localPlayer != (Object)null && PluginConfig.PauseNearWorkbench.Value && IsNearWorkbench(localPlayer);
			_onBoat = PluginConfig.ModEnabled.Value && (Object)(object)localPlayer != (Object)null && PluginConfig.PauseOnBoat.Value && ((Character)localPlayer).IsAttachedToShip();
			if (PluginConfig.ModEnabled.Value && !((Object)(object)localPlayer == (Object)null) && CanAutomate(localPlayer) && (PluginConfig.AutoEat.Value || PluginConfig.ExpiryNotify.Value))
			{
				CheckFoods(localPlayer);
			}
		}

		internal static bool IsVomiting(Player player)
		{
			object obj;
			if (player == null)
			{
				obj = null;
			}
			else
			{
				SEMan sEMan = ((Character)player).GetSEMan();
				obj = ((sEMan != null) ? sEMan.GetStatusEffects() : null);
			}
			List<StatusEffect> list = (List<StatusEffect>)obj;
			if (list == null)
			{
				return false;
			}
			foreach (StatusEffect item in list)
			{
				if (item is SE_Puke)
				{
					return true;
				}
			}
			return false;
		}

		internal static bool CanAutomate(Player player)
		{
			if ((Object)(object)player != (Object)null && !((Character)player).IsDead() && !((Character)player).IsTeleporting() && !((Character)player).InCutscene())
			{
				return !IsVomiting(player);
			}
			return false;
		}

		internal static bool ShouldPauseTimers(Player player)
		{
			if (PluginConfig.ModEnabled.Value && (Object)(object)player == (Object)(object)Player.m_localPlayer && CanAutomate(player))
			{
				if (!PluginConfig.PauseOnBoat.Value || !((Character)player).IsAttachedToShip())
				{
					if (PluginConfig.PauseNearWorkbench.Value)
					{
						return _nearWorkbench;
					}
					return false;
				}
				return true;
			}
			return false;
		}

		internal static float ScaleFoodRate(float rate, Player player)
		{
			if (!PluginConfig.ModEnabled.Value || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return rate;
			}
			if (ShouldPauseTimers(player))
			{
				return 0f;
			}
			return PantryPolicy.ScaleRate(rate, PluginConfig.ScaleWithTimeControl.Value ? GetTimeControlMultiplier() : 1f);
		}

		public static List<Food> CaptureFoodsForDeath(Player player)
		{
			if (!PluginConfig.ModEnabled.Value || !PluginConfig.KeepFoodOnDeath.Value || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return null;
			}
			List<Food> foods = player.GetFoods();
			if (foods == null || foods.Count == 0)
			{
				return null;
			}
			return new List<Food>(foods);
		}

		public static void RestoreFoodsAfterDeath(Player player, List<Food> saved)
		{
			if (saved != null && saved.Count != 0 && !((Object)(object)player == (Object)null))
			{
				List<Food> foods = player.GetFoods();
				if (foods != null)
				{
					foods.Clear();
					foods.AddRange(saved);
				}
			}
		}

		private static void CheckFoods(Player player)
		{
			Inventory inventory = ((Humanoid)player).GetInventory();
			List<Food> foods = player.GetFoods();
			if (inventory == null || foods == null)
			{
				return;
			}
			_seenThisTick.Clear();
			float foodRate = GetFoodRate();
			for (int i = 0; i < foods.Count; i++)
			{
				Food val = foods[i];
				if (val?.m_item?.m_shared == null)
				{
					continue;
				}
				string name = val.m_item.m_shared.m_name;
				_seenThisTick.Add(name);
				float foodBurnTime = val.m_item.m_shared.m_foodBurnTime;
				float num = ((foodBurnTime > 0f) ? (val.m_time / foodBurnTime * 100f) : 0f);
				if (!val.CanEatAgain())
				{
					_expiryShown.Remove(name);
					_lowSupplyShown.Remove(name);
					continue;
				}
				if (PluginConfig.ExpiryNotify.Value && !_expiryShown.Contains(name) && num <= (float)PluginConfig.ExpiryPercent.Value)
				{
					string text = Localization.instance.Localize(name);
					int num2 = Mathf.CeilToInt(val.m_time / foodRate / 60f);
					ShowHud((num2 <= 1) ? ModLocalization.L("hearthpantry_expiry_almost", text) : ModLocalization.L("hearthpantry_expiry_mins", text, num2));
					_expiryShown.Add(name);
				}
				if (!PluginConfig.AutoEat.Value || num > (float)PluginConfig.AutoEatPercent.Value)
				{
					continue;
				}
				ItemData item = inventory.GetItem(name, -1, false);
				if (item != null && !(item.m_shared.m_consumeStatusEffect is SE_Puke) && ((Humanoid)player).ConsumeItem(inventory, item, false))
				{
					_expiryShown.Remove(name);
					if (PluginConfig.AutoEatNotify.Value)
					{
						string text2 = Localization.instance.Localize(name);
						ShowHud(ModLocalization.L("hearthpantry_reeaten", text2));
					}
					MaybeLowSupply(inventory, name);
				}
			}
			_expiryShown.RemoveWhere((string n) => !_seenThisTick.Contains(n));
			_lowSupplyShown.RemoveWhere((string n) => !_seenThisTick.Contains(n));
		}

		private static void MaybeLowSupply(Inventory inventory, string name)
		{
			if (PluginConfig.LowSupplyNotify.Value && PluginConfig.LowSupplyCount.Value > 0 && !_lowSupplyShown.Contains(name))
			{
				int num = inventory.CountItems(name, -1, true);
				if (num <= PluginConfig.LowSupplyCount.Value)
				{
					string text = Localization.instance.Localize(name);
					ShowHud((num <= 0) ? ModLocalization.L("hearthpantry_out", text) : ((num == 1) ? ModLocalization.L("hearthpantry_last", text) : ModLocalization.L("hearthpantry_low", num, text)));
					_lowSupplyShown.Add(name);
				}
			}
		}

		private static bool IsNearWorkbench(Player player)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			return (Object)(object)CraftingStation.FindClosestStationInRange("$piece_workbench", ((Component)player).transform.position, 20f) != (Object)null;
		}

		private static void ShowHud(string message)
		{
			if (!((Object)(object)MessageHud.instance == (Object)null))
			{
				MessageHud.instance.ShowMessage((MessageType)2, message, 0, (Sprite)null, false, true);
			}
		}

		private static float GetFoodRate()
		{
			if (!_foodRateResolved)
			{
				_foodRateResolved = true;
				try
				{
					FieldInfo fieldInfo = AccessTools.Field(typeof(Game), "m_foodRate");
					if (fieldInfo != null)
					{
						_foodRateRef = AccessTools.StaticFieldRefAccess<float>(fieldInfo);
					}
				}
				catch (Exception ex)
				{
					_foodRateRef = null;
					HearthPantryPlugin.Log.LogWarning((object)("resolving Game.m_foodRate failed: " + ex.Message));
				}
			}
			if (_foodRateRef == null)
			{
				return 1f;
			}
			try
			{
				float num = ScaleFoodRate(_foodRateRef.Invoke(), Player.m_localPlayer);
				return (num > 0f) ? num : 1f;
			}
			catch
			{
				_foodRateRef = null;
				return 1f;
			}
		}

		private static float GetTimeControlMultiplier()
		{
			if (!_tcResolved)
			{
				_tcResolved = true;
				try
				{
					if (Chainloader.PluginInfos.ContainsKey("drummercraig.time_control"))
					{
						Type type = AccessTools.TypeByName("TimeControl.TimeControlConfig");
						if (type != null)
						{
							_tcMultiplierGetter = AccessTools.PropertyGetter(type, "EffectiveMultiplier");
						}
						HearthPantryPlugin.Log.LogInfo((object)((_tcMultiplierGetter != null) ? "TimeControl detected; food duration will scale with its multiplier." : "TimeControl detected but EffectiveMultiplier could not be resolved."));
					}
				}
				catch (Exception ex)
				{
					_tcMultiplierGetter = null;
					HearthPantryPlugin.Log.LogWarning((object)("TimeControl detection failed: " + ex.Message));
				}
			}
			if (_tcMultiplierGetter == null)
			{
				return 1f;
			}
			try
			{
				return PantryPolicy.ValidMultiplier((float)_tcMultiplierGetter.Invoke(null, null));
			}
			catch
			{
				return 1f;
			}
		}
	}
	internal static class MeadManager
	{
		private const float MeleeRangeSq = 9f;

		private static float _lastEnemyHitTime = float.MinValue;

		private static readonly Queue<float> _frostHitTimes = new Queue<float>();

		private static readonly List<Character> _nearby = new List<Character>();

		private static Player _trackedPlayer;

		private static void RefreshState()
		{
			Player localPlayer = Player.m_localPlayer;
			if (localPlayer != _trackedPlayer || !PluginConfig.ModEnabled.Value || !FoodManager.CanAutomate(localPlayer))
			{
				_trackedPlayer = localPlayer;
				_lastEnemyHitTime = float.MinValue;
				_frostHitTimes.Clear();
			}
			float num = Time.time - PluginConfig.FrostMeadTickWindow.Value;
			while (_frostHitTimes.Count > 0 && _frostHitTimes.Peek() < num)
			{
				_frostHitTimes.Dequeue();
			}
			if (!PluginConfig.AutoFrostMead.Value)
			{
				_frostHitTimes.Clear();
			}
		}

		public static void OnDamage(Character character, HitData hit)
		{
			if ((Object)(object)character != (Object)(object)Player.m_localPlayer || hit == null)
			{
				return;
			}
			RefreshState();
			if (!PluginConfig.ModEnabled.Value || !FoodManager.CanAutomate(Player.m_localPlayer))
			{
				return;
			}
			Character attacker = hit.GetAttacker();
			if ((Object)(object)attacker != (Object)null && !attacker.IsPlayer() && BaseAI.IsEnemy(attacker, character))
			{
				_lastEnemyHitTime = Time.time;
			}
			if (PluginConfig.AutoFrostMead.Value && hit.m_damage.m_frost > 0f)
			{
				_frostHitTimes.Enqueue(Time.time);
				while (_frostHitTimes.Count > PluginConfig.FrostMeadTickCount.Value)
				{
					_frostHitTimes.Dequeue();
				}
			}
		}

		public static void Tick()
		{
			RefreshState();
			if (PluginConfig.ModEnabled.Value && FoodManager.CanAutomate(Player.m_localPlayer))
			{
				CheckHealthMead();
				CheckProximityResistMeads();
				CheckFrostResistMead();
			}
		}

		private static void CheckHealthMead()
		{
			if (!PluginConfig.AutoHealthMead.Value)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).GetHealthPercentage() * 100f >= (float)PluginConfig.HealthMeadThreshold.Value || (PluginConfig.HealthMeadOnlyEnemyHit.Value && Time.time - _lastEnemyHitTime > PluginConfig.HealthMeadEnemyHitWindow.Value))
			{
				return;
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			SEMan sEMan = ((Character)localPlayer).GetSEMan();
			float maxHealth = ((Character)localPlayer).GetMaxHealth();
			foreach (ItemData healthMead in GetHealthMeads(inventory))
			{
				StatusEffect consumeStatusEffect = healthMead.m_shared.m_consumeStatusEffect;
				if (EffectAlreadyActive(sEMan, consumeStatusEffect))
				{
					continue;
				}
				if (PluginConfig.HealthMeadRequireMaxHealth.Value)
				{
					SE_Stats val = (SE_Stats)(object)((consumeStatusEffect is SE_Stats) ? consumeStatusEffect : null);
					if (val != null && maxHealth < val.m_healthOverTime)
					{
						continue;
					}
				}
				if (((Humanoid)localPlayer).ConsumeItem(inventory, healthMead, false))
				{
					if (PluginConfig.HealthMeadNotify.Value)
					{
						ShowHud(ModLocalization.L("hearthpantry_drank", Localized(healthMead)));
					}
					break;
				}
			}
		}

		private static void CheckProximityResistMeads()
		{
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			bool value = PluginConfig.AutoPoisonMead.Value;
			bool value2 = PluginConfig.AutoFireMead.Value;
			if (!value && !value2)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			float num = 0f;
			if (value)
			{
				num = Mathf.Max(num, PluginConfig.PoisonMeadRange.Value);
			}
			if (value2)
			{
				num = Mathf.Max(num, PluginConfig.FireMeadRange.Value);
			}
			bool flag = false;
			bool flag2 = false;
			_nearby.Clear();
			Character.GetCharactersInRange(((Component)localPlayer).transform.position, num, _nearby);
			float num2 = PluginConfig.PoisonMeadRange.Value * PluginConfig.PoisonMeadRange.Value;
			float num3 = PluginConfig.FireMeadRange.Value * PluginConfig.FireMeadRange.Value;
			foreach (Character item in _nearby)
			{
				if ((Object)(object)item == (Object)null || item.IsPlayer() || item.IsDead())
				{
					continue;
				}
				Vector3 val = ((Component)item).transform.position - ((Component)localPlayer).transform.position;
				float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
				if (IsThreatening(item, localPlayer, sqrMagnitude))
				{
					GetThreatDamage(item, out var poison, out var fire);
					if (value && !flag && sqrMagnitude <= num2 && poison)
					{
						flag = true;
					}
					if (value2 && !flag2 && sqrMagnitude <= num3 && fire)
					{
						flag2 = true;
					}
					if (flag && flag2)
					{
						break;
					}
				}
			}
			if (flag)
			{
				ConsumeResistMead(localPlayer, PluginConfig.PoisonMeadNotify, (DamageType)256);
			}
			if (flag2)
			{
				ConsumeResistMead(localPlayer, PluginConfig.FireMeadNotify, (DamageType)32);
			}
		}

		private static void CheckFrostResistMead()
		{
			if (!PluginConfig.AutoFrostMead.Value)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			float num = Time.time - PluginConfig.FrostMeadTickWindow.Value;
			while (_frostHitTimes.Count > 0 && _frostHitTimes.Peek() < num)
			{
				_frostHitTimes.Dequeue();
			}
			SEMan sEMan = ((Character)localPlayer).GetSEMan();
			bool num2 = sEMan.HaveStatusEffect(StringExtensionMethods.GetStableHashCode("Freezing"));
			bool flag = _frostHitTimes.Count >= PluginConfig.FrostMeadTickCount.Value;
			if (!num2 && !flag)
			{
				return;
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			foreach (ItemData resistMead in GetResistMeads(inventory, (DamageType)64))
			{
				StatusEffect consumeStatusEffect = resistMead.m_shared.m_consumeStatusEffect;
				if (EffectAlreadyActive(sEMan, consumeStatusEffect))
				{
					_frostHitTimes.Clear();
					break;
				}
				if (((Humanoid)localPlayer).ConsumeItem(inventory, resistMead, false))
				{
					_frostHitTimes.Clear();
					if (PluginConfig.FrostMeadNotify.Value)
					{
						ShowHud(ModLocalization.L("hearthpantry_drank", Localized(resistMead)));
					}
					break;
				}
			}
		}

		private static void ConsumeResistMead(Player player, ConfigEntry<bool> notify, DamageType type)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			Inventory inventory = ((Humanoid)player).GetInventory();
			SEMan sEMan = ((Character)player).GetSEMan();
			foreach (ItemData resistMead in GetResistMeads(inventory, type))
			{
				StatusEffect consumeStatusEffect = resistMead.m_shared.m_consumeStatusEffect;
				if (EffectAlreadyActive(sEMan, consumeStatusEffect))
				{
					break;
				}
				if (((Humanoid)player).ConsumeItem(inventory, resistMead, false))
				{
					if (notify.Value)
					{
						ShowHud(ModLocalization.L("hearthpantry_drank", Localized(resistMead)));
					}
					break;
				}
			}
		}

		private static bool IsThreatening(Character c, Player localPlayer, float distSq)
		{
			if (!BaseAI.IsEnemy(c, (Character)(object)localPlayer))
			{
				return false;
			}
			if (distSq <= 9f)
			{
				return true;
			}
			MonsterAI component = ((Component)c).GetComponent<MonsterAI>();
			if ((Object)(object)component != (Object)null)
			{
				return (Object)(object)((BaseAI)component).GetTargetCreature() == (Object)(object)localPlayer;
			}
			return false;
		}

		private static bool EffectAlreadyActive(SEMan seman, StatusEffect effect)
		{
			if ((Object)(object)effect != (Object)null)
			{
				if (!seman.HaveStatusEffect(effect.NameHash()))
				{
					if (!string.IsNullOrEmpty(effect.m_category))
					{
						return seman.HaveStatusEffectCategory(effect.m_category);
					}
					return false;
				}
				return true;
			}
			return false;
		}

		private static void GetThreatDamage(Character c, out bool poison, out bool fire)
		{
			poison = false;
			fire = false;
			Humanoid val = (Humanoid)(object)((c is Humanoid) ? c : null);
			if (val == null)
			{
				return;
			}
			foreach (ItemData allItem in val.GetInventory().GetAllItems())
			{
				if (allItem.m_shared.m_damages.m_poison > 0f)
				{
					poison = true;
				}
				if (allItem.m_shared.m_damages.m_fire > 0f)
				{
					fire = true;
				}
			}
			ItemData currentWeapon = val.GetCurrentWeapon();
			if (currentWeapon != null)
			{
				if (currentWeapon.m_shared.m_damages.m_poison > 0f)
				{
					poison = true;
				}
				if (currentWeapon.m_shared.m_damages.m_fire > 0f)
				{
					fire = true;
				}
			}
		}

		private static List<ItemData> GetHealthMeads(Inventory inventory)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Invalid comparison between Unknown and I4
			List<ItemData> list = new List<ItemData>();
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if ((int)allItem.m_shared.m_itemType == 2 && !(allItem.m_shared.m_consumeStatusEffect is SE_Puke))
				{
					StatusEffect consumeStatusEffect = allItem.m_shared.m_consumeStatusEffect;
					SE_Stats val = (SE_Stats)(object)((consumeStatusEffect is SE_Stats) ? consumeStatusEffect : null);
					if (val != null && val.m_healthOverTime > 0f)
					{
						list.Add(allItem);
					}
				}
			}
			list.Sort(delegate(ItemData a, ItemData b)
			{
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0021: Unknown result type (might be due to invalid IL or missing references)
				float healthOverTime = ((SE_Stats)a.m_shared.m_consumeStatusEffect).m_healthOverTime;
				float healthOverTime2 = ((SE_Stats)b.m_shared.m_consumeStatusEffect).m_healthOverTime;
				return healthOverTime2.CompareTo(healthOverTime);
			});
			return list;
		}

		private static List<ItemData> GetResistMeads(Inventory inventory, DamageType type)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Invalid comparison between Unknown and I4
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			List<ItemData> list = new List<ItemData>();
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if ((int)allItem.m_shared.m_itemType != 2 || allItem.m_shared.m_consumeStatusEffect is SE_Puke)
				{
					continue;
				}
				StatusEffect consumeStatusEffect = allItem.m_shared.m_consumeStatusEffect;
				SE_Stats val = (SE_Stats)(object)((consumeStatusEffect is SE_Stats) ? consumeStatusEffect : null);
				if (val == null)
				{
					continue;
				}
				foreach (DamageModPair mod in val.m_mods)
				{
					if (mod.m_type == type && IsProtective(mod.m_modifier))
					{
						list.Add(allItem);
						break;
					}
				}
			}
			return list;
		}

		internal static bool IsProtective(DamageModifier modifier)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			if ((int)modifier != 1 && (int)modifier != 5 && (int)modifier != 7)
			{
				return (int)modifier == 3;
			}
			return true;
		}

		private static string Localized(ItemData item)
		{
			return Localization.instance.Localize(item.m_shared.m_name);
		}

		private static void ShowHud(string message)
		{
			if (!((Object)(object)MessageHud.instance == (Object)null))
			{
				MessageHud.instance.ShowMessage((MessageType)2, message, 0, (Sprite)null, false, true);
			}
		}
	}
	internal static class ModLocalization
	{
		private static readonly string[] Languages = new string[17]
		{
			"English", "Portuguese_Brazilian", "Portuguese_European", "German", "French", "Spanish", "Russian", "Polish", "Dutch", "Italian",
			"Swedish", "Turkish", "Ukrainian", "Chinese", "Chinese_Trad", "Japanese", "Korean"
		};

		internal static void Register()
		{
			CustomLocalization localization = LocalizationManager.Instance.GetLocalization();
			int num = 0;
			bool flag = false;
			string[] languages = Languages;
			foreach (string text in languages)
			{
				if (TryLoad(localization, text))
				{
					num++;
					if (text == "English")
					{
						flag = true;
					}
				}
			}
			if (!flag)
			{
				RegisterInlineEnglish(localization);
				Logger.LogWarning((object)"HearthPantry: Translations folder missing — English inline fallback");
			}
			else
			{
				Logger.LogInfo((object)$"HearthPantry: loaded localization for {num} language(s)");
			}
		}

		private static bool TryLoad(CustomLocalization loc, string language)
		{
			try
			{
				string text = FindPath(language);
				if (string.IsNullOrEmpty(text) || !File.Exists(text))
				{
					return false;
				}
				string text2 = File.ReadAllText(text);
				if (string.IsNullOrEmpty(text2))
				{
					return false;
				}
				loc.AddJsonFile(language, text2);
				return true;
			}
			catch (Exception ex)
			{
				Logger.LogWarning((object)("HearthPantry: failed loading " + language + " — " + ex.Message));
				return false;
			}
		}

		private static string FindPath(string language)
		{
			string path = Path.Combine("Translations", language, "hearthpantry.json");
			string text = Path.Combine(Path.GetDirectoryName(typeof(HearthPantryPlugin).Assembly.Location) ?? "", path);
			if (File.Exists(text))
			{
				return text;
			}
			string text2 = Path.Combine(Directory.GetCurrentDirectory(), path);
			if (!File.Exists(text2))
			{
				return null;
			}
			return text2;
		}

		private static void RegisterInlineEnglish(CustomLocalization loc)
		{
			string text = "English";
			loc.AddTranslation(ref text, new Dictionary<string, string>
			{
				{ "hearthpantry_expiry_almost", "{0} is almost gone" },
				{ "hearthpantry_expiry_mins", "{0} is running low — about {1} min left" },
				{ "hearthpantry_reeaten", "Had another {0}" },
				{ "hearthpantry_ate_fill", "Ate {0} from the pantry" },
				{ "hearthpantry_out", "Out of {0}" },
				{ "hearthpantry_last", "Last {0} in the bag" },
				{ "hearthpantry_low", "Only {0} {1} left" },
				{ "hearthpantry_drank", "Drank {0}" },
				{ "hearthpantry_toggle_on", "Pantry is watching your food" },
				{ "hearthpantry_toggle_off", "Pantry paused — eat when you want" }
			});
		}

		internal static string L(string token, params object[] args)
		{
			string text = ((Localization.instance != null) ? Localization.instance.Localize("$" + token) : ("$" + token));
			if (args == null || args.Length == 0)
			{
				return text;
			}
			try
			{
				return string.Format(text, args);
			}
			catch
			{
				return text;
			}
		}
	}
	internal static class PantryPolicy
	{
		internal static float ValidMultiplier(float multiplier)
		{
			if (!float.IsNaN(multiplier) && !float.IsInfinity(multiplier) && !(multiplier <= 0f))
			{
				return multiplier;
			}
			return 1f;
		}

		internal static float ScaleRate(float rate, float multiplier)
		{
			return rate / ValidMultiplier(multiplier);
		}
	}
	[HarmonyPatch]
	internal static class Patches
	{
		private static readonly MethodInfo TotalFood = AccessTools.Method(typeof(Player), "GetTotalFoodValue", (Type[])null, (Type[])null);

		private static readonly MethodInfo SetMaxEitr = AccessTools.Method(typeof(Player), "SetMaxEitr", new Type[2]
		{
			typeof(float),
			typeof(bool)
		}, (Type[])null);

		[HarmonyTranspiler]
		[HarmonyPatch(typeof(Player), "UpdateFood")]
		private static IEnumerable<CodeInstruction> UpdateFood_Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			FieldInfo rate = AccessTools.Field(typeof(Game), "m_foodRate");
			MethodInfo scale = AccessTools.Method(typeof(FoodManager), "ScaleFoodRate", (Type[])null, (Type[])null);
			foreach (CodeInstruction instruction in instructions)
			{
				yield return instruction;
				if (CodeInstructionExtensions.LoadsField(instruction, rate, false))
				{
					yield return new CodeInstruction(OpCodes.Ldarg_0, (object)null);
					yield return new CodeInstruction(OpCodes.Call, (object)scale);
				}
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Player), "UpdateFood")]
		private static void UpdateFood_Prefix(Player __instance, out Dictionary<Food, float> __state)
		{
			__state = null;
			if (!FoodManager.ShouldPauseTimers(__instance))
			{
				return;
			}
			__state = new Dictionary<Food, float>();
			foreach (Food food in __instance.GetFoods())
			{
				__state[food] = food.m_time;
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Player), "UpdateFood")]
		private static void UpdateFood_Postfix(Player __instance, Dictionary<Food, float> __state)
		{
			if (__state == null)
			{
				return;
			}
			List<Food> foods = __instance.GetFoods();
			foreach (KeyValuePair<Food, float> item in __state)
			{
				Food key = item.Key;
				key.m_time = item.Value;
				if (!foods.Contains(key))
				{
					foods.Add(key);
				}
				float num = Mathf.Pow(Mathf.Clamp01(key.m_time / key.m_item.m_shared.m_foodBurnTime), 0.3f);
				key.m_health = key.m_item.m_shared.m_food * num;
				key.m_stamina = key.m_item.m_shared.m_foodStamina * num;
				key.m_eitr = key.m_item.m_shared.m_foodEitr * num;
			}
			object[] array = new object[3] { 0f, 0f, 0f };
			TotalFood.Invoke(__instance, array);
			__instance.SetMaxHealth((float)array[0], false);
			__instance.SetMaxStamina((float)array[1], false);
			SetMaxEitr.Invoke(__instance, new object[2]
			{
				array[2],
				false
			});
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Player), "OnDeath")]
		private static void OnDeath_Prefix(Player __instance, out List<Food> __state)
		{
			__state = FoodManager.CaptureFoodsForDeath(__instance);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Player), "OnDeath")]
		private static void OnDeath_Postfix(Player __instance, List<Food> __state)
		{
			FoodManager.RestoreFoodsAfterDeath(__instance, __state);
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Character), "ApplyDamage")]
		private static void ApplyDamage_Prefix(Character __instance, out float __state)
		{
			__state = (((Object)(object)__instance == (Object)(object)Player.m_localPlayer) ? __instance.GetHealth() : float.NaN);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Character), "ApplyDamage")]
		private static void ApplyDamage_Postfix(Character __instance, HitData hit, float __state)
		{
			if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && __instance.GetHealth() < __state)
			{
				MeadManager.OnDamage(__instance, hit);
			}
		}
	}
	[BepInPlugin("com.blackhearthx.hearthpantry", "HearthPantry", "1.0.4")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public class HearthPantryPlugin : BaseUnityPlugin
	{
		public const string PluginGUID = "com.blackhearthx.hearthpantry";

		public const string PluginName = "HearthPantry";

		public const string PluginVersion = "1.0.4";

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			PluginConfig.Bind(((BaseUnityPlugin)this).Config);
			ModLocalization.Register();
			new Harmony("com.blackhearthx.hearthpantry").PatchAll(typeof(HearthPantryPlugin).Assembly);
			((MonoBehaviour)this).StartCoroutine(FoodLoop());
			((MonoBehaviour)this).StartCoroutine(MeadLoop());
			Logger.LogInfo((object)"HearthPantry 1.0.4 loaded");
		}

		private void Update()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			if (!PluginConfig.ModEnabled.Value)
			{
				return;
			}
			KeyboardShortcut value = PluginConfig.ToggleHotkey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				PluginConfig.AutoEat.Value = !PluginConfig.AutoEat.Value;
				if ((Object)(object)MessageHud.instance != (Object)null)
				{
					string text = (PluginConfig.AutoEat.Value ? ModLocalization.L("hearthpantry_toggle_on") : ModLocalization.L("hearthpantry_toggle_off"));
					MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true);
				}
			}
		}

		private IEnumerator FoodLoop()
		{
			WaitForSeconds wait = new WaitForSeconds(1f);
			while (true)
			{
				yield return wait;
				try
				{
					FoodManager.Tick();
				}
				catch (Exception arg)
				{
					Log.LogError((object)$"FoodManager tick failed: {arg}");
				}
			}
		}

		private IEnumerator MeadLoop()
		{
			WaitForSeconds wait = new WaitForSeconds(0.5f);
			while (true)
			{
				yield return wait;
				try
				{
					MeadManager.Tick();
				}
				catch (Exception arg)
				{
					Log.LogError((object)$"MeadManager tick failed: {arg}");
				}
			}
		}
	}
	internal static class PluginConfig
	{
		public static ConfigEntry<bool> ModEnabled;

		public static ConfigEntry<bool> AutoEat;

		public static ConfigEntry<int> AutoEatPercent;

		public static ConfigEntry<bool> AutoEatNotify;

		public static ConfigEntry<bool> ExpiryNotify;

		public static ConfigEntry<int> ExpiryPercent;

		public static ConfigEntry<bool> LowSupplyNotify;

		public static ConfigEntry<int> LowSupplyCount;

		public static ConfigEntry<bool> PauseNearWorkbench;

		public static ConfigEntry<bool> PauseOnBoat;

		public static ConfigEntry<bool> KeepFoodOnDeath;

		public static ConfigEntry<bool> ScaleWithTimeControl;

		public static ConfigEntry<KeyboardShortcut> ToggleHotkey;

		public static ConfigEntry<bool> AutoHealthMead;

		public static ConfigEntry<int> HealthMeadThreshold;

		public static ConfigEntry<bool> HealthMeadOnlyEnemyHit;

		public static ConfigEntry<float> HealthMeadEnemyHitWindow;

		public static ConfigEntry<bool> HealthMeadRequireMaxHealth;

		public static ConfigEntry<bool> HealthMeadNotify;

		public static ConfigEntry<bool> AutoPoisonMead;

		public static ConfigEntry<float> PoisonMeadRange;

		public static ConfigEntry<bool> PoisonMeadNotify;

		public static ConfigEntry<bool> AutoFireMead;

		public static ConfigEntry<float> FireMeadRange;

		public static ConfigEntry<bool> FireMeadNotify;

		public static ConfigEntry<bool> AutoFrostMead;

		public static ConfigEntry<int> FrostMeadTickCount;

		public static ConfigEntry<float> FrostMeadTickWindow;

		public static ConfigEntry<bool> FrostMeadNotify;

		public static void Bind(ConfigFile config)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Expected O, but got Unknown
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Expected O, but got Unknown
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_0230: Expected O, but got Unknown
			//IL_02af: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b9: Expected O, but got Unknown
			//IL_031d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0327: Expected O, but got Unknown
			//IL_0380: Unknown result type (might be due to invalid IL or missing references)
			//IL_038a: Expected O, but got Unknown
			//IL_03b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c2: Expected O, but got Unknown
			ModEnabled = config.Bind<bool>("General", "01. Mod Enabled", true, "Turns the whole pantry on or off.");
			AutoEat = config.Bind<bool>("General", "02. Auto-Eat", true, "Reaches into your bag and eats for you when a meal is about to fade.");
			AutoEatPercent = config.Bind<int>("General", "03. Auto-Eat Percent", 45, new ConfigDescription("How much of the timer is left when it re-eats. The game only lets you eat again once the food starts flashing (50%). Forty-five keeps the buff near full without wasting a stack on the last seconds.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 49), Array.Empty<object>()));
			AutoEatNotify = config.Bind<bool>("General", "04. Auto-Eat Notify", true, "Shows a short line on screen when the pantry eats for you.");
			ExpiryNotify = config.Bind<bool>("General", "07. Expiry Notify", true, "A quiet heads-up when a meal is getting thin, even if you prefer to eat by hand.");
			ExpiryPercent = config.Bind<int>("General", "08. Expiry Percent", 50, new ConfigDescription("When that heads-up appears, as a percent of time left. Keep it at or above Auto-Eat Percent so you hear about it before the pantry acts.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50), Array.Empty<object>()));
			LowSupplyNotify = config.Bind<bool>("General", "09. Low Supply Notify", true, "Warns you when a stack is nearly gone after the pantry eats from it.");
			LowSupplyCount = config.Bind<int>("General", "10. Low Supply Count", 1, "How many pieces left counts as \"running low\".");
			PauseNearWorkbench = config.Bind<bool>("General", "11. Pause Near Workbench", true, "While you are at the workbench, food timers hold still. Healing and stamina from the meal still work.");
			PauseOnBoat = config.Bind<bool>("General", "12. Pause On Boat", true, "Same pause while you are sailing. Effects keep ticking; only the countdown freezes.");
			KeepFoodOnDeath = config.Bind<bool>("General", "13. Keep Food On Death", false, "Come back from death with the same meals still in your belly. Off by default so death still costs something.");
			ScaleWithTimeControl = config.Bind<bool>("General", "14. Scale With TimeControl", true, "If you use DrummerCraig's TimeControl, food lasts in step with the longer day. Harmless if that mod is not installed.");
			ToggleHotkey = config.Bind<KeyboardShortcut>("General", "15. Toggle Hotkey", new KeyboardShortcut((KeyCode)116, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), "Quick key to pause or resume the pantry without opening the config.");
			AutoHealthMead = config.Bind<bool>("Health Mead", "01. Auto Health Mead", true, "Drinks a healing mead when you are hurt badly enough.");
			HealthMeadThreshold = config.Bind<int>("Health Mead", "02. Health Threshold", 30, new ConfigDescription("Drink when your health falls below this percent.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 99), Array.Empty<object>()));
			HealthMeadOnlyEnemyHit = config.Bind<bool>("Health Mead", "03. Only On Enemy Hit", true, "Only after an enemy hit you, so a fall or lava does not waste the bottle.");
			HealthMeadEnemyHitWindow = config.Bind<float>("Health Mead", "04. Enemy Hit Window", 5f, new ConfigDescription("How many seconds after that hit the mead is still allowed to fire.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>()));
			HealthMeadRequireMaxHealth = config.Bind<bool>("Health Mead", "05. Require Sufficient Max Health", false, "Skip a mead that heals more than your max health and try a smaller one instead.");
			HealthMeadNotify = config.Bind<bool>("Health Mead", "06. Notify", true, "Tell you when a health mead was drunk.");
			AutoPoisonMead = config.Bind<bool>("Poison Resist Mead", "01. Auto Poison Mead", true, "Drinks poison resist when something venomous is coming for you.");
			PoisonMeadRange = config.Bind<float>("Poison Resist Mead", "02. Detection Range", 15f, new ConfigDescription("How far to look for that kind of threat.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f), Array.Empty<object>()));
			PoisonMeadNotify = config.Bind<bool>("Poison Resist Mead", "03. Notify", true, "Tell you when poison resist was drunk.");
			AutoFireMead = config.Bind<bool>("Fire Resist Mead", "01. Auto Fire Mead", true, "Drinks fire resist when something fiery is on you.");
			FireMeadRange = config.Bind<float>("Fire Resist Mead", "02. Detection Range", 15f, new ConfigDescription("How far to look for fire threats.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f), Array.Empty<object>()));
			FireMeadNotify = config.Bind<bool>("Fire Resist Mead", "03. Notify", true, "Tell you when fire resist was drunk.");
			AutoFrostMead = config.Bind<bool>("Frost Resist Mead", "01. Auto Frost Mead", true, "Drinks frost resist when you are freezing or taking repeated frost hits.");
			FrostMeadTickCount = config.Bind<int>("Frost Resist Mead", "02. Frost Tick Count", 3, new ConfigDescription("How many frost hits in the window before it drinks.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()));
			FrostMeadTickWindow = config.Bind<float>("Frost Resist Mead", "03. Frost Tick Window", 10f, new ConfigDescription("Seconds those frost hits are counted across.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 60f), Array.Empty<object>()));
			FrostMeadNotify = config.Bind<bool>("Frost Resist Mead", "04. Notify", true, "Tell you when frost resist was drunk.");
		}
	}
}