Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of FineDining v1.1.5
FineDining.dll
Decompiled 12 hours ago
The result has been truncated due to the large size, download it to view full contents!
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 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.1.5")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.5.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(ItemDrop? itemDrop) { if ((Object)(object)itemDrop == (Object)null) { return string.Empty; } string canonicalPrefabName = GetCanonicalPrefabName(itemDrop.m_itemData); if (canonicalPrefabName.Length <= 0) { GameObject gameObject = ((Component)itemDrop).gameObject; return NormalizePrefabName((gameObject != null) ? ((Object)gameObject).name : null); } return canonicalPrefabName; } 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 bool LooksLikeFeastRoutingFood(SharedData? shared) { if (shared != null) { if (!HasChefFoodStats(shared.m_food, shared.m_foodStamina, shared.m_foodEitr)) { return shared.m_isDrink; } return true; } return false; } internal static string NormalizePrefabName(string? name) { if (string.IsNullOrWhiteSpace(name)) { return string.Empty; } string text = name.Trim(); if (!text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase)) { return text; } return text.Substring(0, text.Length - "(Clone)".Length).TrimEnd(Array.Empty<char>()); } } [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); } } [HarmonyFinalizer] private static Exception? Finalizer(Container __instance, bool __state, Exception? __exception) { if (!__state || __exception == null) { return __exception; } try { IceboxSubsystem.ApplyConfiguredStorageSize(__instance); } catch (Exception ex) { try { ManualLogSource log = FineDiningPlugin.Log; if (log != null) { log.LogWarning((object)("Could not restore the Icebox storage size after Container.Load failed: " + ex.GetBaseException().Message)); } } catch { } } return __exception; } } [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.RegisterContent(); 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 bool SourceSpoiled; } 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); inventoryAddMergeState.SourceSpoiled = SpoilageClock.IsSpoiled(source); if (!DecayRuntime.TryGetExpiryTicks(source, out inventoryAddMergeState.SourceClockValue) && !inventoryAddMergeState.SourceSpoiled) { return null; } foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem != null && CanPotentiallyStack(allItem, source)) { inventoryAddMergeState.PreviousStacks[allItem] = allItem.m_stack; inventoryAddMergeState.PreviousLifetimes[allItem] = FreshnessRuntime.CaptureAssignedLifetime(allItem); } } return inventoryAddMergeState; } internal static void Postfix(Inventory inventory, ItemData? source, InventoryAddMergeState? state) { if (state == null || source == null) { return; } bool flag = false; try { 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, state.SourceSpoiled); } } } 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.GetAllItems().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), typeof(bool) })] 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 } private const string ExpiryDataKey = "sighsorry.FineDining.ExpiryWorldTicks"; private const string SpoiledDataKey = "sighsorry.FineDining.Spoiled"; 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.GetInventory() != null && (!ContainersByInventory.TryGetValue(container.GetInventory(), out WeakReference<Container> value) || !value.TryGetTarget(out var target) || target != container)) { ContainersByInventory[container.GetInventory()] = new WeakReference<Container>(container); GetState(container.GetInventory()).Dirty = true; } } internal static void ContainerLoaded(Container? container) { RegisterContainer(container); if (!((Object)(object)container == (Object)null) && IsOwnedContainer(container) && TryGetWorldTicks(out var ticks)) { GetState(container.GetInventory()).Dirty = true; ProcessIfDue(container.GetInventory(), 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 (GameAccess.ItemInstances == null) { return; } ItemDrop[] array = GameAccess.ItemInstances.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 (!SpoilagePolicy.IsEnabled || 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) { return SpoilageClock.TryGetExpiryTicks(item, out clockValue); } internal static bool TryGetSpoilageClock(ItemData? item, long nowTicks, out long remainingTicks, out bool paused) { return SpoilageClock.TryGetSpoilageClock(item, nowTicks, out remainingTicks, out paused); } 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 (!SpoilagePolicy.IsEnabled || item == null || !IsAuthoritativeInventory(inventory) || SpoilageClock.IsSpoiled(item)) { 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); if (rule.State != SpoilageRuleState.Enabled) { return false; } bool paused; return EnsureItemState(item, rule, ticks, ResolveInventoryPausedState(inventory), transitionExisting: false, out clockValue, out paused); } internal static bool ShouldProcessInventorySpoilageClock(bool isPlayerInventory, bool hasClock) { return isPlayerInventory || hasClock; } internal static bool PrepareInheritedItemForAdd(Inventory inventory, ItemData? item, long inheritedRemainingTicks, bool inheritedSpoiled = false) { if (item == null || !IsAuthoritativeInventory(inventory)) { return false; } if (SpoilageClock.IsSpoiled(item)) { return false; } bool flag = PrepareItemForAdd(inventory, item); ResolvedSpoilageRule resolvedSpoilageRule = SpoilagePolicy.Resolve(item); if (inheritedRemainingTicks < 0) { return flag; } if (!SpoilagePolicy.IsEnabled) { SpoilageRuleState state = resolvedSpoilageRule.State; if ((state == SpoilageRuleState.NotReady || state == SpoilageRuleState.Enabled) ? true : false) { return InheritWhileOff(item, inheritedRemainingTicks, inheritedSpoiled, ResolveInventoryPausedState(inventory)); } return false; } if (resolvedSpoilageRule.State != SpoilageRuleState.Enabled) { return flag; } if (inheritedSpoiled || (inheritedRemainingTicks == 0L && resolvedSpoilageRule.ExpiryAction == SpoilageExpiryAction.KeepOriginal)) { return CompleteKeepOriginalExpiry(item) || 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, bool sourceSpoiled) { if (inventory == null || target == null) { return false; } if (sourceSpoiled || SpoilageClock.IsSpoiled(target)) { return CompleteKeepOriginalExpiry(target); } if (!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) { return false; } bool num = SpoilageClock.IsSpoiled(source); bool flag = SpoilageClock.IsSpoiled(target); if (num || flag) { return CompleteKeepOriginalExpiry(target); } if (!TryGetExpiryTicks(source, out var clockValue)) { return false; } AssignedLifetimeSnapshot destination = FreshnessRuntime.CaptureAssignedLifetime(target); AssignedLifetimeSnapshot source2 = FreshnessRuntime.CaptureAssignedLifetime(source); long clockValue2; bool flag2 = TryGetExpiryTicks(target, out clockValue2); if (!TryGetWorldTicks(out var ticks)) { if (flag2 && clockValue2 < 0 != clockValue < 0) { return false; } ticks = 0L; } bool destinationPaused = (flag2 ? (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.NetworkView() == (Object)null) && !((Object)(object)source.NetworkView() == (Object)null) && destination.NetworkView().IsValid() && source.NetworkView().IsValid() && destination.NetworkView().IsOwner() && source.NetworkView().IsOwner()) { long clockValue; long ticks; if (SpoilageClock.IsSpoiled(destination.m_itemData) || SpoilageClock.IsSpoiled(source.m_itemData)) { CompleteKeepOriginalExpiry(destination.m_itemData); RegisterGroundDrop(destination); } else if (TryGetExpiryTicks(source.m_itemData, out clockValue) && TryGetWorldTicks(out 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 (SpoilageClock.IsSpoiled(drop.m_itemData)) { GroundDropsByInstanceId.Remove(instanceID); GroundEnvironmentSamples.Remove(instanceID); } else 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, bool inheritedSpoiled = false) { if ((Object)(object)drop == (Object)null || !HasValidGroundView(drop) || !IsPlacedGroundDrop(drop)) { return; } if (SpoilageClock.IsSpoiled(drop.m_itemData)) { UnregisterGroundDrop(drop); } else { if (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, inheritedSpoiled); } } 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, bool inheritedSpoiled = false) { if (!((Object)(object)drop == (Object)null) && inheritedRemainingTicks >= 0 && IsOwnedGroundDrop(drop)) { InitializeOwnedWorldDrop(drop, inheritedRemainingTicks, 0L, keepPendingWhenNotReady: false, inheritedSpoiled); } } 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.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) { if (!SpoilagePolicy.IsEnabled) { return; } 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 allItem in inventory.GetAllItems()) { if (allItem != null && !SpoilageClock.IsSpoiled(allItem) && SpoilagePolicy.Resolve(allItem).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.GetInventory() == 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); } else { if (!SpoilagePolicy.IsEnabled) { 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; } if (SpoilageClock.IsSpoiled(target.m_itemData)) { if (list == null) { list = new List<int>(); } list.Add(item2.Key); 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, bool inheritedSpoiled = false) { if (!IsOwnedGroundDrop(drop)) { return; } if (SpoilageClock.IsSpoiled(drop.m_itemData)) { UnregisterGroundDrop(drop); } else { if (ReconcileCreatorlessPlacedDrop(drop)) { return; } if (!SpoilagePolicy.IsEnabled) { ResolvedSpoilageRule resolvedSpoilageRule = SpoilagePolicy.ResolveYaml(drop.m_itemData); bool flag = inheritedRemainingTicks >= 0; if (flag) { SpoilageRuleState state = resolvedSpoilageRule.State; bool flag2 = ((state == SpoilageRuleState.NotReady || state == SpoilageRuleState.Enabled) ? true : false); flag = flag2; } if (flag) { Dictionary<string, string> previousCustomData = new Dictionary<string, string>(drop.m_itemData.m_customData, StringComparer.Ordinal); if (InheritWhileOff(drop.m_itemData, inheritedRemainingTicks, inheritedSpoiled, ResolveWorldDropPausedState(drop))) { SaveInheritedMetadata(drop, previousCustomData); } } RegisterGroundDrop(drop); return; } bool flag3 = 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); flag3 = true; } } ResolvedSpoilageRule resolvedSpoilageRule2 = SpoilagePolicy.Resolve(drop.m_itemData); if (resolvedSpoilageRule2.State == SpoilageRuleState.NotReady) { Dictionary<string, string> dictionary = (inheritedSpoiled ? new Dictionary<string, string>(drop.m_itemData.m_customData, StringComparer.Ordinal) : null); if (flag3 | (inheritedSpoiled ? CompleteKeepOriginalExpiry(drop.m_itemData) : (inheritedRemainingTicks >= 0 && ApplyEarlierRemaining(drop.m_itemData, inheritedRemainingTicks, ResolveWorldDropPausedState(drop))))) { if (dictionary != null) { SaveInheritedMetadata(drop, dictionary); } else { drop.Save(); } } if (keepPendingWhenNotReady || inheritedRemainingTicks >= 0) { RegisterGroundDrop(drop); } return; } if (resolvedSpoilageRule2.State != SpoilageRuleState.Enabled) { ClearOwnedGroundExpiry(drop); return; } if (!TryGetWorldTicks(out var ticks2)) { RegisterGroundDrop(drop); return; } bool flag4 = ResolveWorldDropPausedState(drop, resolvedSpoilageRule2.Group); long num = CalculateEffectiveRemainingTicks(ticks2, ticks, resolvedSpoilageRule2.LifetimeTicks, inheritedRemainingTicks, flag4); long clockValue = SpoilageClock.EncodeClockValue(ticks2, num, flag4 && num > 0); flag3 |= SetClockValue(drop.m_itemData, clockValue); flag3 |= FreshnessRuntime.EnsureTrackedMetadata(drop.m_itemData, resolvedSpoilageRule2.LifetimeTicks); if (keepPendingWhenNotReady) { flag3 |= drop.m_itemData.m_customData.Remove("sighsorry.FineDining.PlacedWorldTicks"); } if (inheritedSpoiled || (num <= 0 && resolvedSpoilageRule2.ExpiryAction == SpoilageExpiryAction.KeepOriginal)) { Dictionary<string, string> previousCustomData2 = new Dictionary<string, string>(drop.m_itemData.m_customData, StringComparer.Ordinal); if (flag3 | CompleteKeepOriginalExpiry(drop.m_itemData)) { SaveInheritedMetadata(drop, previousCustomData2); } } else if (flag3) { drop.Save(); } if (SpoilageClock.IsSpoiled(drop.m_itemData)) { UnregisterGroundDrop(drop); } else { RegisterGroundDrop(drop); } } } private static void SaveInheritedMetadata(ItemDrop drop, Dictionary<string, string> previousCustomData) { try { drop.Save(); } catch { drop.m_itemData.m_customData.Clear(); foreach (KeyValuePair<string, string> previousCustomDatum in previousCustomData) { drop.m_itemData.m_customData[previousCustomDatum.Key] = previousCustomDatum.Value; } RegisterGroundDrop(drop); throw; } } private static bool InheritWhileOff(ItemData item, long remainingTicks, bool spoiled, bool paused) { if (!spoiled) { if (remainingTicks >= 0) { return ApplyEarlierRemaining(item, remainingTicks, paused); } return false; } return CompleteKeepOriginalExpiry(item); } private static bool HasValidGroundView(ItemDrop drop) { try { return (Object)(object)drop.NetworkView() != (Object)null && drop.NetworkView().IsValid(); } catch { return false; } } private static bool IsOwnedGroundDrop(ItemDrop drop) { try { return HasValidGroundView(drop) && drop.NetworkView().IsOwner(); } catch { return false; } } private static bool IsPlacedGroundDrop(ItemDrop drop) { try { if (!HasValidGroundView(drop)) { return false; } return drop.NetworkView().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.NetworkView().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 (IceboxSubsystem.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_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: 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.FloatingComponent() ?? ((Component)drop).GetComponent<Floating>(); if ((Object)(object)val2 == (Object)null || val2.WaterLevel() <= -10000f) { return false; } return (((Object)(object)val2.Body() != (Object)null) ? val2.Body().worldCenterOfMass.y : ((Component)drop).transform.position.y) - val2.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 allItem in val.GetAllItems()) { if (TryGetSpoilageClock(allItem, ticks, out var remainingTicks, out var paused) && paused && allItem.m_customData.TryGetValue("sighsorry.FineDining.ExpiryWorldTicks", out var value)) { list.Add(new KeyValuePair<ItemData, string>(allItem, value)); allItem.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.NetworkView() != (Object)null && container.NetworkView().IsValid() && container.NetworkView().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> allItems = inventory.GetAllItems(); for (int num2 = allItems.Count - 1; num2 >= 0; num2--) { ItemData val = allItems[num2]; if (val != null) { if (SpoilageClock.IsSpoiled(val)) { flag |= CompleteKeepOriginalExpiry(val); } else { 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; } if (SpoilageClock.IsSpoiled(item)) { 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; } internal static bool CompleteKeepOriginalExpiry(ItemData? item) { if (item == null) { return false; } bool flag = SpoilageClock.MarkSpoiled(item); if (item.m_customData == null) { return flag; } flag |= item.m_customData.Remove("sighsorry.FineDining.ExpiryWorldTicks"); flag |= item.m_customData.Remove("sighsorry.FineDining.PlacedWorldTicks"); return flag | FreshnessRuntime.ClearTrackedMetadata(item); } private static ReplacementResolution ResolveReplacement(ItemData sourceItem, ResolvedSpoilageRule rule, out string configuredPrefab, out ItemDrop replacementDrop) { configuredPrefab = SpoilagePolicy.NormalizeReplacementPrefabName(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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) if (rule.ExpiryAction == SpoilageExpiryAction.KeepOriginal) { CompleteKeepOriginalExpiry(item); return true; } string configuredPrefab; ItemDrop replacementDrop; switch (ResolveReplacement(item, rule, out configuredPrefab, out replacementDrop)) { case ReplacementResolution.NotReady: return false; case ReplacementResolution.Invalid: return inventory.GetAllItems().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; bool cheated = item.m_cheated; if (!inventory.GetAllItems().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, cheated); int amount = num3; num3 -= InsertReplacementStack(inventory, val, amount, gridPos); if (num3 > 0) { foreach (ItemData allItem in inventory.GetAllItems()) { if (num3 <= 0) { break; } if (CanStackReplacement(allItem, val)) { int num4 = Math.Min(num3, Math.Max(0, num2 - allItem.m_stack)); allItem.m_stack += num4; num3 -= num4; } } } while (num3 > 0) { ItemData val2 = CreateReplacement(itemData, gameObject, worldLevel, cheated); 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_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_0259: 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.NetworkView().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.NetworkView().Destroy(); return; } if (rule.ExpiryAction == SpoilageExpiryAction.KeepOriginal) { Dictionary<string, string> dictionary = new Dictionary<string, string>(itemData.m_customData, StringComparer.Ordinal); CompleteKeepOriginalExpiry(itemData); try { drop.Save(); } catch { itemData.m_customData.Clear(); foreach (KeyValuePair<string, string> item in dictionary) { itemData.m_customData[item.Key] = item.Value; } throw; } UnregisterGroundDrop(drop); 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.NetworkView().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; bool cheated = itemData.m_cheated; int num3 = CalculateReplacementAmount(num); ItemData val = CreateReplacement(itemData2, gameObject, worldLevel, cheated); 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.NetworkView().Destroy(); } } } private static bool GroundSourceStillMatches(ItemDrop drop, ZDOID sourceZdoId, long sourceExpiryTicks, string sourcePrefab, int sourceAmount) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (!IsOwnedGroundDrop(drop) || IsCreatorlessPlacedDrop(drop) || drop.NetworkView().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.NetworkView().GetZDO().GetLong(ZDOVars.s_spawnTime, 0L) : 0; } catch { return 0L; } } private static void InheritGroundSpawnTime(ItemDrop replacement, long sourceSpawnTimeTicks) { if (sourceSpawnTimeTicks > 0 && IsOwnedGroundDrop(replacement)) { replacement.NetworkView().GetZDO().Set(ZDOVars.s_spawnTime, sourceSpawnTimeTicks); } } private static void DestroySpawnedGroundReplacement(ItemDrop? replacement) { if (!((Object)(object)replacement == (Object)null)) { if (IsOwnedGroundDrop(replacement)) { replacement.NetworkView().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, bool sourceCheated) { ItemData obj = template.Clone(); obj.m_dropPrefab = prefab; obj.m_worldLevel = sourceWorldLevel; obj.m_cheated = sourceCheated; obj.m_equipped = false; obj.m_customData.Remove("sighsorry.FineDining.ExpiryWorldTicks"); SpoilageClock.ClearSpoiled(obj); 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_cheated == template.m_cheated && 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) + (values.ContainsKey("sighsorry.FineDining.Spoiled") ? 1 : 0); } private static bool IsStackMetadataKey(string key) { if (!(key == "sighsorry.FineDining.ExpiryWorldTicks") && !(key == "sighsorry.FineDining.AssignedLifetimeTicks")) { return key == "sighsorry.FineDining.Spoiled"; } 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); } } private static bool TryGetWorldTicks(out long ticks) { return SpoilageClock.TryGetWorldTicks(out ticks); } } 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, TierInfo.IsResolved, 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 (!DietConfig.IsChefChoiceEnabled() || (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;