Decompiled source of YoureHungry v0.6.4

YoureHungry.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("0.0.0.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 YoureHungry
{
	internal enum HungerStage
	{
		WellNourished,
		Fed,
		Hungry,
		Famished,
		Starving
	}
	internal enum FullnessStage
	{
		Comfortable,
		Full,
		Overfull,
		Nauseous
	}
	internal struct HungerEffects
	{
		internal float StaminaRegenMultiplier;

		internal float SprintDrainMultiplier;

		internal float JogSpeedMultiplier;

		internal float RunSpeedMultiplier;

		internal HungerEffects(float staminaRegenMultiplier, float sprintDrainMultiplier, float jogSpeedMultiplier, float runSpeedMultiplier)
		{
			StaminaRegenMultiplier = staminaRegenMultiplier;
			SprintDrainMultiplier = sprintDrainMultiplier;
			JogSpeedMultiplier = jogSpeedMultiplier;
			RunSpeedMultiplier = runSpeedMultiplier;
		}
	}
	internal static class HungerRules
	{
		internal const float MinimumNourishment = 0f;

		internal const float MaximumNourishment = 100f;

		internal const float MinimumFullness = 0f;

		internal const float MaximumFullness = 125f;

		internal const float HungryThreshold = 45f;

		internal const float FamishedThreshold = 25f;

		internal const float StarvingThreshold = 10f;

		internal const float StarvationDamageThreshold = 0.5f;

		internal const float FullThreshold = 85f;

		internal const float OverfullThreshold = 100f;

		internal const float NauseousThreshold = 115f;

		internal const float PurgeThreshold = 125f;

		internal const float PreCapPurgeThreshold = 122f;

		internal static float ClampNourishment(float value)
		{
			return Mathf.Clamp(value, 0f, 100f);
		}

		internal static float ClampFullness(float value)
		{
			return Mathf.Clamp(value, 0f, 125f);
		}

		internal static float GetNourishmentGain(float health, float stamina, float eitr, float burnTime, float regeneration)
		{
			health = Mathf.Max(0f, health);
			stamina = Mathf.Max(0f, stamina);
			eitr = Mathf.Max(0f, eitr);
			burnTime = Mathf.Max(0f, burnTime);
			regeneration = Mathf.Max(0f, regeneration);
			float num = health * 0.5f + stamina * 0.38f + eitr * 0.45f;
			float num2 = Mathf.Clamp01(burnTime / 1800f) * 8f;
			float num3 = Mathf.Clamp01(regeneration / 5f) * 2f;
			return Mathf.Clamp(4f + num * 0.09f + num2 + num3, 4f, 32f);
		}

		internal static float GetFullnessGain(float health, float stamina, float eitr, float burnTime, float regeneration)
		{
			health = Mathf.Max(0f, health);
			stamina = Mathf.Max(0f, stamina);
			eitr = Mathf.Max(0f, eitr);
			burnTime = Mathf.Max(0f, burnTime);
			regeneration = Mathf.Max(0f, regeneration);
			float num = health * 0.12f + stamina * 0.1f + eitr * 0.11f;
			float num2 = Mathf.Clamp01(burnTime / 1800f) * 5f;
			float num3 = Mathf.Clamp01(regeneration / 5f) * 2f;
			return Mathf.Clamp(5f + num + num2 + num3, 6f, 42f);
		}

		internal static float ApplyNourishmentDecay(float current, float deltaTime, float drainPerMinute)
		{
			float num = Mathf.Max(0f, drainPerMinute) / 60f;
			return ClampNourishment(current - num * Mathf.Max(0f, deltaTime));
		}

		internal static float DigestFullness(float current, float deltaTime, float digestionPerMinute)
		{
			float num = Mathf.Max(0f, digestionPerMinute) / 60f;
			return ClampFullness(current - num * Mathf.Max(0f, deltaTime));
		}

		internal static HungerStage GetStage(float nourishment)
		{
			nourishment = ClampNourishment(nourishment);
			if (nourishment >= 70f)
			{
				return HungerStage.WellNourished;
			}
			if (nourishment >= 45f)
			{
				return HungerStage.Fed;
			}
			if (nourishment >= 25f)
			{
				return HungerStage.Hungry;
			}
			if (nourishment >= 10f)
			{
				return HungerStage.Famished;
			}
			return HungerStage.Starving;
		}

		internal static string GetStageLabel(float nourishment)
		{
			return GetStage(nourishment) switch
			{
				HungerStage.WellNourished => "WELL NOURISHED", 
				HungerStage.Fed => "FED", 
				HungerStage.Hungry => "HUNGRY", 
				HungerStage.Famished => "FAMISHED", 
				_ => "STARVING", 
			};
		}

		internal static FullnessStage GetFullnessStage(float fullness)
		{
			fullness = ClampFullness(fullness);
			if (fullness < 85f)
			{
				return FullnessStage.Comfortable;
			}
			if (fullness < 100f)
			{
				return FullnessStage.Full;
			}
			if (fullness < 115f)
			{
				return FullnessStage.Overfull;
			}
			return FullnessStage.Nauseous;
		}

		internal static string GetFullnessStageLabel(float fullness)
		{
			return GetFullnessStage(fullness) switch
			{
				FullnessStage.Full => "FULL", 
				FullnessStage.Overfull => "OVERFULL", 
				FullnessStage.Nauseous => "NAUSEOUS", 
				_ => "", 
			};
		}

		internal static HungerEffects GetHungerEffects(float nourishment)
		{
			nourishment = ClampNourishment(nourishment);
			if (nourishment >= 45f)
			{
				return new HungerEffects(1f, 1f, 1f, 1f);
			}
			if (nourishment >= 25f)
			{
				float num = Mathf.InverseLerp(25f, 45f, nourishment);
				return new HungerEffects(Mathf.Lerp(0.9f, 1f, num), Mathf.Lerp(1.08f, 1f, num), Mathf.Lerp(0.99f, 1f, num), Mathf.Lerp(0.99f, 1f, num));
			}
			if (nourishment >= 10f)
			{
				float num2 = Mathf.InverseLerp(10f, 25f, nourishment);
				return new HungerEffects(Mathf.Lerp(0.84f, 0.9f, num2), Mathf.Lerp(1.14f, 1.08f, num2), Mathf.Lerp(0.97f, 0.99f, num2), Mathf.Lerp(0.97f, 0.99f, num2));
			}
			float num3 = Mathf.InverseLerp(10f, 0f, nourishment);
			return new HungerEffects(Mathf.Lerp(0.84f, 0.78f, num3), Mathf.Lerp(1.14f, 1.18f, num3), Mathf.Lerp(0.97f, 0.94f, num3), Mathf.Lerp(0.97f, 0.94f, num3));
		}

		internal static HungerEffects GetFullnessEffects(float fullness)
		{
			fullness = ClampFullness(fullness);
			if (fullness < 100f)
			{
				return new HungerEffects(1f, 1f, 1f, 1f);
			}
			if (fullness < 115f)
			{
				float num = Mathf.InverseLerp(100f, 115f, fullness);
				return new HungerEffects(Mathf.Lerp(0.98f, 0.94f, num), Mathf.Lerp(1.02f, 1.05f, num), Mathf.Lerp(0.995f, 0.985f, num), Mathf.Lerp(0.995f, 0.985f, num));
			}
			float num2 = Mathf.InverseLerp(115f, 125f, fullness);
			return new HungerEffects(Mathf.Lerp(0.94f, 0.88f, num2), Mathf.Lerp(1.05f, 1.1f, num2), Mathf.Lerp(0.985f, 0.97f, num2), Mathf.Lerp(0.985f, 0.97f, num2));
		}

		internal static HungerEffects GetCombinedEffects(float nourishment, float fullness, float postPurgeNauseaSeconds)
		{
			HungerEffects hungerEffects = GetHungerEffects(nourishment);
			HungerEffects fullnessEffects = GetFullnessEffects(fullness);
			float num = hungerEffects.StaminaRegenMultiplier * fullnessEffects.StaminaRegenMultiplier;
			float num2 = hungerEffects.SprintDrainMultiplier * fullnessEffects.SprintDrainMultiplier;
			float num3 = hungerEffects.JogSpeedMultiplier * fullnessEffects.JogSpeedMultiplier;
			float num4 = hungerEffects.RunSpeedMultiplier * fullnessEffects.RunSpeedMultiplier;
			if (postPurgeNauseaSeconds > 0.01f)
			{
				num *= 0.94f;
				num2 *= 1.05f;
				num3 *= 0.99f;
				num4 *= 0.99f;
			}
			return new HungerEffects(Mathf.Clamp(num, 0.74f, 1f), Mathf.Clamp(num2, 1f, 1.26f), Mathf.Clamp(num3, 0.94f, 1f), Mathf.Clamp(num4, 0.94f, 1f));
		}

		internal static string GetMealClass(string foodName, float nourishmentGain)
		{
			string text = (foodName ?? string.Empty).ToLowerInvariant();
			bool flag = text.Contains("stew") || text.Contains("sausage") || text.Contains("soup") || text.Contains("pie") || text.Contains("pudding") || text.Contains("feast") || text.Contains("serpent") || text.Contains("bread") || text.Contains("meat platter");
			if (nourishmentGain >= 24f || text.Contains("feast"))
			{
				return "Hearty meal";
			}
			if (flag || nourishmentGain >= 16f)
			{
				return "Proper meal";
			}
			if (nourishmentGain >= 10f)
			{
				return "Light meal";
			}
			return "Small snack";
		}

		internal static string GetMealClass(float nourishmentGain)
		{
			return GetMealClass(string.Empty, nourishmentGain);
		}

		internal static bool RunSelfTests(out string failure)
		{
			failure = null;
			if (!Approximately(ClampNourishment(-4f), 0f) || !Approximately(ClampNourishment(160f), 100f))
			{
				failure = "Nourishment clamp failed.";
				return false;
			}
			if (!Approximately(ClampFullness(-4f), 0f) || !Approximately(ClampFullness(160f), 125f))
			{
				failure = "Fullness clamp failed.";
				return false;
			}
			float nourishmentGain = GetNourishmentGain(7f, 20f, 0f, 600f, 1f);
			float nourishmentGain2 = GetNourishmentGain(40f, 30f, 0f, 1200f, 2f);
			float nourishmentGain3 = GetNourishmentGain(80f, 80f, 0f, 1800f, 4f);
			if (!(nourishmentGain < nourishmentGain2) || !(nourishmentGain2 < nourishmentGain3))
			{
				failure = "Nourishment ranking failed: prepared meals must score above snacks.";
				return false;
			}
			if (GetFullnessStage(84.9f) != FullnessStage.Comfortable || GetFullnessStage(90f) != FullnessStage.Full || GetFullnessStage(107f) != FullnessStage.Overfull || GetFullnessStage(120f) != FullnessStage.Nauseous)
			{
				failure = "Fullness stage thresholds failed.";
				return false;
			}
			HungerEffects combinedEffects = GetCombinedEffects(100f, 0f, 0f);
			HungerEffects combinedEffects2 = GetCombinedEffects(100f, 124f, 0f);
			HungerEffects combinedEffects3 = GetCombinedEffects(0f, 125f, 30f);
			if (!(combinedEffects2.StaminaRegenMultiplier < combinedEffects.StaminaRegenMultiplier) || !(combinedEffects3.StaminaRegenMultiplier >= 0.74f) || !(combinedEffects3.RunSpeedMultiplier >= 0.94f) || !(combinedEffects3.SprintDrainMultiplier <= 1.26f))
			{
				failure = "Combined hunger/fullness safety cap failed.";
				return false;
			}
			if (!Approximately(ApplyNourishmentDecay(1f, 120f, 1f), 0f) || !Approximately(DigestFullness(1f, 120f, 1f), 0f))
			{
				failure = "Decay clamp failed.";
				return false;
			}
			return true;
		}

		private static bool Approximately(float left, float right)
		{
			return Mathf.Abs(left - right) < 0.001f;
		}
	}
	[BepInPlugin("com.marccunningham.yourehungry", "You're Hungry", "0.7.0")]
	[BepInProcess("valheim.exe")]
	public sealed class YoureHungryPlugin : BaseUnityPlugin
	{
		private sealed class VanillaFoodHudElement
		{
			public readonly GameObject GameObject;

			public readonly bool OriginalActiveSelf;

			public VanillaFoodHudElement(GameObject gameObject, bool originalActiveSelf)
			{
				GameObject = gameObject;
				OriginalActiveSelf = originalActiveSelf;
			}
		}

		private sealed class RecentFood
		{
			internal readonly string DisplayName;

			internal readonly string PrefabName;

			internal readonly string RawFoodName;

			internal Sprite Icon;

			internal RecentFood(string displayName, string prefabName, string rawFoodName, Sprite icon)
			{
				DisplayName = (string.IsNullOrEmpty(displayName) ? "Unknown food" : displayName);
				PrefabName = prefabName ?? string.Empty;
				RawFoodName = rawFoodName ?? string.Empty;
				Icon = icon;
			}
		}

		private sealed class FoodAuditEntry
		{
			internal readonly string DisplayName;

			internal readonly string PrefabName;

			internal readonly string RawFoodName;

			internal readonly Sprite Icon;

			internal readonly float Health;

			internal readonly float Stamina;

			internal readonly float Eitr;

			internal readonly float Regeneration;

			internal readonly float BurnTime;

			internal readonly float NourishmentGain;

			internal readonly float FullnessGain;

			internal readonly int SourceIndex;

			internal FoodAuditEntry(string displayName, string prefabName, string rawFoodName, Sprite icon, float health, float stamina, float eitr, float regeneration, float burnTime, float nourishmentGain, float fullnessGain, int sourceIndex)
			{
				DisplayName = displayName;
				PrefabName = prefabName ?? string.Empty;
				RawFoodName = rawFoodName ?? string.Empty;
				Icon = icon;
				Health = health;
				Stamina = stamina;
				Eitr = eitr;
				Regeneration = regeneration;
				BurnTime = burnTime;
				NourishmentGain = nourishmentGain;
				FullnessGain = fullnessGain;
				SourceIndex = sourceIndex;
			}
		}

		public const string PluginGuid = "com.marccunningham.yourehungry";

		public const string PluginName = "You're Hungry";

		public const string PluginVersion = "0.7.0";

		private const string CustomNourishmentKey = "com.marccunningham.yourehungry.nourishment.v2";

		private const string CustomFullnessKey = "com.marccunningham.yourehungry.fullness.v2";

		private const string CustomStarvationSecondsKey = "com.marccunningham.yourehungry.starvationseconds.v1";

		private const string CustomNauseaSecondsKey = "com.marccunningham.yourehungry.nauseaseconds.v1";

		private const string CustomStarvationVisualShownKey = "com.marccunningham.yourehungry.starvationvfxshown.v1";

		private const string CustomRecentFoodsKey = "com.marccunningham.yourehungry.recentfoods.v2";

		private const string LegacyCustomRecentFoodsKey = "com.marccunningham.yourehungry.recentfoods.v1";

		private const string RecentMemoryVersionPrefix = "v2:";

		private const char RecentFoodRecordSeparator = '|';

		private const char RecentFoodFieldSeparator = ',';

		private const string LegacyZdoNourishment = "yourehungry_nourishment_v2";

		private const string LegacyZdoFullness = "yourehungry_fullness_v2";

		private const float MissingZdoValue = -9999f;

		private const float ReferenceScreenWidth = 2048f;

		private const float ReferenceScreenHeight = 1152f;

		private const float DefaultBarLeftAtReference = 246f;

		private const float DefaultBarTopAtReference = 932f;

		private const float DefaultBarWidthAtReference = 224f;

		private const float DefaultBarHeightAtReference = 38f;

		private const float DefaultRecentLeftAtReference = 54f;

		private const float DefaultRecentTopAtReference = 900f;

		private const float DefaultRecentSizeAtReference = 48f;

		private const float DefaultRecentGapAtReference = 2f;

		private const float DefaultFrameThicknessAtReference = 2f;

		private const int RecentFoodLimit = 3;

		private const float V021BarTopAtReference = 924f;

		private const float V021RecentLeftAtReference = 526f;

		private const float V021RecentSizeAtReference = 34f;

		private const float V021RecentGapAtReference = 8f;

		private const float V030RecentLeftAtReference = 538f;

		private const float V030RecentTopAtReference = 982f;

		private const float V060RecentTopAtReference = 890f;

		private const float V030RecentSizeAtReference = 38f;

		private const float V030RecentGapAtReference = 7f;

		internal static YoureHungryPlugin Instance;

		private readonly List<RecentFood> recentFoods = new List<RecentFood>(3);

		private readonly List<FoodAuditEntry> activeFoods = new List<FoodAuditEntry>();

		private ConfigEntry<bool> modEnabled;

		private ConfigEntry<bool> showHud;

		private ConfigEntry<bool> hideVanillaFoodHud;

		private ConfigEntry<bool> saveRecentFoodMemory;

		private ConfigEntry<KeyCode> diagnosticKey;

		private ConfigEntry<bool> verboseFoodLogging;

		private ConfigEntry<string> difficultyPreset;

		private ConfigEntry<string> lastAppliedDifficultyPreset;

		private ConfigEntry<float> hungerEffectStrength;

		private ConfigEntry<float> minimumStaminaRegenMultiplier;

		private ConfigEntry<float> startingNourishment;

		private ConfigEntry<float> passiveNourishmentDrainPerMinute;

		private ConfigEntry<float> fullnessDigestionPerMinute;

		private ConfigEntry<float> saveIntervalSeconds;

		private ConfigEntry<bool> hungerEffectsEnabled;

		private ConfigEntry<float> starvationGraceSeconds;

		private ConfigEntry<float> starvationDamagePerMinute;

		private ConfigEntry<float> starvationDamageTickSeconds;

		private ConfigEntry<float> starvationWarningIntervalSeconds;

		private ConfigEntry<bool> fullnessEffectsEnabled;

		private ConfigEntry<float> postPurgeNauseaSeconds;

		private ConfigEntry<float> purgeFullnessAfter;

		private ConfigEntry<float> purgeNourishmentLoss;

		private ConfigEntry<float> nauseaWarningIntervalSeconds;

		private ConfigEntry<bool> enableBukeperryVfx;

		private ConfigEntry<bool> playStarvationBukeperryVfx;

		private ConfigEntry<float> hudRetrySeconds;

		private ConfigEntry<bool> enableTestKeys;

		private ConfigEntry<KeyCode> testFeedKey;

		private ConfigEntry<KeyCode> testDrainKey;

		private ConfigEntry<float> barLeftAtReference;

		private ConfigEntry<float> barTopAtReference;

		private ConfigEntry<float> barWidthAtReference;

		private ConfigEntry<float> barHeightAtReference;

		private ConfigEntry<float> recentLeftAtReference;

		private ConfigEntry<float> recentTopAtReference;

		private ConfigEntry<float> recentSizeAtReference;

		private ConfigEntry<float> recentGapAtReference;

		private ConfigEntry<float> frameThicknessAtReference;

		private Harmony harmony;

		private bool canEatPatchInstalled;

		private bool eatFoodPatchInstalled;

		private bool staminaRegenPatchInstalled;

		private bool runDrainPatchInstalled;

		private bool jogSpeedPatchInstalled;

		private bool runSpeedPatchInstalled;

		private Player lastPlayer;

		private bool stateLoaded;

		private float stateLoadNotBefore;

		private bool loadedFromCustomData;

		private bool loadedFromLegacyZdo;

		private string lastRawSavedNourishment = "<not read>";

		private string lastRawSavedFullness = "<not read>";

		private int successfulCustomDataWrites;

		private int failedCustomDataWrites;

		private float nourishment;

		private float fullness;

		private float activeFoodPreview;

		private float refreshTimer;

		private float saveTimer;

		private float lastNourishmentRatePerSecond;

		private float lastFullnessRatePerSecond;

		private int eatFoodHookCalls;

		private int canEatAttemptHookCalls;

		private int eatFoodAttemptHookCalls;

		private int preCapPurgeInterceptions;

		private string lastPurgeTrigger = "None";

		private string lastPurgeFoodName = "None";

		private string lastMealName = "None";

		private float lastMealNourishmentGain;

		private float lastMealFullnessGain;

		private float starvationSeconds;

		private float starvationDamageTimer;

		private float starvationWarningTimer;

		private float nauseaSecondsRemaining;

		private float nauseaWarningTimer;

		private int purgeEvents;

		private int nauseaWarningCount;

		private bool starvationBukeperryVfxPlayed;

		private bool starvationBukeperryVfxLoadedFromCustomData;

		private string lastRawSavedStarvationVfxShown = "<not read>";

		private int bukeperryVfxAttempts;

		private int bukeperryVfxPlays;

		private int bukeperryVfxFailures;

		private string lastBukeperryVfxTrigger = "None";

		private string bukeperryVfxStatus = "not resolved";

		private StatusEffect bukeperryStatusEffectTemplate;

		private MethodInfo bukeperryTriggerStartEffectsMethod;

		private float nextBukeperryVfxResolveAt;

		private string lastRawSavedStarvationSeconds = "<not read>";

		private string lastRawSavedNauseaSeconds = "<not read>";

		private HungerStage lastHungerStage = HungerStage.Fed;

		private FullnessStage lastFullnessStage;

		private int staminaRegenHookCalls;

		private int runDrainHookCalls;

		private int jogSpeedHookCalls;

		private int runSpeedHookCalls;

		private int starvationDamageTicks;

		private float lastStaminaRegenBefore = 1f;

		private float lastStaminaRegenAfter = 1f;

		private float lastRunDrainBefore = 1f;

		private float lastRunDrainAfter = 1f;

		private float lastJogSpeedBefore = 1f;

		private float lastJogSpeedAfter = 1f;

		private float lastRunSpeedBefore = 1f;

		private float lastRunSpeedAfter = 1f;

		private float nextVanillaFoodHudResolveAt;

		private bool guiFailureLogged;

		private Texture2D pixelTexture;

		private GUIStyle headerStyle;

		private GUIStyle percentStyle;

		private GUIStyle recentIndexStyle;

		private GUIStyle recentEmptyStyle;

		private FieldInfo vanillaFoodBarRootField;

		private FieldInfo vanillaFoodBarsField;

		private FieldInfo vanillaFoodIconsField;

		private FieldInfo vanillaFoodTimeField;

		private RectTransform vanillaFoodBarRoot;

		private readonly List<VanillaFoodHudElement> vanillaFoodHudElements = new List<VanillaFoodHudElement>(12);

		private Hud vanillaFoodHudOwner;

		private bool vanillaFoodHudHiddenByMod;

		private bool vanillaFoodHudFailureLogged;

		private bool hudUpdateFoodPatchInstalled;

		private int vanillaFoodHudHideAttempts;

		private int vanillaFoodHudSuccessfulHides;

		private int vanillaFoodHudTrackedElements;

		private int vanillaFoodHudActiveElementsRemaining;

		private bool movementBridgeConnected;

		private string movementBridgeStatus = "not resolved";

		private int movementBridgeResolveAttempts;

		private int movementBridgeMovementCalls;

		private int movementBridgeRunDrainCalls;

		private float nextMovementBridgeResolveAt;

		private Type movementBridgeType;

		private FieldInfo movementBridgeMovementField;

		private FieldInfo movementBridgeRunDrainField;

		private Delegate movementBridgePreviousMovementProvider;

		private Delegate movementBridgePreviousRunDrainProvider;

		private Func<Player, float> movementBridgeMovementProvider;

		private Func<Player, float> movementBridgeRunDrainProvider;

		private bool recentFoodMemoryLoaded;

		private int recentFoodMemoryMigrations;

		private string recentMemorySummary = "<not read>";

		private string lastRawRecentFoods = "<not read>";

		private void Awake()
		{
			Instance = this;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"You're Hungry 0.7.0 loading.");
			BindConfig();
			MigrateHudLayoutToV060();
			MigrateLegacyDefaultsToNormal();
			ApplyDifficultyPresetIfChanged();
			CreatePixelTexture();
			if (!HungerRules.RunSelfTests(out var failure))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("You're Hungry core-rule self-test failed: " + failure));
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"You're Hungry core-rule self-test passed.");
			}
			InstallFoodObserverPatch();
			InstallVanillaFoodHudRefreshPatch();
			InstallHungerEffectPatches();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"You're Hungry 0.7.0 loaded. V0.6.3 keeps the Bukeperry Feeling Sick status and explicitly triggers the real Bukeperry start VFX at the player once per sickness event. The Nourishment bar now shows nourishment only; sickness remains in Valheim's normal status-effect area. Press F7 to write hunger, fullness, sickness, HUD, bridge and patch diagnostics to LogOutput.log.");
		}

		private void OnDestroy()
		{
			TrySaveCurrentPlayer();
			RestoreVanillaFoodHud();
			DisconnectMovementBridge();
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			if ((Object)(object)pixelTexture != (Object)null)
			{
				Object.Destroy((Object)(object)pixelTexture);
				pixelTexture = null;
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		private void Update()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Unknown result type (might be due to invalid IL or missing references)
			if (Input.GetKeyDown(diagnosticKey.Value))
			{
				WriteDiagnostic();
			}
			if (!modEnabled.Value)
			{
				RestoreVanillaFoodHud();
				DisconnectMovementBridge();
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				RestoreVanillaFoodHud();
				DisconnectMovementBridge();
				return;
			}
			TryConnectMovementBridge();
			UpdateVanillaFoodHudVisibility();
			if ((Object)(object)localPlayer != (Object)(object)lastPlayer)
			{
				lastPlayer = localPlayer;
				stateLoaded = false;
				stateLoadNotBefore = Time.unscaledTime + 1f;
				loadedFromCustomData = false;
				loadedFromLegacyZdo = false;
				lastRawSavedNourishment = "<waiting>";
				lastRawSavedFullness = "<waiting>";
				lastRawSavedStarvationSeconds = "<waiting>";
				lastRawSavedNauseaSeconds = "<waiting>";
				lastRawSavedStarvationVfxShown = "<waiting>";
				starvationBukeperryVfxPlayed = false;
				starvationBukeperryVfxLoadedFromCustomData = false;
				bukeperryStatusEffectTemplate = null;
				bukeperryTriggerStartEffectsMethod = null;
				nextBukeperryVfxResolveAt = 0f;
				starvationSeconds = 0f;
				starvationDamageTimer = 0f;
				starvationWarningTimer = 0f;
				nauseaSecondsRemaining = 0f;
				nauseaWarningTimer = 0f;
				lastHungerStage = HungerStage.Fed;
				lastFullnessStage = FullnessStage.Comfortable;
				nextVanillaFoodHudResolveAt = 0f;
				saveTimer = 0f;
				lastMealName = "None";
				lastMealNourishmentGain = 0f;
				lastMealFullnessGain = 0f;
				recentFoodMemoryLoaded = false;
				lastRawRecentFoods = "<waiting>";
				SeedRecentFoodsFromActiveFoods(localPlayer);
				RefreshActiveFoodAudit(localPlayer);
			}
			EnsureStateLoaded(localPlayer);
			if (!stateLoaded)
			{
				return;
			}
			if (enableTestKeys.Value)
			{
				if (Input.GetKeyDown(testFeedKey.Value))
				{
					ApplyTestChange(localPlayer, 10f, 10f, "test feed");
				}
				if (Input.GetKeyDown(testDrainKey.Value))
				{
					ApplyTestChange(localPlayer, -10f, 0f, "test drain");
				}
			}
			float num = Mathf.Max(0f, Time.deltaTime);
			TickNourishmentAndFullness(num);
			TickFullnessState(localPlayer, num);
			TickHungerState(localPlayer, num);
			refreshTimer += num;
			if (refreshTimer >= 0.5f)
			{
				refreshTimer = 0f;
				RefreshActiveFoodAudit(localPlayer);
				ResolveMissingRecentFoodIcons();
			}
			saveTimer += num;
			if (saveTimer >= Mathf.Max(1f, saveIntervalSeconds.Value))
			{
				saveTimer = 0f;
				SaveState(localPlayer);
			}
		}

		private void OnGUI()
		{
			Player localPlayer = Player.m_localPlayer;
			if (!modEnabled.Value || !showHud.Value || (Object)(object)localPlayer == (Object)null || (Object)(object)localPlayer != (Object)(object)lastPlayer || !stateLoaded)
			{
				return;
			}
			try
			{
				EnsureGuiStyles();
				DrawNourishmentHud();
			}
			catch (Exception ex)
			{
				if (!guiFailureLogged)
				{
					guiFailureLogged = true;
					((BaseUnityPlugin)this).Logger.LogError((object)("You're Hungry HUD error: " + ex));
				}
			}
		}

		private void BindConfig()
		{
			modEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enables the Nourishment and hidden Fullness core.");
			showHud = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "ShowNourishmentBar", true, "Shows the real Nourishment bar and three recent-food memory slots in Valheim's original food-slot location.");
			hideVanillaFoodHud = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "HideVanillaFoodHud", true, "Hides Valheim's old food icons / food bar after the custom Nourishment HUD is active. It does not hide the red health bar, stamina bar, eitr bar, hotbar or boss power.");
			saveRecentFoodMemory = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "SaveRecentFoodMemory", true, "Saves the last three meal-memory icons with the character so they survive relogging.");
			diagnosticKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "DiagnosticKey", (KeyCode)288, "Writes Nourishment, hidden Fullness, active food values, save state and HUD state to BepInEx\\LogOutput.log.");
			verboseFoodLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "VerboseFoodLogging", true, "Logs each successfully eaten food and its Nourishment / Fullness gains.");
			difficultyPreset = ((BaseUnityPlugin)this).Config.Bind<string>("Difficulty", "Preset", "Normal", "Choose Easy, Normal, Hard, or Extreme. Change this setting, then restart Valheim to apply that mod's matching survival balance.");
			lastAppliedDifficultyPreset = ((BaseUnityPlugin)this).Config.Bind<string>("Difficulty", "LastAppliedPreset", "Normal", "Internal preset tracking. Leave this alone; it records the last difficulty preset applied.");
			hungerEffectStrength = ((BaseUnityPlugin)this).Config.Bind<float>("Difficulty", "EffectStrength", 0.75f, "How strongly low Nourishment, fullness and sickness penalties are applied. Difficulty presets manage this value.");
			minimumStaminaRegenMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Difficulty", "MinimumStaminaRegenMultiplier", 0.82f, "Safety floor for this mod's own hunger penalty. It prevents hunger from combining with other Scavengers penalties into a no-escape state.");
			startingNourishment = ((BaseUnityPlugin)this).Config.Bind<float>("Core", "StartingNourishment", 65f, "Nourishment used once for a character with no saved YoureHungry state.");
			passiveNourishmentDrainPerMinute = ((BaseUnityPlugin)this).Config.Bind<float>("Core", "PassiveNourishmentDrainPerMinute", 0.8f, "Nourishment lost per real-time minute. V0.4 adds gradual hunger effects below 45 Nourishment.");
			fullnessDigestionPerMinute = ((BaseUnityPlugin)this).Config.Bind<float>("Core", "FullnessDigestionPerMinute", 1.1f, "Hidden stomach fullness digested per real-time minute.");
			saveIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Core", "SaveIntervalSeconds", 5f, "How often Nourishment, hidden Fullness, starvation time and the recent-food memory save to Player.m_customData.");
			hungerEffectsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Hunger Effects", "EnableHungerEffects", true, "Applies gradual stamina, sprint and movement effects below 45 Nourishment, plus starvation health damage only after the grace period.");
			starvationGraceSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Hunger Effects", "StarvationGraceSeconds", 150f, "Seconds at 0 Nourishment before starvation starts taking health. Eating any food resets this grace period.");
			starvationDamagePerMinute = ((BaseUnityPlugin)this).Config.Bind<float>("Hunger Effects", "StarvationDamagePerMinute", 4f, "Health lost per real-time minute after the starvation grace period. The default never reduces the player below 1 health.");
			starvationDamageTickSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Hunger Effects", "StarvationDamageTickSeconds", 2.5f, "How often starvation health loss is applied after the grace period.");
			starvationWarningIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Hunger Effects", "StarvationWarningIntervalSeconds", 50f, "Minimum seconds between hunger/starvation warning messages.");
			fullnessEffectsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Fullness Effects", "EnableFullnessEffects", true, "Enables hidden-stomach fullness stages: Full, Overfull, Nauseous and a Bukeperry-style purge only at maximum capacity.");
			postPurgeNauseaSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Fullness Effects", "PostPurgeNauseaSeconds", 25f, "Seconds of mild sickness after a forced purge. This saves with the character so relogging cannot clear it.");
			purgeFullnessAfter = ((BaseUnityPlugin)this).Config.Bind<float>("Fullness Effects", "FullnessAfterPurge", 40f, "Hidden fullness remaining after a stomach-capacity purge. The player is relieved, but not instantly empty.");
			purgeNourishmentLoss = ((BaseUnityPlugin)this).Config.Bind<float>("Fullness Effects", "NourishmentLostOnPurge", 14f, "Nourishment lost when severe overeating causes a purge. Current Valheim food buffs are also cleared, matching Bukeperries.");
			nauseaWarningIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Fullness Effects", "NauseaWarningIntervalSeconds", 40f, "Minimum seconds between nausea warnings while fullness remains in the nauseous range.");
			enableBukeperryVfx = ((BaseUnityPlugin)this).Config.Bind<bool>("Visual Sickness", "EnableBukeperryVfx", true, "Plays Valheim's actual Bukeperry vomiting visual once when forced overeating causes a purge.");
			playStarvationBukeperryVfx = ((BaseUnityPlugin)this).Config.Bind<bool>("Visual Sickness", "PlayBukeperryVfxOnStarvation", true, "Plays the same Bukeperry retching visual once when Nourishment first reaches starvation. It resets only after you eat again.");
			hudRetrySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "VanillaFoodHudRetrySeconds", 2f, "How often the old food HUD is resolved as a fallback. Hud.UpdateFood is patched for immediate refreshes, so this stays low-cost.");
			enableTestKeys = ((BaseUnityPlugin)this).Config.Bind<bool>("Testing", "EnableTestKeys", false, "Enables F6 (+10 Nourishment and +10 Fullness) and F10 (-10 Nourishment) for testing only.");
			testFeedKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Testing", "TestFeedKey", (KeyCode)287, "Adds 10 Nourishment and 10 hidden Fullness when test keys are enabled.");
			testDrainKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Testing", "TestDrainKey", (KeyCode)291, "Removes 10 Nourishment when test keys are enabled.");
			barLeftAtReference = ((BaseUnityPlugin)this).Config.Bind<float>("HUD Layout", "BarLeftAt2048x1152", 246f, "Nourishment bar left edge at 2048x1152.");
			barTopAtReference = ((BaseUnityPlugin)this).Config.Bind<float>("HUD Layout", "BarTopAt2048x1152", 932f, "Nourishment bar top edge at 2048x1152. V0.3 places it 12 px above the Cold/Injured stack.");
			barWidthAtReference = ((BaseUnityPlugin)this).Config.Bind<float>("HUD Layout", "BarWidthAt2048x1152", 224f, "Nourishment bar width at 2048x1152.");
			barHeightAtReference = ((BaseUnityPlugin)this).Config.Bind<float>("HUD Layout", "BarHeightAt2048x1152", 38f, "Nourishment bar height at 2048x1152.");
			recentLeftAtReference = ((BaseUnityPlugin)this).Config.Bind<float>("HUD Layout", "RecentFoodLeftAt2048x1152", 54f, "Left edge of the vertical recent-food memory list at 2048x1152. V0.6 places it in Valheim's original three-food-slot column beside the health bar.");
			recentTopAtReference = ((BaseUnityPlugin)this).Config.Bind<float>("HUD Layout", "RecentFoodTopAt2048x1152", 900f, "Top edge of the newest recent-food memory slot at 2048x1152. V0.6.2 nudges the cards down into the exact original food-slot gap.");
			recentSizeAtReference = ((BaseUnityPlugin)this).Config.Bind<float>("HUD Layout", "RecentFoodSizeAt2048x1152", 48f, "Width and height of each recent-food memory slot at 2048x1152. V0.6 matches the old Valheim food-card size.");
			recentGapAtReference = ((BaseUnityPlugin)this).Config.Bind<float>("HUD Layout", "RecentFoodGapAt2048x1152", 2f, "Vertical gap between recent-food memory slots at 2048x1152. V0.6 matches the old Valheim food-card spacing.");
			frameThicknessAtReference = ((BaseUnityPlugin)this).Config.Bind<float>("HUD Layout", "FrameThicknessAt2048x1152", 2f, "Frame thickness at 2048x1152.");
		}

		private void MigrateLegacyDefaultsToNormal()
		{
			if (Mathf.Abs(passiveNourishmentDrainPerMinute.Value - 1f) < 0.001f && Mathf.Abs(fullnessDigestionPerMinute.Value - 1f) < 0.001f && Mathf.Abs(starvationGraceSeconds.Value - 120f) < 0.001f && Mathf.Abs(starvationDamagePerMinute.Value - 6f) < 0.001f && Mathf.Abs(postPurgeNauseaSeconds.Value - 30f) < 0.001f && Mathf.Abs(purgeFullnessAfter.Value - 35f) < 0.001f && Mathf.Abs(purgeNourishmentLoss.Value - 18f) < 0.001f)
			{
				ApplyDifficultyPreset("Normal");
				((BaseUnityPlugin)this).Config.Save();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"You're Hungry: migrated untouched legacy hunger defaults to the balanced Normal preset.");
			}
		}

		private void ApplyDifficultyPresetIfChanged()
		{
			string text = NormalizeDifficultyPreset(difficultyPreset.Value);
			if (!string.Equals(difficultyPreset.Value, text, StringComparison.OrdinalIgnoreCase))
			{
				difficultyPreset.Value = text;
			}
			if (!string.Equals(lastAppliedDifficultyPreset.Value, text, StringComparison.OrdinalIgnoreCase))
			{
				ApplyDifficultyPreset(text);
				lastAppliedDifficultyPreset.Value = text;
				((BaseUnityPlugin)this).Config.Save();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("You're Hungry: applied " + text + " difficulty preset."));
			}
		}

		private static string NormalizeDifficultyPreset(string value)
		{
			if (string.Equals(value, "Easy", StringComparison.OrdinalIgnoreCase))
			{
				return "Easy";
			}
			if (string.Equals(value, "Hard", StringComparison.OrdinalIgnoreCase))
			{
				return "Hard";
			}
			if (string.Equals(value, "Extreme", StringComparison.OrdinalIgnoreCase))
			{
				return "Extreme";
			}
			return "Normal";
		}

		private void ApplyDifficultyPreset(string preset)
		{
			switch (preset)
			{
			case "Easy":
				passiveNourishmentDrainPerMinute.Value = 0.5f;
				fullnessDigestionPerMinute.Value = 1.3f;
				starvationGraceSeconds.Value = 210f;
				starvationDamagePerMinute.Value = 2.5f;
				starvationWarningIntervalSeconds.Value = 60f;
				postPurgeNauseaSeconds.Value = 18f;
				purgeFullnessAfter.Value = 45f;
				purgeNourishmentLoss.Value = 10f;
				nauseaWarningIntervalSeconds.Value = 50f;
				hungerEffectStrength.Value = 0.55f;
				minimumStaminaRegenMultiplier.Value = 0.9f;
				break;
			case "Hard":
				passiveNourishmentDrainPerMinute.Value = 1.15f;
				fullnessDigestionPerMinute.Value = 0.85f;
				starvationGraceSeconds.Value = 100f;
				starvationDamagePerMinute.Value = 7f;
				starvationWarningIntervalSeconds.Value = 35f;
				postPurgeNauseaSeconds.Value = 40f;
				purgeFullnessAfter.Value = 35f;
				purgeNourishmentLoss.Value = 20f;
				nauseaWarningIntervalSeconds.Value = 28f;
				hungerEffectStrength.Value = 1.15f;
				minimumStaminaRegenMultiplier.Value = 0.75f;
				break;
			case "Extreme":
				passiveNourishmentDrainPerMinute.Value = 1.55f;
				fullnessDigestionPerMinute.Value = 0.65f;
				starvationGraceSeconds.Value = 60f;
				starvationDamagePerMinute.Value = 10f;
				starvationWarningIntervalSeconds.Value = 25f;
				postPurgeNauseaSeconds.Value = 55f;
				purgeFullnessAfter.Value = 30f;
				purgeNourishmentLoss.Value = 25f;
				nauseaWarningIntervalSeconds.Value = 20f;
				hungerEffectStrength.Value = 1.4f;
				minimumStaminaRegenMultiplier.Value = 0.68f;
				break;
			default:
				passiveNourishmentDrainPerMinute.Value = 0.8f;
				fullnessDigestionPerMinute.Value = 1.1f;
				starvationGraceSeconds.Value = 150f;
				starvationDamagePerMinute.Value = 4f;
				starvationWarningIntervalSeconds.Value = 50f;
				postPurgeNauseaSeconds.Value = 25f;
				purgeFullnessAfter.Value = 40f;
				purgeNourishmentLoss.Value = 14f;
				nauseaWarningIntervalSeconds.Value = 40f;
				hungerEffectStrength.Value = 0.75f;
				minimumStaminaRegenMultiplier.Value = 0.82f;
				break;
			}
		}

		private void MigrateHudLayoutToV060()
		{
			bool flag = false;
			if (Mathf.Approximately(barTopAtReference.Value, 924f))
			{
				barTopAtReference.Value = 932f;
				flag = true;
			}
			bool num = Mathf.Approximately(recentLeftAtReference.Value, 526f) || Mathf.Approximately(recentLeftAtReference.Value, 538f);
			bool flag2 = Mathf.Approximately(recentTopAtReference.Value, 982f) || Mathf.Approximately(recentTopAtReference.Value, 890f);
			bool flag3 = Mathf.Approximately(recentSizeAtReference.Value, 34f) || Mathf.Approximately(recentSizeAtReference.Value, 38f);
			bool flag4 = Mathf.Approximately(recentGapAtReference.Value, 8f) || Mathf.Approximately(recentGapAtReference.Value, 7f);
			if (num)
			{
				recentLeftAtReference.Value = 54f;
				flag = true;
			}
			if (flag2)
			{
				recentTopAtReference.Value = 900f;
				flag = true;
			}
			if (flag3)
			{
				recentSizeAtReference.Value = 48f;
				flag = true;
			}
			if (flag4)
			{
				recentGapAtReference.Value = 2f;
				flag = true;
			}
			if (flag)
			{
				((BaseUnityPlugin)this).Config.Save();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"You're Hungry migrated untouched meal-memory HUD values into Valheim's original food-slot column.");
			}
		}

		private void UpdateVanillaFoodHudVisibility()
		{
			if (!hideVanillaFoodHud.Value || !showHud.Value || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				RestoreVanillaFoodHud();
			}
			else if (!(Time.unscaledTime < nextVanillaFoodHudResolveAt))
			{
				nextVanillaFoodHudResolveAt = Time.unscaledTime + Mathf.Max(0.25f, hudRetrySeconds.Value);
				TryHideVanillaFoodHud(ResolveHudInstance());
			}
		}

		private void TryHideVanillaFoodHud(Hud hud)
		{
			if (!hideVanillaFoodHud.Value || !showHud.Value || (Object)(object)hud == (Object)null)
			{
				return;
			}
			try
			{
				if ((Object)(object)vanillaFoodHudOwner != (Object)null && (Object)(object)vanillaFoodHudOwner != (Object)(object)hud)
				{
					vanillaFoodBarRoot = null;
					vanillaFoodHudOwner = null;
					vanillaFoodHudHiddenByMod = false;
					vanillaFoodHudElements.Clear();
					vanillaFoodHudTrackedElements = 0;
					vanillaFoodHudActiveElementsRemaining = 0;
				}
				ResolveVanillaFoodHudFields();
				if (vanillaFoodBarRootField == null || vanillaFoodBarsField == null || vanillaFoodIconsField == null)
				{
					LogVanillaFoodHudFailure("could not resolve Hud food-display fields; Valheim's old food HUD was left visible.");
					return;
				}
				vanillaFoodHudHideAttempts++;
				object? value = vanillaFoodBarRootField.GetValue(hud);
				RectTransform val = (RectTransform)((value is RectTransform) ? value : null);
				if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).gameObject != (Object)null)
				{
					vanillaFoodBarRoot = val;
					vanillaFoodHudOwner = hud;
					CaptureAndHideVanillaFoodElement(((Component)val).gameObject);
				}
				else
				{
					vanillaFoodHudOwner = hud;
				}
				CaptureAndHideVanillaFoodField(hud, vanillaFoodBarsField);
				CaptureAndHideVanillaFoodField(hud, vanillaFoodIconsField);
				CaptureAndHideVanillaFoodField(hud, vanillaFoodTimeField);
				vanillaFoodHudTrackedElements = vanillaFoodHudElements.Count;
				vanillaFoodHudActiveElementsRemaining = CountActiveVanillaFoodHudElements();
				vanillaFoodHudHiddenByMod = vanillaFoodHudTrackedElements > 0 && vanillaFoodHudActiveElementsRemaining == 0;
				vanillaFoodHudFailureLogged = false;
			}
			catch (Exception ex)
			{
				LogVanillaFoodHudFailure("failed while hiding the old food HUD: " + ex.Message);
			}
		}

		private void ResolveVanillaFoodHudFields()
		{
			if (vanillaFoodBarRootField == null)
			{
				vanillaFoodBarRootField = AccessTools.Field(typeof(Hud), "m_foodBarRoot");
			}
			if (vanillaFoodBarsField == null)
			{
				vanillaFoodBarsField = AccessTools.Field(typeof(Hud), "m_foodBars");
			}
			if (vanillaFoodIconsField == null)
			{
				vanillaFoodIconsField = AccessTools.Field(typeof(Hud), "m_foodIcons");
			}
			if (vanillaFoodTimeField == null)
			{
				vanillaFoodTimeField = AccessTools.Field(typeof(Hud), "m_foodTime");
			}
		}

		private void CaptureAndHideVanillaFoodField(Hud hud, FieldInfo field)
		{
			if ((Object)(object)hud == (Object)null || field == null)
			{
				return;
			}
			object value = field.GetValue(hud);
			if (value == null)
			{
				return;
			}
			if (value is IEnumerable enumerable && !(value is string))
			{
				foreach (object item in enumerable)
				{
					CaptureAndHideVanillaFoodElement(TryGetHudElementGameObject(item));
				}
				return;
			}
			CaptureAndHideVanillaFoodElement(TryGetHudElementGameObject(value));
		}

		private void CaptureAndHideVanillaFoodElement(GameObject gameObject)
		{
			if ((Object)(object)gameObject == (Object)null)
			{
				return;
			}
			VanillaFoodHudElement vanillaFoodHudElement = null;
			for (int i = 0; i < vanillaFoodHudElements.Count; i++)
			{
				if ((Object)(object)vanillaFoodHudElements[i].GameObject == (Object)(object)gameObject)
				{
					vanillaFoodHudElement = vanillaFoodHudElements[i];
					break;
				}
			}
			if (vanillaFoodHudElement == null)
			{
				vanillaFoodHudElement = new VanillaFoodHudElement(gameObject, gameObject.activeSelf);
				vanillaFoodHudElements.Add(vanillaFoodHudElement);
			}
			if (gameObject.activeSelf)
			{
				gameObject.SetActive(false);
				vanillaFoodHudSuccessfulHides++;
			}
		}

		private static GameObject TryGetHudElementGameObject(object value)
		{
			if (value == null)
			{
				return null;
			}
			GameObject val = (GameObject)((value is GameObject) ? value : null);
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
			Component val2 = (Component)((value is Component) ? value : null);
			if ((Object)(object)val2 != (Object)null)
			{
				return val2.gameObject;
			}
			return null;
		}

		private int CountActiveVanillaFoodHudElements()
		{
			int num = 0;
			for (int i = 0; i < vanillaFoodHudElements.Count; i++)
			{
				GameObject gameObject = vanillaFoodHudElements[i].GameObject;
				if ((Object)(object)gameObject != (Object)null && gameObject.activeSelf)
				{
					num++;
				}
			}
			return num;
		}

		private void RestoreVanillaFoodHud()
		{
			try
			{
				for (int num = vanillaFoodHudElements.Count - 1; num >= 0; num--)
				{
					VanillaFoodHudElement vanillaFoodHudElement = vanillaFoodHudElements[num];
					if (vanillaFoodHudElement != null && (Object)(object)vanillaFoodHudElement.GameObject != (Object)null)
					{
						vanillaFoodHudElement.GameObject.SetActive(vanillaFoodHudElement.OriginalActiveSelf);
					}
				}
			}
			catch
			{
			}
			finally
			{
				vanillaFoodHudHiddenByMod = false;
				vanillaFoodBarRoot = null;
				vanillaFoodHudOwner = null;
				vanillaFoodHudElements.Clear();
				vanillaFoodHudTrackedElements = 0;
				vanillaFoodHudActiveElementsRemaining = 0;
			}
		}

		private static Hud ResolveHudInstance()
		{
			try
			{
				PropertyInfo propertyInfo = AccessTools.Property(typeof(Hud), "instance");
				Hud val = (Hud)((propertyInfo != null) ? /*isinst with value type is only supported in some contexts*/: null);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			catch
			{
			}
			return Object.FindFirstObjectByType<Hud>();
		}

		private void LogVanillaFoodHudFailure(string message)
		{
			if (!vanillaFoodHudFailureLogged)
			{
				vanillaFoodHudFailureLogged = true;
				((BaseUnityPlugin)this).Logger.LogWarning((object)("You're Hungry " + message));
			}
		}

		private void InstallFoodObserverPatch()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Expected O, but got Unknown
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Expected O, but got Unknown
			//IL_0150: Expected O, but got Unknown
			harmony = new Harmony("com.marccunningham.yourehungry");
			MethodInfo methodInfo = AccessTools.Method(typeof(Player), "CanEat", new Type[2]
			{
				typeof(ItemData),
				typeof(bool)
			}, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(Player), "EatFood", new Type[1] { typeof(ItemData) }, (Type[])null);
			MethodInfo methodInfo3 = AccessTools.Method(typeof(YoureHungryPlugin), "CanEatPrefix", (Type[])null, (Type[])null);
			MethodInfo methodInfo4 = AccessTools.Method(typeof(YoureHungryPlugin), "EatFoodPrefix", (Type[])null, (Type[])null);
			MethodInfo methodInfo5 = AccessTools.Method(typeof(YoureHungryPlugin), "EatFoodPostfix", (Type[])null, (Type[])null);
			if (methodInfo != null && methodInfo3 != null)
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				canEatPatchInstalled = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"You're Hungry: patched Player.CanEat(ItemDrop.ItemData, bool) for pre-capacity purge interception.");
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"You're Hungry could not find Player.CanEat(ItemDrop.ItemData, bool). EatFood fallback interception remains available.");
			}
			if (methodInfo2 == null || methodInfo4 == null || methodInfo5 == null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"You're Hungry could not find Player.EatFood(ItemDrop.ItemData). Food observation and direct-attempt purge fallback are disabled.");
				return;
			}
			harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(methodInfo4), new HarmonyMethod(methodInfo5), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			eatFoodPatchInstalled = true;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"You're Hungry: patched Player.EatFood(ItemDrop.ItemData) for food observation and direct-attempt purge fallback.");
		}

		private void InstallVanillaFoodHudRefreshPatch()
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(Hud), "UpdateFood", (Type[])null, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(YoureHungryPlugin), "HudUpdateFoodPostfix", (Type[])null, (Type[])null);
			if (harmony == null || methodInfo == null || methodInfo2 == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"You're Hungry could not patch Hud.UpdateFood; the normal Update retry will still hide the old food HUD when possible.");
				return;
			}
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			hudUpdateFoodPatchInstalled = true;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"You're Hungry: patched Hud.UpdateFood() to re-hide only the old food display after Valheim refreshes it.");
		}

		private void InstallHungerEffectPatches()
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Expected O, but got Unknown
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Expected O, but got Unknown
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Expected O, but got Unknown
			if (harmony == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"You're Hungry could not install hunger effects because Harmony was not initialized.");
				return;
			}
			staminaRegenPatchInstalled = TryPatch(FindFirstInstanceMethod(typeof(SEMan), "ModifyStaminaRegen"), new HarmonyMethod(typeof(YoureHungryPatches), "SEManModifyStaminaRegenPostfix", (Type[])null), "SEMan.ModifyStaminaRegen(...)");
			runDrainPatchInstalled = TryPatch(FindFirstInstanceMethod(typeof(SEMan), "ModifyRunStaminaDrain"), new HarmonyMethod(typeof(YoureHungryPatches), "SEManModifyRunStaminaDrainPostfix", (Type[])null), "SEMan.ModifyRunStaminaDrain(...)");
			jogSpeedPatchInstalled = TryPatch(AccessTools.Method(typeof(Player), "GetJogSpeedFactor", Type.EmptyTypes, (Type[])null), new HarmonyMethod(typeof(YoureHungryPatches), "PlayerGetJogSpeedFactorPostfix", (Type[])null), "Player.GetJogSpeedFactor() fallback");
			runSpeedPatchInstalled = TryPatch(AccessTools.Method(typeof(Player), "GetRunSpeedFactor", Type.EmptyTypes, (Type[])null), new HarmonyMethod(typeof(YoureHungryPatches), "PlayerGetRunSpeedFactorPostfix", (Type[])null), "Player.GetRunSpeedFactor() fallback");
		}

		private bool TryPatch(MethodInfo target, HarmonyMethod postfix, string name)
		{
			if (target == null || postfix == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("You're Hungry could not find " + name + ". That hunger effect is disabled."));
				return false;
			}
			try
			{
				harmony.Patch((MethodBase)target, (HarmonyMethod)null, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("You're Hungry: patched " + name + " for hunger effects."));
				return true;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("You're Hungry: failed to patch " + name + ": " + ex));
				return false;
			}
		}

		private static MethodInfo FindFirstInstanceMethod(Type type, string name)
		{
			MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			for (int i = 0; i < methods.Length; i++)
			{
				if (methods[i].Name == name)
				{
					return methods[i];
				}
			}
			return null;
		}

		private void TryConnectMovementBridge()
		{
			if (movementBridgeConnected || Time.unscaledTime < nextMovementBridgeResolveAt)
			{
				return;
			}
			nextMovementBridgeResolveAt = Time.unscaledTime + 3f;
			movementBridgeResolveAttempts++;
			try
			{
				if (movementBridgeType == null)
				{
					Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
					for (int i = 0; i < assemblies.Length; i++)
					{
						Type type = assemblies[i].GetType("SurvivalMovementExertion.SurvivalMovementExertionBridge", throwOnError: false);
						if (type != null)
						{
							movementBridgeType = type;
							break;
						}
					}
				}
				if (movementBridgeType == null)
				{
					movementBridgeStatus = "Realistic Running not installed";
					return;
				}
				movementBridgeMovementField = movementBridgeType.GetField("ExternalMovementMultiplier", BindingFlags.Static | BindingFlags.Public);
				movementBridgeRunDrainField = movementBridgeType.GetField("ExternalRunDrainMultiplier", BindingFlags.Static | BindingFlags.Public);
				if (movementBridgeMovementField == null || movementBridgeRunDrainField == null)
				{
					movementBridgeStatus = "bridge fields unavailable";
					return;
				}
				movementBridgePreviousMovementProvider = movementBridgeMovementField.GetValue(null) as Delegate;
				movementBridgePreviousRunDrainProvider = movementBridgeRunDrainField.GetValue(null) as Delegate;
				movementBridgeMovementProvider = GetBridgeMovementMultiplier;
				movementBridgeRunDrainProvider = GetBridgeRunDrainMultiplier;
				movementBridgeMovementField.SetValue(null, movementBridgeMovementProvider);
				movementBridgeRunDrainField.SetValue(null, movementBridgeRunDrainProvider);
				movementBridgeConnected = true;
				movementBridgeStatus = "connected via SurvivalMovementExertionBridge";
				((BaseUnityPlugin)this).Logger.LogInfo((object)"You're Hungry connected to Realistic Running's shared movement bridge. Hungry movement and sprint drain now compose there instead of fighting direct speed patches.");
			}
			catch (Exception ex)
			{
				movementBridgeConnected = false;
				movementBridgeStatus = "connection failed: " + ex.GetType().Name;
			}
		}

		private void DisconnectMovementBridge()
		{
			if (!movementBridgeConnected)
			{
				return;
			}
			try
			{
				if (movementBridgeMovementField != null && object.Equals(movementBridgeMovementField.GetValue(null), movementBridgeMovementProvider))
				{
					movementBridgeMovementField.SetValue(null, movementBridgePreviousMovementProvider);
				}
				if (movementBridgeRunDrainField != null && object.Equals(movementBridgeRunDrainField.GetValue(null), movementBridgeRunDrainProvider))
				{
					movementBridgeRunDrainField.SetValue(null, movementBridgePreviousRunDrainProvider);
				}
			}
			catch
			{
			}
			finally
			{
				movementBridgeConnected = false;
				movementBridgeStatus = "disconnected";
				movementBridgePreviousMovementProvider = null;
				movementBridgePreviousRunDrainProvider = null;
				movementBridgeMovementProvider = null;
				movementBridgeRunDrainProvider = null;
			}
		}

		private float GetBridgeMovementMultiplier(Player player)
		{
			float num = InvokeBridgeProvider(movementBridgePreviousMovementProvider, player);
			float scaledMovementMultiplier = GetScaledMovementMultiplier(GetGameplayEffects().JogSpeedMultiplier);
			if (ShouldApplyGameplayEffects(player))
			{
				movementBridgeMovementCalls++;
			}
			return Mathf.Clamp(num * scaledMovementMultiplier, 0.2f, 1.35f);
		}

		private float GetBridgeRunDrainMultiplier(Player player)
		{
			float num = InvokeBridgeProvider(movementBridgePreviousRunDrainProvider, player);
			float scaledCostMultiplier = GetScaledCostMultiplier(GetGameplayEffects().SprintDrainMultiplier);
			if (ShouldApplyGameplayEffects(player))
			{
				movementBridgeRunDrainCalls++;
			}
			return Mathf.Clamp(num * scaledCostMultiplier, 0.25f, 4f);
		}

		private static float InvokeBridgeProvider(Delegate provider, Player player)
		{
			if (!(provider is Func<Player, float> func))
			{
				return 1f;
			}
			try
			{
				return func(player);
			}
			catch
			{
				return 1f;
			}
		}

		private static void HudUpdateFoodPostfix(Hud __instance)
		{
			YoureHungryPlugin instance = Instance;
			if (!((Object)(object)instance == (Object)null) && instance.modEnabled.Value && instance.showHud.Value && instance.hideVanillaFoodHud.Value)
			{
				instance.TryHideVanillaFoodHud(__instance);
			}
		}

		private static bool CanEatPrefix(Player __instance, ItemData __0, bool __1, ref bool __result)
		{
			YoureHungryPlugin instance = Instance;
			if ((Object)(object)instance == (Object)null || !instance.modEnabled.Value || !__1 || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return true;
			}
			instance.canEatAttemptHookCalls++;
			if (!instance.TryInterceptMaximumFullnessFoodAttempt(__instance, __0, "CanEat"))
			{
				return true;
			}
			__result = false;
			return false;
		}

		private static bool EatFoodPrefix(Player __instance, ItemData __0, ref bool __result)
		{
			YoureHungryPlugin instance = Instance;
			if ((Object)(object)instance == (Object)null || !instance.modEnabled.Value || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return true;
			}
			instance.eatFoodAttemptHookCalls++;
			if (!instance.TryInterceptMaximumFullnessFoodAttempt(__instance, __0, "EatFood"))
			{
				return true;
			}
			__result = false;
			return false;
		}

		private static void EatFoodPostfix(Player __instance, ItemData __0, bool __result)
		{
			YoureHungryPlugin instance = Instance;
			if (!((Object)(object)instance == (Object)null) && instance.modEnabled.Value && __result && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
			{
				instance.eatFoodHookCalls++;
				instance.RecordSuccessfullyEatenFood(__instance, __0);
				instance.RefreshActiveFoodAudit(__instance);
			}
		}

		private void RecordSuccessfullyEatenFood(Player player, ItemData item)
		{
			FoodAuditEntry foodAuditEntry = BuildFoodEntry(item, -1);
			if (foodAuditEntry == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"You're Hungry detected a successful eat event but could not read item food data.");
				return;
			}
			EnsureStateLoaded(player);
			if (!stateLoaded)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"You're Hungry could not load player state before recording food. The meal was not added to Nourishment.");
				return;
			}
			float num = nourishment;
			float num2 = fullness;
			nourishment = HungerRules.ClampNourishment(nourishment + foodAuditEntry.NourishmentGain);
			fullness = HungerRules.ClampFullness(fullness + foodAuditEntry.FullnessGain);
			starvationSeconds = 0f;
			starvationDamageTimer = 0f;
			starvationWarningTimer = 0f;
			lastMealName = foodAuditEntry.DisplayName;
			lastMealNourishmentGain = nourishment - num;
			lastMealFullnessGain = fullness - num2;
			AddRecentFood(foodAuditEntry);
			bool flag = TryHandlePurgeAfterEating(player);
			SaveState(player);
			if (verboseFoodLogging.Value)
			{
				string text = " (food +" + lastMealNourishmentGain.ToString("0.0") + ")";
				string text2 = " (food +" + lastMealFullnessGain.ToString("0.0") + ")";
				if (flag)
				{
					text = text + "; purge -" + Mathf.Max(0f, purgeNourishmentLoss.Value).ToString("0.0");
					text2 += "; purge reset";
				}
				ManualLogSource logger = ((BaseUnityPlugin)this).Logger;
				string[] array = new string[29];
				array[0] = "You're Hungry meal recorded: name=";
				array[1] = foodAuditEntry.DisplayName;
				array[2] = ", health=";
				float health = foodAuditEntry.Health;
				array[3] = health.ToString("0.0");
				array[4] = ", stamina=";
				health = foodAuditEntry.Stamina;
				array[5] = health.ToString("0.0");
				array[6] = ", eitr=";
				health = foodAuditEntry.Eitr;
				array[7] = health.ToString("0.0");
				array[8] = ", regen=";
				health = foodAuditEntry.Regeneration;
				array[9] = health.ToString("0.0");
				array[10] = ", burn=";
				health = foodAuditEntry.BurnTime;
				array[11] = health.ToString("0");
				array[12] = "s, nourishment=";
				array[13] = num.ToString("0.0");
				array[14] = "->";
				array[15] = nourishment.ToString("0.0");
				array[16] = text;
				array[17] = ", fullness=";
				array[18] = num2.ToString("0.0");
				array[19] = "->";
				array[20] = fullness.ToString("0.0");
				array[21] = text2;
				array[22] = ", class=";
				array[23] = HungerRules.GetMealClass(foodAuditEntry.DisplayName, foodAuditEntry.NourishmentGain);
				array[24] = ", fullnessState=";
				array[25] = HungerRules.GetFullnessStageLabel(fullness);
				array[26] = ", purged=";
				array[27] = flag.ToString();
				array[28] = ".";
				logger.LogInfo((object)string.Concat(array));
			}
		}

		private bool TryHandlePurgeAfterEating(Player player)
		{
			return TryTriggerPurge(player, "successful meal reached capacity", lastMealName, 125f);
		}

		private bool TryInterceptMaximumFullnessFoodAttempt(Player player, ItemData item, string route)
		{
			if (!fullnessEffectsEnabled.Value || (Object)(object)player == (Object)null)
			{
				return false;
			}
			if (IsBukeperry(item))
			{
				return false;
			}
			EnsureStateLoaded(player);
			if (!stateLoaded || fullness < 121.99f)
			{
				return false;
			}
			FoodAuditEntry foodAuditEntry = BuildFoodEntry(item, -1);
			string foodName = ((foodAuditEntry != null) ? foodAuditEntry.DisplayName : GetItemDisplayName(item));
			if (!TryTriggerPurge(player, route + " intercepted a near-capacity food attempt", foodName, 122f))
			{
				return false;
			}
			preCapPurgeInterceptions++;
			return true;
		}

		private bool TryTriggerPurge(Player player, string trigger, string foodName, float requiredFullness)
		{
			if (!fullnessEffectsEnabled.Value || (Object)(object)player == (Object)null || fullness < requiredFullness - 0.01f)
			{
				return false;
			}
			try
			{
				player.ClearFood();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("You're Hungry could not clear food during purge: " + ex.Message));
				return false;
			}
			fullness = HungerRules.ClampFullness(purgeFullnessAfter.Value);
			nourishment = HungerRules.ClampNourishment(nourishment - Mathf.Max(0f, purgeNourishmentLoss.Value));
			nauseaSecondsRemaining = Mathf.Max(nauseaSecondsRemaining, Mathf.Max(0f, postPurgeNauseaSeconds.Value));
			starvationSeconds = 0f;
			starvationDamageTimer = 0f;
			starvationWarningTimer = 0f;
			purgeEvents++;
			lastPurgeTrigger = (string.IsNullOrEmpty(trigger) ? "Unknown" : trigger);
			lastPurgeFoodName = (string.IsNullOrEmpty(foodName) ? "Unknown" : foodName);
			PlayBukeperryVfxOnce(player, "purge: " + lastPurgeTrigger);
			RefreshActiveFoodAudit(player);
			SaveState(player);
			ShowTopLeftMessage("You are sick and vomit. Rest before eating again.");
			return true;
		}

		private bool PlayBukeperryVfxOnce(Player player, string trigger)
		{
			if (!enableBukeperryVfx.Value || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return false;
			}
			bukeperryVfxAttempts++;
			StatusEffect val = ResolveBukeperryStatusEffect();
			if ((Object)(object)val == (Object)null)
			{
				bukeperryVfxFailures++;
				return false;
			}
			SEMan sEMan = ((Character)player).GetSEMan();
			if (sEMan == null)
			{
				bukeperryVfxFailures++;
				bukeperryVfxStatus = "player SEMan unavailable";
				return false;
			}
			try
			{
				int num = val.NameHash();
				if (sEMan.HaveStatusEffect(num))
				{
					bukeperryVfxStatus = "Bukeperry sickness already active; VFX lockout respected";
					lastBukeperryVfxTrigger = (string.IsNullOrEmpty(trigger) ? "Unknown" : trigger);
					return true;
				}
				if ((Object)(object)sEMan.AddStatusEffect(val, true, 0, 0f) == (Object)null)
				{
					bukeperryVfxFailures++;
					bukeperryVfxStatus = "SEMan rejected the Bukeperry sickness effect";
					return false;
				}
				if (!TryPlayBukeperryStartEffects(val, player, out var failure))
				{
					bukeperryVfxFailures++;
					lastBukeperryVfxTrigger = (string.IsNullOrEmpty(trigger) ? "Unknown" : trigger);
					bukeperryVfxStatus = "Feeling Sick added, but Bukeperry VFX failed: " + failure;
					return false;
				}
				bukeperryVfxPlays++;
				lastBukeperryVfxTrigger = (string.IsNullOrEmpty(trigger) ? "Unknown" : trigger);
				bukeperryVfxStatus = "Feeling Sick added and Bukeperry start VFX played at player";
				return true;
			}
			catch (Exception ex)
			{
				bukeperryVfxFailures++;
				bukeperryVfxStatus = "sickness/VFX failed: " + ex.GetType().Name;
				((BaseUnityPlugin)this).Logger.LogWarning((object)("You're Hungry could not apply Bukeperry sickness/VFX: " + ex.Message));
				return false;
			}
		}

		private bool TryPlayBukeperryStartEffects(StatusEffect template, Player player, out string failure)
		{
			failure = string.Empty;
			StatusEffect val = null;
			try
			{
				val = template.Clone();
				if ((Object)(object)val == (Object)null)
				{
					failure = "status-effect clone failed";
					return false;
				}
				val.Setup((Character)(object)player);
				if (bukeperryTriggerStartEffectsMethod == null)
				{
					bukeperryTriggerStartEffectsMethod = typeof(StatusEffect).GetMethod("TriggerStartEffects", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
				}
				if (bukeperryTriggerStartEffectsMethod == null)
				{
					failure = "StatusEffect.TriggerStartEffects() was not found";
					return false;
				}
				bukeperryTriggerStartEffectsMethod.Invoke(val, null);
				Object.Destroy((Object)(object)val, 5f);
				val = null;
				return true;
			}
			catch (TargetInvocationException ex)
			{
				Exception innerException = ex.InnerException;
				failure = ((innerException != null) ? innerException.GetType().Name : ex.GetType().Name);
				return false;
			}
			catch (Exception ex2)
			{
				failure = ex2.GetType().Name;
				return false;
			}
			finally
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
			}
		}

		private StatusEffect ResolveBukeperryStatusEffect()
		{
			if ((Object)(object)bukeperryStatusEffectTemplate != (Object)null)
			{
				return bukeperryStatusEffectTemplate;
			}
			if (Time.unscaledTime < nextBukeperryVfxResolveAt)
			{
				return null;
			}
			nextBukeperryVfxResolveAt = Time.unscaledTime + 2f;
			try
			{
				ObjectDB instance = ObjectDB.instance;
				if ((Object)(object)instance == (Object)null)
				{
					bukeperryVfxStatus = "ObjectDB not ready";
					return null;
				}
				GameObject val = instance.GetItemPrefab("Bukeperries");
				if ((Object)(object)val == (Object)null)
				{
					val = instance.GetItemPrefab("Bukeperry");
				}
				if ((Object)(object)val == (Object)null && instance.m_items != null)
				{
					for (int i = 0; i < instance.m_items.Count; i++)
					{
						GameObject val2 = instance.m_items[i];
						if ((Object)(object)val2 != (Object)null && ((Object)val2).name.IndexOf("buke", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							val = val2;
							break;
						}
					}
				}
				ItemDrop val3 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null);
				StatusEffect val4 = (((Object)(object)val3 != (Object)null && val3.m_itemData != null && val3.m_itemData.m_shared != null) ? val3.m_itemData.m_shared.m_consumeStatusEffect : null);
				if ((Object)(object)val4 == (Object)null)
				{
					bukeperryVfxStatus = "Bukeperry consume effect not found";
					return null;
				}
				bukeperryStatusEffectTemplate = val4;
				bukeperryVfxStatus = "resolved Bukeperry consume effect";
				return bukeperryStatusEffectTemplate;
			}
			catch (Exception ex)
			{
				bukeperryVfxStatus = "resolve failed: " + ex.GetType().Name;
				return null;
			}
		}

		private static bool IsBukeperry(ItemData item)
		{
			return GetItemDisplayName(item).ToLowerInvariant().Contains("bukeperr");
		}

		private static string GetItemDisplayName(ItemData item)
		{
			if (item != null && item.m_shared != null && !string.IsNullOrEmpty(item.m_shared.m_name))
			{
				return item.m_shared.m_name;
			}
			string foodPrefabName = GetFoodPrefabName(item);
			if (!string.IsNullOrEmpty(foodPrefabName))
			{
				return foodPrefabName;
			}
			return "food";
		}

		private void TickFullnessState(Player player, float deltaTime)
		{
			nauseaSecondsRemaining = Mathf.Max(0f, nauseaSecondsRemaining - Mathf.Max(0f, deltaTime));
			if (!fullnessEffectsEnabled.Value || (Object)(object)player == (Object)null)
			{
				lastFullnessStage = HungerRules.GetFullnessStage(fullness);
				nauseaWarningTimer = 0f;
				return;
			}
			FullnessStage fullnessStage = HungerRules.GetFullnessStage(fullness);
			if (fullnessStage != lastFullnessStage)
			{
				switch (fullnessStage)
				{
				case FullnessStage.Full:
					ShowTopLeftMessage("You are full.");
					break;
				case FullnessStage.Overfull:
					ShowTopLeftMessage("You are overfull. Moving hard will feel uncomfortable.");
					break;
				case FullnessStage.Nauseous:
					ShowTopLeftMessage("You feel nauseous. Do not force down more food.");
					break;
				}
				lastFullnessStage = fullnessStage;
			}
			if (fullnessStage == FullnessStage.Nauseous)
			{
				nauseaWarningTimer += Mathf.Max(0f, deltaTime);
				if (nauseaWarningTimer >= Mathf.Max(5f, nauseaWarningIntervalSeconds.Value))
				{
					nauseaWarningTimer = 0f;
					nauseaWarningCount++;
					ShowTopLeftMessage("You feel sick from overeating.");
				}
			}
			else
			{
				nauseaWarningTimer = 0f;
			}
		}

		private void TickNourishmentAndFullness(float deltaTime)
		{
			float num = nourishment;
			float num2 = fullness;
			nourishment = HungerRules.ApplyNourishmentDecay(nourishment, deltaTime, passiveNourishmentDrainPerMinute.Value);
			fullness = HungerRules.DigestFullness(fullness, deltaTime, fullnessDigestionPerMinute.Value);
			lastNourishmentRatePerSecond = ((deltaTime > 0f) ? ((nourishment - num) / deltaTime) : 0f);
			lastFullnessRatePerSecond = ((deltaTime > 0f) ? ((fullness - num2) / deltaTime) : 0f);
		}

		private void TickHungerState(Player player, float deltaTime)
		{
			HungerStage stage = HungerRules.GetStage(nourishment);
			if (stage != lastHungerStage)
			{
				NotifyHungerStage(stage);
				lastHungerStage = stage;
			}
			if (!hungerEffectsEnabled.Value || (Object)(object)player == (Object)null || nourishment > 0.5f)
			{
				starvationSeconds = 0f;
				starvationDamageTimer = 0f;
				starvationWarningTimer = 0f;
				if (starvationBukeperryVfxPlayed)
				{
					starvationBukeperryVfxPlayed = false;
					starvationBukeperryVfxLoadedFromCustomData = false;
				}
				return;
			}
			if (playStarvationBukeperryVfx.Value && !starvationBukeperryVfxPlayed)
			{
				starvationBukeperryVfxPlayed = true;
				starvationBukeperryVfxLoadedFromCustomData = false;
				PlayBukeperryVfxOnce(player, "starvation began");
			}
			starvationSeconds += Mathf.Max(0f, deltaTime);
			float num = Mathf.Max(0f, starvationGraceSeconds.Value);
			if (!(starvationSeconds < num))
			{
				starvationWarningTimer += Mathf.Max(0f, deltaTime);
				float num2 = Mathf.Max(5f, starvationWarningIntervalSeconds.Value);
				if (starvationWarningTimer >= num2)
				{
					starvationWarningTimer = 0f;
					ShowTopLeftMessage("You are starving. Eat to recover.");
				}
				starvationDamageTimer += Mathf.Max(0f, deltaTime);
				float num3 = Mathf.Max(0.5f, starvationDamageTickSeconds.Value);
				if (!(starvationDamageTimer < num3))
				{
					starvationDamageTimer -= num3;
					float damage = Mathf.Max(0f, starvationDamagePerMinute.Value) * (num3 / 60f);
					ApplySafeStarvationDamage(player, damage);
				}
			}
		}

		private void NotifyHungerStage(HungerStage stage)
		{
			if (hungerEffectsEnabled.Value)
			{
				switch (stage)
				{
				case HungerStage.Hungry:
					ShowTopLeftMessage("You are hungry.");
					break;
				case HungerStage.Famished:
					ShowTopLeftMessage("You are famished. Your stamina is suffering.");
					break;
				case HungerStage.Starving:
					ShowTopLeftMessage("You are starving. Find food soon.");
					break;
				}
			}
		}

		private void ApplySafeStarvationDamage(Player player, float damage)
		{
			if ((Object)(object)player == (Object)null || damage <= 0f)
			{
				return;
			}
			try
			{
				float health = ((Character)player).GetHealth();
				float num = Mathf.Min(damage, Mathf.Max(0f, health - 1f));
				if (!(num <= 0f))
				{
					((Character)player).UseHealth(num);
					starvationDamageTicks++;
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("You're Hungry could not apply starvation health loss: " + ex.Message));
			}
		}

		private HungerEffects GetGameplayEffects()
		{
			float num = (hungerEffectsEnabled.Value ? nourishment : 100f);
			float num2 = (fullnessEffectsEnabled.Value ? fullness : 0f);
			float num3 = (fullnessEffectsEnabled.Value ? nauseaSecondsRemaining : 0f);
			return HungerRules.GetCombinedEffects(num, num2, num3);
		}

		private float GetScaledStaminaRegenMultiplier(float baseMultiplier)
		{
			float num = Mathf.Clamp(hungerEffectStrength.Value, 0.1f, 2f);
			return Mathf.Max(1f - (1f - Mathf.Clamp(baseMultiplier, 0f, 1f)) * num, Mathf.Clamp(minimumStaminaRegenMultiplier.Value, 0.1f, 1f));
		}

		private float GetScaledCostMultiplier(float baseMultiplier)
		{
			float num = Mathf.Clamp(hungerEffectStrength.Value, 0.1f, 2f);
			return Mathf.Max(0.25f, 1f + (Mathf.Max(0f, baseMultiplier) - 1f) * num);
		}

		private float GetScaledMovementMultiplier(float baseMultiplier)
		{
			float num = Mathf.Clamp(hungerEffectStrength.Value, 0.1f, 2f);
			return Mathf.Clamp(1f - (1f - Mathf.Clamp(baseMultiplier, 0f, 1.5f)) * num, 0.35f, 1.35f);
		}

		private bool ShouldApplyGameplayEffects(Player player)
		{
			if (modEnabled.Value && stateLoaded && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && (Object)(object)player == (Object)(object)lastPlayer)
			{
				if (!hungerEffectsEnabled.Value)
				{
					return fullnessEffectsEnabled.Value;
				}
				return true;
			}
			return false;
		}

		internal void ApplyStaminaRegenEffect(Player player, ref float staminaMultiplier)
		{
			if (ShouldApplyGameplayEffects(player))
			{
				HungerEffects gameplayEffects = GetGameplayEffects();
				staminaRegenHookCalls++;
				lastStaminaRegenBefore = staminaMultiplier;
				staminaMultiplier *= GetScaledStaminaRegenMultiplier(gameplayEffects.StaminaRegenMultiplier);
				lastStaminaRegenAfter = staminaMultiplier;
			}
		}

		internal void ApplyRunDrainEffect(Player player, ref float staminaDrain)
		{
			if (!movementBridgeConnected && ShouldApplyGameplayEffects(player))
			{
				HungerEffects gameplayEffects = GetGameplayEffects();
				runDrainHookCalls++;
				lastRunDrainBefore = staminaDrain;
				staminaDrain *= GetScaledCostMultiplier(gameplayEffects.SprintDrainMultiplier);
				lastRunDrainAfter = staminaDrain;
			}
		}

		internal void ApplyMovementEffect(Player player, ref float speedFactor, bool running)
		{
			if (!movementBridgeConnected && ShouldApplyGameplayEffects(player))
			{
				HungerEffects gameplayEffects = GetGameplayEffects();
				if (running)
				{
					runSpeedHookCalls++;
					lastRunSpeedBefore = speedFactor;
					speedFactor *= GetScaledMovementMultiplier(gameplayEffects.RunSpeedMultiplier);
					lastRunSpeedAfter = speedFactor;
				}
				else
				{
					jogSpeedHookCalls++;
					lastJogSpeedBefore = speedFactor;
					speedFactor *= GetScaledMovementMultiplier(gameplayEffects.JogSpeedMultiplier);
					lastJogSpeedAfter = speedFactor;
				}
			}
		}

		private static void ShowTopLeftMessage(string text)
		{
			if ((Object)(object)MessageHud.instance != (Object)null)
			{
				MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false);
			}
		}

		private void ApplyTestChange(Player player, float nourishmentChange, float fullnessChange, string reason)
		{
			EnsureStateLoaded(player);
			if (stateLoaded)
			{
				nourishment = HungerRules.ClampNourishment(nourishment + nourishmentChange);
				fullness = HungerRules.ClampFullness(fullness + fullnessChange);
				if (nourishmentChange > 0f)
				{
					starvationSeconds = 0f;
					starvationDamageTimer = 0f;
					starvationWarningTimer = 0f;
				}
				SaveState(player);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("You're Hungry " + reason + ": nourishment=" + nourishment.ToString("0") + "%, fullness=" + fullness.ToString("0.0") + "."));
			}
		}

		private void EnsureStateLoaded(Player player)
		{
			if (stateLoaded || (Object)(object)player == (Object)null || Time.unscaledTime < stateLoadNotBefore)
			{
				return;
			}
			Dictionary<string, string> orCreatePlayerCustomData = GetOrCreatePlayerCustomData(player);
			if (orCreatePlayerCustomData == null)
			{
				return;
			}
			string value;
			bool flag = orCreatePlayerCustomData.TryGetValue("com.marccunningham.yourehungry.nourishment.v2", out value);
			string value2;
			bool flag2 = orCreatePlayerCustomData.TryGetValue("com.marccunningham.yourehungry.fullness.v2", out value2);
			string value3;
			bool flag3 = orCreatePlayerCustomData.TryGetValue("com.marccunningham.yourehungry.starvationseconds.v1", out value3);
			string value4;
			bool flag4 = orCreatePlayerCustomData.TryGetValue("com.marccunningham.yourehungry.nauseaseconds.v1", out value4);
			string value5;
			bool flag5 = orCreatePlayerCustomData.TryGetValue("com.marccunningham.yourehungry.starvationvfxshown.v1", out value5);
			string value6;
			bool flag6 = orCreatePlayerCustomData.TryGetValue("com.marccunningham.yourehungry.recentfoods.v2", out value6);
			bool flag7 = false;
			if (!flag6 && orCreatePlayerCustomData.TryGetValue("com.marccunningham.yourehungry.recentfoods.v1", out value6))
			{
				flag6 = true;
				flag7 = true;
			}
			lastRawSavedNourishment = (flag ? value : "<missing>");
			lastRawSavedFullness = (flag2 ? value2 : "<missing>");
			lastRawSavedStarvationSeconds = (flag3 ? value3 : "<missing>");
			lastRawSavedNauseaSeconds = (flag4 ? value4 : "<missing>");
			lastRawSavedStarvationVfxShown = (flag5 ? value5 : "<missing>");
			lastRawRecentFoods = (flag6 ? value6 : "<missing>");
			recentFoodMemoryLoaded = false;
			recentMemorySummary = "<none>";
			if (saveRecentFoodMemory.Value && flag6)
			{
				recentFoodMemoryLoaded = TryLoadRecentFoodMemory(value6);
			}
			bool flag8;
			if (recentFoodMemoryLoaded)
			{
				flag8 = BackfillRecentFoodsFromActiveFoods(player);
				if (flag7)
				{
					recentFoodMemoryMigrations++;
					flag8 = true;
				}
			}
			else
			{
				flag8 = SeedRecentFoodsFromActiveFoods(player);
			}
			recentMemorySummary = BuildRecentMemorySummary();
			float savedNourishment;
			float savedFullness;
			if (flag && TryParseSavedFloat(value, out var value7))
			{
				nourishment = HungerRules.ClampNourishment(value7);
				fullness = ((flag2 && TryParseSavedFloat(value2, out var value8)) ? HungerRules.ClampFullness(value8) : 0f);
				starvationSeconds = ((flag3 && TryParseSavedFloat(value3, out var value9)) ? Mathf.Max(0f, value9) : 0f);
				nauseaSecondsRemaining = ((flag4 && TryParseSavedFloat(value4, out var value10)) ? Mathf.Max(0f, value10) : 0f);
				starvationBukeperryVfxPlayed = flag5 && string.Equals(value5, "1", StringComparison.Ordinal);
				starvationBukeperryVfxLoadedFromCustomData = flag5;
				starvationDamageTimer = 0f;
				starvationWarningTimer = 0f;
				nauseaWarningTimer = 0f;
				lastHungerStage = HungerRules.GetStage(nourishment);
				lastFullnessStage = HungerRules.GetFullnessStage(fullness);
				loadedFromCustomData = true;
				loadedFromLegacyZdo = false;
				stateLoaded = true;
				if (flag8 || flag7)
				{
					orCreatePlayerCustomData.Remove("com.marccunningham.yourehungry.recentfoods.v1");
				}
				SaveState(player);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("You're Hungry loaded character custom-data state: nourishment=" + nourishment.ToString("0.0", CultureInfo.InvariantCulture) + "%, fullness=" + fullness.ToString("0.0", CultureInfo.InvariantCulture) + "."));
			}
			else if (TryReadLegacyZdoState(player, out savedNourishment, out savedFullness))
			{
				nourishment = HungerRules.ClampNourishment(savedNourishment);
				fullness = HungerRules.ClampFullness(savedFullness);
				starvationSeconds = 0f;
				starvationDamageTimer = 0f;
				starvationWarningTimer = 0f;
				nauseaSecondsRemaining = 0f;
				nauseaWarningTimer = 0f;
				starvationBukeperryVfxPlayed = false;
				starvationBukeperryVfxLoadedFromCustomData = false;
				lastHungerStage = HungerRules.GetStage(nourishment);
				lastFullnessStage = HungerRules.GetFullnessStage(fullness);
				loadedFromCustomData = false;
				loadedFromLegacyZdo = true;
				stateLoaded = true;
				if (flag7)
				{
					orCreatePlayerCustomData.Remove("com.marccunningham.yourehungry.recentfoods.v1");
				}
				SaveState(player);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("You're Hungry migrated V0.2.0 state from the current player ZDO into character custom data: nourishment=" + nourishment.ToString("0.0", CultureInfo.InvariantCulture) + "%, fullness=" + fullness.ToString("0.0", CultureInfo.InvariantCulture) + "."));
			}
			else
			{
				nourishment = HungerRules.ClampNourishment(startingNourishment.Value);
				fullness = 0f;
				starvationSeconds = 0f;
				starvationDamageTimer = 0f;
				starvationWarningTimer = 0f;
				nauseaSecondsRemaining = 0f;
				nauseaWarningTimer = 0f;
				starvationBukeperryVfxPlayed = false;
				starvationBukeperryVfxLoadedFromCustomData = false;
				lastHungerStage = HungerRules.GetStage(nourishment);
				lastFullnessStage = HungerRules.GetFullnessStage(fullness);
				loadedFromCustomData = false;
				loadedFromLegacyZdo = false;
				stateLoaded = true;
				if (flag7)
				{
					orCreatePlayerCustomData.Remove("com.marccunningham.yourehungry.recentfoods.v1");
				}
				SaveState(player);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("You're Hungry created first character custom-data state: nourishment=" + nourishment.ToString("0.0", CultureInfo.InvariantCulture) + "%, fullness=" + fullness.ToString("0.0", CultureInfo.InvariantCulture) + "."));
			}
		}

		private void SaveState(Player player)
		{
			if (!stateLoaded || (Object)(object)player == (Object)null)
			{
				return;
			}
			Dictionary<string, string> orCreatePlayerCustomData = GetOrCreatePlayerCustomData(player);
			if (orCreatePlayerCustomData == null)
			{
				failedCustomDataWrites++;
				return;
			}
			string value = HungerRules.ClampNourishment(nourishment).ToString("R", CultureInfo.InvariantCulture);
			string value2 = HungerRules.ClampFullness(fullness).ToString("R", CultureInfo.InvariantCulture);
			string value3 = Mathf.Max(0f, starvationSeconds).ToString("R", CultureInfo.InvariantCulture);
			string value4 = Mathf.Max(0f, nauseaSecondsRemaining).ToString("R", CultureInfo.InvariantCulture);
			string value5 = (starvationBukeperryVfxPlayed ? "1" : "0");
			orCreatePlayerCustomData["com.marccunningham.yourehungry.nourishment.v2"] = value;
			orCreatePlayerCustomData["com.marccunningham.yourehungry.fullness.v2"] = value2;
			orCreatePlayerCustomData["com.marccunningham.yourehungry.starvationseconds.v1"] = value3;
			orCreatePlayerCustomData["com.marccunningham.yourehungry.nauseaseconds.v1"] = value4;
			orCreatePlayerCustomData["com.marccunningham.yourehungry.starvationvfxshown.v1"] = value5;
			if (saveRecentFoodMemory.Value)
			{
				string text = (orCreatePlayerCustomData["com.marccunningham.yourehungry.recentfoods.v2"] = SerializeRecentFoodMemory());
				lastRawRecentFoods = (string.IsNullOrEmpty(text) ? "<empty>" : text);
			}
			else
			{
				orCreatePlayerCustomData.Remove("com.marccunningham.yourehungry.recentfoods.v2");
				lastRawRecentFoods = "<disabled>";
			}
			lastRawSavedNourishment = value;
			lastRawSavedFullness = value2;
			lastRawSavedStarvationSeconds = value3;
			lastRawSavedNauseaSeconds = value4;
			lastRawSavedStarvationVfxShown = value5;
			successfulCustomDataWrites++;
		}

		private void TrySaveCurrentPlayer()
		{
			try
			{
				SaveState(Player.m_localPlayer);
			}
			catch
			{
			}
		}

		private static Dictionary<string, string> GetOrCreatePlayerCustomData(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return null;
			}
			if (player.m_customData == null)
			{
				player.m_customData = new Dictionary<string, string>();
			}
			return player.m_customData;
		}

		private static bool TryParseSavedFloat(string raw, out float value)
		{
			value = 0f;
			if (string.IsNullOrEmpty(raw))
			{
				return false;
			}
			return float.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out value);
		}

		private static bool TryReadLegacyZdoState(Player player, out float savedNourishment, out float savedFullness)
		{
			savedNourishment = 0f;
			savedFullness = 0f;
			ZNetView val = (((Object)(object)player != (Object)null) ? ((Component)player).GetComponent<ZNetView>() : null);
			if ((Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner())
			{
				return false;
			}
			ZDO zDO = val.GetZDO();
			if (zDO == null)
			{
				return false;
			}
			float num = zDO.GetFloat("yourehungry_nourishment_v2", -9999f);
			if (num <= -9998f)
			{
				return false;
			}
			savedNourishment = num;
			float num2 = zDO.GetFloat("yourehungry_fullness_v2", -9999f);
			savedFullness = ((num2 <= -9998f) ? 0f : num2);
			return true;
		}

		private bool TryLoadRecentFoodMemory(string serializedMemory)
		{
			if (serializedMemory == null)
			{
				return false;
			}
			recentFoods.Clear();
			if (serializedMemory.Length == 0)
			{
				recentMemorySummary = "<empty>";
				return true;
			}
			if (serializedMemory.StartsWith("v2:", StringComparison.Ordinal))
			{
				string[] array = serializedMemory.Substring("v2:".Length).Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries);
				for (int i = 0; i < array.Length; i++)
				{
					if (recentFoods.Count >= 3)
					{
						break;
					}
					if (TryParseRecentFoodRecord(array[i], out var food))
					{
						recentFoods.Add(food);
					}
				}
				recentMemorySummary = BuildRecentMemorySummary();
				return true;
			}
			string[] array2 = serializedMemory.Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries);
			for (int j = 0; j < array2.Length; j++)
			{
				if (recentFoods.Count >= 3)
				{
					break;
				}
				string text = array2[j].Trim();
				if (text.Length != 0)
				{
					recentFoods.Add(new RecentFood(CleanDisplayName(text), text, string.Empty, ResolveRecentFoodIcon(text, string.Empty, CleanDisplayName(text))));
				}
			}
			recentMemorySummary = BuildRecentMemorySummary();
			return true;
		}

		private string SerializeRecentFoodMemory()
		{
			List<string> list = new List<string>(3);
			for (int i = 0; i < recentFoods.Count; i++)
			{
				if (list.Count >= 3)
				{
					break;
				}
				RecentFood food = recentFoods[i];
				list.Add(SerializeRecentFoodRecord(food));
			}
			return "v2:" + string.Join('|'.ToString(), list.ToArray());
		}

		private static string SerializeRecentFoodRecord(RecentFood food)
		{
			if (food == null)
			{
				return string.Empty;
			}
			return EncodeRecentFoodField(food.DisplayName) + "," + EncodeRecentFoodField(food.PrefabName) + "," + EncodeRecentFoodField(food.RawFoodName);
		}

		private static bool TryParseRecentFoodRecord(string record, out RecentFood food)
		{
			food = null;
			if (string.IsNullOrEmpty(record))
			{
				return false;
			}
			string[] array = record.Split(new char[1] { ',' }, StringSplitOptions.None);
			if (array.Length != 3)
			{
				return false;
			}
			if (!TryDecodeRecentFoodField(array[0], out var value) || !TryDecodeRecentFoodField(array[1], out var value2) || !TryDecodeRecentFoodField(array[2], out var value3))
			{
				return false;
			}
			if (string.IsNullOrEmpty(value))
			{
				value = ((!string.IsNullOrEmpty(value3)) ? CleanDisplayName(value3) : ((!string.IsNullOrEmpty(value2)) ? CleanDisplayName(value2) : "Unknown food"));
			}
			food = new RecentFood(value, value2, value3, ResolveRecentFoodIcon(value2, value3, value));
			return true;
		}

		private static string EncodeRecentFoodField(string value)
		{
			value = value ?? string.Empty;
			return Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
		}

		private static bool TryDecodeRecentFoodField(string encoded, out string value)
		{
			value = string.Empty;
			try
			{
				value = Encoding.UTF8.GetString(Convert.FromBase64String(encoded ?? string.Empty));
				return true;
			}
			catch
			{
				return false;
			}
		}

		private void ResolveMissingRecentFoodIcons()
		{
			for (int i = 0; i < recentFoods.Count; i++)
			{
				RecentFood recentFood = recentFoods[i];
				if ((Object)(object)recentFood.Icon == (Object)null)
				{
					recentFood.Icon = ResolveRecentFoodIcon(recentFood.PrefabName, recentFood.RawFoodName, recentFood.DisplayName);
				}
			}
		}

		private static string GetFoodPrefabName(ItemData item)
		{
			if (item == null)
			{
				return string.Empty;
			}
			try
			{
				FieldInfo fieldInfo = AccessTools.Field(typeof(ItemData), "m_dropPrefab");
				GameObject val = (GameObject)((fieldInfo != null) ? /*isinst with value type is only supported in some contexts*/: null);
				return ((Object)(object)val != (Object)null) ? ((Object)val).name : string.Empty;
			}
			catch
			{
				return string.Empty;
			}
		}

		private static Sprite ResolveRecentFoodIcon(string prefabName, string rawFoodName, string displayName)
		{
			try
			{
				ObjectDB instance = ObjectDB.instance;
				if ((Object)(object)instance == (Object)null)
				{
					return null;
				}
				if (!string.IsNullOrEmpty(prefabName))
				{
					MethodInfo methodInfo = AccessTools.Method(typeof(ObjectDB), "GetItemPrefab", new Type[1] { typeof(string) }, (Type[])null);
					Sprite iconFromPrefab = GetIconFromPrefab((GameObject)((methodInfo != null) ? /*isinst with value type is only supported in some contexts*/: null));
					if ((Object)(object)iconFromPrefab != (Object)null)
					{
						return iconFromPrefab;
					}
				}
				FieldInfo fieldInfo = AccessTools.Field(typeof(ObjectDB), "m_items");
				IEnumerable<GameObject> enumerable = ((fieldInfo != null) ? (fieldInfo.GetValue(instance) as IEnumerable<GameObject>) : null);
				if (enumerable == null)
				{
					return null;
				}
				foreach (GameObject item in enumerable)
				{
					if ((Object)(object)item == (Object)null)
					{
						continue;
					}
					ItemDrop component = item.GetComponent<ItemDrop>();
					ItemData val = (((Object)(object)component != (Object)null) ? component.m_itemData : null);
					if (val != null && val.m_shared != null)
					{
						string text = val.m_shared.m_name ?? string.Empty;
						string a = CleanDisplayName(text);
						if ((!string.IsNullOrEmpty(rawFoodName) && string.Equals(text, rawFoodName, StringComparison.Ordinal)) || (!string.IsNullOrEmpty(displayName) && string.Equals(a, displayName, StringComparison.OrdinalIgnoreCase)))
						{
							return val.GetIcon();
						}
					}
				}
			}
			catch
			{
			}
			return null;
		}

		private static Sprite GetIconFromPrefab(GameObject prefab)
		{
			try
			{
				ItemDrop val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<ItemDrop>() : null);
				ItemData val2 = (((Object)(object)val != (Object)null) ? val.m_itemData : null);
				return (val2 != null) ? val2.GetIcon() : null;
			}
			catch
			{
				return null;
			}
		}

		private bool SeedRecentFoodsFromActiveFoods(Player player)
		{
			recentFoods.Clear();
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			List<Food> foods = player.GetFoods();
			if (foods == null)
			{
				return false;
			}
			for (int i = 0; i < foods.Count; i++)
			{
				if (recentFoods.Count >= 3)
				{
					break;
				}
				FoodAuditEntry foodAuditEntry = BuildFoodEntry(foods[i].m_item, i);
				if (foodAuditEntry != null)
				{
					AddRecentFood(foodAuditEntry);
				}
			}
			recentMemorySummary = BuildRecentMemorySummary();
			return recentFoods.Count > 0;
		}

		private bool BackfillRecentFoodsFromActiveFoods(Player player)
		{
			if ((Object)(object)player == (Object)null || recentFoods.Count >= 3)
			{
				return false;
			}
			List<Food> foods = player.GetFoods();
			if (foods == null)
			{
				return false;
			}
			bool flag = false;
			int num = foods.Count - 1;
			while (num >= 0 && recentFoods.Count < 3)
			{
				FoodAuditEntry foodAuditEntry = BuildFoodEntry(foods[num].m_item, num);
				if (foodAuditEntry != null && !RecentMemoryContains(foodAuditEntry))
				{
					recentFoods.Add(new RecentFood(foodAuditEntry.DisplayName, foodAuditEntry.PrefabName, foodAuditEntry.RawFoodName, foodAuditEntry.Icon));
					flag = true;
				}
				num--;
			}
			if (flag)
			{
				recentMemorySummary = BuildRecentMemorySummary();
			}
			return flag;
		}

		private bool RecentMemoryContains(FoodAuditEntry entry)
		{
			for (int i = 0; i < recentFoods.Count; i++)
			{
				RecentFood recentFood = recentFoods[i];
				if (!string.IsNullOrEmpty(entry.PrefabName) && string.Equals(recentFood.PrefabName, entry.PrefabName, StringComparison.Ordinal))
				{
					return true;
				}
				if (!string.IsNullOrEmpty(entry.RawFoodName) && string.Equals(recentFood.RawFoodName, entry.RawFoodName, StringComparison.Ordinal))
				{
					return true;
				}
				if (string.Equals(NormalizeFoodIdentity(recentFood.DisplayName), NormalizeFoodIdentity(entry.DisplayName), StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static string NormalizeFoodIdentity(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			foreach (char c in value)
			{
				if (char.IsLetterOrDigit(c))
				{
					stringBuilder.Append(char.ToLowerInvariant(c));
				}
			}
			return stringBuilder.ToString();
		}

		private string BuildRecentMemorySummary()
		{
			if (recentFoods.Count == 0)
			{
				return "[]";
			}
			List<string> list = new List<string>(recentFoods.Count);
			for (int i = 0; i < recentFoods.Count; i++)
			{
				list.Add(recentFoods[i].DisplayName);
			}
			return "[" + string.Join(",", list.ToArray()) + "]";
		}

		private void RefreshActiveFoodAudit(Player player)
		{
			activeFoods.Clear();
			activeFoodPreview = 0f;
			List<Food> foods = player.GetFoods();
			if (foods != null)
			{
				for (int i = 0; i < foods.Count; i++)
				{
					Food val = foods[i];
					FoodAuditEntry foodAuditEntry = BuildFoodEntry(val.m_item, i);
					foodAuditEntry = ((foodAuditEntry != null) ? new FoodAuditEntry(foodAuditEntry.DisplayName, foodAuditEntry.PrefabName, foodAuditEntry.RawFoodName, foodAuditEntry.Icon, val.m_health, val.m_stamina, val.m_eitr, foodAuditEntry.Regeneration, foodAuditEntry.BurnTime, HungerRules.GetNourishmentGain(val.m_health, val.m_stamina, val.m_eitr, foodAuditEntry.BurnTime, foodAuditEntry.Regeneration), HungerRules.GetFullnessGain(val.m_health, val.m_stamina, val.m_eitr, foodAuditEntry.BurnTime, foodAuditEntry.Regeneration), i) : new FoodAuditEntry(CleanDisplayName(val.m_name), string.Empty, string.Empty, null, val.m_health, val.m_stamina, val.m_eitr, 0f, 0f, 0f, 0f, i));
					activeFoods.Add(foodAuditEntry);
					activeFoodPreview += foodAuditEntry.NourishmentGain;
				}
				activeFoodPreview = HungerRules.ClampNourishment(activeFoodPreview);
			}
		}

		private FoodAuditEntry BuildFoodEntry(ItemData item, int sourceIndex)
		{
			if (item == null || item.m_shared == null)
			{
				return null;
			}
			SharedData shared = item.m_shared;
			float nourishmentGain = HungerRules.GetNourishmentGain(shared.m_food, shared.m_foodStamina, shared.m_foodEitr, shared.m_foodBurnTime, shared.m_foodRegen);
			float fullnessGain = HungerRules.GetFullnessGain(shared.m_food, shared.m_foodStamina, shared.m_foodEitr, shared.m_foodBurnTime, shared.m_foodRegen);
			return new FoodAuditEntry(CleanDisplayName(shared.m_name), GetFoodPrefabName(item), shared.m_name ?? string.Empty, item.GetIcon(), shared.m_food, shared.m_foodStamina, shared.m_foodEitr, shared.m_foodRegen, shared.m_foodBurnTime, nourishmentGain, fullnessGain, sourceIndex);
		}

		private void AddRecentFood(FoodAuditEntry entry)
		{
			if (entry != null)
			{
				recentFoods.Insert(0, new RecentFood(entry.DisplayName, entry.PrefabName, entry.RawFoodName, entry.Icon));
				while (recentFoods.Count > 3)
				{
					recentFoods.RemoveAt(recentFoods.Count - 1);
				}
				recentMemorySummary = BuildRecentMemorySummary();
			}
		}

		private void EnsureGuiStyles()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected O, but got Unknown
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Expected O, but got Unknown
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Expected O, but got Unknown
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Expected O, but got Unknown
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			if (headerStyle == null)
			{
				Font font = (((Object)(object)GUI.skin != (Object)null) ? GUI.skin.font : null);
				headerStyle = new GUIStyle();
				headerStyle.font = font;
				headerStyle.fontSize = 12;
				headerStyle.fontStyle = (FontStyle)1;
				headerStyle.alignment = (TextAnchor)3;
				headerStyle.normal.textColor = new Color(0.95f, 0.78f, 0.28f, 1f);
				percentStyle = new GUIStyle();
				percentStyle.font = font;
				percentStyle.fontSize = 12;
				percentStyle.fontStyle = (FontStyle)1;
				percentStyle.alignment = (TextAnchor)5;
				percentStyle.normal.textColor = Color.white;
				recentIndexStyle = new GUIStyle();
				recentIndexStyle.font = font;
				recentIndexStyle.fontSize = 10;
				recentIndexStyle.fontStyle = (FontStyle)1;
				recentIndexStyle.alignment = (TextAnchor)0;
				recentIndexStyle.normal.textColor = new Color(1f, 0.88f, 0.45f, 1f);
				recentEmptyStyle = new GUIStyle();
				recentEmptyStyle.font = font;
				recentEmptyStyle.fontSize = 11;
				recentEmptyStyle.alignment = (TextAnchor)4;
				recentEmptyStyle.normal.textColor = new Color(0.6f, 0.6f, 0.6f, 0.85f);
			}
		}

		private void DrawNourishmentHud()
		{
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_022d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0232: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Unknown result type (might be due to invalid IL or missing references)
			//IL_026a: Unknown result type (might be due to invalid IL or missing references)
			float num = (float)Screen.width / 2048f;
			float num2 = (float)Screen.height / 1152f;
			float num3 = Mathf.Min(num, num2);
			float num4 = barLeftAtReference.Value * num;
			float num5 = barTopAtReference.Value * num2;
			float num6 = barWidthAtReference.Value * num;
			float num7 = barHeightAtReference.Value * num2;
			float num8 = Mathf.Max(1f, frameThicknessAtReference.Value * num3);
			float num9 = (stateLoaded ? nourishment : HungerRules.ClampNourishment(startingNourishment.Value));
			Rect rect = default(Rect);
			((Rect)(ref rect))..ctor(num4, num5, num6, num7);
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(num4 + 6f * num3, num5 + 1f * num3, num6 - 12f * num3, 14f * num3);
			Rect val2 = new Rect(num4 + 6f * num3, num5 + 1f * num3, num6 - 12f * num3, 14f * num3);
			Rect rect2 = default(Rect);
			((Rect)(ref rect2))..ctor(num4 + 6f * num3, num5 + 19f * num3, num6 - 12f * num3, 12f * num3);
			DrawRect(rect, new Colo