Decompiled source of Hunger Pangs v1.3.0

plugins/HungerPangs.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("HungerPangs")]
[assembly: AssemblyDescription("Valheim HungerPangs Mod by DrummerCraig")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("HungerPangs")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("ea00b146-e2c4-41cd-8e70-7bbada299b8e")]
[assembly: AssemblyFileVersion("1.2.2.0")]
[assembly: AssemblyVersion("1.2.2.0")]
namespace DrummerCraig.HungerPangs;

public static class AdminSync
{
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	private static class OnNewConnectionPatch
	{
		private static void Postfix(ZNetPeer peer, ZNet __instance)
		{
			if (peer != null && peer.m_rpc != null && !((Object)(object)__instance == (Object)null) && !__instance.IsServer())
			{
				peer.m_rpc.Register<bool>("HungerPangs_Admin", (Action<ZRpc, bool>)OnServerAdminStatus);
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "Shutdown")]
	private static class ShutdownPatch
	{
		private static void Postfix()
		{
			IsAdmin = false;
			OnAdminStatusChanged?.Invoke();
		}
	}

	private const string RpcAdmin = "HungerPangs_Admin";

	public static Action OnAdminStatusChanged;

	private static FieldInfo _peersField;

	private static FieldInfo _adminListField;

	private static MethodInfo _listContainsId;

	public static bool IsAdmin { get; private set; }

	public static void PushTo(ZRpc rpc)
	{
		if (rpc == null || (Object)(object)ZNet.instance == (Object)null)
		{
			return;
		}
		try
		{
			bool flag = SenderIsAdmin(rpc);
			rpc.Invoke("HungerPangs_Admin", new object[1] { flag });
			ManualLogSource log = HungerPangs.Log;
			if (log != null)
			{
				log.LogInfo((object)$"[HungerPangs] Pushed admin status {flag} to peer.");
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log2 = HungerPangs.Log;
			if (log2 != null)
			{
				log2.LogWarning((object)("[HungerPangs] admin push failed: " + ex.Message));
			}
		}
	}

	private static void OnServerAdminStatus(ZRpc rpc, bool isAdmin)
	{
		if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer())
		{
			IsAdmin = isAdmin;
			ManualLogSource log = HungerPangs.Log;
			if (log != null)
			{
				log.LogInfo((object)$"[HungerPangs] Server reports this client admin status: {isAdmin}.");
			}
			OnAdminStatusChanged?.Invoke();
		}
	}

	public static bool IsSenderAdmin(ZRpc rpc)
	{
		return SenderIsAdmin(rpc);
	}

	private static bool SenderIsAdmin(ZRpc rpc)
	{
		try
		{
			ZNet instance = ZNet.instance;
			_peersField = _peersField ?? typeof(ZNet).GetField("m_peers", BindingFlags.Instance | BindingFlags.NonPublic);
			List<ZNetPeer> list = _peersField?.GetValue(instance) as List<ZNetPeer>;
			string text = null;
			if (list != null)
			{
				foreach (ZNetPeer item in list)
				{
					if (item.m_rpc == rpc)
					{
						ISocket socket = item.m_socket;
						text = ((socket != null) ? socket.GetHostName() : null);
						break;
					}
				}
			}
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			_adminListField = _adminListField ?? typeof(ZNet).GetField("m_adminList", BindingFlags.Instance | BindingFlags.NonPublic);
			_listContainsId = _listContainsId ?? typeof(ZNet).GetMethod("ListContainsId", BindingFlags.Instance | BindingFlags.NonPublic);
			object obj = _adminListField?.GetValue(instance);
			if (obj == null || _listContainsId == null)
			{
				return false;
			}
			return (bool)_listContainsId.Invoke(instance, new object[2] { obj, text });
		}
		catch (Exception)
		{
			return false;
		}
	}
}
public static class ConfigSync
{
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	private static class OnNewConnectionPatch
	{
		private static void Postfix(ZNetPeer peer, ZNet __instance)
		{
			if (peer != null && peer.m_rpc != null && !((Object)(object)__instance == (Object)null))
			{
				if (__instance.IsServer())
				{
					peer.m_rpc.Register<ZPackage>("HungerPangs_AdminConfig", (Action<ZRpc, ZPackage>)OnAdminConfig);
					return;
				}
				_serverRpc = peer.m_rpc;
				peer.m_rpc.Register<ZPackage>("HungerPangs_Config", (Action<ZRpc, ZPackage>)OnServerConfig);
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "Shutdown")]
	private static class ShutdownPatch
	{
		private static void Postfix()
		{
			_serverRpc = null;
			ServerConfig.ResetToLocal();
			ServerConfig.RefreshLockState();
		}
	}

	private const string RpcConfig = "HungerPangs_Config";

	private const string RpcAdminConfig = "HungerPangs_AdminConfig";

	private static ZRpc _serverRpc;

	internal static bool Suppressing;

	private static FieldInfo _peersField;

	public static void PushTo(ZRpc rpc)
	{
		try
		{
			rpc.Invoke("HungerPangs_Config", new object[1] { WriteBundle() });
			ManualLogSource log = HungerPangs.Log;
			if (log != null)
			{
				log.LogInfo((object)($"[HungerPangs] Pushed server config to peer (ModStatus={ServerConfig.ModStatus.Value}, " + $"AutoEat={ServerConfig.AutoEat.Value}, AutoMead={ServerConfig.AutoMead.Value})."));
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log2 = HungerPangs.Log;
			if (log2 != null)
			{
				log2.LogWarning((object)("[HungerPangs] config push failed: " + ex.Message));
			}
		}
	}

	public static void HookConfigWatch(ConfigFile cfg)
	{
		if (cfg != null)
		{
			cfg.SettingChanged += OnConfigChanged;
		}
	}

	private static void OnConfigChanged(object sender, SettingChangedEventArgs args)
	{
		if (Suppressing)
		{
			return;
		}
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null)
		{
			ServerConfig.RefreshLockState();
			return;
		}
		if (instance.IsServer())
		{
			BroadcastConfig();
		}
		else if (_serverRpc != null && AdminSync.IsAdmin)
		{
			try
			{
				_serverRpc.Invoke("HungerPangs_AdminConfig", new object[1] { WriteBundle() });
			}
			catch (Exception)
			{
			}
		}
		ServerConfig.RefreshLockState();
	}

	private static void OnAdminConfig(ZRpc rpc, ZPackage z)
	{
		ZNet instance = ZNet.instance;
		if (!((Object)(object)instance == (Object)null) && instance.IsServer() && z != null && AdminSync.IsSenderAdmin(rpc))
		{
			Suppressing = true;
			try
			{
				ApplyBundleToLocal(z);
			}
			finally
			{
				Suppressing = false;
			}
			BroadcastConfig();
		}
	}

	private static void BroadcastConfig()
	{
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null || !instance.IsServer())
		{
			return;
		}
		List<ZNetPeer> list = Peers(instance);
		if (list == null)
		{
			return;
		}
		foreach (ZNetPeer item in list)
		{
			if (item?.m_rpc != null)
			{
				try
				{
					item.m_rpc.Invoke("HungerPangs_Config", new object[1] { WriteBundle() });
				}
				catch (Exception)
				{
				}
			}
		}
	}

	private static List<ZNetPeer> Peers(ZNet net)
	{
		try
		{
			_peersField = _peersField ?? typeof(ZNet).GetField("m_peers", BindingFlags.Instance | BindingFlags.NonPublic);
			return _peersField?.GetValue(net) as List<ZNetPeer>;
		}
		catch (Exception)
		{
			return null;
		}
	}

	private static ZPackage WriteBundle()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: 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_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: 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_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Expected O, but got Unknown
		ZPackage val = new ZPackage();
		val.Write(ServerConfig.ModStatus.Value);
		val.Write(ServerConfig.AutoEat.Value);
		val.Write(ServerConfig.PauseNearWorkbench.Value);
		val.Write(ServerConfig.PauseOnBoat.Value);
		val.Write(ServerConfig.KeepFoodOnDeath.Value);
		val.Write(ServerConfig.AutoMead.Value);
		val.Write(ServerConfig.AutoPoisonMead.Value);
		val.Write(ServerConfig.AutoFireMead.Value);
		val.Write(ServerConfig.AutoFrostMead.Value);
		return val;
	}

	private static void OnServerConfig(ZRpc rpc, ZPackage z)
	{
		if (z == null)
		{
			return;
		}
		if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
		{
			ManualLogSource log = HungerPangs.Log;
			if (log != null)
			{
				log.LogInfo((object)"[HungerPangs] Ignoring server config push (this instance is the server/host).");
			}
			return;
		}
		z.SetPos(0);
		bool flag = z.ReadBool();
		bool flag2 = z.ReadBool();
		bool flag3 = z.ReadBool();
		bool flag4 = z.ReadBool();
		bool flag5 = z.ReadBool();
		bool flag6 = z.ReadBool();
		bool flag7 = z.ReadBool();
		bool flag8 = z.ReadBool();
		bool flag9 = z.ReadBool();
		ServerConfig.ApplyServer(flag, flag2, flag3, flag4, flag5, flag6, flag7, flag8, flag9);
		ServerConfig.RefreshLockState();
		ManualLogSource log2 = HungerPangs.Log;
		if (log2 != null)
		{
			log2.LogInfo((object)($"[HungerPangs] Adopted server config: ModStatus={flag}, AutoEat={flag2}, " + $"PauseNearWorkbench={flag3}, PauseOnBoat={flag4}, KeepFoodOnDeath={flag5}, " + $"AutoMead={flag6}, AutoPoisonMead={flag7}, AutoFireMead={flag8}, AutoFrostMead={flag9}. " + $"ConfigEntry now reads ModStatus={ServerConfig.ModStatus.Value}, AutoEat={ServerConfig.AutoEat.Value}."));
		}
	}

	private static void ApplyBundleToLocal(ZPackage z)
	{
		z.SetPos(0);
		bool modStatus = z.ReadBool();
		bool autoEat = z.ReadBool();
		bool pauseNearWorkbench = z.ReadBool();
		bool pauseOnBoat = z.ReadBool();
		bool keepFoodOnDeath = z.ReadBool();
		bool autoMead = z.ReadBool();
		bool autoPoisonMead = z.ReadBool();
		bool autoFireMead = z.ReadBool();
		bool autoFrostMead = z.ReadBool();
		ServerConfig.ApplyLocalFromBundle(modStatus, autoEat, pauseNearWorkbench, pauseOnBoat, keepFoodOnDeath, autoMead, autoPoisonMead, autoFireMead, autoFrostMead);
	}
}
public sealed class ConfigurationManagerAttributes
{
	public bool? ReadOnly = false;
}
[BepInPlugin("drummercraig.hungerpangs", "Hunger Pangs", "1.3.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class HungerPangs : BaseUnityPlugin
{
	[HarmonyPatch(typeof(Player), "UpdateFood", new Type[]
	{
		typeof(float),
		typeof(bool)
	})]
	private static class Player_UpdateFood_Patch
	{
		private static void Postfix(Player __instance)
		{
			try
			{
				if ((!_nearWorkbench && !_onBoat) || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
				{
					return;
				}
				float deltaTime = Time.deltaTime;
				foreach (Food food in __instance.GetFoods())
				{
					if (food?.m_item != null)
					{
						food.m_time += deltaTime;
					}
				}
			}
			catch (Exception arg)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogError((object)$"[HungerPangs] UpdateFood postfix failed: {arg}");
				}
			}
		}
	}

	[HarmonyPatch(typeof(Player), "OnDeath", new Type[] { })]
	private static class Player_OnDeath_Patch
	{
		private static void Prefix(Player __instance, out List<Food> __state)
		{
			__state = null;
			try
			{
				if (ServerConfig.ModStatusValue && ServerConfig.KeepFoodOnDeathValue && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
				{
					List<Food> foods = __instance.GetFoods();
					if (foods != null && foods.Count > 0)
					{
						__state = new List<Food>(foods);
					}
				}
			}
			catch (Exception arg)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogError((object)$"[HungerPangs] OnDeath prefix failed: {arg}");
				}
			}
		}

		private static void Postfix(Player __instance, List<Food> __state)
		{
			try
			{
				if (__state != null && __state.Count != 0)
				{
					List<Food> foods = __instance.GetFoods();
					if (foods != null)
					{
						foods.Clear();
						foods.AddRange(__state);
					}
				}
			}
			catch (Exception arg)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogError((object)$"[HungerPangs] OnDeath postfix failed: {arg}");
				}
			}
		}
	}

	[HarmonyPatch(typeof(Player), "EatFood", new Type[] { typeof(ItemData) })]
	private static class Player_EatFood_Patch
	{
		private static void Postfix(Player __instance, ItemData item, bool __result)
		{
			try
			{
				if (!__result || item?.m_shared == null || !ServerConfig.ModStatusValue || s_scaleWithTimeControl == null || !s_scaleWithTimeControl.Value || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
				{
					return;
				}
				float timeControlMultiplier = GetTimeControlMultiplier();
				if (timeControlMultiplier <= 0f || Mathf.Abs(timeControlMultiplier - 1f) <= 0.001f)
				{
					return;
				}
				string name = item.m_shared.m_name;
				foreach (Food food in __instance.GetFoods())
				{
					if (food?.m_item?.m_shared != null && food.m_item.m_shared.m_name == name)
					{
						food.m_time = food.m_item.m_shared.m_foodBurnTime * timeControlMultiplier;
						break;
					}
				}
			}
			catch (Exception arg)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogError((object)$"[HungerPangs] EatFood postfix failed: {arg}");
				}
			}
		}
	}

	[HarmonyPatch(typeof(Character), "RPC_Damage", new Type[]
	{
		typeof(long),
		typeof(HitData)
	})]
	private static class Character_RPC_Damage_Patch
	{
		private static void Prefix(Character __instance, HitData hit)
		{
			try
			{
				if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
				{
					Character attacker = hit.GetAttacker();
					if ((Object)(object)attacker != (Object)null && !attacker.IsPlayer())
					{
						_lastEnemyHitTime = Time.time;
					}
					if (hit.m_damage.m_frost > 0f)
					{
						_frostHitTimes.Enqueue(Time.time);
					}
				}
			}
			catch (Exception arg)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogError((object)$"[HungerPangs] RPC_Damage prefix failed: {arg}");
				}
			}
		}
	}

	public const string PluginGuid = "drummercraig.hungerpangs";

	public const string PluginName = "Hunger Pangs";

	public const string PluginVersion = "1.3.0";

	private ConfigEntry<bool> modStatus;

	private ConfigEntry<bool> autoEat;

	private ConfigEntry<bool> foodExpiryNotify;

	private ConfigEntry<int> autoEatPercent;

	private ConfigEntry<int> foodExpiryPercent;

	private ConfigEntry<bool> autoEatNotify;

	private ConfigEntry<bool> lowSupplyNotify;

	private ConfigEntry<int> lowSupplyCount;

	private ConfigEntry<bool> autoMead;

	private ConfigEntry<int> autoMeadHealthPercent;

	private ConfigEntry<bool> autoMeadOnlyOnEnemyHit;

	private ConfigEntry<float> autoMeadEnemyHitWindow;

	private ConfigEntry<bool> autoMeadRequireMaxHealth;

	private ConfigEntry<bool> autoMeadNotify;

	private ConfigEntry<bool> autoPoisonMead;

	private ConfigEntry<float> autoPoisonMeadRange;

	private ConfigEntry<bool> autoPoisonMeadNotify;

	private ConfigEntry<bool> autoFireMead;

	private ConfigEntry<float> autoFireMeadRange;

	private ConfigEntry<bool> autoFireMeadNotify;

	private ConfigEntry<bool> autoFrostMead;

	private ConfigEntry<int> autoFrostMeadTickCount;

	private ConfigEntry<float> autoFrostMeadTickWindow;

	private ConfigEntry<bool> autoFrostMeadNotify;

	private ConfigEntry<bool> pauseFoodNearWorkbench;

	private ConfigEntry<bool> pauseFoodOnBoat;

	private ConfigEntry<bool> keepFoodOnDeath;

	private ConfigEntry<bool> scaleWithTimeControl;

	private static float _lastEnemyHitTime = float.MinValue;

	private static ManualLogSource _log;

	internal static ManualLogSource Log;

	private static ConfigEntry<bool> s_scaleWithTimeControl;

	private const string TimeControlGuid = "drummercraig.time_control";

	private static bool _tcResolved;

	private static MethodInfo _tcEffectiveMultiplierGetter;

	private static bool _nearWorkbench;

	private static bool _onBoat;

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

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

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

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

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

	private const float MeleeRange = 3f;

	private const float MeleeRangeSq = 9f;

	private readonly HashSet<string> hasShown = new HashSet<string>();

	private readonly HashSet<string> seenThisTick = new HashSet<string>();

	private readonly HashSet<string> lowSupplyShown = new HashSet<string>();

	private Predicate<string> _notSeenThisTick;

	private void Awake()
	{
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Expected O, but got Unknown
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Expected O, but got Unknown
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Expected O, but got Unknown
		//IL_0104: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Expected O, but got Unknown
		//IL_017b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0185: Expected O, but got Unknown
		//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ba: Expected O, but got Unknown
		//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ef: Expected O, but got Unknown
		//IL_023b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0245: Expected O, but got Unknown
		//IL_026f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0279: Expected O, but got Unknown
		//IL_02ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d8: Expected O, but got Unknown
		//IL_0345: Unknown result type (might be due to invalid IL or missing references)
		//IL_034f: Expected O, but got Unknown
		//IL_0383: Unknown result type (might be due to invalid IL or missing references)
		//IL_038d: Expected O, but got Unknown
		//IL_03d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_03e3: Expected O, but got Unknown
		//IL_0417: Unknown result type (might be due to invalid IL or missing references)
		//IL_0421: Expected O, but got Unknown
		//IL_046d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0477: Expected O, but got Unknown
		//IL_04a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_04aa: Expected O, but got Unknown
		//IL_04de: Unknown result type (might be due to invalid IL or missing references)
		//IL_04e8: Expected O, but got Unknown
		//IL_05f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0613: Expected O, but got Unknown
		modStatus = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "01. Mod Status", true, new ConfigDescription("Master toggle for the entire mod. When disabled, no auto-eating or notifications occur.", (AcceptableValueBase)null, new object[1] { ServerConfig.NewLock() }));
		autoEat = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "02. Auto-Eat", true, new ConfigDescription("Automatically re-eat food before it expires. When disabled, the mod will only show notifications if NotificationsEnabled is on, allowing you to eat manually on cue.", (AcceptableValueBase)null, new object[1] { ServerConfig.NewLock() }));
		autoEatPercent = ((BaseUnityPlugin)this).Config.Bind<int>("General", "03. Auto-Eat Percent", 5, new ConfigDescription("Percentage of a food's total duration remaining when it is automatically re-eaten. Food becomes eligible to re-eat at 50% remaining (when it starts blinking), so this is your window. Lower values eat later and waste less food; higher values re-eat sooner after becoming eligible. Example: 5 = eat when 5% remains (e.g. ~1.5 min of a 30-min food). Requires Auto-Eat Enabled.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 49), Array.Empty<object>()));
		autoEatNotify = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "04. Auto-Eat Notify", true, "Show a HUD notification when a food item is automatically eaten. Requires AutomaticallyEat.");
		foodExpiryNotify = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "05. Food Expiry Notify", true, "Show a HUD notification when a food item nears its expiry threshold. Useful on its own when Auto-Eat is Disabled,as a manual reminder to eat.");
		foodExpiryPercent = ((BaseUnityPlugin)this).Config.Bind<int>("General", "06. Food Expiry Percent", 30, new ConfigDescription("Percentage of a food's total duration remaining when the HUD notification appears. Food becomes eligible to re-eat at 50% remaining (when it starts blinking). Set this higher than Auto-Eat Percent so the notification fires before auto-eat. Example: 30 = notify when 30% remains (e.g. ~9 min of a 30-min food). Requires Notifications.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50), Array.Empty<object>()));
		lowSupplyNotify = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "07. Low Supply Notify", true, "Show a HUD notification when AutomaticallyEat is enabled, a food item is consumed and the remaining count in your inventory is at or below the LowSupplyThreshold.");
		lowSupplyCount = ((BaseUnityPlugin)this).Config.Bind<int>("General", "08. Low Supply Count", 1, "The number of food items in your inventory for the LowSupplyNotification to appear.");
		pauseFoodNearWorkbench = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "09. Pause Near Workbench", true, new ConfigDescription("Pause food expiry timers when within range of a workbench. Food effects (health and stamina regen) continue normally — only the countdown is paused.", (AcceptableValueBase)null, new object[1] { ServerConfig.NewLock() }));
		pauseFoodOnBoat = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "10. Pause On Boat", true, new ConfigDescription("Pause food expiry timers while sailing on a boat. Food effects (health and stamina regen) continue normally — only the countdown is paused.", (AcceptableValueBase)null, new object[1] { ServerConfig.NewLock() }));
		keepFoodOnDeath = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "11. Keep Food On Death", false, new ConfigDescription("When enabled, the food you have eaten is preserved when you die — you respawn with the same active food buffs and their remaining timers, instead of losing all food on death. Disabled by default, as this removes a normal death penalty.", (AcceptableValueBase)null, new object[1] { ServerConfig.NewLock() }));
		scaleWithTimeControl = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "12. Scale With TimeControl", true, "Scale food expiry timers by the TimeControl mod's day/night multiplier so food lasts proportionally longer when days are longer. Example: if TimeControl is set to 4x, food lasts ~4x longer. Uses TimeControl's effective multiplier, so on a server that pushes its own value everyone's food scales with the shared clock. Has no effect if TimeControl is not installed.");
		autoMead = ((BaseUnityPlugin)this).Config.Bind<bool>("Health Mead", "01. Auto Health Mead", true, new ConfigDescription("Automatically drink a health mead when your health drops below the configured threshold.", (AcceptableValueBase)null, new object[1] { ServerConfig.NewLock() }));
		autoMeadHealthPercent = ((BaseUnityPlugin)this).Config.Bind<int>("Health Mead", "02. Health Threshold", 30, new ConfigDescription("Health percentage below which a health mead will be automatically consumed. Example: 40 = drink when health drops below 40% of your maximum.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 99), Array.Empty<object>()));
		autoMeadOnlyOnEnemyHit = ((BaseUnityPlugin)this).Config.Bind<bool>("Health Mead", "03. Only On Enemy Hit", true, "Only auto-drink when the health drop was caused by an enemy. Prevents wasting meads on fall damage, lava, or other environmental hazards when not in combat.");
		autoMeadEnemyHitWindow = ((BaseUnityPlugin)this).Config.Bind<float>("Health Mead", "04. Enemy Hit Window", 5f, new ConfigDescription("Seconds after the last enemy hit during which the mead trigger remains active. Requires Only On Enemy Hit. Example: 5 = the mead can fire within 5 seconds of being struck.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>()));
		autoMeadRequireMaxHealth = ((BaseUnityPlugin)this).Config.Bind<bool>("Health Mead", "05. Require Sufficient Max Health", false, "Only drink a mead if your maximum health is at least equal to the mead's total healing value. Prevents drinking a mead that heals more than your health cap, which would waste the excess. Meads that exceed your max health are skipped; the next-strongest eligible mead is tried instead.");
		autoMeadNotify = ((BaseUnityPlugin)this).Config.Bind<bool>("Health Mead", "06. Notify", true, "Show a HUD notification when a health mead is automatically consumed.");
		autoPoisonMead = ((BaseUnityPlugin)this).Config.Bind<bool>("Poison Resist Mead", "01. Auto Poison Mead", true, new ConfigDescription("Automatically drink a poison resist mead when a nearby enemy can deal poison damage.", (AcceptableValueBase)null, new object[1] { ServerConfig.NewLock() }));
		autoPoisonMeadRange = ((BaseUnityPlugin)this).Config.Bind<float>("Poison Resist Mead", "02. Detection Range", 4f, new ConfigDescription("Radius in meters to scan for poison-capable enemies.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f), Array.Empty<object>()));
		autoPoisonMeadNotify = ((BaseUnityPlugin)this).Config.Bind<bool>("Poison Resist Mead", "03. Notify", true, "Show a HUD notification when a poison resist mead is automatically consumed.");
		autoFireMead = ((BaseUnityPlugin)this).Config.Bind<bool>("Fire Resist Mead", "01. Auto Fire Mead", true, new ConfigDescription("Automatically drink a fire resist mead when a nearby enemy can deal fire damage.", (AcceptableValueBase)null, new object[1] { ServerConfig.NewLock() }));
		autoFireMeadRange = ((BaseUnityPlugin)this).Config.Bind<float>("Fire Resist Mead", "02. Detection Range", 15f, new ConfigDescription("Radius in meters to scan for fire-capable enemies.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f), Array.Empty<object>()));
		autoFireMeadNotify = ((BaseUnityPlugin)this).Config.Bind<bool>("Fire Resist Mead", "03. Notify", true, "Show a HUD notification when a fire resist mead is automatically consumed.");
		autoFrostMead = ((BaseUnityPlugin)this).Config.Bind<bool>("Frost Resist Mead", "01. Auto Frost Mead", true, new ConfigDescription("Automatically drink a frost resist mead after receiving repeated frost damage hits.", (AcceptableValueBase)null, new object[1] { ServerConfig.NewLock() }));
		autoFrostMeadTickCount = ((BaseUnityPlugin)this).Config.Bind<int>("Frost Resist Mead", "02. Frost Tick Count", 3, new ConfigDescription("Number of frost damage hits within the time window required to trigger auto-consume.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()));
		autoFrostMeadTickWindow = ((BaseUnityPlugin)this).Config.Bind<float>("Frost Resist Mead", "03. Frost Tick Window", 10f, new ConfigDescription("Seconds over which frost hits are counted toward the trigger threshold.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 60f), Array.Empty<object>()));
		autoFrostMeadNotify = ((BaseUnityPlugin)this).Config.Bind<bool>("Frost Resist Mead", "04. Notify", true, "Show a HUD notification when a frost resist mead is automatically consumed.");
		_notSeenThisTick = (string key) => !seenThisTick.Contains(key);
		_log = ((BaseUnityPlugin)this).Logger;
		Log = ((BaseUnityPlugin)this).Logger;
		ServerConfig.ModStatus = modStatus;
		ServerConfig.AutoEat = autoEat;
		ServerConfig.PauseNearWorkbench = pauseFoodNearWorkbench;
		ServerConfig.PauseOnBoat = pauseFoodOnBoat;
		ServerConfig.KeepFoodOnDeath = keepFoodOnDeath;
		ServerConfig.AutoMead = autoMead;
		ServerConfig.AutoPoisonMead = autoPoisonMead;
		ServerConfig.AutoFireMead = autoFireMead;
		ServerConfig.AutoFrostMead = autoFrostMead;
		s_scaleWithTimeControl = scaleWithTimeControl;
		VersionGate.OnPeerValidated = delegate(ZRpc rpc)
		{
			ConfigSync.PushTo(rpc);
			AdminSync.PushTo(rpc);
		};
		AdminSync.OnAdminStatusChanged = ServerConfig.RefreshLockState;
		ConfigSync.HookConfigWatch(((BaseUnityPlugin)this).Config);
		ResilientPatcher.ApplyAll(new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID), typeof(HungerPangs).Assembly, ((BaseUnityPlugin)this).Logger);
		((MonoBehaviour)this).StartCoroutine(FoodCheckLoop());
		((MonoBehaviour)this).StartCoroutine(MeadCheckLoop());
	}

	private IEnumerator FoodCheckLoop()
	{
		WaitForSeconds wait = new WaitForSeconds(1f);
		while (true)
		{
			yield return wait;
			try
			{
				CheckFoods();
			}
			catch (Exception arg)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogError((object)$"[HungerPangs] CheckFoods failed: {arg}");
				}
			}
		}
	}

	private IEnumerator MeadCheckLoop()
	{
		WaitForSeconds wait = new WaitForSeconds(0.5f);
		while (true)
		{
			yield return wait;
			try
			{
				CheckMeads();
				CheckProximityResistMeads();
				CheckFrostResistMeads();
			}
			catch (Exception arg)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogError((object)$"[HungerPangs] MeadCheckLoop failed: {arg}");
				}
			}
		}
	}

	private void CheckFoods()
	{
		Player localPlayer = Player.m_localPlayer;
		_nearWorkbench = (Object)(object)localPlayer != (Object)null && ServerConfig.PauseNearWorkbenchValue && NearWorkbench(localPlayer);
		_onBoat = (Object)(object)localPlayer != (Object)null && ServerConfig.PauseOnBoatValue && IsOnBoat(localPlayer);
		if (!ServerConfig.ModStatusValue || (Object)(object)localPlayer == (Object)null || !ServerConfig.ModStatusValue || (Object)(object)localPlayer == (Object)null)
		{
			return;
		}
		Humanoid val = (Humanoid)(object)localPlayer;
		Inventory inventory = val.GetInventory();
		List<Food> foods = localPlayer.GetFoods();
		seenThisTick.Clear();
		for (int i = 0; i < foods.Count; i++)
		{
			Food val2 = foods[i];
			string name = val2.m_item.m_shared.m_name;
			seenThisTick.Add(name);
			float time = val2.m_time;
			float foodBurnTime = val2.m_item.m_shared.m_foodBurnTime;
			float num = ((foodBurnTime > 0f) ? (time / foodBurnTime * 100f) : 0f);
			if (!val2.CanEatAgain())
			{
				hasShown.Remove(name);
				lowSupplyShown.Remove(name);
				continue;
			}
			if (foodExpiryNotify.Value && !hasShown.Contains(name) && num <= (float)foodExpiryPercent.Value)
			{
				string arg = Localization.instance.Localize(val2.m_item.m_shared.m_name);
				int num2 = Mathf.CeilToInt(time / 60f);
				MessageHud instance = MessageHud.instance;
				if (instance != null)
				{
					instance.ShowMessage((MessageType)2, $"{arg} expires in {num2} min", 0, (Sprite)null, false);
				}
				hasShown.Add(name);
			}
			if (!ServerConfig.AutoEatValue || !(num <= (float)autoEatPercent.Value))
			{
				continue;
			}
			ItemData item = inventory.GetItem(name, -1, false);
			if (item == null || !val.ConsumeItem(inventory, item, false))
			{
				continue;
			}
			hasShown.Remove(name);
			if (autoEatNotify.Value)
			{
				string text = Localization.instance.Localize(val2.m_item.m_shared.m_name);
				MessageHud instance2 = MessageHud.instance;
				if (instance2 != null)
				{
					instance2.ShowMessage((MessageType)2, text + " automatically re-eaten", 0, (Sprite)null, false);
				}
			}
			if (!lowSupplyNotify.Value || lowSupplyCount.Value <= 0 || lowSupplyShown.Contains(name))
			{
				continue;
			}
			int num3 = inventory.CountItems(name, -1, true);
			if (num3 <= lowSupplyCount.Value)
			{
				string arg2 = Localization.instance.Localize(val2.m_item.m_shared.m_name);
				MessageHud instance3 = MessageHud.instance;
				if (instance3 != null)
				{
					instance3.ShowMessage((MessageType)2, $"Low supply: {num3} {arg2} remaining", 0, (Sprite)null, false);
				}
				lowSupplyShown.Add(name);
			}
		}
		hasShown.RemoveWhere(_notSeenThisTick);
		lowSupplyShown.RemoveWhere(_notSeenThisTick);
	}

	private void CheckMeads()
	{
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		if (!ServerConfig.ModStatusValue || !ServerConfig.AutoMeadValue)
		{
			return;
		}
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).GetHealthPercentage() * 100f >= (float)autoMeadHealthPercent.Value || (autoMeadOnlyOnEnemyHit.Value && Time.time - _lastEnemyHitTime > autoMeadEnemyHitWindow.Value))
		{
			return;
		}
		Humanoid val = (Humanoid)(object)localPlayer;
		Inventory inventory = val.GetInventory();
		List<ItemData> healthMeads = GetHealthMeads(inventory);
		float maxHealth = ((Character)localPlayer).GetMaxHealth();
		SEMan sEMan = ((Character)localPlayer).GetSEMan();
		foreach (ItemData item in healthMeads)
		{
			StatusEffect consumeStatusEffect = item.m_shared.m_consumeStatusEffect;
			if ((Object)(object)consumeStatusEffect != (Object)null && sEMan.HaveStatusEffect(consumeStatusEffect.NameHash()))
			{
				continue;
			}
			if (autoMeadRequireMaxHealth.Value)
			{
				float healthOverTime = ((SE_Stats)item.m_shared.m_consumeStatusEffect).m_healthOverTime;
				if (maxHealth < healthOverTime)
				{
					continue;
				}
			}
			if (!val.ConsumeItem(inventory, item, false))
			{
				continue;
			}
			if (autoMeadNotify.Value)
			{
				string text = Localization.instance.Localize(item.m_shared.m_name);
				MessageHud instance = MessageHud.instance;
				if (instance != null)
				{
					instance.ShowMessage((MessageType)2, text + " automatically consumed", 0, (Sprite)null, false);
				}
			}
			break;
		}
	}

	private static bool NearWorkbench(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 bool IsOnBoat(Player player)
	{
		return ((Character)player).IsAttachedToShip();
	}

	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)
					{
						_tcEffectiveMultiplierGetter = AccessTools.PropertyGetter(type, "EffectiveMultiplier");
					}
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogInfo((object)((_tcEffectiveMultiplierGetter != null) ? "[HungerPangs] TimeControl detected; food duration will scale with its multiplier." : "[HungerPangs] TimeControl detected but EffectiveMultiplier could not be resolved; food scaling disabled."));
					}
				}
			}
			catch (Exception ex)
			{
				_tcEffectiveMultiplierGetter = null;
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[HungerPangs] TimeControl detection failed: " + ex.Message));
				}
			}
		}
		if (_tcEffectiveMultiplierGetter == null)
		{
			return 1f;
		}
		try
		{
			return (float)_tcEffectiveMultiplierGetter.Invoke(null, null);
		}
		catch (Exception ex2)
		{
			ManualLogSource log3 = _log;
			if (log3 != null)
			{
				log3.LogWarning((object)("[HungerPangs] reading TimeControl multiplier failed: " + ex2.Message));
			}
			return 1f;
		}
	}

	private void CheckProximityResistMeads()
	{
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0105: Unknown result type (might be due to invalid IL or missing references)
		//IL_0110: Unknown result type (might be due to invalid IL or missing references)
		//IL_0115: Unknown result type (might be due to invalid IL or missing references)
		//IL_011a: Unknown result type (might be due to invalid IL or missing references)
		bool flag = ServerConfig.ModStatusValue && ServerConfig.AutoPoisonMeadValue;
		bool flag2 = ServerConfig.ModStatusValue && ServerConfig.AutoFireMeadValue;
		if (!flag && !flag2)
		{
			return;
		}
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return;
		}
		float num = 0f;
		if (flag)
		{
			num = Mathf.Max(num, autoPoisonMeadRange.Value);
		}
		if (flag2)
		{
			num = Mathf.Max(num, autoFireMeadRange.Value);
		}
		bool flag3 = false;
		bool flag4 = false;
		_nearbyCharacterBuffer.Clear();
		Character.GetCharactersInRange(((Component)localPlayer).transform.position, num, _nearbyCharacterBuffer);
		float num2 = autoPoisonMeadRange.Value * autoPoisonMeadRange.Value;
		float num3 = autoFireMeadRange.Value * autoFireMeadRange.Value;
		foreach (Character item in _nearbyCharacterBuffer)
		{
			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 (IsThreateningPlayer(item, localPlayer, sqrMagnitude))
			{
				string prefabName = GetPrefabName(item);
				EnsureCached(prefabName, item);
				if (flag && !flag3 && sqrMagnitude <= num2 && _poisonEnemyPrefabs.Contains(prefabName))
				{
					flag3 = true;
				}
				if (flag2 && !flag4 && sqrMagnitude <= num3 && _fireEnemyPrefabs.Contains(prefabName))
				{
					flag4 = true;
				}
				if (flag3 && flag4)
				{
					break;
				}
			}
		}
		if (flag3)
		{
			ConsumeResistMead(localPlayer, autoPoisonMeadNotify, (DamageType)256);
		}
		if (flag4)
		{
			ConsumeResistMead(localPlayer, autoFireMeadNotify, (DamageType)32);
		}
	}

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

	private static string GetPrefabName(Character c)
	{
		return ((Object)((Component)c).gameObject).name.Replace("(Clone)", "").Trim();
	}

	private static void EnsureCached(string prefab, Character c)
	{
		if (_checkedPrefabs.Contains(prefab))
		{
			return;
		}
		Humanoid val = (Humanoid)(object)((c is Humanoid) ? c : null);
		if ((Object)(object)val == (Object)null)
		{
			_checkedPrefabs.Add(prefab);
			return;
		}
		foreach (ItemData allItem in val.GetInventory().GetAllItems())
		{
			if (allItem.m_shared.m_damages.m_poison > 0f)
			{
				_poisonEnemyPrefabs.Add(prefab);
			}
			if (allItem.m_shared.m_damages.m_fire > 0f)
			{
				_fireEnemyPrefabs.Add(prefab);
			}
		}
		ItemData currentWeapon = val.GetCurrentWeapon();
		if (currentWeapon != null)
		{
			if (currentWeapon.m_shared.m_damages.m_poison > 0f)
			{
				_poisonEnemyPrefabs.Add(prefab);
			}
			if (currentWeapon.m_shared.m_damages.m_fire > 0f)
			{
				_fireEnemyPrefabs.Add(prefab);
			}
		}
		_checkedPrefabs.Add(prefab);
	}

	private void ConsumeResistMead(Player localPlayer, ConfigEntry<bool> notify, DamageType type)
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		Inventory inventory = ((Humanoid)localPlayer).GetInventory();
		SEMan sEMan = ((Character)localPlayer).GetSEMan();
		foreach (ItemData resistMead in GetResistMeads(inventory, type))
		{
			StatusEffect consumeStatusEffect = resistMead.m_shared.m_consumeStatusEffect;
			if ((Object)(object)consumeStatusEffect != (Object)null && sEMan.HaveStatusEffect(consumeStatusEffect.NameHash()))
			{
				break;
			}
			if (!((Humanoid)localPlayer).ConsumeItem(inventory, resistMead, false))
			{
				continue;
			}
			if (notify.Value)
			{
				string text = Localization.instance.Localize(resistMead.m_shared.m_name);
				MessageHud instance = MessageHud.instance;
				if (instance != null)
				{
					instance.ShowMessage((MessageType)2, text + " automatically consumed", 0, (Sprite)null, false);
				}
			}
			break;
		}
	}

	private void CheckFrostResistMeads()
	{
		if (!ServerConfig.ModStatusValue || !ServerConfig.AutoFrostMeadValue)
		{
			return;
		}
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return;
		}
		float num = Time.time - autoFrostMeadTickWindow.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 >= autoFrostMeadTickCount.Value;
		if (!num2 && !flag)
		{
			return;
		}
		Humanoid val = (Humanoid)(object)localPlayer;
		Inventory inventory = val.GetInventory();
		foreach (ItemData resistMead in GetResistMeads(inventory, (DamageType)64))
		{
			StatusEffect consumeStatusEffect = resistMead.m_shared.m_consumeStatusEffect;
			if ((Object)(object)consumeStatusEffect != (Object)null && sEMan.HaveStatusEffect(consumeStatusEffect.NameHash()))
			{
				_frostHitTimes.Clear();
				break;
			}
			if (!val.ConsumeItem(inventory, resistMead, false))
			{
				continue;
			}
			_frostHitTimes.Clear();
			if (autoFrostMeadNotify.Value)
			{
				string text = Localization.instance.Localize(resistMead.m_shared.m_name);
				MessageHud instance = MessageHud.instance;
				if (instance != null)
				{
					instance.ShowMessage((MessageType)2, text + " automatically consumed", 0, (Sprite)null, false);
				}
			}
			break;
		}
	}

	private static List<ItemData> GetResistMeads(Inventory inventory, DamageType type)
	{
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Invalid comparison between Unknown and I4
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: 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)
			{
				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 && (int)mod.m_modifier != 0)
				{
					list.Add(allItem);
					break;
				}
			}
		}
		return list;
	}

	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)
			{
				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;
	}
}
internal static class ResilientPatcher
{
	internal static void ApplyAll(Harmony harmony, Assembly assembly, ManualLogSource log)
	{
		if (harmony == null || assembly == null)
		{
			return;
		}
		int num = 0;
		int num2 = 0;
		List<string> list = null;
		Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(assembly);
		foreach (Type type in typesFromAssembly)
		{
			try
			{
				List<MethodInfo> list2 = harmony.CreateClassProcessor(type).Patch();
				if (list2 != null && list2.Count > 0)
				{
					num++;
					if (log != null)
					{
						log.LogDebug((object)$"[HungerPangs] patched {type.Name} ({list2.Count} method(s)).");
					}
				}
			}
			catch (Exception ex)
			{
				num2++;
				(list ?? (list = new List<string>())).Add(type.Name);
				if (log != null)
				{
					log.LogWarning((object)("[HungerPangs] SKIP patch class " + type.Name + ": " + ex.Message));
				}
			}
		}
		if (num2 == 0)
		{
			if (log != null)
			{
				log.LogInfo((object)$"[HungerPangs] patches: {num} applied, 0 skipped.");
			}
		}
		else if (log != null)
		{
			log.LogWarning((object)(string.Format("[HungerPangs] patches: {0} applied, {1} skipped ({2}). ", num, num2, string.Join(", ", list)) + "A skipped patch usually means a Valheim update changed a hooked method; that feature is disabled but the mod is still running."));
		}
	}
}
internal static class ServerConfig
{
	public static ConfigEntry<bool> ModStatus;

	public static ConfigEntry<bool> AutoEat;

	public static ConfigEntry<bool> PauseNearWorkbench;

	public static ConfigEntry<bool> PauseOnBoat;

	public static ConfigEntry<bool> KeepFoodOnDeath;

	public static ConfigEntry<bool> AutoMead;

	public static ConfigEntry<bool> AutoPoisonMead;

	public static ConfigEntry<bool> AutoFireMead;

	public static ConfigEntry<bool> AutoFrostMead;

	private static readonly List<ConfigurationManagerAttributes> _lockable = new List<ConfigurationManagerAttributes>();

	private static bool _usingServer;

	private static bool _haveBackup;

	private static bool _bModStatus;

	private static bool _bAutoEat;

	private static bool _bPauseNearWorkbench;

	private static bool _bPauseOnBoat;

	private static bool _bKeepFoodOnDeath;

	private static bool _bAutoMead;

	private static bool _bAutoPoisonMead;

	private static bool _bAutoFireMead;

	private static bool _bAutoFrostMead;

	public static bool UsingServerConfig => _usingServer;

	public static bool ModStatusValue => ModStatus.Value;

	public static bool AutoEatValue => AutoEat.Value;

	public static bool PauseNearWorkbenchValue => PauseNearWorkbench.Value;

	public static bool PauseOnBoatValue => PauseOnBoat.Value;

	public static bool KeepFoodOnDeathValue => KeepFoodOnDeath.Value;

	public static bool AutoMeadValue => AutoMead.Value;

	public static bool AutoPoisonMeadValue => AutoPoisonMead.Value;

	public static bool AutoFireMeadValue => AutoFireMead.Value;

	public static bool AutoFrostMeadValue => AutoFrostMead.Value;

	public static ConfigurationManagerAttributes NewLock()
	{
		ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes();
		_lockable.Add(configurationManagerAttributes);
		return configurationManagerAttributes;
	}

	public static void ApplyServer(bool modStatus, bool autoEat, bool pauseNearWorkbench, bool pauseOnBoat, bool keepFoodOnDeath, bool autoMead, bool autoPoisonMead, bool autoFireMead, bool autoFrostMead)
	{
		if (!_haveBackup)
		{
			_bModStatus = ModStatus.Value;
			_bAutoEat = AutoEat.Value;
			_bPauseNearWorkbench = PauseNearWorkbench.Value;
			_bPauseOnBoat = PauseOnBoat.Value;
			_bKeepFoodOnDeath = KeepFoodOnDeath.Value;
			_bAutoMead = AutoMead.Value;
			_bAutoPoisonMead = AutoPoisonMead.Value;
			_bAutoFireMead = AutoFireMead.Value;
			_bAutoFrostMead = AutoFrostMead.Value;
			_haveBackup = true;
		}
		ConfigSync.Suppressing = true;
		try
		{
			ModStatus.Value = modStatus;
			AutoEat.Value = autoEat;
			PauseNearWorkbench.Value = pauseNearWorkbench;
			PauseOnBoat.Value = pauseOnBoat;
			KeepFoodOnDeath.Value = keepFoodOnDeath;
			AutoMead.Value = autoMead;
			AutoPoisonMead.Value = autoPoisonMead;
			AutoFireMead.Value = autoFireMead;
			AutoFrostMead.Value = autoFrostMead;
		}
		finally
		{
			ConfigSync.Suppressing = false;
		}
		_usingServer = true;
	}

	public static void ResetToLocal()
	{
		if (_haveBackup)
		{
			ConfigSync.Suppressing = true;
			try
			{
				ModStatus.Value = _bModStatus;
				AutoEat.Value = _bAutoEat;
				PauseNearWorkbench.Value = _bPauseNearWorkbench;
				PauseOnBoat.Value = _bPauseOnBoat;
				KeepFoodOnDeath.Value = _bKeepFoodOnDeath;
				AutoMead.Value = _bAutoMead;
				AutoPoisonMead.Value = _bAutoPoisonMead;
				AutoFireMead.Value = _bAutoFireMead;
				AutoFrostMead.Value = _bAutoFrostMead;
			}
			finally
			{
				ConfigSync.Suppressing = false;
			}
			_haveBackup = false;
		}
		_usingServer = false;
	}

	public static void ApplyLocalFromBundle(bool modStatus, bool autoEat, bool pauseNearWorkbench, bool pauseOnBoat, bool keepFoodOnDeath, bool autoMead, bool autoPoisonMead, bool autoFireMead, bool autoFrostMead)
	{
		ModStatus.Value = modStatus;
		AutoEat.Value = autoEat;
		PauseNearWorkbench.Value = pauseNearWorkbench;
		PauseOnBoat.Value = pauseOnBoat;
		KeepFoodOnDeath.Value = keepFoodOnDeath;
		AutoMead.Value = autoMead;
		AutoPoisonMead.Value = autoPoisonMead;
		AutoFireMead.Value = autoFireMead;
		AutoFrostMead.Value = autoFrostMead;
	}

	public static void SetUiLocked(bool locked)
	{
		for (int i = 0; i < _lockable.Count; i++)
		{
			_lockable[i].ReadOnly = locked;
		}
	}

	public static void RefreshLockState()
	{
		ZNet instance = ZNet.instance;
		SetUiLocked((Object)(object)instance != (Object)null && !instance.IsServer() && _usingServer && !AdminSync.IsAdmin);
	}
}
public static class VersionGate
{
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	private static class OnNewConnectionPatch
	{
		private static void Postfix(ZNetPeer peer, ZNet __instance)
		{
			if (peer != null && peer.m_rpc != null && !((Object)(object)__instance == (Object)null))
			{
				if (__instance.IsServer())
				{
					peer.m_rpc.Register<string>("HungerPangs_Version", (Action<ZRpc, string>)OnServerReceiveClientVersion);
					peer.m_rpc.Invoke("HungerPangs_Version", new object[1] { "1.3.0" });
				}
				else
				{
					_serverSentVersion = false;
					peer.m_rpc.Register<string>("HungerPangs_Version", (Action<ZRpc, string>)OnClientReceiveServerVersion);
					peer.m_rpc.Invoke("HungerPangs_Version", new object[1] { "1.3.0" });
				}
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	private static class RpcPeerInfoPatch
	{
		private static bool Prefix(ZRpc rpc, ZNet __instance)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Invalid comparison between Unknown and I4
			if (rpc == null || (Object)(object)__instance == (Object)null)
			{
				return true;
			}
			if (__instance.IsServer())
			{
				if (!_validatedPeers.Contains(rpc))
				{
					ManualLogSource log = HungerPangs.Log;
					if (log != null)
					{
						log.LogWarning((object)"[HungerPangs] Kicking peer without a matching HungerPangs version.");
					}
					rpc.Invoke("Error", new object[1] { 3 });
					return false;
				}
				return true;
			}
			if (!_serverSentVersion)
			{
				if ((int)ZNet.GetConnectionStatus() != 3)
				{
					AccessTools.Field(typeof(ZNet), "m_connectionStatus")?.SetValue(null, (object)(ConnectionStatus)3);
				}
				if ((Object)(object)Game.instance != (Object)null)
				{
					Game.instance.Logout(true, true);
				}
				return false;
			}
			return true;
		}

		private static void Postfix(ZRpc rpc, ZNet __instance)
		{
			if (rpc != null && !((Object)(object)__instance == (Object)null) && __instance.IsServer() && _validatedPeers.Contains(rpc))
			{
				ManualLogSource log = HungerPangs.Log;
				if (log != null)
				{
					log.LogInfo((object)"[HungerPangs] Peer validated; pushing config + admin status.");
				}
				OnPeerValidated?.Invoke(rpc);
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "Shutdown")]
	private static class ShutdownPatch
	{
		private static void Postfix()
		{
			_validatedPeers.Clear();
			_serverSentVersion = false;
		}
	}

	private const string RpcVersion = "HungerPangs_Version";

	private static readonly HashSet<ZRpc> _validatedPeers = new HashSet<ZRpc>();

	private static bool _serverSentVersion;

	public static Action<ZRpc> OnPeerValidated;

	private static void OnServerReceiveClientVersion(ZRpc rpc, string clientVersion)
	{
		if (string.Equals(clientVersion, "1.3.0", StringComparison.Ordinal))
		{
			_validatedPeers.Add(rpc);
			ManualLogSource log = HungerPangs.Log;
			if (log != null)
			{
				log.LogInfo((object)("[HungerPangs] Client presented matching version " + clientVersion + "."));
			}
		}
		else
		{
			ManualLogSource log2 = HungerPangs.Log;
			if (log2 != null)
			{
				log2.LogWarning((object)("[HungerPangs] Client version '" + clientVersion + "' != server '1.3.0'; will be disconnected at handshake."));
			}
		}
	}

	private static void OnClientReceiveServerVersion(ZRpc rpc, string serverVersion)
	{
		_serverSentVersion = true;
	}
}