Decompiled source of FineDining v1.0.0

FineDining.dll

Decompiled 10 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Timers;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using ServerSync;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Core.ObjectPool;
using YamlDotNet.Core.Tokens;
using YamlDotNet.Helpers;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.BufferedDeserialization;
using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators;
using YamlDotNet.Serialization.Callbacks;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
using YamlDotNet.Serialization.NodeTypeResolvers;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.ObjectGraphTraversalStrategies;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.Schemas;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using YamlDotNet.Serialization.Utilities;
using YamlDotNet.Serialization.ValueDeserializers;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("FineDining")]
[assembly: AssemblyDescription("Integrated food spoilage, diet, cooking, and station guidance for Valheim.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("sighsorry")]
[assembly: AssemblyProduct("FineDining")]
[assembly: AssemblyCopyright("Copyright © sighsorry 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("FDE6E067-900F-4380-9530-A1EF8D16E7B8")]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 FineDining
{
	internal static class AzuExtendedPlayerInventoryCompatibility
	{
		private readonly struct DirectMergeSite
		{
			internal int TargetLoadIndex { get; }

			internal int AmountLoadIndex { get; }

			internal int SourceLoadIndex { get; }

			internal int SourceLoadCount { get; }

			internal int InsertIndex { get; }

			internal DirectMergeSite(int targetLoadIndex, int amountLoadIndex, int sourceLoadIndex, int sourceLoadCount, int insertIndex)
			{
				TargetLoadIndex = targetLoadIndex;
				AmountLoadIndex = amountLoadIndex;
				SourceLoadIndex = sourceLoadIndex;
				SourceLoadCount = sourceLoadCount;
				InsertIndex = insertIndex;
			}
		}

		internal const string PluginGuid = "Azumatt.AzuExtendedPlayerInventory";

		private const string RecoveryPatchTypeName = "AzuEPI.Game.Patches.InventoryPatches+Load_TrackAndFixHiddenItems_Patch";

		private static readonly FieldInfo StackField = AccessTools.Field(typeof(ItemData), "m_stack");

		private static readonly MethodInfo ComposeMethod = AccessTools.Method(typeof(AzuExtendedPlayerInventoryCompatibility), "ComposeDirectRecoveryMergeExpirySafe", (Type[])null, (Type[])null);

		private static int _matchedMergeSites;

		private static bool _runtimeFailureLogged;

		internal static void TryInstall(Harmony harmony)
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Expected O, but got Unknown
			if (!Chainloader.PluginInfos.TryGetValue("Azumatt.AzuExtendedPlayerInventory", out var value))
			{
				return;
			}
			MethodInfo methodInfo = null;
			bool flag = false;
			try
			{
				methodInfo = ResolveTargetMethod(value);
				if (methodInfo == null)
				{
					FineDiningPlugin.Log.LogWarning((object)"AzuExtendedPlayerInventory is installed, but its hidden-slot recovery method no longer has the expected signature. Direct recovery merges will not compose spoilage timers.");
					return;
				}
				Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo);
				if (patchInfo == null || !patchInfo.Transpilers.Any((Patch patch) => string.Equals(patch.owner, harmony.Id, StringComparison.Ordinal) && object.Equals(patch.PatchMethod, AccessTools.Method(typeof(AzuExtendedPlayerInventoryCompatibility), "Transpiler", (Type[])null, (Type[])null))))
				{
					_matchedMergeSites = 0;
					HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(AzuExtendedPlayerInventoryCompatibility), "Transpiler", (Type[])null, (Type[])null));
					flag = true;
					harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
					if (_matchedMergeSites != 1)
					{
						harmony.Unpatch((MethodBase)methodInfo, (HarmonyPatchType)3, harmony.Id);
						FineDiningPlugin.Log.LogWarning((object)"AzuExtendedPlayerInventory is installed, but its direct recovery merge no longer matches the verified pattern. The compatibility patch was left disabled so inventory loading remains unchanged.");
					}
					else
					{
						FineDiningPlugin.Log.LogInfo((object)("Enabled AzuExtendedPlayerInventory " + value.Metadata.Version?.ToString() + " hidden-slot merge timer compatibility."));
					}
				}
			}
			catch (Exception ex)
			{
				if (flag && methodInfo != null)
				{
					try
					{
						harmony.Unpatch((MethodBase)methodInfo, (HarmonyPatchType)3, harmony.Id);
					}
					catch
					{
					}
				}
				FineDiningPlugin.Log.LogWarning((object)("Could not enable AzuExtendedPlayerInventory merge timer compatibility. " + ex));
			}
		}

		internal static void Shutdown()
		{
			_matchedMergeSites = 0;
			_runtimeFailureLogged = false;
		}

		private static MethodInfo? ResolveTargetMethod(PluginInfo pluginInfo)
		{
			MethodInfo methodInfo = ((((object)pluginInfo.Instance)?.GetType().Assembly)?.GetType("AzuEPI.Game.Patches.InventoryPatches+Load_TrackAndFixHiddenItems_Patch", throwOnError: false))?.GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic);
			if (methodInfo == null || methodInfo.ReturnType != typeof(void))
			{
				return null;
			}
			ParameterInfo[] parameters = methodInfo.GetParameters();
			if (parameters.Length != 1 || !(parameters[0].ParameterType == typeof(Inventory)))
			{
				return null;
			}
			return methodInfo;
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Expected O, but got Unknown
			List<CodeInstruction> list = instructions.ToList();
			List<DirectMergeSite> list2 = new List<DirectMergeSite>();
			for (int i = 5; i < list.Count; i++)
			{
				if (TryMatchDirectMerge(list, i, out var site))
				{
					list2.Add(site);
				}
			}
			_matchedMergeSites = list2.Count;
			if (list2.Count != 1 || ComposeMethod == null)
			{
				if (ComposeMethod == null)
				{
					_matchedMergeSites = 0;
				}
				return list;
			}
			DirectMergeSite directMergeSite = list2[0];
			List<CodeInstruction> list3 = new List<CodeInstruction> { CloneWithoutFlowMetadata(list[directMergeSite.TargetLoadIndex]) };
			for (int j = 0; j < directMergeSite.SourceLoadCount; j++)
			{
				list3.Add(CloneWithoutFlowMetadata(list[directMergeSite.SourceLoadIndex + j]));
			}
			list3.Add(CloneWithoutFlowMetadata(list[directMergeSite.AmountLoadIndex]));
			list3.Add(new CodeInstruction(OpCodes.Call, (object)ComposeMethod));
			list.InsertRange(directMergeSite.InsertIndex, list3);
			return list;
		}

		private static bool TryMatchDirectMerge(IReadOnlyList<CodeInstruction> code, int targetStoreIndex, out DirectMergeSite site)
		{
			site = default(DirectMergeSite);
			int num = targetStoreIndex - 5;
			if (!IsLocalLoad(code[num]) || code[num + 1].opcode != OpCodes.Dup || !IsFieldLoad(code[num + 2], StackField) || !IsLocalLoad(code[num + 3]) || code[num + 4].opcode != OpCodes.Add || !IsFieldStore(code[targetStoreIndex], StackField))
			{
				return false;
			}
			int num2 = targetStoreIndex + 1;
			int sourceLoadCount;
			int num3;
			if (num2 + 5 < code.Count && IsLocalLoad(code[num2]) && code[num2 + 1].opcode == OpCodes.Dup)
			{
				sourceLoadCount = 1;
				num3 = num2 + 1;
			}
			else
			{
				if (num2 + 6 >= code.Count || !IsLocalLoad(code[num2]) || !IsItemDataFieldLoad(code[num2 + 1]) || !(code[num2 + 2].opcode == OpCodes.Dup))
				{
					return false;
				}
				sourceLoadCount = 2;
				num3 = num2 + 2;
			}
			int index = num3 + 1;
			int index2 = num3 + 2;
			int index3 = num3 + 3;
			int num4 = num3 + 4;
			if (!IsFieldLoad(code[index], StackField) || !IsLocalLoad(code[index2]) || !AreSameLocalLoads(code[num + 3], code[index2]) || code[index3].opcode != OpCodes.Sub || !IsFieldStore(code[num4], StackField))
			{
				return false;
			}
			site = new DirectMergeSite(num, num + 3, num2, sourceLoadCount, num4 + 1);
			return true;
		}

		private static void ComposeDirectRecoveryMergeExpirySafe(ItemData? target, ItemData? source, int movedAmount)
		{
			try
			{
				DecayRuntime.ComposeDirectRecoveryMergeExpiry(target, source, movedAmount);
			}
			catch (Exception ex)
			{
				if (!_runtimeFailureLogged)
				{
					_runtimeFailureLogged = true;
					FineDiningPlugin.Log.LogWarning((object)("AzuExtendedPlayerInventory merged a recovered stack, but FineDining could not compose its expiry timer. The original inventory merge will continue unchanged. " + ex));
				}
			}
		}

		private static CodeInstruction CloneWithoutFlowMetadata(CodeInstruction instruction)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			CodeInstruction val = new CodeInstruction(instruction);
			val.labels.Clear();
			val.blocks.Clear();
			return val;
		}

		private static bool IsItemDataFieldLoad(CodeInstruction instruction)
		{
			if (instruction.opcode == OpCodes.Ldfld && instruction.operand is FieldInfo fieldInfo)
			{
				return fieldInfo.FieldType == typeof(ItemData);
			}
			return false;
		}

		private static bool IsFieldLoad(CodeInstruction instruction, FieldInfo field)
		{
			if (instruction.opcode == OpCodes.Ldfld)
			{
				return object.Equals(instruction.operand, field);
			}
			return false;
		}

		private static bool IsFieldStore(CodeInstruction instruction, FieldInfo field)
		{
			if (instruction.opcode == OpCodes.Stfld)
			{
				return object.Equals(instruction.operand, field);
			}
			return false;
		}

		private static bool IsLocalLoad(CodeInstruction instruction)
		{
			if (!(instruction.opcode == OpCodes.Ldloc) && !(instruction.opcode == OpCodes.Ldloc_S) && !(instruction.opcode == OpCodes.Ldloc_0) && !(instruction.opcode == OpCodes.Ldloc_1) && !(instruction.opcode == OpCodes.Ldloc_2))
			{
				return instruction.opcode == OpCodes.Ldloc_3;
			}
			return true;
		}

		private static bool AreSameLocalLoads(CodeInstruction first, CodeInstruction second)
		{
			if (first.opcode != second.opcode)
			{
				return false;
			}
			if (!(first.opcode == OpCodes.Ldloc_0) && !(first.opcode == OpCodes.Ldloc_1) && !(first.opcode == OpCodes.Ldloc_2) && !(first.opcode == OpCodes.Ldloc_3))
			{
				return object.Equals(first.operand, second.operand);
			}
			return true;
		}
	}
	internal static class ConfigPresentation
	{
		internal readonly struct SectionDefinition
		{
			internal string Name { get; }

			internal int CategoryOrder { get; }

			internal SectionDefinition(string name, int categoryOrder)
			{
				Name = name;
				CategoryOrder = categoryOrder;
			}
		}

		internal static readonly SectionDefinition General = new SectionDefinition("1 - General", 500);

		internal static readonly SectionDefinition ClientSection = new SectionDefinition("2 - Client", 400);

		internal static readonly SectionDefinition Diet = new SectionDefinition("3 - Diet", 300);

		internal static readonly SectionDefinition ChefChoice = new SectionDefinition("4 - Chef Choice", 200);

		internal static readonly SectionDefinition Spoilage = new SectionDefinition("5 - Spoilage", 100);

		internal static ConfigDescription Synced(string description, SectionDefinition section, int order, AcceptableValueBase? acceptableValues = null)
		{
			return Create(description, "[Synced with Server]", section, order, acceptableValues);
		}

		internal static ConfigDescription Client(string description, SectionDefinition section, int order, AcceptableValueBase? acceptableValues = null)
		{
			return Create(description, "[Client Only]", section, order, acceptableValues);
		}

		private static ConfigDescription Create(string description, string scope, SectionDefinition section, int order, AcceptableValueBase? acceptableValues)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			return new ConfigDescription(description + " " + scope, acceptableValues, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					CategoryOrder = section.CategoryOrder,
					Order = order
				}
			});
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public int? CategoryOrder { get; set; }

		public int? Order { get; set; }
	}
	internal enum FoodStatAxis
	{
		None,
		Health,
		Stamina,
		Eitr
	}
	internal static class FoodIdentity
	{
		internal static string GetCanonicalPrefabName(ItemData? item)
		{
			if (item == null)
			{
				return string.Empty;
			}
			if ((Object)(object)item.m_dropPrefab != (Object)null)
			{
				return NormalizePrefabName(((Object)item.m_dropPrefab).name);
			}
			ObjectDB instance = ObjectDB.instance;
			GameObject val = default(GameObject);
			if ((Object)(object)instance != (Object)null && item.m_shared != null && instance.TryGetItemPrefab(item.m_shared, ref val) && (Object)(object)val != (Object)null)
			{
				return NormalizePrefabName(((Object)val).name);
			}
			return string.Empty;
		}

		internal static string GetCanonicalPrefabName(Food? food)
		{
			if (food == null)
			{
				return string.Empty;
			}
			string text = NormalizePrefabName(food.m_name);
			if (text.Length <= 0)
			{
				return GetCanonicalPrefabName(food.m_item);
			}
			return text;
		}

		internal static bool IsDirectlyEdible(ItemData? item)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			SharedData val = item?.m_shared;
			if (val != null && (int)val.m_itemType == 2)
			{
				return HasChefFoodStats(val.m_food, val.m_foodStamina, val.m_foodEitr);
			}
			return false;
		}

		internal static bool IsDirectlyEdible(Food? food)
		{
			return IsDirectlyEdible(food?.m_item);
		}

		internal static bool TryGetFoodStatAxis(float health, float stamina, float eitr, out FoodStatAxis axis)
		{
			axis = FoodStatAxis.None;
			if (!HasChefFoodStats(health, stamina, eitr))
			{
				return false;
			}
			if (eitr > 0f)
			{
				axis = FoodStatAxis.Eitr;
				return true;
			}
			axis = ((!(health <= stamina)) ? FoodStatAxis.Health : FoodStatAxis.Stamina);
			return true;
		}

		internal static bool TryGetFoodStatAxis(ItemData? item, out FoodStatAxis axis)
		{
			SharedData val = item?.m_shared;
			if (val == null || !IsDirectlyEdible(item))
			{
				axis = FoodStatAxis.None;
				return false;
			}
			return TryGetFoodStatAxis(val.m_food, val.m_foodStamina, val.m_foodEitr, out axis);
		}

		internal static bool HasChefFoodStats(float health, float stamina, float eitr)
		{
			if (!(health > 0f) && !(stamina > 0f))
			{
				return eitr > 0f;
			}
			return true;
		}

		internal static string NormalizePrefabName(string? name)
		{
			if (string.IsNullOrWhiteSpace(name))
			{
				return string.Empty;
			}
			return Utils.GetPrefabName(name.Trim()).Trim();
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "SlowUpdate")]
	internal static class ItemDropSlowUpdateSpoilageSafetyPatch
	{
		private static void Postfix(ItemDrop __instance)
		{
			DecayRuntime.RefreshOwnedPlacedDrop(__instance);
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "Load")]
	internal static class ItemDropLoadSpoilagePatch
	{
		private static void Postfix(ItemDrop __instance)
		{
			DecayRuntime.RegisterGroundDrop(__instance);
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "LoadFromExternalZDO")]
	internal static class ItemDropExternalLoadSpoilagePatch
	{
		private static void Postfix(ItemDrop __instance)
		{
			DecayRuntime.RegisterGroundDrop(__instance);
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "Save")]
	internal static class ItemDropSavedSpoilagePatch
	{
		private static void Postfix(ItemDrop __instance)
		{
			DecayRuntime.RegisterGroundDrop(__instance);
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "OnDestroy")]
	internal static class ItemDropDestroyedSpoilagePatch
	{
		private static void Prefix(ItemDrop __instance)
		{
			DecayRuntime.UnregisterGroundDrop(__instance);
		}
	}
	[HarmonyPatch(typeof(Container), "Awake")]
	internal static class ContainerAwakeSpoilagePatch
	{
		private static void Prefix(Container __instance)
		{
			IceboxSubsystem.ApplyConfiguredStorageSize(__instance);
		}

		private static void Postfix(Container __instance)
		{
			IceboxSubsystem.ApplyConfiguredStorageSize(__instance);
			IceboxSubsystem.ApplyStoredRecipe(__instance);
			DecayRuntime.ContainerLoaded(__instance);
		}
	}
	[HarmonyPatch(typeof(Container), "Load")]
	internal static class ContainerLoadSpoilagePatch
	{
		private static void Prefix(Container __instance, out bool __state)
		{
			__state = IceboxSubsystem.PrepareStorageLoad(__instance);
			DecayRuntime.RegisterContainer(__instance);
		}

		private static void Postfix(Container __instance, bool __result, bool __state)
		{
			if (__state)
			{
				IceboxSubsystem.ApplyConfiguredStorageSize(__instance);
			}
			if (__result)
			{
				DecayRuntime.ContainerLoaded(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Inventory), "Changed")]
	internal static class InventoryChangedSpoilagePatch
	{
		private static void Postfix(Inventory __instance)
		{
			DecayRuntime.MarkDirty(__instance);
			IceboxSubsystem.HandleInventoryChanged(__instance);
		}
	}
	[HarmonyPatch(typeof(Player), "Save", new Type[] { typeof(ZPackage) })]
	internal static class PlayerSaveSpoilageClockPatch
	{
		private static void Prefix(Player __instance, out List<KeyValuePair<ItemData, string>> __state)
		{
			__state = DecayRuntime.BeginPlayerSaveClockSnapshot(__instance);
		}

		private static Exception? Finalizer(List<KeyValuePair<ItemData, string>> __state, Exception? __exception)
		{
			DecayRuntime.EndPlayerSaveClockSnapshot(__state);
			return __exception;
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "AutoStackItems")]
	internal static class ItemDropAutoStackSpoilagePatch
	{
		private static readonly FieldInfo ItemDataField = AccessTools.Field(typeof(ItemDrop), "m_itemData");

		private static readonly FieldInfo StackField = AccessTools.Field(typeof(ItemData), "m_stack");

		private static readonly MethodInfo ComposeMethod = AccessTools.Method(typeof(DecayRuntime), "ComposeGroundStackExpiry", (Type[])null, (Type[])null);

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Expected O, but got Unknown
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			List<int> list2 = new List<int>();
			for (int i = 8; i < list.Count; i++)
			{
				if (IsGroundStackIncrement(list, i))
				{
					list2.Add(i - 8);
				}
			}
			if (list2.Count != 1 || ComposeMethod == null)
			{
				FineDiningPlugin.Log.LogWarning((object)("Could not safely patch ItemDrop.AutoStackItems expiry merge; expected one stack increment but found " + list2.Count + ". Ground-stack timers may not compose on this game version."));
				return list;
			}
			int num = list2[0];
			CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null);
			val.labels.AddRange(list[num].labels);
			list[num].labels.Clear();
			val.blocks.AddRange(list[num].blocks);
			list[num].blocks.Clear();
			CodeInstruction val2 = new CodeInstruction(list[num + 4]);
			val2.labels.Clear();
			val2.blocks.Clear();
			list.InsertRange(num, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[3]
			{
				val,
				val2,
				new CodeInstruction(OpCodes.Call, (object)ComposeMethod)
			});
			return list;
		}

		private static bool IsGroundStackIncrement(IReadOnlyList<CodeInstruction> code, int storeIndex)
		{
			int num = storeIndex - 8;
			if (code[storeIndex].opcode == OpCodes.Stfld && object.Equals(code[storeIndex].operand, StackField) && code[num].opcode == OpCodes.Ldarg_0 && IsFieldLoad(code[num + 1], ItemDataField) && code[num + 2].opcode == OpCodes.Dup && IsFieldLoad(code[num + 3], StackField) && IsLocalLoad(code[num + 4]) && IsFieldLoad(code[num + 5], ItemDataField) && IsFieldLoad(code[num + 6], StackField))
			{
				return code[num + 7].opcode == OpCodes.Add;
			}
			return false;
		}

		private static bool IsFieldLoad(CodeInstruction instruction, FieldInfo field)
		{
			if (instruction.opcode == OpCodes.Ldfld)
			{
				return object.Equals(instruction.operand, field);
			}
			return false;
		}

		private static bool IsLocalLoad(CodeInstruction instruction)
		{
			if (!(instruction.opcode == OpCodes.Ldloc) && !(instruction.opcode == OpCodes.Ldloc_S) && !(instruction.opcode == OpCodes.Ldloc_0) && !(instruction.opcode == OpCodes.Ldloc_1) && !(instruction.opcode == OpCodes.Ldloc_2))
			{
				return instruction.opcode == OpCodes.Ldloc_3;
			}
			return true;
		}
	}
	internal static class SpoilageContentLifecycle
	{
		internal static void Refresh()
		{
			GeneratedPrefabRegistry.RefreshConfiguredContent();
			FoodClassifier.Invalidate();
			DietModule.InvalidateChefTierCatalog();
			DecayRuntime.InvalidateAll();
		}
	}
	[HarmonyPatch(typeof(ObjectDB), "UpdateRegisters")]
	internal static class ObjectDbUpdateRegistersSpoilagePatch
	{
		[HarmonyPriority(0)]
		private static void Postfix(ObjectDB __instance)
		{
			SpoilageContentLifecycle.Refresh();
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	internal static class ZNetDestroySpoilagePatch
	{
		private static void Postfix()
		{
			DecayRuntime.Reset();
			FoodClassifier.Invalidate();
		}
	}
	internal sealed class InventoryAddMergeState
	{
		internal readonly Dictionary<ItemData, int> PreviousStacks = new Dictionary<ItemData, int>();

		internal readonly Dictionary<ItemData, AssignedLifetimeSnapshot> PreviousLifetimes = new Dictionary<ItemData, AssignedLifetimeSnapshot>();

		internal bool SourceHadValidClock;

		internal bool SourceHadExpiryKey;

		internal string? OriginalSourceExpiryValue;

		internal bool SourceHadLifetimeKey;

		internal string? OriginalSourceLifetimeValue;

		internal long SourceClockValue;

		internal AssignedLifetimeSnapshot SourceLifetime;
	}
	internal static class InventoryAddMergeTracker
	{
		private static bool _loggedTemporarySourceRestoreFailure;

		internal static InventoryAddMergeState? Prefix(Inventory inventory, ItemData? source)
		{
			if (source == null || DecayRuntime.IsContainerLoading(inventory) || !DecayRuntime.IsAuthoritativeInventory(inventory))
			{
				return null;
			}
			InventoryAddMergeState inventoryAddMergeState = new InventoryAddMergeState();
			CaptureOriginalSourceMetadata(source, inventoryAddMergeState);
			DecayRuntime.PrepareItemForAdd(inventory, source);
			PieceRecoverySpoilageTracker.ApplyToInventoryItem(inventory, source);
			inventoryAddMergeState.SourceLifetime = FreshnessRuntime.CaptureAssignedLifetime(source);
			if (!DecayRuntime.TryGetExpiryTicks(source, out inventoryAddMergeState.SourceClockValue))
			{
				return null;
			}
			foreach (ItemData item in inventory.m_inventory)
			{
				if (item != null && CanPotentiallyStack(item, source))
				{
					inventoryAddMergeState.PreviousStacks[item] = item.m_stack;
					inventoryAddMergeState.PreviousLifetimes[item] = FreshnessRuntime.CaptureAssignedLifetime(item);
				}
			}
			return inventoryAddMergeState;
		}

		internal static void Postfix(Inventory inventory, ItemData? source, InventoryAddMergeState? state)
		{
			if (state == null || source == null)
			{
				return;
			}
			bool flag = false;
			try
			{
				if (state.SourceClockValue != 0L)
				{
					foreach (KeyValuePair<ItemData, int> previousStack in state.PreviousStacks)
					{
						ItemData key = previousStack.Key;
						if (key != source && key.m_stack > previousStack.Value)
						{
							flag |= DecayRuntime.ComposeInventoryStackMetadata(inventory, key, state.SourceClockValue, state.PreviousLifetimes[key], state.SourceLifetime);
						}
					}
				}
			}
			finally
			{
				RestoreTemporarySourceMetadataSafe(inventory, source, state);
			}
			if (flag)
			{
				inventory.Changed();
			}
		}

		internal static void RestoreTemporarySourceMetadataSafe(Inventory inventory, ItemData? source, InventoryAddMergeState? state)
		{
			if (source == null || state == null)
			{
				return;
			}
			try
			{
				RestoreTemporarySourceMetadata(inventory, source, state);
			}
			catch (Exception ex)
			{
				if (!_loggedTemporarySourceRestoreFailure)
				{
					_loggedTemporarySourceRestoreFailure = true;
					FineDiningPlugin.Log.LogWarning((object)("Could not restore a temporarily activated source stack after Inventory.AddItem: " + ex));
				}
			}
		}

		private static void CaptureOriginalSourceMetadata(ItemData source, InventoryAddMergeState state)
		{
			state.SourceHadValidClock = DecayRuntime.TryGetExpiryTicks(source, out var _);
			if (source.m_customData != null)
			{
				state.SourceHadExpiryKey = source.m_customData.TryGetValue("sighsorry.FineDining.ExpiryWorldTicks", out state.OriginalSourceExpiryValue);
				state.SourceHadLifetimeKey = source.m_customData.TryGetValue("sighsorry.FineDining.AssignedLifetimeTicks", out state.OriginalSourceLifetimeValue);
			}
		}

		private static void RestoreTemporarySourceMetadata(Inventory inventory, ItemData source, InventoryAddMergeState state)
		{
			if (!state.SourceHadValidClock && !inventory.m_inventory.Contains(source))
			{
				if (source.m_customData == null)
				{
					source.m_customData = new Dictionary<string, string>();
				}
				RestoreKey(source.m_customData, "sighsorry.FineDining.ExpiryWorldTicks", state.SourceHadExpiryKey, state.OriginalSourceExpiryValue);
				RestoreKey(source.m_customData, "sighsorry.FineDining.AssignedLifetimeTicks", state.SourceHadLifetimeKey, state.OriginalSourceLifetimeValue);
			}
		}

		private static void RestoreKey(Dictionary<string, string> customData, string key, bool hadKey, string? originalValue)
		{
			if (hadKey)
			{
				customData[key] = originalValue ?? string.Empty;
			}
			else
			{
				customData.Remove(key);
			}
		}

		private static bool CanPotentiallyStack(ItemData existing, ItemData source)
		{
			if (existing.m_shared == null || source.m_shared == null)
			{
				return false;
			}
			if (existing.m_shared.m_name == source.m_shared.m_name && existing.m_worldLevel == source.m_worldLevel)
			{
				if (existing.m_shared.m_maxQuality > 1)
				{
					return existing.m_quality == source.m_quality;
				}
				return true;
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData) })]
	internal static class InventoryAddItemSpoilagePatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		[HarmonyBefore(new string[] { "sighsorry.InventorySlots" })]
		private static void Prefix(Inventory __instance, ItemData item, out InventoryAddMergeState? __state)
		{
			__state = InventoryAddMergeTracker.Prefix(__instance, item);
		}

		[HarmonyPostfix]
		[HarmonyPriority(0)]
		[HarmonyAfter(new string[] { "sighsorry.InventorySlots" })]
		private static void Postfix(Inventory __instance, ItemData item, InventoryAddMergeState? __state)
		{
			InventoryAddMergeTracker.Postfix(__instance, item, __state);
		}

		private static Exception? Finalizer(Inventory __instance, ItemData item, InventoryAddMergeState? __state, Exception? __exception)
		{
			InventoryAddMergeTracker.RestoreTemporarySourceMetadataSafe(__instance, item, __state);
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Inventory), "AddItem", new Type[]
	{
		typeof(ItemData),
		typeof(Vector2i)
	})]
	internal static class InventoryAddItemAtPositionSpoilagePatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		[HarmonyBefore(new string[] { "sighsorry.InventorySlots" })]
		private static void Prefix(Inventory __instance, ItemData item, out InventoryAddMergeState? __state)
		{
			__state = InventoryAddMergeTracker.Prefix(__instance, item);
		}

		[HarmonyPostfix]
		[HarmonyPriority(0)]
		[HarmonyAfter(new string[] { "sighsorry.InventorySlots" })]
		private static void Postfix(Inventory __instance, ItemData item, InventoryAddMergeState? __state)
		{
			InventoryAddMergeTracker.Postfix(__instance, item, __state);
		}

		private static Exception? Finalizer(Inventory __instance, ItemData item, InventoryAddMergeState? __state, Exception? __exception)
		{
			InventoryAddMergeTracker.RestoreTemporarySourceMetadataSafe(__instance, item, __state);
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Inventory), "AddItem", new Type[]
	{
		typeof(ItemData),
		typeof(int),
		typeof(int),
		typeof(int)
	})]
	internal static class InventoryAddItemAmountAtPositionSpoilagePatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		[HarmonyBefore(new string[] { "sighsorry.InventorySlots" })]
		private static void Prefix(Inventory __instance, ItemData item, out InventoryAddMergeState? __state)
		{
			__state = InventoryAddMergeTracker.Prefix(__instance, item);
		}

		[HarmonyPostfix]
		[HarmonyPriority(0)]
		[HarmonyAfter(new string[] { "sighsorry.InventorySlots" })]
		private static void Postfix(Inventory __instance, ItemData item, InventoryAddMergeState? __state)
		{
			InventoryAddMergeTracker.Postfix(__instance, item, __state);
		}

		private static Exception? Finalizer(Inventory __instance, ItemData item, InventoryAddMergeState? __state, Exception? __exception)
		{
			InventoryAddMergeTracker.RestoreTemporarySourceMetadataSafe(__instance, item, __state);
			return __exception;
		}
	}
	internal static class DecayRuntime
	{
		private sealed class InventoryState
		{
			internal bool Dirty = true;

			internal bool Reconciling;

			internal bool SuppressDirty;

			internal bool PendingNotification;

			internal bool EnvironmentKnown;

			internal bool PausedByCold;

			internal bool BiomeSampleKnown;

			internal bool SampledBiomePaused;

			internal Vector3 SampledBiomePosition;

			internal float NextVisiblePreparationAt;

			internal long NextExpiryTicks = long.MaxValue;
		}

		private sealed class GroundEnvironmentSample
		{
			internal bool Paused;

			internal Vector3 Position;
		}

		private enum PreservationState
		{
			Unknown,
			Running,
			Paused
		}

		private enum ReplacementResolution
		{
			NotReady,
			Invalid,
			Ready
		}

		internal const string ExpiryDataKey = "sighsorry.FineDining.ExpiryWorldTicks";

		internal const string PlacedAnchorDataKey = "sighsorry.FineDining.PlacedWorldTicks";

		private static readonly Dictionary<Inventory, InventoryState> InventoryStates = new Dictionary<Inventory, InventoryState>();

		private static readonly Dictionary<Inventory, WeakReference<Container>> ContainersByInventory = new Dictionary<Inventory, WeakReference<Container>>();

		private static readonly Dictionary<int, WeakReference<ItemDrop>> GroundDropsByInstanceId = new Dictionary<int, WeakReference<ItemDrop>>();

		private static readonly Dictionary<int, GroundEnvironmentSample> GroundEnvironmentSamples = new Dictionary<int, GroundEnvironmentSample>();

		private static readonly List<KeyValuePair<int, WeakReference<ItemDrop>>> GroundDropSnapshot = new List<KeyValuePair<int, WeakReference<ItemDrop>>>();

		private static readonly HashSet<string> LoggedReplacementWarnings = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static float _nextTickAt;

		private static bool _loggedFirstGroundLoadFailure;

		internal static void Tick()
		{
			if (Time.unscaledTime < _nextTickAt)
			{
				return;
			}
			_nextTickAt = Time.unscaledTime + 1f;
			if (!TryGetWorldTicks(out var ticks))
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				Inventory inventory = ((Humanoid)localPlayer).GetInventory();
				if (inventory != null)
				{
					ProcessIfDue(inventory, ticks);
				}
			}
			PruneAndProcessContainers(ticks);
			PruneAndProcessGroundDrops(ticks);
		}

		internal static void RegisterContainer(Container? container)
		{
			if (!((Object)(object)container == (Object)null) && container.m_inventory != null && (!ContainersByInventory.TryGetValue(container.m_inventory, out WeakReference<Container> value) || !value.TryGetTarget(out var target) || target != container))
			{
				ContainersByInventory[container.m_inventory] = new WeakReference<Container>(container);
				GetState(container.m_inventory).Dirty = true;
			}
		}

		internal static void ContainerLoaded(Container? container)
		{
			RegisterContainer(container);
			if (!((Object)(object)container == (Object)null) && IsOwnedContainer(container) && TryGetWorldTicks(out var ticks))
			{
				GetState(container.m_inventory).Dirty = true;
				ProcessIfDue(container.m_inventory, ticks);
			}
		}

		internal static void MarkDirty(Inventory? inventory)
		{
			if (inventory != null && InventoryStates.TryGetValue(inventory, out InventoryState value) && !value.SuppressDirty)
			{
				value.Dirty = true;
				value.NextVisiblePreparationAt = 0f;
			}
		}

		internal static void InvalidateAll()
		{
			foreach (InventoryState value in InventoryStates.Values)
			{
				value.Dirty = true;
				value.EnvironmentKnown = false;
				value.BiomeSampleKnown = false;
				value.NextExpiryTicks = long.MinValue;
				value.NextVisiblePreparationAt = 0f;
			}
			GroundEnvironmentSamples.Clear();
			if (ItemDrop.s_instances == null)
			{
				return;
			}
			ItemDrop[] array = ItemDrop.s_instances.ToArray();
			foreach (ItemDrop val in array)
			{
				if ((Object)(object)val != (Object)null && IsPlacedGroundDrop(val))
				{
					RegisterGroundDrop(val);
				}
			}
		}

		internal static void Reset()
		{
			InventoryStates.Clear();
			ContainersByInventory.Clear();
			GroundDropsByInstanceId.Clear();
			GroundEnvironmentSamples.Clear();
			GroundDropSnapshot.Clear();
			LoggedReplacementWarnings.Clear();
			_nextTickAt = 0f;
			_loggedFirstGroundLoadFailure = false;
		}

		internal static bool TryPrepareVisibleInventoryTimers(Inventory? inventory, out long nowTicks)
		{
			nowTicks = 0L;
			if (inventory == null || !TryGetWorldTicks(out nowTicks))
			{
				return false;
			}
			if (IsAuthoritativeInventory(inventory))
			{
				InventoryState state = GetState(inventory);
				if (Time.unscaledTime >= state.NextVisiblePreparationAt)
				{
					state.NextVisiblePreparationAt = Time.unscaledTime + 1f;
					if (IsPolicyReadyForInventory(inventory))
					{
						ProcessIfDue(inventory, nowTicks);
					}
				}
			}
			return true;
		}

		internal static bool TryGetExpiryTicks(ItemData? item, out long clockValue)
		{
			clockValue = 0L;
			if (item?.m_customData != null && item.m_customData.TryGetValue("sighsorry.FineDining.ExpiryWorldTicks", out var value))
			{
				return SpoilageClock.TryParseClockValue(value, out clockValue);
			}
			return false;
		}

		internal static bool TryGetSpoilageClock(ItemData? item, long nowTicks, out long remainingTicks, out bool paused)
		{
			remainingTicks = 0L;
			paused = false;
			if (TryGetExpiryTicks(item, out var clockValue))
			{
				return SpoilageClock.TryDecodeClockValue(clockValue, nowTicks, out remainingTicks, out paused);
			}
			return false;
		}

		internal static string? ComposeStackClockValues(string? destinationValue, string? sourceValue)
		{
			if (!SpoilageClock.TryParseClockValue(sourceValue, out var clockValue))
			{
				return destinationValue;
			}
			if (!SpoilageClock.TryParseClockValue(destinationValue, out var clockValue2))
			{
				if (destinationValue != null)
				{
					return destinationValue;
				}
				return clockValue.ToString(CultureInfo.InvariantCulture);
			}
			long ticks;
			bool flag = TryGetWorldTicks(out ticks);
			if (!flag && clockValue2 < 0 != clockValue < 0)
			{
				return clockValue2.ToString(CultureInfo.InvariantCulture);
			}
			long nowTicks = (flag ? ticks : 0);
			return SpoilageClock.ComposeClockValues(clockValue2, clockValue, nowTicks, clockValue2 < 0).ToString(CultureInfo.InvariantCulture);
		}

		internal static bool CanMergeStackClockValues(string? destinationValue, string? sourceValue)
		{
			bool flag = destinationValue != null;
			bool flag2 = sourceValue != null;
			long clockValue = 0L;
			long clockValue2 = 0L;
			if (flag && !SpoilageClock.TryParseClockValue(destinationValue, out clockValue))
			{
				return false;
			}
			if (flag2 && !SpoilageClock.TryParseClockValue(sourceValue, out clockValue2))
			{
				return false;
			}
			if (!flag || !flag2 || clockValue < 0 == clockValue2 < 0)
			{
				return true;
			}
			long ticks;
			return TryGetWorldTicks(out ticks);
		}

		internal static bool PrepareItemForAdd(Inventory inventory, ItemData? item)
		{
			if (item == null || !IsAuthoritativeInventory(inventory))
			{
				return false;
			}
			bool isPlayerInventory = IsLocalPlayerInventory(inventory);
			long clockValue;
			bool hasClock = TryGetExpiryTicks(item, out clockValue);
			if (!ShouldProcessInventorySpoilageClock(isPlayerInventory, hasClock) || !TryGetWorldTicks(out var ticks))
			{
				return false;
			}
			ResolvedSpoilageRule rule = SpoilagePolicy.Resolve(item);
			bool paused;
			if (rule.State == SpoilageRuleState.Enabled)
			{
				return EnsureItemState(item, rule, ticks, ResolveInventoryPausedState(inventory), transitionExisting: false, out clockValue, out paused);
			}
			return false;
		}

		internal static bool ShouldProcessInventorySpoilageClock(bool isPlayerInventory, bool hasClock)
		{
			return isPlayerInventory || hasClock;
		}

		internal static bool PrepareInheritedItemForAdd(Inventory inventory, ItemData? item, long inheritedRemainingTicks)
		{
			if (item == null || !IsAuthoritativeInventory(inventory))
			{
				return false;
			}
			bool flag = PrepareItemForAdd(inventory, item);
			if (inheritedRemainingTicks < 0 || SpoilagePolicy.Resolve(item).State != SpoilageRuleState.Enabled)
			{
				return flag;
			}
			bool destinationPaused = ResolveInventoryPausedState(inventory);
			return ApplyEarlierRemaining(item, inheritedRemainingTicks, destinationPaused) || flag;
		}

		internal static bool ApplyEarlierInventoryClock(Inventory inventory, ItemData? target, long sourceClockValue)
		{
			if (target == null || !SpoilageClock.IsValidClockValue(sourceClockValue) || !TryGetWorldTicks(out var ticks))
			{
				return false;
			}
			return ApplyEarlierClock(target, sourceClockValue, ticks, ResolveInventoryPausedState(inventory));
		}

		internal static bool ComposeInventoryStackMetadata(Inventory inventory, ItemData target, long sourceClockValue, AssignedLifetimeSnapshot targetLifetime, AssignedLifetimeSnapshot sourceLifetime)
		{
			if (inventory == null || target == null || !SpoilageClock.IsValidClockValue(sourceClockValue))
			{
				return false;
			}
			return ApplyEarlierInventoryClock(inventory, target, sourceClockValue) | FreshnessRuntime.ComposeAssignedLifetime(target, targetLifetime, sourceLifetime);
		}

		internal static bool ComposeDirectRecoveryMergeExpiry(ItemData? target, ItemData? source, int movedAmount)
		{
			if (movedAmount <= 0 || target == null || source == null || target == source || target.m_customData == null || !TryGetExpiryTicks(source, out var clockValue))
			{
				return false;
			}
			AssignedLifetimeSnapshot destination = FreshnessRuntime.CaptureAssignedLifetime(target);
			AssignedLifetimeSnapshot source2 = FreshnessRuntime.CaptureAssignedLifetime(source);
			long clockValue2;
			bool flag = TryGetExpiryTicks(target, out clockValue2);
			if (!TryGetWorldTicks(out var ticks))
			{
				if (flag && clockValue2 < 0 != clockValue < 0)
				{
					return false;
				}
				ticks = 0L;
			}
			bool destinationPaused = (flag ? (clockValue2 < 0) : (clockValue < 0));
			return ApplyEarlierClock(target, clockValue, ticks, destinationPaused) | FreshnessRuntime.ComposeAssignedLifetime(target, destination, source2);
		}

		internal static void ComposeGroundStackExpiry(ItemDrop? destination, ItemDrop? source)
		{
			if (!((Object)(object)destination == (Object)null) && !((Object)(object)source == (Object)null) && !((Object)(object)destination.m_nview == (Object)null) && !((Object)(object)source.m_nview == (Object)null) && destination.m_nview.IsValid() && source.m_nview.IsValid() && destination.m_nview.IsOwner() && source.m_nview.IsOwner() && TryGetExpiryTicks(source.m_itemData, out var clockValue) && TryGetWorldTicks(out var ticks))
			{
				AssignedLifetimeSnapshot destination2 = FreshnessRuntime.CaptureAssignedLifetime(destination.m_itemData);
				AssignedLifetimeSnapshot source2 = FreshnessRuntime.CaptureAssignedLifetime(source.m_itemData);
				bool destinationPaused = ResolveWorldDropPausedState(destination);
				ApplyEarlierClock(destination.m_itemData, clockValue, ticks, destinationPaused);
				FreshnessRuntime.ComposeAssignedLifetime(destination.m_itemData, destination2, source2);
				RegisterGroundDrop(destination);
			}
		}

		internal static void RegisterGroundDrop(ItemDrop? drop)
		{
			if ((Object)(object)drop == (Object)null)
			{
				return;
			}
			int instanceID = ((Object)drop).GetInstanceID();
			if (!ReconcileCreatorlessPlacedDrop(drop))
			{
				WeakReference<ItemDrop> value;
				ItemDrop target;
				if (!HasValidGroundView(drop) || (!TryGetExpiryTicks(drop.m_itemData, out var _) && !IsPlacedGroundDrop(drop)))
				{
					GroundDropsByInstanceId.Remove(instanceID);
					GroundEnvironmentSamples.Remove(instanceID);
				}
				else if (!GroundDropsByInstanceId.TryGetValue(instanceID, out value) || !value.TryGetTarget(out target) || target != drop)
				{
					GroundDropsByInstanceId[instanceID] = new WeakReference<ItemDrop>(drop);
				}
			}
		}

		internal static void RefreshOwnedPlacedDrop(ItemDrop? drop)
		{
			if ((Object)(object)drop == (Object)null || !IsOwnedGroundDrop(drop) || !IsPlacedGroundDrop(drop))
			{
				return;
			}
			try
			{
				drop.Load();
				RegisterGroundDrop(drop);
			}
			catch (Exception ex)
			{
				if (!_loggedFirstGroundLoadFailure)
				{
					_loggedFirstGroundLoadFailure = true;
					FineDiningPlugin.Log.LogWarning((object)("Could not refresh an owned placed food item; FineDining will retry: " + ex));
				}
			}
		}

		internal static void InitializePlacedDrop(ItemDrop? drop, long inheritedRemainingTicks, long placementTicks = 0L)
		{
			if ((Object)(object)drop == (Object)null || !HasValidGroundView(drop) || !IsPlacedGroundDrop(drop) || ReconcileCreatorlessPlacedDrop(drop))
			{
				return;
			}
			if (IsOwnedGroundDrop(drop))
			{
				long clockValue;
				bool hasPersistedExpiry = TryGetExpiryTicks(drop.m_itemData, out clockValue);
				bool hasPendingAnchor = drop.m_itemData?.m_customData.ContainsKey("sighsorry.FineDining.PlacedWorldTicks") ?? false;
				if (!ShouldInitializePlacedDeadline(placementTicks, hasPersistedExpiry, hasPendingAnchor))
				{
					RegisterGroundDrop(drop);
				}
				else
				{
					InitializeOwnedWorldDrop(drop, inheritedRemainingTicks, placementTicks, keepPendingWhenNotReady: true);
				}
			}
			else
			{
				RegisterGroundDrop(drop);
			}
		}

		internal static bool ShouldInitializePlacedDeadline(long placementTicks, bool hasPersistedExpiry, bool hasPendingAnchor)
		{
			return placementTicks > 0 || !hasPersistedExpiry || hasPendingAnchor;
		}

		internal static void InitializeRecoveredDrop(ItemDrop? drop, long inheritedRemainingTicks)
		{
			if (!((Object)(object)drop == (Object)null) && inheritedRemainingTicks >= 0 && IsOwnedGroundDrop(drop))
			{
				InitializeOwnedWorldDrop(drop, inheritedRemainingTicks, 0L, keepPendingWhenNotReady: false);
			}
		}

		internal static long CalculateEffectiveRemainingTicks(long nowTicks, long anchorTicks, long lifetimeTicks, long inheritedRemainingTicks, bool paused)
		{
			long num = Math.Max(10000000L, lifetimeTicks);
			long num2 = ((!paused && anchorTicks > 0 && nowTicks > anchorTicks) ? (nowTicks - anchorTicks) : 0);
			long num3 = ((num2 >= num) ? 0 : (num - num2));
			if (inheritedRemainingTicks < 0)
			{
				return num3;
			}
			return Math.Min(inheritedRemainingTicks, num3);
		}

		internal static void UnregisterGroundDrop(ItemDrop? drop)
		{
			if ((Object)(object)drop != (Object)null)
			{
				int instanceID = ((Object)drop).GetInstanceID();
				GroundDropsByInstanceId.Remove(instanceID);
				GroundEnvironmentSamples.Remove(instanceID);
			}
		}

		internal static bool IsAuthoritativeInventory(Inventory? inventory)
		{
			if (inventory == null)
			{
				return false;
			}
			if (IsLocalPlayerInventory(inventory))
			{
				GetState(inventory);
				return true;
			}
			if (!ContainersByInventory.TryGetValue(inventory, out WeakReference<Container> value) || !value.TryGetTarget(out var target) || (Object)(object)target == (Object)null)
			{
				return false;
			}
			return IsOwnedContainer(target);
		}

		private static bool IsLocalPlayerInventory(Inventory? inventory)
		{
			Player localPlayer = Player.m_localPlayer;
			if (inventory != null && (Object)(object)localPlayer != (Object)null)
			{
				return ((Humanoid)localPlayer).GetInventory() == inventory;
			}
			return false;
		}

		internal static bool IsContainerLoading(Inventory? inventory)
		{
			if (TryGetContainer(inventory, out Container container))
			{
				return container.m_loading;
			}
			return false;
		}

		internal static bool TryGetContainer(Inventory? inventory, out Container? container)
		{
			container = null;
			if (inventory == null || !ContainersByInventory.TryGetValue(inventory, out WeakReference<Container> value) || !value.TryGetTarget(out var target) || (Object)(object)target == (Object)null)
			{
				return false;
			}
			container = target;
			return true;
		}

		private static void ProcessIfDue(Inventory inventory, long nowTicks)
		{
			InventoryState state = GetState(inventory);
			PreservationState preservationState = ResolveInventoryPreservationState(inventory);
			if (preservationState != PreservationState.Unknown)
			{
				bool flag = preservationState == PreservationState.Paused;
				if (!state.EnvironmentKnown || state.PausedByCold != flag)
				{
					state.EnvironmentKnown = true;
					state.PausedByCold = flag;
					state.Dirty = true;
				}
			}
			if (state.Dirty || nowTicks >= state.NextExpiryTicks)
			{
				Reconcile(inventory, state, nowTicks);
			}
		}

		private static bool IsPolicyReadyForInventory(Inventory inventory)
		{
			foreach (ItemData item in inventory.m_inventory)
			{
				if (item != null && SpoilagePolicy.Resolve(item).State == SpoilageRuleState.NotReady)
				{
					return false;
				}
			}
			return true;
		}

		private static void PruneAndProcessContainers(long nowTicks)
		{
			List<Inventory> list = null;
			foreach (KeyValuePair<Inventory, WeakReference<Container>> item in ContainersByInventory)
			{
				if (!item.Value.TryGetTarget(out var target) || (Object)(object)target == (Object)null || target.m_inventory == null)
				{
					if (list == null)
					{
						list = new List<Inventory>();
					}
					list.Add(item.Key);
				}
				else if (IsOwnedContainer(target))
				{
					ProcessIfDue(item.Key, nowTicks);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (Inventory item2 in list)
			{
				ContainersByInventory.Remove(item2);
				InventoryStates.Remove(item2);
			}
		}

		private static void PruneAndProcessGroundDrops(long nowTicks)
		{
			GroundDropSnapshot.Clear();
			foreach (KeyValuePair<int, WeakReference<ItemDrop>> item in GroundDropsByInstanceId)
			{
				GroundDropSnapshot.Add(item);
			}
			List<int> list = null;
			List<ItemDrop> list2 = null;
			foreach (KeyValuePair<int, WeakReference<ItemDrop>> item2 in GroundDropSnapshot)
			{
				if (!item2.Value.TryGetTarget(out var target) || (Object)(object)target == (Object)null || !HasValidGroundView(target))
				{
					if (list == null)
					{
						list = new List<int>();
					}
					list.Add(item2.Key);
					continue;
				}
				try
				{
					target.Load();
				}
				catch (Exception ex)
				{
					if (!_loggedFirstGroundLoadFailure)
					{
						_loggedFirstGroundLoadFailure = true;
						FineDiningPlugin.Log.LogWarning((object)("Could not refresh a tracked world item; FineDining will retry: " + ex));
					}
					continue;
				}
				bool flag = IsPlacedGroundDrop(target);
				if (flag && ReconcileCreatorlessPlacedDrop(target))
				{
					if (list == null)
					{
						list = new List<int>();
					}
					list.Add(item2.Key);
					continue;
				}
				if (flag && IsOwnedGroundDrop(target))
				{
					ItemData itemData = target.m_itemData;
					if (itemData != null && itemData.m_customData.ContainsKey("sighsorry.FineDining.PlacedWorldTicks"))
					{
						long inheritedRemainingTicks = -1L;
						if (TryGetSpoilageClock(target.m_itemData, nowTicks, out var remainingTicks, out var _))
						{
							inheritedRemainingTicks = remainingTicks;
						}
						InitializeOwnedWorldDrop(target, inheritedRemainingTicks, 0L, keepPendingWhenNotReady: true);
					}
				}
				if (!TryGetExpiryTicks(target.m_itemData, out var clockValue))
				{
					if (!flag)
					{
						if (list == null)
						{
							list = new List<int>();
						}
						list.Add(item2.Key);
						continue;
					}
					if (!IsOwnedGroundDrop(target))
					{
						if (list == null)
						{
							list = new List<int>();
						}
						list.Add(item2.Key);
						continue;
					}
					InitializeOwnedWorldDrop(target, -1L, 0L, keepPendingWhenNotReady: true);
					if (!TryGetExpiryTicks(target.m_itemData, out clockValue))
					{
						continue;
					}
				}
				if (!IsOwnedGroundDrop(target))
				{
					continue;
				}
				ResolvedSpoilageRule rule = SpoilagePolicy.Resolve(target.m_itemData);
				if (rule.State == SpoilageRuleState.NotReady)
				{
					continue;
				}
				bool expired;
				if (rule.State != SpoilageRuleState.Enabled)
				{
					ClearOwnedGroundExpiry(target);
					if (list == null)
					{
						list = new List<int>();
					}
					list.Add(item2.Key);
				}
				else if (TryEvaluateGroundExpiry(target, rule, nowTicks, out expired) && expired)
				{
					if (list == null)
					{
						list = new List<int>();
					}
					list.Add(item2.Key);
					if (list2 == null)
					{
						list2 = new List<ItemDrop>();
					}
					list2.Add(target);
				}
			}
			GroundDropSnapshot.Clear();
			if (list != null)
			{
				foreach (int item3 in list)
				{
					GroundDropsByInstanceId.Remove(item3);
					GroundEnvironmentSamples.Remove(item3);
				}
			}
			if (list2 == null)
			{
				return;
			}
			foreach (ItemDrop item4 in list2)
			{
				try
				{
					if (!IsOwnedGroundDrop(item4))
					{
						RegisterGroundDrop(item4);
						continue;
					}
					ResolvedSpoilageRule rule2 = SpoilagePolicy.Resolve(item4.m_itemData);
					bool expired2;
					if (rule2.State == SpoilageRuleState.NotReady)
					{
						RegisterGroundDrop(item4);
					}
					else if (rule2.State != SpoilageRuleState.Enabled)
					{
						ClearOwnedGroundExpiry(item4);
					}
					else if (!TryEvaluateGroundExpiry(item4, rule2, nowTicks, out expired2) || !expired2)
					{
						RegisterGroundDrop(item4);
					}
					else
					{
						ExpireGroundStack(item4, rule2);
					}
				}
				catch (Exception ex2)
				{
					FineDiningPlugin.Log.LogError((object)("Failed to expire a tracked world item: " + ex2));
					RegisterGroundDrop(item4);
				}
			}
		}

		private static bool TryEvaluateGroundExpiry(ItemDrop drop, ResolvedSpoilageRule rule, long nowTicks, out bool expired)
		{
			expired = false;
			if (ReconcileCreatorlessPlacedDrop(drop))
			{
				return false;
			}
			ItemData itemData = drop.m_itemData;
			if (itemData == null)
			{
				return false;
			}
			if (EnsureItemState(itemData, rule, nowTicks, ResolveWorldDropPausedState(drop, rule.Group), transitionExisting: true, out var remainingTicks, out var paused))
			{
				drop.Save();
			}
			expired = !paused && remainingTicks <= 0;
			return true;
		}

		private static void ClearOwnedGroundExpiry(ItemDrop drop)
		{
			if (IsOwnedGroundDrop(drop))
			{
				bool flag = false;
				if (drop.m_itemData?.m_customData != null)
				{
					flag |= drop.m_itemData.m_customData.Remove("sighsorry.FineDining.ExpiryWorldTicks");
					flag |= drop.m_itemData.m_customData.Remove("sighsorry.FineDining.PlacedWorldTicks");
					flag |= FreshnessRuntime.ClearTrackedMetadata(drop.m_itemData);
				}
				if (flag)
				{
					drop.Save();
				}
				UnregisterGroundDrop(drop);
			}
		}

		private static void InitializeOwnedWorldDrop(ItemDrop drop, long inheritedRemainingTicks, long anchorTicks, bool keepPendingWhenNotReady)
		{
			if (!IsOwnedGroundDrop(drop) || ReconcileCreatorlessPlacedDrop(drop))
			{
				return;
			}
			bool flag = false;
			long ticks = 0L;
			if (keepPendingWhenNotReady && !TryGetPositiveCustomTicks(drop.m_itemData, "sighsorry.FineDining.PlacedWorldTicks", out ticks))
			{
				ticks = anchorTicks;
				if (ticks <= 0)
				{
					TryGetWorldTicks(out ticks);
				}
				if (ticks > 0)
				{
					drop.m_itemData.m_customData["sighsorry.FineDining.PlacedWorldTicks"] = ticks.ToString(CultureInfo.InvariantCulture);
					flag = true;
				}
			}
			ResolvedSpoilageRule resolvedSpoilageRule = SpoilagePolicy.Resolve(drop.m_itemData);
			if (resolvedSpoilageRule.State == SpoilageRuleState.NotReady)
			{
				if (flag | (inheritedRemainingTicks >= 0 && ApplyEarlierRemaining(drop.m_itemData, inheritedRemainingTicks, ResolveWorldDropPausedState(drop))))
				{
					drop.Save();
				}
				if (keepPendingWhenNotReady || inheritedRemainingTicks >= 0)
				{
					RegisterGroundDrop(drop);
				}
				return;
			}
			if (resolvedSpoilageRule.State != SpoilageRuleState.Enabled)
			{
				ClearOwnedGroundExpiry(drop);
				return;
			}
			if (!TryGetWorldTicks(out var ticks2))
			{
				RegisterGroundDrop(drop);
				return;
			}
			bool flag2 = ResolveWorldDropPausedState(drop, resolvedSpoilageRule.Group);
			long num = CalculateEffectiveRemainingTicks(ticks2, ticks, resolvedSpoilageRule.LifetimeTicks, inheritedRemainingTicks, flag2);
			long clockValue = SpoilageClock.EncodeClockValue(ticks2, num, flag2 && num > 0);
			flag |= SetClockValue(drop.m_itemData, clockValue);
			flag |= FreshnessRuntime.EnsureTrackedMetadata(drop.m_itemData, resolvedSpoilageRule.LifetimeTicks);
			if (keepPendingWhenNotReady)
			{
				flag |= drop.m_itemData.m_customData.Remove("sighsorry.FineDining.PlacedWorldTicks");
			}
			if (flag)
			{
				drop.Save();
			}
			RegisterGroundDrop(drop);
		}

		private static bool HasValidGroundView(ItemDrop drop)
		{
			try
			{
				return (Object)(object)drop.m_nview != (Object)null && drop.m_nview.IsValid();
			}
			catch
			{
				return false;
			}
		}

		private static bool IsOwnedGroundDrop(ItemDrop drop)
		{
			try
			{
				return HasValidGroundView(drop) && drop.m_nview.IsOwner();
			}
			catch
			{
				return false;
			}
		}

		private static bool IsPlacedGroundDrop(ItemDrop drop)
		{
			try
			{
				if (!HasValidGroundView(drop))
				{
					return false;
				}
				return drop.m_nview.GetZDO().GetBool(ZDOVars.s_piece, false) || drop.IsPiece();
			}
			catch
			{
				return false;
			}
		}

		internal static bool IsCreatorlessPlacedDrop(ItemDrop? drop)
		{
			try
			{
				return (Object)(object)drop != (Object)null && IsPlacedGroundDrop(drop) && drop.m_nview.GetZDO().GetLong(ZDOVars.s_creator, 0L) == 0;
			}
			catch
			{
				return false;
			}
		}

		private static bool ReconcileCreatorlessPlacedDrop(ItemDrop? drop)
		{
			if (!IsCreatorlessPlacedDrop(drop))
			{
				return false;
			}
			if (IsOwnedGroundDrop(drop))
			{
				ClearOwnedGroundExpiry(drop);
			}
			else
			{
				UnregisterGroundDrop(drop);
			}
			return true;
		}

		private static bool TryGetPositiveCustomTicks(ItemData? item, string key, out long ticks)
		{
			ticks = 0L;
			if (item?.m_customData != null && item.m_customData.TryGetValue(key, out var value) && long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out ticks) && ticks > 0)
			{
				return string.Equals(value, ticks.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal);
			}
			return false;
		}

		private static PreservationState ResolveInventoryPreservationState(Inventory inventory)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			InventoryState state = GetState(inventory);
			Player localPlayer = Player.m_localPlayer;
			if (IsLocalPlayerInventory(inventory))
			{
				return SampleInventoryBiome(state, ((Component)localPlayer).transform.position);
			}
			if (ContainersByInventory.TryGetValue(inventory, out WeakReference<Container> value) && value.TryGetTarget(out var target) && (Object)(object)target != (Object)null)
			{
				if (GeneratedPrefabRegistry.IsIcebox(target))
				{
					return PreservationState.Paused;
				}
				return SampleInventoryBiome(state, ((Component)target).transform.position);
			}
			return PreservationState.Unknown;
		}

		private static bool ResolveInventoryPausedState(Inventory inventory)
		{
			PreservationState preservationState = ResolveInventoryPreservationState(inventory);
			if (preservationState != PreservationState.Unknown)
			{
				return preservationState == PreservationState.Paused;
			}
			InventoryState state = GetState(inventory);
			if (state.EnvironmentKnown)
			{
				return state.PausedByCold;
			}
			return false;
		}

		private static bool ResolveWorldDropPausedState(ItemDrop drop, SpoilageGroup? knownGroup = null)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			if (IsFishPreservedInWater(drop, knownGroup))
			{
				return true;
			}
			int instanceID = ((Object)drop).GetInstanceID();
			Vector3 position = ((Component)drop).transform.position;
			if (GroundEnvironmentSamples.TryGetValue(instanceID, out GroundEnvironmentSample value) && PositionsMatch(value.Position, position))
			{
				return value.Paused;
			}
			if (TryIsNoSpoilBiome(position, out var preserved, out var cacheable))
			{
				if (cacheable)
				{
					GroundEnvironmentSamples[instanceID] = new GroundEnvironmentSample
					{
						Position = position,
						Paused = preserved
					};
				}
				return preserved;
			}
			if (TryGetExpiryTicks(drop.m_itemData, out var clockValue))
			{
				return clockValue < 0;
			}
			return false;
		}

		private static bool IsFishPreservedInWater(ItemDrop? drop, SpoilageGroup? knownGroup)
		{
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)drop == (Object)null)
			{
				return false;
			}
			try
			{
				SpoilageGroup spoilageGroup;
				if (knownGroup.HasValue)
				{
					spoilageGroup = knownGroup.Value;
				}
				else
				{
					ResolvedSpoilageRule resolvedSpoilageRule = SpoilagePolicy.Resolve(drop.m_itemData);
					if (resolvedSpoilageRule.State != SpoilageRuleState.Enabled)
					{
						return false;
					}
					spoilageGroup = resolvedSpoilageRule.Group;
				}
				if (spoilageGroup != SpoilageGroup.Fish)
				{
					return false;
				}
				Fish val = ((Component)drop).GetComponent<Fish>() ?? ((Component)drop).GetComponentInParent<Fish>() ?? ((Component)drop).GetComponentInChildren<Fish>(true);
				if ((Object)(object)val != (Object)null)
				{
					return !val.IsOutOfWater();
				}
				Floating val2 = drop.m_floating ?? ((Component)drop).GetComponent<Floating>();
				if ((Object)(object)val2 == (Object)null || val2.m_waterLevel <= -10000f)
				{
					return false;
				}
				return (((Object)(object)val2.m_body != (Object)null) ? val2.m_body.worldCenterOfMass.y : ((Component)drop).transform.position.y) - val2.m_waterLevel - val2.m_waterLevelOffset <= 0.05f;
			}
			catch
			{
				return false;
			}
		}

		private static PreservationState SampleInventoryBiome(InventoryState state, Vector3 position)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			if (state.BiomeSampleKnown && PositionsMatch(state.SampledBiomePosition, position))
			{
				if (!state.SampledBiomePaused)
				{
					return PreservationState.Running;
				}
				return PreservationState.Paused;
			}
			if (!TryIsNoSpoilBiome(position, out var preserved, out var cacheable))
			{
				return PreservationState.Unknown;
			}
			if (cacheable)
			{
				state.BiomeSampleKnown = true;
				state.SampledBiomePosition = position;
				state.SampledBiomePaused = preserved;
			}
			if (!preserved)
			{
				return PreservationState.Running;
			}
			return PreservationState.Paused;
		}

		private static bool PositionsMatch(Vector3 left, Vector3 right)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = left - right;
			return ((Vector3)(ref val)).sqrMagnitude <= 0.0001f;
		}

		private static bool TryIsNoSpoilBiome(Vector3 position, out bool preserved, out bool cacheable)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Invalid comparison between Unknown and I4
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			preserved = false;
			cacheable = false;
			try
			{
				Biome val = Heightmap.FindBiome(position);
				cacheable = (int)val > 0;
				if ((int)val == 0 && WorldGenerator.instance != null)
				{
					val = WorldGenerator.instance.GetBiome(position.x, position.z, 0.02f, false);
				}
				if ((int)val == 0)
				{
					return false;
				}
				preserved = PreservationConfig.IsNoSpoilBiome(val);
				return true;
			}
			catch
			{
				cacheable = false;
				return false;
			}
		}

		internal static List<KeyValuePair<ItemData, string>> BeginPlayerSaveClockSnapshot(Player? player)
		{
			List<KeyValuePair<ItemData, string>> list = new List<KeyValuePair<ItemData, string>>();
			Inventory val = ((player != null) ? ((Humanoid)player).GetInventory() : null);
			if (val == null || !TryGetWorldTicks(out var ticks))
			{
				return list;
			}
			foreach (ItemData item in val.m_inventory)
			{
				if (TryGetSpoilageClock(item, ticks, out var remainingTicks, out var paused) && paused && item.m_customData.TryGetValue("sighsorry.FineDining.ExpiryWorldTicks", out var value))
				{
					list.Add(new KeyValuePair<ItemData, string>(item, value));
					item.m_customData["sighsorry.FineDining.ExpiryWorldTicks"] = SpoilageClock.EncodeClockValue(ticks, remainingTicks, paused: false).ToString(CultureInfo.InvariantCulture);
				}
			}
			return list;
		}

		internal static void EndPlayerSaveClockSnapshot(List<KeyValuePair<ItemData, string>>? pausedClocks)
		{
			if (pausedClocks == null)
			{
				return;
			}
			foreach (KeyValuePair<ItemData, string> pausedClock in pausedClocks)
			{
				if (pausedClock.Key?.m_customData != null)
				{
					pausedClock.Key.m_customData["sighsorry.FineDining.ExpiryWorldTicks"] = pausedClock.Value;
				}
			}
		}

		private static bool IsOwnedContainer(Container container)
		{
			try
			{
				return (Object)(object)container.m_nview != (Object)null && container.m_nview.IsValid() && container.m_nview.IsOwner();
			}
			catch
			{
				return false;
			}
		}

		private static InventoryState GetState(Inventory inventory)
		{
			if (!InventoryStates.TryGetValue(inventory, out InventoryState value))
			{
				value = new InventoryState();
				InventoryStates.Add(inventory, value);
			}
			return value;
		}

		private static void Reconcile(Inventory inventory, InventoryState state, long nowTicks)
		{
			if (state.Reconciling)
			{
				return;
			}
			state.Reconciling = true;
			bool flag = state.PendingNotification;
			long num = long.MaxValue;
			bool shouldPause = state.EnvironmentKnown && state.PausedByCold;
			bool isPlayerInventory = IsLocalPlayerInventory(inventory);
			try
			{
				List<ItemData> inventory2 = inventory.m_inventory;
				for (int num2 = inventory2.Count - 1; num2 >= 0; num2--)
				{
					ItemData val = inventory2[num2];
					if (val != null)
					{
						ResolvedSpoilageRule rule = SpoilagePolicy.Resolve(val);
						if (rule.State != SpoilageRuleState.NotReady)
						{
							if (rule.State != SpoilageRuleState.Enabled)
							{
								if (val.m_customData.Remove("sighsorry.FineDining.ExpiryWorldTicks"))
								{
									flag = true;
								}
								flag |= FreshnessRuntime.ClearTrackedMetadata(val);
							}
							else
							{
								long clockValue;
								bool hasClock = TryGetExpiryTicks(val, out clockValue);
								if (ShouldProcessInventorySpoilageClock(isPlayerInventory, hasClock))
								{
									if (EnsureItemState(val, rule, nowTicks, shouldPause, state.EnvironmentKnown, out var remainingTicks, out var paused))
									{
										flag = true;
									}
									if (!paused)
									{
										if (remainingTicks <= 0)
										{
											bool flag2 = ExpireItem(inventory, val, rule);
											flag = flag || flag2;
											if (!flag2)
											{
												num = Math.Min(num, nowTicks);
											}
										}
										else
										{
											long val2 = SpoilageClock.AddTicksSaturating(nowTicks, remainingTicks);
											num = Math.Min(num, val2);
										}
									}
								}
							}
						}
					}
				}
				if (flag)
				{
					state.PendingNotification = true;
					state.SuppressDirty = true;
					inventory.Changed();
					state.PendingNotification = false;
				}
				state.Dirty = false;
				state.NextExpiryTicks = num;
			}
			catch (Exception ex)
			{
				state.Dirty = true;
				FineDiningPlugin.Log.LogError((object)("Failed to reconcile inventory spoilage: " + ex));
			}
			finally
			{
				state.SuppressDirty = false;
				state.Reconciling = false;
			}
		}

		private static bool EnsureItemState(ItemData item, ResolvedSpoilageRule rule, long nowTicks, bool shouldPause, bool transitionExisting, out long remainingTicks, out bool paused)
		{
			remainingTicks = 0L;
			paused = false;
			if (rule.State != SpoilageRuleState.Enabled)
			{
				return false;
			}
			bool result = FreshnessRuntime.EnsureTrackedMetadata(item, rule.LifetimeTicks);
			if (TryGetSpoilageClock(item, nowTicks, out remainingTicks, out paused))
			{
				if (!transitionExisting || paused == shouldPause)
				{
					return result;
				}
				if (!paused && remainingTicks <= 0)
				{
					return result;
				}
				paused = shouldPause;
				long num = SpoilageClock.EncodeClockValue(nowTicks, remainingTicks, paused);
				item.m_customData["sighsorry.FineDining.ExpiryWorldTicks"] = num.ToString(CultureInfo.InvariantCulture);
				return true;
			}
			long num2 = Math.Max(10000000L, rule.LifetimeTicks);
			remainingTicks = num2;
			paused = shouldPause;
			long num3 = SpoilageClock.EncodeClockValue(nowTicks, remainingTicks, paused);
			item.m_customData["sighsorry.FineDining.ExpiryWorldTicks"] = num3.ToString(CultureInfo.InvariantCulture);
			return true;
		}

		private static ReplacementResolution ResolveReplacement(ItemData sourceItem, ResolvedSpoilageRule rule, out string configuredPrefab, out ItemDrop replacementDrop)
		{
			configuredPrefab = FoodIdentity.NormalizePrefabName(rule.ReplacementPrefab);
			replacementDrop = null;
			ObjectDB instance = ObjectDB.instance;
			if (!IsObjectDatabaseReady(instance))
			{
				return ReplacementResolution.NotReady;
			}
			bool flag = GeneratedPrefabRegistry.IsGeneratedReplacementPrefabName(configuredPrefab);
			if (flag && !GeneratedPrefabRegistry.EnsureGeneratedReplacementAvailable(configuredPrefab))
			{
				return ReplacementResolution.NotReady;
			}
			GameObject val = ResolveItemPrefab(instance, configuredPrefab);
			if ((Object)(object)val == (Object)null && flag)
			{
				return ReplacementResolution.NotReady;
			}
			ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null);
			string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(sourceItem);
			if (val2?.m_itemData?.m_shared == null || string.Equals(canonicalPrefabName, configuredPrefab, StringComparison.OrdinalIgnoreCase))
			{
				WarnReplacementOnce(configuredPrefab, "Spoiled prefab '" + configuredPrefab + "' is missing, invalid, or the same as the expired item. Removing expired stacks instead.");
				return ReplacementResolution.Invalid;
			}
			replacementDrop = val2;
			return ReplacementResolution.Ready;
		}

		private static bool ExpireItem(Inventory inventory, ItemData item, ResolvedSpoilageRule rule)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: 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_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			string configuredPrefab;
			ItemDrop replacementDrop;
			switch (ResolveReplacement(item, rule, out configuredPrefab, out replacementDrop))
			{
			case ReplacementResolution.NotReady:
				return false;
			case ReplacementResolution.Invalid:
				return inventory.m_inventory.Remove(item);
			default:
			{
				GameObject gameObject = ((Component)replacementDrop).gameObject;
				ItemData itemData = replacementDrop.m_itemData;
				int num = Math.Max(0, item.m_stack);
				Vector2i gridPos = item.m_gridPos;
				int worldLevel = item.m_worldLevel;
				if (!inventory.m_inventory.Remove(item))
				{
					return false;
				}
				if (num <= 0)
				{
					return true;
				}
				int num2 = Math.Max(1, itemData.m_shared.m_maxStackSize);
				int num3 = CalculateReplacementAmount(num);
				ItemData val = CreateReplacement(itemData, gameObject, worldLevel);
				int amount = num3;
				num3 -= InsertReplacementStack(inventory, val, amount, gridPos);
				if (num3 > 0)
				{
					foreach (ItemData item2 in inventory.m_inventory)
					{
						if (num3 <= 0)
						{
							break;
						}
						if (CanStackReplacement(item2, val))
						{
							int num4 = Math.Min(num3, Math.Max(0, num2 - item2.m_stack));
							item2.m_stack += num4;
							num3 -= num4;
						}
					}
				}
				while (num3 > 0)
				{
					ItemData val2 = CreateReplacement(itemData, gameObject, worldLevel);
					Vector2i val3 = inventory.FindEmptySlot(inventory.TopFirst(val2));
					if (val3.x < 0)
					{
						break;
					}
					int amount2 = Math.Min(num2, num3);
					int num5 = InsertReplacementStack(inventory, val2, amount2, val3);
					if (num5 <= 0)
					{
						break;
					}
					num3 -= num5;
				}
				if (num3 > 0)
				{
					WarnReplacementOnce("overflow:" + configuredPrefab, "Inventory had no room for " + num3 + " additional '" + configuredPrefab + "' items; overflow was discarded safely.");
				}
				return true;
			}
			}
		}

		private static void ExpireGroundStack(ItemDrop drop, ResolvedSpoilageRule rule)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: 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_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			if (!IsOwnedGroundDrop(drop))
			{
				RegisterGroundDrop(drop);
			}
			else
			{
				if (ReconcileCreatorlessPlacedDrop(drop))
				{
					return;
				}
				ZDOID uid = drop.m_nview.GetZDO().m_uid;
				if (!TryGetExpiryTicks(drop.m_itemData, out var clockValue))
				{
					UnregisterGroundDrop(drop);
					return;
				}
				if (drop.m_autoDestroy && drop.GetTimeSinceSpawned() >= 3600.0)
				{
					drop.TimedDestruction();
					if (!IsOwnedGroundDrop(drop))
					{
						return;
					}
				}
				ItemData itemData = drop.m_itemData;
				int num = Math.Max(0, itemData?.m_stack ?? 0);
				if (itemData == null || num <= 0)
				{
					drop.m_nview.Destroy();
					return;
				}
				string configuredPrefab;
				ItemDrop replacementDrop;
				ReplacementResolution replacementResolution = ResolveReplacement(itemData, rule, out configuredPrefab, out replacementDrop);
				if (replacementResolution == ReplacementResolution.NotReady)
				{
					RegisterGroundDrop(drop);
					return;
				}
				string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(itemData);
				if (replacementResolution == ReplacementResolution.Invalid)
				{
					drop.m_nview.Destroy();
					return;
				}
				GameObject gameObject = ((Component)replacementDrop).gameObject;
				ItemData itemData2 = replacementDrop.m_itemData;
				Vector3 position = ((Component)drop).transform.position;
				Quaternion rotation = ((Component)drop).transform.rotation;
				long num2 = (ShouldInheritGroundSpawnTime(drop.m_autoDestroy, IsPlacedGroundDrop(drop)) ? GetGroundSpawnTimeTicks(drop) : 0);
				if (num2 > 0 && !replacementDrop.m_autoDestroy)
				{
					WarnReplacementOnce("cleanup:" + configuredPrefab, "Spoiled prefab '" + configuredPrefab + "' does not use vanilla ItemDrop auto-destruction; the original ground cleanup age cannot be enforced for that replacement.");
				}
				int worldLevel = itemData.m_worldLevel;
				int num3 = CalculateReplacementAmount(num);
				ItemData val = CreateReplacement(itemData2, gameObject, worldLevel);
				ItemDrop val2 = null;
				try
				{
					val2 = ItemDrop.DropItem(val, num3, position, rotation);
					if (!IsOwnedGroundDrop(val2))
					{
						throw new InvalidOperationException("A newly spawned spoilage replacement was not locally owned.");
					}
					val2.OnPlayerDrop();
					InheritGroundSpawnTime(val2, num2);
				}
				catch (Exception ex)
				{
					DestroySpawnedGroundReplacement(val2);
					FineDiningPlugin.Log.LogError((object)("Could not create a ground spoilage replacement; the original stack was kept: " + ex));
					RegisterGroundDrop(drop);
					return;
				}
				if (!GroundSourceStillMatches(drop, uid, clockValue, canonicalPrefabName, num))
				{
					DestroySpawnedGroundReplacement(val2);
					RegisterGroundDrop(drop);
				}
				else
				{
					drop.m_nview.Destroy();
				}
			}
		}

		private static bool GroundSourceStillMatches(ItemDrop drop, ZDOID sourceZdoId, long sourceExpiryTicks, string sourcePrefab, int sourceAmount)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			if (!IsOwnedGroundDrop(drop) || IsCreatorlessPlacedDrop(drop) || drop.m_nview.GetZDO().m_uid != sourceZdoId || drop.m_itemData == null || drop.m_itemData.m_stack != sourceAmount || !TryGetExpiryTicks(drop.m_itemData, out var clockValue) || clockValue != sourceExpiryTicks)
			{
				return false;
			}
			return string.Equals(FoodIdentity.GetCanonicalPrefabName(drop.m_itemData), sourcePrefab, StringComparison.OrdinalIgnoreCase);
		}

		internal static bool ShouldInheritGroundSpawnTime(bool autoDestroy, bool isPiece)
		{
			if (autoDestroy)
			{
				return !isPiece;
			}
			return false;
		}

		private static long GetGroundSpawnTimeTicks(ItemDrop drop)
		{
			try
			{
				return HasValidGroundView(drop) ? drop.m_nview.GetZDO().GetLong(ZDOVars.s_spawnTime, 0L) : 0;
			}
			catch
			{
				return 0L;
			}
		}

		private static void InheritGroundSpawnTime(ItemDrop replacement, long sourceSpawnTimeTicks)
		{
			if (sourceSpawnTimeTicks > 0 && IsOwnedGroundDrop(replacement))
			{
				replacement.m_nview.GetZDO().Set(ZDOVars.s_spawnTime, sourceSpawnTimeTicks);
			}
		}

		private static void DestroySpawnedGroundReplacement(ItemDrop? replacement)
		{
			if (!((Object)(object)replacement == (Object)null))
			{
				if (IsOwnedGroundDrop(replacement))
				{
					replacement.m_nview.Destroy();
				}
				else if (HasValidGroundView(replacement))
				{
					WarnReplacementOnce("ground-rollback-owner", "A spoilage replacement changed owner during rollback and could not be removed locally.");
				}
			}
		}

		internal static int CalculateReplacementAmount(int sourceAmount)
		{
			return Math.Max(0, sourceAmount);
		}

		private static int InsertReplacementStack(Inventory inventory, ItemData item, int amount, Vector2i requestedPosition)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			if (amount <= 0)
			{
				return 0;
			}
			item.m_stack = amount;
			int stack = item.m_stack;
			inventory.AddItem(item, amount, requestedPosition.x, requestedPosition.y);
			return Math.Max(0, stack - item.m_stack);
		}

		private static ItemData CreateReplacement(ItemData template, GameObject prefab, int sourceWorldLevel)
		{
			ItemData obj = template.Clone();
			obj.m_dropPrefab = prefab;
			obj.m_worldLevel = sourceWorldLevel;
			obj.m_equipped = false;
			obj.m_customData.Remove("sighsorry.FineDining.ExpiryWorldTicks");
			FreshnessRuntime.ClearTrackedMetadata(obj);
			return obj;
		}

		private static GameObject? ResolveItemPrefab(ObjectDB? objectDb, string prefabName)
		{
			if ((Object)(object)objectDb == (Object)null || string.IsNullOrWhiteSpace(prefabName))
			{
				return null;
			}
			GameObject itemPrefab = objectDb.GetItemPrefab(prefabName);
			if ((Object)(object)itemPrefab != (Object)null && string.Equals(FoodIdentity.NormalizePrefabName(((Object)itemPrefab).name), prefabName, StringComparison.OrdinalIgnoreCase))
			{
				return itemPrefab;
			}
			return objectDb.m_items?.FirstOrDefault((Func<GameObject, bool>)((GameObject prefab) => (Object)(object)prefab != (Object)null && string.Equals(FoodIdentity.NormalizePrefabName(((Object)prefab).name), prefabName, StringComparison.OrdinalIgnoreCase)));
		}

		private static bool IsObjectDatabaseReady(ObjectDB? objectDb)
		{
			if ((Object)(object)objectDb != (Object)null && objectDb.m_items != null)
			{
				return objectDb.m_items.Count > 0;
			}
			return false;
		}

		private static bool CanStackReplacement(ItemData candidate, ItemData template)
		{
			if (candidate.m_shared.m_name == template.m_shared.m_name && candidate.m_quality == template.m_quality && candidate.m_worldLevel == template.m_worldLevel && candidate.m_stack < candidate.m_shared.m_maxStackSize)
			{
				return CustomDataEqual(candidate.m_customData, template.m_customData);
			}
			return false;
		}

		private static bool CustomDataEqual(Dictionary<string, string> left, Dictionary<string, string> right)
		{
			int num = left.Count - CountStackMetadataKeys(left);
			int num2 = right.Count - CountStackMetadataKeys(right);
			string value;
			if (num == num2)
			{
				return left.All<KeyValuePair<string, string>>((KeyValuePair<string, string> pair) => IsStackMetadataKey(pair.Key) || (right.TryGetValue(pair.Key, out value) && value == pair.Value));
			}
			return false;
		}

		private static int CountStackMetadataKeys(Dictionary<string, string> values)
		{
			return (values.ContainsKey("sighsorry.FineDining.ExpiryWorldTicks") ? 1 : 0) + (values.ContainsKey("sighsorry.FineDining.AssignedLifetimeTicks") ? 1 : 0);
		}

		private static bool IsStackMetadataKey(string key)
		{
			if (!(key == "sighsorry.FineDining.ExpiryWorldTicks"))
			{
				return key == "sighsorry.FineDining.AssignedLifetimeTicks";
			}
			return true;
		}

		private static bool ApplyEarlierClock(ItemData target, long sourceClockValue, long nowTicks, bool destinationPaused)
		{
			if (!SpoilageClock.IsValidClockValue(sourceClockValue))
			{
				return false;
			}
			long clockValue2;
			long clockValue = (TryGetExpiryTicks(target, out clockValue2) ? SpoilageClock.ComposeClockValues(clockValue2, sourceClockValue, nowTicks, destinationPaused) : ReencodeClockValue(sourceClockValue, nowTicks, destinationPaused));
			return SetClockValue(target, clockValue);
		}

		private static bool ApplyEarlierRemaining(ItemData target, long sourceRemainingTicks, bool destinationPaused)
		{
			if (sourceRemainingTicks < 0)
			{
				return false;
			}
			long ticks;
			long nowTicks = (TryGetWorldTicks(out ticks) ? ticks : 0);
			long sourceClockValue = SpoilageClock.EncodeClockValue(nowTicks, sourceRemainingTicks, destinationPaused);
			return ApplyEarlierClock(target, sourceClockValue, nowTicks, destinationPaused);
		}

		private static long ReencodeClockValue(long clockValue, long nowTicks, bool paused)
		{
			if (!SpoilageClock.TryDecodeClockValue(clockValue, nowTicks, out var remainingTicks, out var _))
			{
				return clockValue;
			}
			return SpoilageClock.EncodeClockValue(nowTicks, remainingTicks, paused && remainingTicks > 0);
		}

		private static bool SetClockValue(ItemData target, long clockValue)
		{
			if (!SpoilageClock.IsValidClockValue(clockValue))
			{
				return false;
			}
			string text = clockValue.ToString(CultureInfo.InvariantCulture);
			if (target.m_customData.TryGetValue("sighsorry.FineDining.ExpiryWorldTicks", out var value) && value == text)
			{
				return false;
			}
			target.m_customData["sighsorry.FineDining.ExpiryWorldTicks"] = text;
			return true;
		}

		private static void WarnReplacementOnce(string key, string message)
		{
			if (LoggedReplacementWarnings.Add(key ?? ""))
			{
				FineDiningPlugin.Log.LogWarning((object)message);
			}
		}

		internal static bool TryGetWorldTicks(out long ticks)
		{
			ticks = 0L;
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			try
			{
				ticks = instance.GetTime().Ticks;
				return ticks > 0;
			}
			catch
			{
				return false;
			}
		}
	}
	internal static class ChefChoiceMath
	{
		internal const float MinimumAllowedMultiplier = 1f;

		internal const float MaximumTierSelectionStrength = 20f;

		internal static float ClampCookingFactor(float cookingFactor)
		{
			return ClampUnit(cookingFactor);
		}

		internal static float ClampNormalizedTier(float normalizedTier)
		{
			return ClampUnit(normalizedTier);
		}

		internal static float ClampPercentage(float percentage)
		{
			if (float.IsNaN(percentage) || percentage <= 0f)
			{
				return 0f;
			}
			if (!float.IsPositiveInfinity(percentage) && !(percentage >= 100f))
			{
				return percentage;
			}
			return 100f;
		}

		internal static float BlendCategoryProbability(float baseProbability, float historyProbability, float percentage)
		{
			float num = ClampPercentage(percentage) / 100f;
			if (num <= 0f)
			{
				return baseProbability;
			}
			if (num >= 1f)
			{
				return historyProbability;
			}
			return baseProbability + (historyProbability - baseProbability) * num;
		}

		internal static float GetFoodSelectionWeight(float cookingFactor, float normalizedTier, bool tierResolved, float highTierSelectionStrength)
		{
			if (!tierResolved)
			{
				return 1f;
			}
			float num = ClampCookingFactor(cookingFactor);
			float num2 = ClampNormalizedTier(normalizedTier);
			return (float)Math.Exp(ClampTierSelectionStrength(highTierSelectionStrength) * num * num2);
		}

		internal static float GetInterpolatedMultiplierMode(float minimum, float maximum, float cookingLevelOneHundredMode, float cookingFactor)
		{
			minimum = ClampMultiplierMinimum(minimum);
			maximum = ClampMultiplierMaximum(maximum, minimum);
			float num = ClampMultiplierMode(cookingLevelOneHundredMode, minimum, maximum);
			return minimum + (num - minimum) * ClampCookingFactor(cookingFactor);
		}

		internal static float GetTriangularQuantile(float uniformSample, float normalizedMode)
		{
			double num = ClampUnit(uniformSample);
			double num2 = ClampUnit(normalizedMode);
			return ClampUnit((float)((num < num2) ? Math.Sqrt(num * num2) : (1.0 - Math.Sqrt((1.0 - num) * (1.0 - num2)))));
		}

		internal static float GetChefMultiplier(float minimum, float maximum, float uniformSample, float cookingFactor, float cookingLevelOneHundredMode)
		{
			minimum = ClampMultiplierMinimum(minimum);
			maximum = ClampMultiplierMaximum(maximum, minimum);
			if (minimum >= maximum)
			{
				return minimum;
			}
			double num = ((double)GetInterpolatedMultiplierMode(minimum, maximum, cookingLevelOneHundredMode, cookingFactor) - (double)minimum) / (double)(maximum - minimum);
			double num2 = GetTriangularQuantile(uniformSample, (float)num);
			return (float)((1.0 - num2) * (double)minimum + num2 * (double)maximum);
		}

		internal static int ChooseWeightedIndex(IReadOnlyList<float>? weights, float uniformSample)
		{
			if (weights == null || weights.Count == 0)
			{
				return -1;
			}
			double num = 0.0;
			int num2 = -1;
			for (int i = 0; i < weights.Count; i++)
			{
				float num3 = weights[i];
				if (!float.IsNaN(num3) && !float.IsInfinity(num3) && !(num3 <= 0f))
				{
					num += (double)num3;
					num2 = i;
				}
			}
			if (num2 < 0 || num <= 0.0 || double.IsInfinity(num))
			{
				return -1;
			}
			double num4 = (double)ClampUnit(uniformSample) * num;
			double num5 = 0.0;
			for (int j = 0; j < weights.Count; j++)
			{
				float num6 = weights[j];
				if (!float.IsNaN(num6) && !float.IsInfinity(num6) && !(num6 <= 0f))
				{
					num5 += (double)num6;
					if (num4 < num5)
					{
						return j;
					}
				}
			}
			return num2;
		}

		private static float ClampUnit(float value)
		{
			if (float.IsNaN(value) || value <= 0f)
			{
				return 0f;
			}
			if (!(value >= 1f))
			{
				return value;
			}
			return 1f;
		}

		internal static float ClampMultiplierMinimum(float value)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return Math.Max(1f, value);
			}
			return 1f;
		}

		internal static float ClampMultiplierMaximum(float value, float minimum)
		{
			minimum = ClampMultiplierMinimum(minimum);
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return Math.Max(minimum, value);
			}
			return minimum;
		}

		internal static float ClampMultiplierMode(float value, float minimum, float maximum)
		{
			minimum = ClampMultiplierMinimum(minimum);
			maximum = ClampMultiplierMaximum(maximum, minimum);
			if (float.IsNaN(value) || value <= minimum)
			{
				return minimum;
			}
			if (!float.IsPositiveInfinity(value) && !(value >= maximum))
			{
				return value;
			}
			return maximum;
		}

		private static float ClampTierSelectionStrength(float value)
		{
			if (float.IsNaN(value) || value <= 0f)
			{
				return 0f;
			}
			if (!float.IsPositiveInfinity(value) && !(value >= 20f))
			{
				return value;
			}
			return 20f;
		}
	}
	internal static class ChefCollectionService
	{
		private sealed class ChefCandidate
		{
			internal string Key { get; }

			internal int AxisIndex { get; }

			private ChefFoodTierInfo? TierInfo { get; }

			internal ChefCandidate(string key, ChefFoodTierInfo? tierInfo, FoodStatAxis axis)
			{
				Key = key;
				TierInfo = tierInfo;
				AxisIndex = GetFoodStatAxisIndex(axis);
			}

			internal float GetSelectionWeight(float cookingFactor)
			{
				return ChefChoiceMath.GetFoodSelectionWeight(cookingFactor, TierInfo?.NormalizedTier ?? 0f, TierInfo?.IsResolved ?? false, DietConfig.GetChefHighTierSelectionStrength());
			}
		}

		private readonly struct RecentFoodComposition
		{
			private readonly int _health;

			private readonly int _stamina;

			private readonly int _eitr;

			private readonly int _total;

			internal RecentFoodComposition(int health, int stamina, int eitr)
			{
				_health = Math.Max(0, health);
				_stamina = Math.Max(0, stamina);
				_eitr = Math.Max(0, eitr);
				_total = _health + _stamina + _eitr;
			}

			internal float GetShare(int axisIndex)
			{
				if (_total <= 0)
				{
					return 0f;
				}
				return (float)(axisIndex switch
				{
					0 => _health, 
					1 => _stamina, 
					2 => _eitr, 
					_ => 0, 
				}) / (float)_total;
			}
		}

		private static readonly object RandomLock = new object();

		private static readonly Random RandomSource = new Random();

		private const int FoodStatAxisCount = 3;

		internal static bool EnsureChefCollection(Player? player, PlayerFoodStateData state)
		{
			return EnsureChefCollection(player, state, null);
		}

		private static bool EnsureChefCollection(Player? player, PlayerFoodStateData state, string? excludedRefillKey)
		{
			if ((Object)(object)player == (Object)null || (Object)(object)ObjectDB.instance == (Object)null)
			{
				return false;
			}
			ChefFoodTierCatalog.Tick();
			if (!ChefFoodTierCatalog.IsReady)
			{
				return false;
			}
			Dictionary<string, FoodStatAxis> foodAxesByKey = new Dictionary<string, FoodStatAxis>(StringComparer.Ordinal);
			List<ChefCandidate> knownFoodCandidates = GetKnownFoodCandidates(player, foodAxesByKey);
			RecentFoodComposition recentFoodComposition = GetRecentFoodComposition(state, foodAxesByKey);
			HashSet<string> eligibleSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (ChefCandidate item in knownFoodCandidates)
			{
				eligibleSet.Add(item.Key);
			}
			bool flag = false;
			int count = state.Chef.Count;
			state.Chef.RemoveAll((ChefEntryData entry) => entry == null || string.IsNullOrWhiteSpace(entry.Key) || !eligibleSet.Contains(entry.Key));
			flag |= state.Chef.Count != count;
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			for (int num = state.Chef.Count - 1; num >= 0; num--)
			{
				if (!hashSet.Add(state.Chef[num].Key))
				{
					state.Chef.RemoveAt(num);
					flag = true;
				}
			}
			int num2 = Mathf.Min(DietConfig.GetChefCollectionSize(), knownFoodCandidates.Count);
			float cookingFactor = ChefChoiceMath.ClampCookingFactor(((Character)player).GetSkillFactor((SkillType)105));
			ChefCandidate selected;
			while (state.Chef.Count < num2 && (TrySelectWeightedCandidate(knownFoodCandidates, hashSet, excludedRefillKey, cookingFactor, recentFoodComposition, out selected) || (!string.IsNullOrWhiteSpace(excludedRefillKey) && TrySelectWeightedCandidate(knownFoodCandidates, hashSet, null, cookingFactor, recentFoodComposition, out selected))))
			{
				state.Chef.Add(new ChefEntryData
				{
					Key = selected.Key,
					Multiplier = RollChefMultiplier(cookingFactor)
				});
				hashSet.Add(selected.Key);
				flag = true;
			}
			while (state.Chef.Count > num2)
			{
				state.Chef.RemoveAt(state.Chef.Count - 1);
				flag = true;
			}
			return flag;
		}

		internal static bool TryConsumeChefEntry(Player player, PlayerFoodStateData state, string key, out float multiplier)
		{
			EnsureChefCollection(player, state);
			for (int i = 0; i < state.Chef.Count; i++)
			{
				ChefEntryData chefEntryData = state.Chef[i];
				if (!(chefEntryData.Key != key))
				{
					multiplier = chefEntryData.Multiplier;
					state.Chef.RemoveAt(i);
					return true;
				}
			}
			multiplier = 1f;
			return false;
		}

		internal static bool RefillAfterConsumption(Player player, PlayerFoodStateData state, string consumedKey)
		{
			return EnsureChefCollection(player, state, consumedKey);
		}

		internal static ChefEntryData? GetEntry(PlayerFoodStateData state, string key)
		{
			if (SpoilagePolicy.IsChefChoiceBlacklisted(key))
			{
				return null;
			}
			foreach (ChefEntryData item in state.Chef)
			{
				if (item.Key == key)
				{
					return item;
				}
			}
			return null;
		}

		internal static void RerollAll(Player? player)
		{
			if (!((Object)(object)player == (Object)null) && !((Object)(object)ObjectDB.instance == (Object)null))
			{
				PlayerFoodStateData state = FoodStateStore.GetState(player);
				state.Chef.Clear();
				EnsureChefCollection(player, state);
				FoodStateStore.SaveState(player, state);
			}
		}

		internal static void RotateOldest(Player? player, int count)
		{
			if ((Object)(object)player == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || count <= 0)
			{
				return;
			}
			PlayerFoodStateData state = FoodStateStore.GetState(player);
			bool flag = EnsureChefCollection(player, state);
			for (int i = 0; i < count; i++)
			{
				if (state.Chef.Count <= 0)
				{
					break;
				}
				string key = state.Chef[0].Key;
				state.Chef.RemoveAt(0);
				flag = true;
				flag |= EnsureChefCollection(player, state, key);
			}
			if (flag)
			{
				FoodStateStore.SaveState(player, state);
			}
		}

		private static bool TrySelectWeightedCandidate(IReadOnlyList<ChefCandidate> eligible, ISet<string> currentKeys, string? excludedKey, float cookingFactor, RecentFoodComposition recentFoodComposition, out ChefCandidate selected)
		{
			List<ChefCandidate> list = new List<ChefCandidate>();
			List<float> list2 = new List<float>();
			float[] array = new float[3];
			foreach (ChefCandidate item in eligible)
			{
				if (!currentKeys.Contains(item.Key) && !item.Key.Equals(excludedKey, StringComparison.Ordinal))
				{
					list.Add(item);
					float selectionWeight = item.GetSelectionWeight(cookingFactor);
					list2.Add(selectionWeight);
					array[item.AxisIndex] += selectionWeight;
				}
			}
			if (list.Count == 0)
			{
				selected = null;
				return false;
			}
			float num = 0f;
			float num2 = 0f;
			for (int i = 0; i < 3; i++)
			{
				if (!(array[i] <= 0f))
				{
					num += array[i];
					num2 += recentFoodComposition.GetShare(i);
				}
			}
			List<float> list3 = new List<float>(list.Count);
			float percentage = ((num2 > 0f) ? DietConfig.GetChefRecentFoodPreferencePercent() : 0f);
			for (int j = 0; j < list.Count; j++)
			{
				ChefCandidate chefCandidate = list[j];
				float num3 = array[chefCandidate.AxisIndex];
				float num4 = ((num > 0f) ? (num3 / num) : 0f);
				float historyProbability = ((num2 > 0f) ? (recentFoodComposition.GetShare(chefCandidate.AxisIndex) / num2) : num4);
				float num5 = ChefChoiceMath.BlendCategoryProbability(num4, historyProbability, percentage);
				list3.Add((num3 > 0f) ? (num5 * list2[j] / num3) : 0f);
			}
			float uniformSample;
			lock (RandomLock)
			{
				uniformSample = (float)RandomSource.NextDouble();
			}
			int num6 = ChefChoiceMath.ChooseWeightedIndex(list3, uniformSample);
			if (num6 < 0)
			{
				selected = null;
				return false;
			}
			selected = list[num6];
			return true;
		}

		private static List<ChefCandidate> GetKnownFoodCandidates(Player player, IDictionary<string, FoodStatAxis> foodAxesByKey)
		{
			List<ChefCandidate> list = new List<ChefCandidate>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (ChefFoodTierInfo item in ChefFoodTierCatalog.GetSnapshot())
			{
				string prefabName = item.PrefabName;
				if (!string.IsNullOrWhiteSpace(prefabName) && item.Axis != FoodStatAxis.None)
				{
					foodAxesByKey[prefabName] = item.Axis;
					if ((player.IsRecipeKnown(item.ItemNameToken) || player.IsKnownMaterial(item.ItemNameToken)) && !SpoilagePolicy.IsChefChoiceBlacklisted(prefabName) && hashSet.Add(prefabName))
					{
						list.Add(new ChefCandidate(prefabName, item, item.Axis));
					}
				}
			}
			list.Sort((ChefCandidate left, ChefCandidate right) => StringComparer.Ordinal.Compare(left.Key, right.Key));
			return list;
		}

		private static RecentFoodComposition GetRecentFoodComposition(PlayerFoodStateData state, IReadOnlyDictionary<string, FoodStatAxis> foodAxesByKey)
		{
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			foreach (HistoryEntryData item in state.Recent)
			{
				if (item != null && foodAxesByKey.TryGetValue(item.Key, out var value))
				{
					switch (value)
					{
					case FoodStatAxis.Health:
						num++;
						break;
					case FoodStatAxis.Stamina:
						num2++;
						break;
					case FoodStatAxis.Eitr:
						num3++;
						break;
					}
				}
			}
			return new RecentFoodComposition(num, num2, num3);
		}

		private static float RollChefMultiplier(float cookingFactor)
		{
			float uniformSample;
			lock (RandomLock)
			{
				uniformSample = (float)RandomSource.NextDouble();
			}
			return ChefChoiceMath.GetChefMultiplier(DietConfig.GetChefMultiplierMin(), DietConfig.GetChefMultiplierMax(), uniformSample, cookingFactor, DietConfig.GetChefMultiplierModeAtMaxCooking());
		}

		private static int GetFoodStatAxisIndex(FoodStatAxis axis)
		{
			return axis switch
			{
				FoodStatAxis.Health => 0, 
				FoodStatAxis.Stamina => 1, 
				FoodStatAxis.Eitr => 2, 
				_ => throw new ArgumentOutOfRangeException("axis", axis, "Chef food must have a classified food-stat axis."), 
			};
		}
	}
	internal sealed class ChefFoodTierInfo
	{
		internal string PrefabName { get; }

		internal int Tier { get; }

		internal int TierCount { get; }

		internal string TierName { get; }

		internal string FallbackReason { get; }

		internal IReadOnlyList<string> Sources { get; }

		internal string ItemNameToken { get; private set; } = "";

		internal FoodStatAxis Axis { get; private set; }

		internal bool IsResolved => Tier >= 0;

		internal bool IsAllTier => !IsResolved;

		internal float NormalizedTier
		{
			get
			{
				if (IsResolved && TierCount > 1)
				{
					return Mathf.Clamp01((float)Tier / (float)(TierCount - 1));
				}
				return 0f;
			}
		}

		internal ChefFoodTierInfo(string prefabName, int tier, int ti