Decompiled source of Larder v0.2.16

plugins/Larder/Larder.Core.dll

Decompiled 6 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using ExtraPotions.Larder.Foods;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ExtraPotions")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (C) 2026 expDARE (ExtraPotions Meadworks)")]
[assembly: AssemblyFileVersion("0.2.16.0")]
[assembly: AssemblyInformationalVersion("0.2.16")]
[assembly: AssemblyProduct("Larder.Core")]
[assembly: AssemblyTitle("Larder.Core")]
[assembly: AssemblyVersion("0.2.16.0")]
[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 ExtraPotions.Larder
{
	public static class LarderCore
	{
		public const string ProductId = "com.meadworks.larder";

		public const string Name = "Larder";

		public const string Version = "0.2.16";

		public const string Family = "Hearth";

		public const string GameBuild = "1.0.17";

		public const string CharacterDataPrefix = "larder.";

		public const string ZdoPrefix = "larder_";

		public const string TokenPrefix = "$meadworks_larder_";
	}
	public static class LarderMath
	{
		public static float Clamp01(float value)
		{
			if (!(value < 0f))
			{
				if (!(value > 1f))
				{
					return value;
				}
				return 1f;
			}
			return 0f;
		}

		public static float Clamp(float value, float min, float max)
		{
			if (!(value < min))
			{
				if (!(value > max))
				{
					return value;
				}
				return max;
			}
			return min;
		}

		public static float Lerp(float from, float to, float t)
		{
			return from + (to - from) * Clamp01(t);
		}

		public static float NonNegative(float value)
		{
			if (!(value > 0f) || float.IsInfinity(value))
			{
				return 0f;
			}
			return value;
		}

		public static bool Approximately(float a, float b)
		{
			return Math.Abs(b - a) < Math.Max(1E-06f * Math.Max(Math.Abs(a), Math.Abs(b)), 1.1E-44f);
		}
	}
}
namespace ExtraPotions.Larder.Recovery
{
	public static class EitrRegenRules
	{
		public static float HookMultiplier(float statusEffectMultiplier, float eitrVigorPercent, float curveFactor, bool blocking, float blockingFactor)
		{
			float num = statusEffectMultiplier + eitrVigorPercent / 100f;
			num *= curveFactor;
			if (blocking)
			{
				num *= blockingFactor / 0.8f;
			}
			return num;
		}
	}
	public static class FoodHealing
	{
		public const float TickSeconds = 10f;

		public static float PerFrame(float totalFoodRegen, float healthRegenMultiplier, float dt)
		{
			if (dt <= 0f || totalFoodRegen <= 0f)
			{
				return 0f;
			}
			return totalFoodRegen * healthRegenMultiplier * dt / 10f;
		}
	}
	public static class RegenCurve
	{
		public static float Factor(float fraction, float strength, float pivot)
		{
			if (strength <= 0f || LarderMath.Approximately(strength, 1f))
			{
				return 1f;
			}
			fraction = LarderMath.Clamp01(fraction);
			pivot = LarderMath.Clamp01(pivot);
			if (fraction < pivot)
			{
				return LarderMath.Lerp(strength, 1f, fraction / pivot);
			}
			float num = 1f - pivot;
			if (!(num <= 0f))
			{
				return LarderMath.Lerp(1f, 1f / strength, (fraction - pivot) / num);
			}
			return 1f;
		}
	}
	public static class RestedRules
	{
		public static float Duration(float baseDuration, float perComfort, int comfort)
		{
			int num = ((comfort > 1) ? (comfort - 1) : 0);
			return baseDuration + perComfort * (float)num;
		}
	}
	public sealed class RecoverySettings
	{
		public static RecoverySettings Vanilla => new RecoverySettings();

		public float StaminaRegenMultiplier { get; set; } = 1f;

		public float LowStaminaRegenBonus { get; set; } = 1f;

		public float RegenCurveStrength { get; set; } = 1f;

		public float RegenCurvePivot { get; set; } = 0.5f;

		public float BlockingRegenFactor { get; set; } = 0.8f;
	}
	public struct StaminaRegenInput
	{
		public float StaminaRegen { get; set; }

		public float StaminaRegenTimeMultiplier { get; set; }

		public float Stamina { get; set; }

		public float MaxStamina { get; set; }

		public float StatusEffectMultiplier { get; set; }

		public float VigorPercent { get; set; }

		public float ExtraStaminaPercent { get; set; }

		public float SneakPercent { get; set; }

		public bool Blocking { get; set; }

		public float WorldRegenRate { get; set; }
	}
	public static class StaminaRegenRules
	{
		public const float GameBlockingRegenFactor = 0.8f;

		public static float ExtraStamina(float maxStamina, float baseStamina, float skillBonus, bool countFoodStaminaOnly, float foodStamina)
		{
			if (!countFoodStaminaOnly)
			{
				return maxStamina - baseStamina - skillBonus;
			}
			return foodStamina;
		}

		public static float ExtraStaminaBonus(float extraStamina, float perPoint)
		{
			if (perPoint == 0f)
			{
				return 0f;
			}
			return ((extraStamina > 0f) ? extraStamina : 0f) * perPoint;
		}

		public static float SneakBonus(float bonusPercent, bool crouching, bool sneakingWhileMoving, float sneakSkillFactor)
		{
			if (bonusPercent == 0f || !crouching || sneakingWhileMoving)
			{
				return 0f;
			}
			return bonusPercent * sneakSkillFactor;
		}

		public static float HookMultiplier(float statusEffectMultiplier, float vigorPercent, float extraStaminaPercent, float sneakPercent, float curveFactor, bool blocking, float blockingFactor)
		{
			float num = statusEffectMultiplier + (vigorPercent + extraStaminaPercent + sneakPercent) / 100f;
			num *= curveFactor;
			if (blocking)
			{
				num *= blockingFactor / 0.8f;
			}
			return num;
		}

		public static float PerSecond(StaminaRegenInput input, RecoverySettings settings)
		{
			float num = input.StaminaRegen * LarderMath.NonNegative(settings.StaminaRegenMultiplier);
			float num2 = input.StaminaRegenTimeMultiplier * LarderMath.NonNegative(settings.LowStaminaRegenBonus);
			float num3 = ((input.MaxStamina > 0f) ? (input.Stamina / input.MaxStamina) : 1f);
			float num4 = num + (1f - num3) * num * num2;
			float num5 = HookMultiplier(curveFactor: RegenCurve.Factor(num3, settings.RegenCurveStrength, settings.RegenCurvePivot), statusEffectMultiplier: input.StatusEffectMultiplier, vigorPercent: input.VigorPercent, extraStaminaPercent: input.ExtraStaminaPercent, sneakPercent: input.SneakPercent, blocking: input.Blocking, blockingFactor: settings.BlockingRegenFactor);
			if (input.Blocking)
			{
				num5 *= 0.8f;
			}
			return num4 * num5 * input.WorldRegenRate;
		}

		public static float RestrictedFraction(bool encumbered, bool swimming, bool onGround, bool swimDelayElapsed, float encumberedFraction, float swimmingFraction)
		{
			if (encumbered)
			{
				return LarderMath.Clamp01(encumberedFraction);
			}
			if (!swimming || onGround)
			{
				return 0f;
			}
			if (!swimDelayElapsed)
			{
				return 0f;
			}
			return LarderMath.Clamp01(swimmingFraction);
		}

		public static bool SwimDelayElapsed(float now, float lastStroke, float delay)
		{
			return now - lastStroke >= LarderMath.NonNegative(delay);
		}

		public static float Delay(float configured)
		{
			return LarderMath.NonNegative(configured);
		}
	}
}
namespace ExtraPotions.Larder.Kitchen
{
	public static class KitchenRules
	{
		public static float RecipeTime(float? stationEntry, float ownTime, float multiplier)
		{
			return (stationEntry ?? ownTime) * LarderMath.NonNegative(multiplier);
		}

		public static bool CanBurn(bool stationCanOvercook, bool foodCanBurn)
		{
			return stationCanOvercook && foodCanBurn;
		}

		public static float FermentationDuration(float barrelOwn, float configured)
		{
			if (!(configured > 0f))
			{
				return barrelOwn;
			}
			return configured;
		}

		public static int BatchYield(int recipeOwn, int configured)
		{
			if (configured <= 0)
			{
				return recipeOwn;
			}
			return configured;
		}
	}
}
namespace ExtraPotions.Larder.Foods
{
	public static class FoodDuration
	{
		public static float BurnTime(float itemDuration, float durationModifier)
		{
			return itemDuration * LarderMath.NonNegative(durationModifier);
		}

		public static float RunDown(float timeLeft, float seconds, float foodRate, bool endless)
		{
			if (endless)
			{
				return timeLeft;
			}
			float num = timeLeft - seconds * LarderMath.NonNegative(foodRate);
			if (!(num > 0f))
			{
				return 0f;
			}
			return num;
		}
	}
	public readonly struct FoodStats : IEquatable<FoodStats>
	{
		public float Health { get; }

		public float Stamina { get; }

		public float Eitr { get; }

		public float Duration { get; }

		public float HealthRegen { get; }

		public FoodStats(float health, float stamina, float eitr, float duration, float healthRegen)
		{
			Health = health;
			Stamina = stamina;
			Eitr = eitr;
			Duration = duration;
			HealthRegen = healthRegen;
		}

		public bool Equals(FoodStats other)
		{
			if (Health == other.Health && Stamina == other.Stamina && Eitr == other.Eitr && Duration == other.Duration)
			{
				return HealthRegen == other.HealthRegen;
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			if (obj is FoodStats other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return HashCode.Combine(Health, Stamina, Eitr, Duration, HealthRegen);
		}

		public override string ToString()
		{
			return $"{Health}/{Stamina}/{Eitr} {Duration}s regen {HealthRegen}";
		}
	}
	public readonly struct FoodModifiers
	{
		public static FoodModifiers Vanilla => new FoodModifiers(1f, 1f, 1f, 1f, 1f);

		public float Health { get; }

		public float Stamina { get; }

		public float Eitr { get; }

		public float Duration { get; }

		public float HealthRegen { get; }

		public bool IsVanilla
		{
			get
			{
				if (Health == 1f && Stamina == 1f && Eitr == 1f && Duration == 1f)
				{
					return HealthRegen == 1f;
				}
				return false;
			}
		}

		public FoodModifiers(float health, float stamina, float eitr, float duration, float healthRegen)
		{
			Health = health;
			Stamina = stamina;
			Eitr = eitr;
			Duration = duration;
			HealthRegen = healthRegen;
		}
	}
	public static class FoodValues
	{
		public static FoodStats Apply(FoodStats configured, FoodModifiers modifiers)
		{
			return new FoodStats(configured.Health * LarderMath.NonNegative(modifiers.Health), configured.Stamina * LarderMath.NonNegative(modifiers.Stamina), configured.Eitr * LarderMath.NonNegative(modifiers.Eitr), configured.Duration * LarderMath.NonNegative(modifiers.Duration), configured.HealthRegen * LarderMath.NonNegative(modifiers.HealthRegen));
		}

		public static bool ShouldWrite(bool customizedNow, bool writtenBefore)
		{
			return customizedNow || writtenBefore;
		}

		public static float KeepProgress(float elapsed, float oldDuration, float newDuration)
		{
			return ((oldDuration > 0f) ? (elapsed / oldDuration) : 0f) * newDuration;
		}

		public static float Vigor(float vigor, float configuredStamina, float staminaModifier, float perStaminaPoint, float multiplier)
		{
			float num = configuredStamina * LarderMath.NonNegative(staminaModifier);
			return (vigor + num * perStaminaPoint) * multiplier;
		}

		public static float EitrVigor(float eitrVigor, float configuredEitr, float eitrModifier, float perEitrPoint, float multiplier)
		{
			return Vigor(eitrVigor, configuredEitr, eitrModifier, perEitrPoint, multiplier);
		}
	}
	public static class SettingTemplates
	{
		public const string Food = "food";

		public const string Mead = "mead";

		public const string Station = "station";

		public const string Recipe = "recipe";

		public static readonly IReadOnlyDictionary<string, string> SectionPrefix;

		public static readonly IReadOnlyList<(string Kind, string Key, string Help)> All;

		private static readonly Dictionary<(string, string), string> help;

		static SettingTemplates()
		{
			SectionPrefix = new Dictionary<string, string>
			{
				["food"] = "Food: ",
				["mead"] = "Mead: ",
				["station"] = "Station: "
			};
			All = new(string, string, string)[17]
			{
				("food", "Health", "Health {0} adds to your maximum while it lasts. Health Modifier in 2 - Foods applies on top. The game's value is the default."),
				("food", "Stamina", "Stamina {0} adds to your maximum while it lasts. Stamina Modifier in 2 - Foods applies on top. The game's value is the default."),
				("food", "Duration", "Seconds {0} lasts after eating. Duration Modifier in 2 - Foods and Food Rate in 5 - Exertion also apply. The game's value is the default."),
				("food", "HealthRegen", "Health {0} restores on each regeneration tick while it lasts. Health Regen Modifier in 2 - Foods applies on top. The game's value is the default."),
				("food", "Eitr", "Eitr {0} adds to your maximum while it lasts. Eitr Modifier in 2 - Foods applies on top. The game's value is the default."),
				("food", "Vigor", "Extra Vigor of {0}: percent added to stamina regeneration while the food is active, on top of its Stamina times Vigor Per Stamina Point. Does not fade with the food. Stacks with Regen Per Extra Stamina Point."),
				("food", "EitrVigor", "Extra Eitr Vigor of {0}: percent added to eitr regeneration while the food is active, on top of its Eitr times Eitr Vigor Per Eitr Point. Does not fade with the food."),
				("mead", "Duration", "Duration of {0} effect in seconds. While it runs, neither this mead nor another of its group (the healing meads, for example) can be drunk, so this is also the mead's cooldown."),
				("mead", "HealthOverTime", "Total health restored over time by {0}."),
				("mead", "StaminaOverTime", "Total stamina restored over time by {0}."),
				("mead", "EitrOverTime", "Total eitr restored over time by {0}."),
				("mead", "HealthRegenMultiplier", "Health regeneration multiplier while {0} lasts (1 = no change)."),
				("mead", "StaminaRegenMultiplier", "Stamina regeneration multiplier while {0} lasts (1 = no change). Combines with the stamina rules in 4 - Recovery, like the game's own meads."),
				("mead", "EitrRegenMultiplier", "Eitr regeneration multiplier while {0} lasts (1 = no change). Combines with the eitr rules in 4 - Recovery."),
				("mead", "RunStaminaModifier", "Added fraction of the run stamina drain while {0} lasts: -0.2 makes running 20% cheaper."),
				("mead", "JumpStaminaModifier", "Added fraction of the jump stamina cost while {0} lasts: -0.2 makes jumping 20% cheaper."),
				("station", "recipe", "Seconds {0} takes to cook into {1} on this station. Cook Time Multiplier in 6 - Kitchen applies on top.")
			};
			help = new Dictionary<(string, string), string>();
			foreach (var (item, item2, value) in All)
			{
				help[(item, item2)] = value;
			}
		}

		public static string Help(string kind, string key, params object[] arguments)
		{
			return string.Format(CultureInfo.InvariantCulture, help[(kind, key)], arguments);
		}

		public static string HeadingKey(string kind)
		{
			return "tpl_" + kind + "_heading";
		}

		public static string NameKey(string kind, string key)
		{
			return "tpl_" + kind + "__" + key.ToLowerInvariant() + "_name";
		}

		public static string HelpKey(string kind, string key)
		{
			return "tpl_" + kind + "__" + key.ToLowerInvariant() + "_help";
		}

		public static string Heading(string kind)
		{
			return SectionPrefix[kind] + "{0}";
		}
	}
}
namespace ExtraPotions.Larder.Exertion
{
	public static class BaseValues
	{
		public const float DefaultHealth = 25f;

		public const float DefaultStamina = 75f;

		public static float Effective(float own, float configured, float defaultValue)
		{
			if (!LarderMath.Approximately(configured, defaultValue))
			{
				return configured;
			}
			return own;
		}

		public static float SkillStamina(ExertionSettings settings, float run, float jump, float sneak, float swim, float fishing)
		{
			return Bonus(settings.RunSkillStamina, run) + Bonus(settings.JumpSkillStamina, jump) + Bonus(settings.SneakSkillStamina, sneak) + Bonus(settings.SwimSkillStamina, swim) + Bonus(settings.FishingSkillStamina, fishing);
		}

		private static float Bonus(float atSkill100, float skillFactor)
		{
			if (atSkill100 != 0f)
			{
				return atSkill100 * skillFactor;
			}
			return 0f;
		}
	}
	public enum CostSite
	{
		Run,
		Jump,
		Dodge,
		Block,
		Attack,
		Tool,
		Sneak,
		Swim,
		Encumbered,
		FishingPull,
		FishingHooked,
		Harpoon
	}
	public readonly struct CostContext
	{
		public bool OutOfCombat { get; }

		public bool SneakingIsFree { get; }

		public float SkillFactor { get; }

		public CostContext(bool outOfCombat, bool sneakingIsFree, float skillFactor)
		{
			OutOfCombat = outOfCombat;
			SneakingIsFree = sneakingIsFree;
			SkillFactor = skillFactor;
		}
	}
	public static class CostRules
	{
		public static bool OutOfCombat(bool targeted, bool sensed, bool attacking)
		{
			if (!targeted && !sensed)
			{
				return !attacking;
			}
			return false;
		}

		public static float OutOfCombatFactor(bool outOfCombat, float outOfCombatCost)
		{
			if (outOfCombatCost == 1f)
			{
				return 1f;
			}
			if (!outOfCombat)
			{
				return 1f;
			}
			return LarderMath.NonNegative(outOfCombatCost);
		}

		public static float SkillDiscountFactor(float discountPercent, float skillFactor)
		{
			if (discountPercent == 0f)
			{
				return 1f;
			}
			return Math.Max(0f, 1f - discountPercent / 100f * skillFactor);
		}

		public static bool SneakingIsFree(bool enabled, bool inStealthRange, bool sensed, bool targeted)
		{
			if (enabled && !inStealthRange && !sensed)
			{
				return !targeted;
			}
			return false;
		}

		public static float Multiplier(CostSite site, CostContext context, ExertionSettings settings)
		{
			switch (site)
			{
			case CostSite.Run:
				return LarderMath.NonNegative(settings.RunCost) * OutOfCombatFactor(context.OutOfCombat, settings.OutOfCombatRunCost);
			case CostSite.Jump:
				return LarderMath.NonNegative(settings.JumpCost) * OutOfCombatFactor(context.OutOfCombat, settings.OutOfCombatJumpCost) * SkillDiscountFactor(settings.SkillDiscount, context.SkillFactor);
			case CostSite.Dodge:
				return LarderMath.NonNegative(settings.DodgeCost) * OutOfCombatFactor(context.OutOfCombat, settings.OutOfCombatDodgeCost) * SkillDiscountFactor(settings.SkillDiscount, context.SkillFactor);
			case CostSite.Block:
				return LarderMath.NonNegative(settings.BlockCost) * SkillDiscountFactor(settings.SkillDiscount, context.SkillFactor);
			case CostSite.Sneak:
				if (!context.SneakingIsFree || !settings.FreeSneakingWithoutEnemies)
				{
					return LarderMath.NonNegative(settings.SneakCost) * OutOfCombatFactor(context.OutOfCombat, settings.OutOfCombatSneakCost);
				}
				return 0f;
			case CostSite.Attack:
				return LarderMath.NonNegative(settings.AttackCost);
			case CostSite.Tool:
				return LarderMath.NonNegative(settings.ToolCost);
			case CostSite.Swim:
				return LarderMath.NonNegative(settings.SwimCost);
			case CostSite.Encumbered:
				return LarderMath.NonNegative(settings.EncumberedCost);
			case CostSite.FishingPull:
				return LarderMath.NonNegative(settings.FishingPullCost);
			case CostSite.FishingHooked:
				return LarderMath.NonNegative(settings.FishingHookedCost);
			case CostSite.Harpoon:
				return LarderMath.NonNegative(settings.HarpoonCost);
			default:
				return 1f;
			}
		}

		public static bool JumpAllowed(float stamina, float jumpStaminaUsage, float multiplier)
		{
			float num = jumpStaminaUsage * multiplier;
			if (!(num <= 0f))
			{
				return stamina > num;
			}
			return true;
		}
	}
	public enum ExertionSkill
	{
		Run,
		Jump,
		Sneak,
		Swim,
		Fishing,
		Other
	}
	public static class ExertionRules
	{
		public static float DrowningDamage(float damage, float multiplier, bool isPlayer, bool isDrowning)
		{
			if (!(isPlayer && isDrowning))
			{
				return damage;
			}
			return damage * LarderMath.NonNegative(multiplier);
		}

		public static float SkillGain(ExertionSkill skill, float factor, ExertionSettings settings)
		{
			return factor * LarderMath.NonNegative(Multiplier(skill, settings));
		}

		private static float Multiplier(ExertionSkill skill, ExertionSettings settings)
		{
			return skill switch
			{
				ExertionSkill.Run => settings.RunSkillGain, 
				ExertionSkill.Jump => settings.JumpSkillGain, 
				ExertionSkill.Sneak => settings.SneakSkillGain, 
				ExertionSkill.Swim => settings.SwimSkillGain, 
				ExertionSkill.Fishing => settings.FishingSkillGain, 
				_ => 1f, 
			};
		}
	}
	public sealed class ExertionSettings
	{
		public float RunCost { get; set; } = 1f;

		public float JumpCost { get; set; } = 1f;

		public float DodgeCost { get; set; } = 1f;

		public float BlockCost { get; set; } = 1f;

		public float AttackCost { get; set; } = 1f;

		public float SneakCost { get; set; } = 1f;

		public float SwimCost { get; set; } = 1f;

		public float EncumberedCost { get; set; } = 1f;

		public float ToolCost { get; set; } = 1f;

		public float FishingPullCost { get; set; } = 1f;

		public float FishingHookedCost { get; set; } = 1f;

		public float HarpoonCost { get; set; } = 1f;

		public float DrowningDamage { get; set; } = 1f;

		public float OutOfCombatRunCost { get; set; } = 1f;

		public float OutOfCombatJumpCost { get; set; } = 1f;

		public float OutOfCombatDodgeCost { get; set; } = 1f;

		public float OutOfCombatSneakCost { get; set; } = 1f;

		public bool FreeSneakingWithoutEnemies { get; set; }

		public float SkillDiscount { get; set; }

		public float BaseHealth { get; set; } = 25f;

		public float BaseStamina { get; set; } = 75f;

		public float RunSkillStamina { get; set; }

		public float JumpSkillStamina { get; set; }

		public float SneakSkillStamina { get; set; }

		public float SwimSkillStamina { get; set; }

		public float FishingSkillStamina { get; set; }

		public float RunSkillGain { get; set; } = 1f;

		public float JumpSkillGain { get; set; } = 1f;

		public float SneakSkillGain { get; set; } = 1f;

		public float SwimSkillGain { get; set; } = 1f;

		public float FishingSkillGain { get; set; } = 1f;

		public float FoodRate { get; set; }

		public float StaminaRate { get; set; }

		public float MoveStaminaRate { get; set; }

		public float StaminaRegenRate { get; set; }

		public bool ChangesCosts
		{
			get
			{
				if (RunCost == 1f && JumpCost == 1f && DodgeCost == 1f && BlockCost == 1f && AttackCost == 1f && SneakCost == 1f && SwimCost == 1f && EncumberedCost == 1f && ToolCost == 1f && FishingPullCost == 1f && FishingHookedCost == 1f && HarpoonCost == 1f && OutOfCombatRunCost == 1f && OutOfCombatJumpCost == 1f && OutOfCombatDodgeCost == 1f && OutOfCombatSneakCost == 1f && !FreeSneakingWithoutEnemies)
				{
					return SkillDiscount != 0f;
				}
				return true;
			}
		}

		public bool OverridesWorldRates
		{
			get
			{
				if (!(FoodRate > 0f) && !(StaminaRate > 0f) && !(MoveStaminaRate > 0f))
				{
					return StaminaRegenRate > 0f;
				}
				return true;
			}
		}
	}
	public sealed class IgnoredCreatures
	{
		private readonly HashSet<string> names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		public IgnoredCreatures(string specification)
		{
			string[] array = (specification ?? "").Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					names.Add(text);
				}
			}
		}

		public bool Contains(string prefab, string displayedName)
		{
			if (string.IsNullOrEmpty(prefab) || !names.Contains(prefab))
			{
				if (!string.IsNullOrEmpty(displayedName))
				{
					return names.Contains(displayedName);
				}
				return false;
			}
			return true;
		}
	}
	[Flags]
	public enum StaminaBlock
	{
		None = 0,
		Swimming = 1,
		Attacking = 2,
		Dodging = 4,
		WallRunning = 8,
		Encumbered = 0x10
	}
	public static class SafeStaminaRules
	{
		public static bool AllowsBlockedRegeneration(StaminaBlock state, bool swimming, bool attacks)
		{
			return AllowsBlockedRegeneration(state, swimming, attacks, encumbered: false);
		}

		public static bool AllowsBlockedRegeneration(StaminaBlock state, bool swimming, bool attacks, bool encumbered)
		{
			bool flag = (state & StaminaBlock.Encumbered) != 0;
			if (flag && !encumbered)
			{
				return false;
			}
			if ((state & StaminaBlock.Swimming) != StaminaBlock.None)
			{
				return swimming;
			}
			if ((state & (StaminaBlock.Attacking | StaminaBlock.Dodging)) != StaminaBlock.None)
			{
				return attacks;
			}
			if (!flag)
			{
				return (state & StaminaBlock.WallRunning) != 0;
			}
			return true;
		}
	}
	public static class WorldRates
	{
		public static float Effective(float world, float configured)
		{
			if (!(configured > 0f))
			{
				return world;
			}
			return configured;
		}
	}
}
namespace ExtraPotions.Larder.Eating
{
	public readonly struct AutoEatCandidate
	{
		public string SharedName { get; }

		public bool InFoodSlot { get; }

		public int Order { get; }

		public AutoEatCandidate(string sharedName, bool inFoodSlot, int order)
		{
			SharedName = sharedName;
			InFoodSlot = inFoodSlot;
			Order = order;
		}
	}
	public static class AutoEat
	{
		public const bool AsFromInventory = true;

		public const float ExpiringAt = 1f;

		public static bool Active(bool allowAutoEat, bool ownAutoEat, bool isLocalPlayer)
		{
			return allowAutoEat && ownAutoEat && isLocalPlayer;
		}

		public static int Expiring(IReadOnlyList<float> timesLeft)
		{
			for (int i = 0; i < timesLeft.Count; i++)
			{
				if (timesLeft[i] <= 1f)
				{
					return i;
				}
			}
			return -1;
		}

		public static int Select(string sharedName, IReadOnlyList<AutoEatCandidate> items)
		{
			if (string.IsNullOrEmpty(sharedName))
			{
				return -1;
			}
			int num = -1;
			for (int i = 0; i < items.Count; i++)
			{
				AutoEatCandidate a = items[i];
				if (!(a.SharedName != sharedName) && (num < 0 || Before(a, items[num])))
				{
					num = i;
				}
			}
			return num;
		}

		private static bool Before(AutoEatCandidate a, AutoEatCandidate b)
		{
			if (a.InFoodSlot != b.InFoodSlot)
			{
				return a.InFoodSlot;
			}
			return a.Order < b.Order;
		}
	}
	public static class EatAgain
	{
		public const float GameFraction = 0.5f;

		public static bool CanEatAgain(float timeLeft, float burnTime, float fraction, bool eatAnything)
		{
			float num = (eatAnything ? 1f : LarderMath.Clamp01(fraction));
			return timeLeft < burnTime * num;
		}
	}
	public static class FoodDegradation
	{
		public const float GameCurve = 0.3f;

		public static float Strength(float timeLeft, float burnTime, float curve, bool disabled)
		{
			if (disabled)
			{
				return 1f;
			}
			curve = LarderMath.NonNegative(curve);
			if (curve <= 0f)
			{
				return 1f;
			}
			return (float)Math.Pow((burnTime > 0f) ? LarderMath.Clamp01(timeLeft / burnTime) : 0f, curve);
		}

		public static bool Changes(bool disabled, float curve)
		{
			if (!disabled)
			{
				return !LarderMath.Approximately(LarderMath.NonNegative(curve), 0.3f);
			}
			return true;
		}

		public static FoodStats Faded(FoodStats full, float strength)
		{
			return new FoodStats(full.Health * strength, full.Stamina * strength, full.Eitr * strength, full.Duration, full.HealthRegen);
		}
	}
	public enum EatOutcome
	{
		Add,
		ReplaceMostDepleted,
		RefreshSame,
		NoMore,
		Full
	}
	public static class FoodSlots
	{
		public const int GameSlots = 3;

		public const int MinSlots = 1;

		public const int MaxSlots = 5;

		public static int Clamp(int slots)
		{
			if (slots >= 1)
			{
				if (slots <= 5)
				{
					return slots;
				}
				return 5;
			}
			return 1;
		}

		public static EatOutcome Decide(int slots, int held, bool hasSame, bool sameCanEatAgain, bool anyCanEatAgain, bool eatAnything)
		{
			if (hasSame)
			{
				if (!(sameCanEatAgain || eatAnything))
				{
					return EatOutcome.NoMore;
				}
				return EatOutcome.RefreshSame;
			}
			if (held < Clamp(slots))
			{
				return EatOutcome.Add;
			}
			if (!(anyCanEatAgain || eatAnything))
			{
				return EatOutcome.Full;
			}
			return EatOutcome.ReplaceMostDepleted;
		}

		public static int Kept(int slots, int held)
		{
			return held;
		}

		public static int HudSlots(int slots, int held)
		{
			int num = Clamp(slots);
			if (held <= num)
			{
				return num;
			}
			return held;
		}

		public static int MostDepleted(IReadOnlyList<float> timesLeft, IReadOnlyList<bool> canEatAgain, bool eatAnything)
		{
			int num = -1;
			for (int i = 0; i < timesLeft.Count; i++)
			{
				if (canEatAgain[i] && (num < 0 || timesLeft[i] < timesLeft[num]))
				{
					num = i;
				}
			}
			if (num >= 0 || !eatAnything)
			{
				return num;
			}
			for (int j = 0; j < timesLeft.Count; j++)
			{
				if (num < 0 || timesLeft[j] < timesLeft[num])
				{
					num = j;
				}
			}
			return num;
		}
	}
	public enum FoodTopUpReason
	{
		Spawn,
		Retrieval,
		Automatic
	}
	public static class FoodTopUpRules
	{
		public static bool Allowed(FoodTopUpReason reason, bool onSpawnAndRetrieval, bool automatic)
		{
			switch (reason)
			{
			case FoodTopUpReason.Spawn:
			case FoodTopUpReason.Retrieval:
				return onSpawnAndRetrieval;
			case FoodTopUpReason.Automatic:
				return automatic;
			default:
				return false;
			}
		}
	}
}
namespace ExtraPotions.Larder.Cooking
{
	public enum SlotState
	{
		Cooking,
		Done,
		Burnt
	}
	public static class CookClock
	{
		public static float Advance(float cooked, float elapsed, float cookTime, float speed, float extraBurn)
		{
			float num = 1f / (1f + extraBurn);
			if (cooked >= cookTime)
			{
				return cooked + elapsed * num;
			}
			float num2 = (cookTime - cooked) / speed;
			if (elapsed <= num2)
			{
				return cooked + elapsed * speed;
			}
			return cookTime + (elapsed - num2) * num;
		}

		public static bool IsVanilla(float speed, float extraBurn)
		{
			if (speed <= 1f)
			{
				return extraBurn <= 0f;
			}
			return false;
		}

		public static SlotState State(float cooked, float cookTime, bool canBurn)
		{
			if (canBurn && cooked > cookTime * 2f)
			{
				return SlotState.Burnt;
			}
			if (!(cooked > cookTime))
			{
				return SlotState.Cooking;
			}
			return SlotState.Done;
		}

		public static float SecondsUntilDone(float cooked, float cookTime, float speed)
		{
			if (!(cooked >= cookTime))
			{
				return (cookTime - cooked) / speed;
			}
			return 0f;
		}

		public static float SecondsUntilBurnt(float cooked, float cookTime, float extraBurn)
		{
			float num = ((cooked > cookTime) ? cooked : cookTime);
			float num2 = cookTime * 2f - num;
			if (!(num2 > 0f))
			{
				return 0f;
			}
			return num2 * (1f + extraBurn);
		}
	}
	public static class CookingPerks
	{
		public const float MaxLevel = 100f;

		public const float DefaultCookingSpeed = 50f;

		public const float DefaultExtraBurnTime = 100f;

		public const float DefaultFermentingSpeed = 30f;

		public const float DefaultExtraFoodChance = 50f;

		public const int DefaultExtraFoodAmount = 1;

		public const float DefaultIngredientSaveChance = 10f;

		public static float Factor(float level)
		{
			return LarderMath.Clamp01(level / 100f);
		}

		public static float CookingSpeed(float percentAt100, float level)
		{
			return 1f + Share(percentAt100, level);
		}

		public static float ExtraBurnTime(float percentAt100, float level)
		{
			return Share(percentAt100, level);
		}

		public static float FermentingSpeed(float percentAt100, float level)
		{
			return 1f + Share(percentAt100, level);
		}

		public static float ExtraFoodChance(float percentAt100, float level)
		{
			return LarderMath.Clamp01(Share(percentAt100, level));
		}

		public static int ExtraFoodAmount(int configured)
		{
			if (configured >= 1)
			{
				return configured;
			}
			return 1;
		}

		public static float IngredientSaveChance(float percentAt100, float level)
		{
			return LarderMath.Clamp01(Share(percentAt100, level));
		}

		private static float Share(float percentAt100, float level)
		{
			return LarderMath.NonNegative(percentAt100) / 100f * Factor(level);
		}
	}
	public static class FermentCountdown
	{
		public static string? Format(double scaledElapsed, double duration, double speed)
		{
			if (double.IsNaN(scaledElapsed) || double.IsInfinity(scaledElapsed) || scaledElapsed < 0.0 || double.IsNaN(duration) || double.IsInfinity(duration) || duration <= 0.0 || double.IsNaN(speed) || double.IsInfinity(speed) || speed <= 0.0 || scaledElapsed > duration)
			{
				return null;
			}
			double num = Math.Ceiling((duration - scaledElapsed) / speed);
			if (num > 2147483647.0)
			{
				return null;
			}
			int num2 = (int)num;
			if (num2 < 3600)
			{
				return string.Format(CultureInfo.InvariantCulture, "{0}:{1:00}", num2 / 60, num2 % 60);
			}
			return string.Format(CultureInfo.InvariantCulture, "{0}:{1:00}:{2:00}", num2 / 3600, num2 / 60 % 60, num2 % 60);
		}
	}
	public sealed class FermenterCredits
	{
		public const int Capacity = 4096;

		private const int MaxKeyLength = 160;

		private readonly Dictionary<string, long> starts = new Dictionary<string, long>(StringComparer.Ordinal);

		public int Count => starts.Count;

		public bool CanCredit(long world, string barrel, long start)
		{
			if (!TryKey(world, barrel, out string key) || start <= 0)
			{
				return false;
			}
			if (!starts.TryGetValue(key, out var value))
			{
				return starts.Count < 4096;
			}
			return start > value;
		}

		public bool TryCredit(long world, string barrel, long start)
		{
			if (!CanCredit(world, barrel, start))
			{
				return false;
			}
			TryKey(world, barrel, out string key);
			starts[key] = start;
			return true;
		}

		public string Save()
		{
			StringBuilder stringBuilder = new StringBuilder("1");
			foreach (KeyValuePair<string, long> start in starts)
			{
				stringBuilder.Append('\n').Append(start.Key).Append('\t')
					.Append(start.Value.ToString(CultureInfo.InvariantCulture));
			}
			return stringBuilder.ToString();
		}

		public static bool TryRestore(string text, out FermenterCredits history)
		{
			history = null;
			FermenterCredits fermenterCredits = new FermenterCredits();
			if (string.IsNullOrEmpty(text))
			{
				history = fermenterCredits;
				return true;
			}
			if (text.Length > 745473)
			{
				return false;
			}
			string[] array = text.Split('\n');
			if (array[0] != "1" || array.Length > 4097)
			{
				return false;
			}
			for (int i = 1; i < array.Length; i++)
			{
				int num = array[i].IndexOf('\t');
				if (num <= 0)
				{
					return false;
				}
				string text2 = array[i].Substring(0, num);
				int num2 = text2.IndexOf(':');
				if (num2 <= 0 || !long.TryParse(text2.Substring(0, num2), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || !TryKey(result, text2.Substring(num2 + 1), out string key) || text2 != key || !long.TryParse(array[i].Substring(num + 1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) || result2 <= 0 || fermenterCredits.starts.ContainsKey(text2))
				{
					return false;
				}
				fermenterCredits.starts.Add(text2, result2);
			}
			history = fermenterCredits;
			return true;
		}

		private static bool TryKey(long world, string barrel, out string key)
		{
			key = null;
			if (world == 0L || string.IsNullOrEmpty(barrel) || barrel.Length > 128 || barrel.IndexOf('\n') >= 0 || barrel.IndexOf('\r') >= 0 || barrel.IndexOf('\t') >= 0)
			{
				return false;
			}
			key = world.ToString(CultureInfo.InvariantCulture) + ":" + barrel;
			return key.Length <= 160;
		}
	}
	public static class FermentRules
	{
		public static double ScaledElapsed(double elapsed, float speed)
		{
			if (!(elapsed <= 0.0))
			{
				return elapsed * (double)speed;
			}
			return elapsed;
		}

		public static bool Ready(double elapsed, float duration, float speed)
		{
			if (elapsed > 0.0)
			{
				return ScaledElapsed(elapsed, speed) >= (double)duration;
			}
			return false;
		}
	}
	public static class RecipeQuality
	{
		public struct Stack
		{
			public string Name;

			public int Quality;

			public int Amount;

			public Stack(string name, int quality, int amount)
			{
				Name = name;
				Quality = quality;
				Amount = amount;
			}
		}

		public static bool TracksEveryIngredient(bool hasRecipe, bool requireOnlyOneIngredient)
		{
			if (hasRecipe)
			{
				return !requireOnlyOneIngredient;
			}
			return false;
		}

		public static bool CountEveryQuality(bool trackingEveryIngredient, int requestedQuality, int maxQuality)
		{
			if (trackingEveryIngredient && requestedQuality == 1)
			{
				return maxQuality <= 1;
			}
			return false;
		}

		public static int Count(IEnumerable<Stack> stacks, string name, int requestedQuality, int maxQuality, bool trackingEveryIngredient)
		{
			if (name == null || stacks == null)
			{
				return 0;
			}
			bool flag = CountEveryQuality(trackingEveryIngredient, requestedQuality, maxQuality);
			int num = 0;
			foreach (Stack stack in stacks)
			{
				if (!(stack.Name != name) && stack.Amount > 0 && (flag || requestedQuality < 0 || stack.Quality == requestedQuality))
				{
					num += stack.Amount;
				}
			}
			return num;
		}
	}
	public enum StationAddDecision
	{
		New,
		Accepted,
		Rejected,
		Stale
	}
	public sealed class StationAddReceipts
	{
		private readonly struct Receipt
		{
			public readonly long Sequence;

			public readonly bool Accepted;

			public Receipt(long sequence, bool accepted)
			{
				Sequence = sequence;
				Accepted = accepted;
			}
		}

		public const int Capacity = 256;

		private const int MaxSerializedLength = 11009;

		private readonly SortedDictionary<long, Receipt> receipts = new SortedDictionary<long, Receipt>();

		public int Count => receipts.Count;

		public StationAddDecision Check(long character, long sequence)
		{
			if (character == 0L || sequence <= 0)
			{
				return StationAddDecision.Stale;
			}
			if (!receipts.TryGetValue(character, out var value))
			{
				if (receipts.Count >= 256)
				{
					return StationAddDecision.Rejected;
				}
				return StationAddDecision.New;
			}
			if (sequence < value.Sequence)
			{
				return StationAddDecision.Stale;
			}
			if (sequence > value.Sequence)
			{
				return StationAddDecision.New;
			}
			if (!value.Accepted)
			{
				return StationAddDecision.Rejected;
			}
			return StationAddDecision.Accepted;
		}

		public string Record(long character, long sequence, bool accepted)
		{
			Receipt value;
			if (Check(character, sequence) == StationAddDecision.New)
			{
				receipts[character] = new Receipt(sequence, accepted);
			}
			else if (!receipts.TryGetValue(character, out value) || value.Sequence != sequence || value.Accepted != accepted)
			{
				throw new InvalidOperationException("Only a new station-add request or its unchanged receipt can be recorded.");
			}
			return Save();
		}

		public static StationAddReceipts Parse(string? serialized)
		{
			StationAddReceipts stationAddReceipts = new StationAddReceipts();
			if (string.IsNullOrEmpty(serialized))
			{
				return stationAddReceipts;
			}
			if (serialized.Length > 11009)
			{
				throw InvalidData();
			}
			string[] array = serialized.Split('\n');
			if (array[0] != "1" || array.Length > 257)
			{
				throw InvalidData();
			}
			for (int i = 1; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(':');
				if (array2.Length != 3 || !TryCharacter(array2[0], out var parsed) || !TryPositive(array2[1], out var parsed2) || (array2[2] != "0" && array2[2] != "1") || stationAddReceipts.receipts.ContainsKey(parsed))
				{
					throw InvalidData();
				}
				stationAddReceipts.receipts.Add(parsed, new Receipt(parsed2, array2[2] == "1"));
			}
			return stationAddReceipts;
		}

		private string Save()
		{
			StringBuilder stringBuilder = new StringBuilder("1");
			foreach (KeyValuePair<long, Receipt> receipt in receipts)
			{
				stringBuilder.Append('\n').Append(receipt.Key.ToString(CultureInfo.InvariantCulture)).Append(':')
					.Append(receipt.Value.Sequence.ToString(CultureInfo.InvariantCulture))
					.Append(':')
					.Append(receipt.Value.Accepted ? '1' : '0');
			}
			return stringBuilder.ToString();
		}

		private static bool TryPositive(string value, out long parsed)
		{
			if (long.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out parsed) && parsed > 0)
			{
				return value == parsed.ToString(CultureInfo.InvariantCulture);
			}
			return false;
		}

		private static bool TryCharacter(string value, out long parsed)
		{
			if (long.TryParse(value, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out parsed) && parsed != 0L)
			{
				return value == parsed.ToString(CultureInfo.InvariantCulture);
			}
			return false;
		}

		private static FormatException InvalidData()
		{
			return new FormatException("Invalid station-add receipt ledger.");
		}
	}
}
namespace ExtraPotions.Larder.Cheats
{
	public enum LarderCheat
	{
		InstantKitchen,
		EndlessFood,
		EatAnything
	}
	public enum CheatBlock
	{
		None,
		NotAdmin,
		ProductDisabled,
		VanillaServer,
		AuthorityPending
	}
	public static class LarderCheats
	{
		public static IReadOnlyList<LarderCheat> All { get; } = new LarderCheat[3]
		{
			LarderCheat.InstantKitchen,
			LarderCheat.EndlessFood,
			LarderCheat.EatAnything
		};

		public static bool SafeValue(LarderCheat cheat)
		{
			return false;
		}

		public static bool Effective(LarderCheat cheat, CheatBlock block, bool value)
		{
			if (block != CheatBlock.None)
			{
				return SafeValue(cheat);
			}
			return value;
		}
	}
	public static class KitchenCheats
	{
		public static float RecipeTime(float time, bool instantKitchen)
		{
			if (!instantKitchen)
			{
				return time;
			}
			return 0f;
		}

		public static bool CanBurn(bool canBurn, bool instantKitchen)
		{
			if (canBurn)
			{
				return !instantKitchen;
			}
			return false;
		}

		public static float FermentationDuration(float duration, bool instantKitchen)
		{
			if (!instantKitchen)
			{
				return duration;
			}
			return 0f;
		}
	}
}

plugins/Larder/Larder.dll

Decompiled 6 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExtraPotions.Larder.Cooking;
using ExtraPotions.Larder.Eating;
using ExtraPotions.Larder.Exertion;
using ExtraPotions.Larder.Foods;
using ExtraPotions.Larder.Kitchen;
using ExtraPotions.Larder.Runtime.Adopted.FeastMaster;
using ExtraPotions.Larder.Runtime.Adopted.FeastMaster.ItemCopies;
using ExtraPotions.Larder.Runtime.Adopted.FoodHud;
using ExtraPotions.Larder.Runtime.Adopted.GrindstoneCooking;
using ExtraPotions.Meadworks.Crucible;
using ExtraPotions.Meadworks.OriginalArt;
using HarmonyLib;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_guiutils")]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ExtraPotions")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (C) 2026 expDARE (ExtraPotions Meadworks)")]
[assembly: AssemblyFileVersion("0.2.16.0")]
[assembly: AssemblyInformationalVersion("0.2.16")]
[assembly: AssemblyProduct("Larder")]
[assembly: AssemblyTitle("Larder")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.2.16.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 ExtraPotions.Meadworks.OriginalArt
{
	internal static class OriginalItemArt
	{
		[Serializable]
		private sealed class Catalog
		{
			public Entry[] assets;

			public Surface[] materials;
		}

		[Serializable]
		private sealed class Entry
		{
			public string id;

			public string[] prefabs;
		}

		[Serializable]
		private sealed class Surface
		{
			public string id;

			public string nativePrefab;
		}

		private static AssetBundle bundle;

		private static Dictionary<string, string> ids;

		private static Dictionary<string, string> nativeMaterials;

		private static readonly List<Material> ownedMaterials = new List<Material>();

		private static readonly Dictionary<string, GameObject> models = new Dictionary<string, GameObject>(StringComparer.Ordinal);

		private static void Load()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Invalid comparison between Unknown and I4
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Invalid comparison between Unknown and I4
			if ((Object)(object)bundle != (Object)null)
			{
				return;
			}
			Assembly assembly = typeof(OriginalItemArt).Assembly;
			string platform = (((int)Application.platform == 13 || (int)Application.platform == 43) ? "linux" : "windows");
			string text = assembly.GetManifestResourceNames().Single((string n) => n.EndsWith(".original-items." + platform, StringComparison.Ordinal));
			using (Stream stream = assembly.GetManifestResourceStream(text))
			{
				using MemoryStream memoryStream = new MemoryStream();
				stream.CopyTo(memoryStream);
				bundle = AssetBundle.LoadFromMemory(memoryStream.ToArray());
			}
			if ((Object)(object)bundle == (Object)null)
			{
				throw new InvalidOperationException("Original item art bundle did not load: " + text);
			}
			using Stream stream2 = assembly.GetManifestResourceStream("MeadWorks.OriginalItems.catalog.json");
			using StreamReader streamReader = new StreamReader(stream2);
			ids = new Dictionary<string, string>(StringComparer.Ordinal);
			Catalog catalog = JsonConvert.DeserializeObject<Catalog>(streamReader.ReadToEnd());
			if (catalog == null || catalog.assets == null || catalog.assets.Length == 0)
			{
				throw new InvalidDataException("Original item catalog contains no assets");
			}
			nativeMaterials = new Dictionary<string, string>(StringComparer.Ordinal);
			Surface[] array = catalog.materials ?? Array.Empty<Surface>();
			foreach (Surface surface in array)
			{
				if (!string.IsNullOrEmpty(surface.nativePrefab))
				{
					nativeMaterials.Add(surface.id, surface.nativePrefab);
				}
			}
			Entry[] assets = catalog.assets;
			foreach (Entry entry in assets)
			{
				ids.Add(entry.id, entry.id);
				string[] array2 = entry.prefabs ?? Array.Empty<string>();
				foreach (string text2 in array2)
				{
					if (text2 != entry.id)
					{
						ids.Add(text2, entry.id);
					}
				}
			}
		}

		internal static Sprite Icon(string prefab)
		{
			if (MeadworksHeadless.IsHeadless)
			{
				return null;
			}
			Load();
			if (!ids.TryGetValue(prefab, out string value))
			{
				throw new KeyNotFoundException("Original icon missing: " + prefab);
			}
			return bundle.LoadAsset<Sprite>(value + "_icon") ?? throw new InvalidDataException("Original icon sprite missing: " + value);
		}

		internal static GameObject Model(ZNetScene scene, string prefab)
		{
			if (MeadworksHeadless.IsHeadless)
			{
				return null;
			}
			GameObject val = (((Object)(object)scene != (Object)null) ? (scene.GetPrefab("SerpentScale") ?? scene.GetPrefab("TrollHide")) : null);
			if ((Object)(object)val == (Object)null && (Object)(object)ObjectDB.instance != (Object)null)
			{
				val = ObjectDB.instance.GetItemPrefab("SerpentScale") ?? ObjectDB.instance.GetItemPrefab("TrollHide");
			}
			return ModelCore(val, prefab, scene);
		}

		internal static GameObject Model(GameObject native, string prefab)
		{
			if (MeadworksHeadless.IsHeadless)
			{
				return null;
			}
			return ModelCore(native, prefab, ZNetScene.instance);
		}

		private static bool ActiveBelowRoot(Renderer renderer, Transform root)
		{
			Transform val = ((Component)renderer).transform;
			while ((Object)(object)val != (Object)(object)root)
			{
				if (!((Component)val).gameObject.activeSelf)
				{
					return false;
				}
				val = val.parent;
			}
			return true;
		}

		internal static Material NativeBaseMaterial(GameObject source)
		{
			Material? obj = (((Object)(object)source != (Object)null) ? (from renderer in source.GetComponentsInChildren<Renderer>(true)
				where (renderer is MeshRenderer || renderer is SkinnedMeshRenderer) && renderer.enabled && ActiveBelowRoot(renderer, source.transform)
				select renderer).SelectMany((Renderer renderer) => renderer.sharedMaterials).FirstOrDefault((Func<Material, bool>)((Material candidate) => (Object)(object)candidate != (Object)null && (Object)(object)candidate.shader != (Object)null && ((Object)candidate.shader).name.IndexOf("Snow", StringComparison.OrdinalIgnoreCase) < 0 && candidate.renderQueue <= 2500 && candidate.HasProperty("_MainTex") && (Object)(object)candidate.GetTexture("_MainTex") != (Object)null)) : null);
			if ((Object)(object)obj == (Object)null)
			{
				throw new InvalidOperationException("Native opaque base material with albedo unavailable: " + (((Object)(object)source != (Object)null) ? ((Object)source).name : "<null>"));
			}
			return obj;
		}

		private static GameObject ModelCore(GameObject native, string prefab, ZNetScene scene)
		{
			Load();
			if (!ids.TryGetValue(prefab, out string value))
			{
				throw new KeyNotFoundException("Original model missing: " + prefab);
			}
			if (models.TryGetValue(value, out GameObject value2) && (Object)(object)value2 != (Object)null)
			{
				return value2;
			}
			GameObject obj = bundle.LoadAsset<GameObject>(value) ?? throw new InvalidDataException("Original model prefab missing: " + value);
			Material nativeMaterial = NativeBaseMaterial(native);
			GameObject val = Object.Instantiate<GameObject>(obj);
			((Object)val).name = value;
			val.SetActive(false);
			Object.DontDestroyOnLoad((Object)(object)val);
			Dictionary<Material, Material> dress = new Dictionary<Material, Material>();
			Renderer[] componentsInChildren = val.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer obj2 in componentsInChildren)
			{
				obj2.sharedMaterials = ((IEnumerable<Material>)obj2.sharedMaterials).Select((Func<Material, Material>)delegate(Material original)
				{
					//IL_0088: Unknown result type (might be due to invalid IL or missing references)
					//IL_008d: Unknown result type (might be due to invalid IL or missing references)
					//IL_00a4: Expected O, but got Unknown
					//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
					//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
					//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
					//IL_0242: Unknown result type (might be due to invalid IL or missing references)
					//IL_0218: Unknown result type (might be due to invalid IL or missing references)
					if (dress.TryGetValue(original, out Material value3))
					{
						return value3;
					}
					Material val2 = nativeMaterial;
					if (nativeMaterials.TryGetValue(((Object)original).name.Replace(" (Instance)", ""), out string value4))
					{
						GameObject val3 = (((Object)(object)scene != (Object)null) ? scene.GetPrefab(value4) : null);
						if ((Object)(object)val3 == (Object)null && (Object)(object)ObjectDB.instance != (Object)null)
						{
							val3 = ObjectDB.instance.GetItemPrefab(value4);
						}
						val2 = NativeBaseMaterial(val3);
					}
					Material val4 = new Material(val2)
					{
						name = "MW_" + ((Object)original).name
					};
					if (val4.HasProperty("_MainTex"))
					{
						val4.SetTexture("_MainTex", original.mainTexture);
					}
					if (val4.HasProperty("_Color"))
					{
						val4.SetColor("_Color", original.color);
					}
					val4.mainTextureScale = Vector2.one;
					val4.mainTextureOffset = Vector2.zero;
					if (val4.HasProperty("_BumpMap"))
					{
						val4.SetTexture("_BumpMap", (Texture)null);
					}
					string[] array = new string[3] { "_MetallicGlossMap", "_EmissionMap", "_StyleTex" };
					foreach (string text in array)
					{
						if (val4.HasProperty(text))
						{
							val4.SetTexture(text, (Texture)null);
						}
					}
					if (val4.HasProperty("_UseStyles"))
					{
						val4.SetFloat("_UseStyles", 0f);
					}
					val4.DisableKeyword("_USESTYLES_ON");
					if (val4.HasProperty("_Glossiness"))
					{
						val4.SetFloat("_Glossiness", original.GetFloat("_Glossiness"));
					}
					if (val4.HasProperty("_Metallic"))
					{
						val4.SetFloat("_Metallic", original.GetFloat("_Metallic"));
					}
					if (val4.HasProperty("_MetalGloss"))
					{
						val4.SetFloat("_MetalGloss", 0f);
					}
					if (val4.HasProperty("_EmissionColor") && original.IsKeywordEnabled("_EMISSION"))
					{
						val4.EnableKeyword("_EMISSION");
						val4.SetColor("_EmissionColor", original.GetColor("_EmissionColor"));
					}
					else if (val4.HasProperty("_EmissionColor"))
					{
						val4.DisableKeyword("_EMISSION");
						val4.SetColor("_EmissionColor", Color.black);
					}
					ownedMaterials.Add(val4);
					dress.Add(original, val4);
					return val4;
				}).ToArray();
			}
			models[value] = val;
			return val;
		}

		internal static GameObject Instantiate(ZNetScene scene, string prefab, Transform parent)
		{
			GameObject val = Model(scene, prefab);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			GameObject obj = Object.Instantiate<GameObject>(val, parent, false);
			((Object)obj).name = "meadworks_original_model";
			obj.SetActive(true);
			return obj;
		}

		internal static GameObject Instantiate(GameObject nativeMaterialItem, string prefab, Transform parent)
		{
			GameObject val = Model(nativeMaterialItem, prefab);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			GameObject obj = Object.Instantiate<GameObject>(val, parent, false);
			((Object)obj).name = "meadworks_original_model";
			obj.SetActive(true);
			return obj;
		}

		internal static void Shutdown()
		{
			foreach (GameObject value in models.Values)
			{
				if ((Object)(object)value != (Object)null)
				{
					Object.Destroy((Object)(object)value);
				}
			}
			models.Clear();
			foreach (Material ownedMaterial in ownedMaterials)
			{
				if ((Object)(object)ownedMaterial != (Object)null)
				{
					Object.Destroy((Object)(object)ownedMaterial);
				}
			}
			ownedMaterials.Clear();
			if ((Object)(object)bundle != (Object)null)
			{
				bundle.Unload(false);
			}
			bundle = null;
			ids = null;
			nativeMaterials = null;
		}
	}
}
namespace ExtraPotions.Larder.Runtime
{
	internal static class FoodRetrieval
	{
		internal sealed class Capture
		{
			internal TombStone Tomb;

			internal Container Container;

			internal Inventory Inventory;

			internal Player Player;

			internal Game Game;

			internal ZDO Zdo;

			internal long World;

			internal long PlayerId;

			internal long Generation;
		}

		[HarmonyPatch(typeof(Container), "RPC_TakeAllResponse")]
		internal static class TakeAllPatch
		{
			[HarmonyPrefix]
			internal static void Before(Container __instance, bool granted, out Capture __state)
			{
				__state = (granted ? FoodRetrieval.Before(__instance) : null);
			}

			[HarmonyPostfix]
			internal static void After(Capture __state, bool __runOriginal)
			{
				Complete(__state, __runOriginal);
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "OnSelectedItem")]
		internal static class TransferPatch
		{
			[HarmonyPrefix]
			internal static void Before(InventoryGui __instance, InventoryGrid grid, ItemData item, Modifier mod, out Capture __state)
			{
				//IL_0005: Unknown result type (might be due to invalid IL or missing references)
				__state = BeforeTransfer(__instance, grid, item, mod);
			}

			[HarmonyPostfix]
			internal static void After(Capture __state, bool __runOriginal)
			{
				Complete(__state, __runOriginal);
			}
		}

		private static readonly FieldInfo currentContainer = AccessTools.Field(typeof(InventoryGui), "m_currentContainer");

		private static readonly FieldInfo dragInventory = AccessTools.Field(typeof(InventoryGui), "m_dragInventory");

		private static readonly FieldInfo dragObject = AccessTools.Field(typeof(InventoryGui), "m_dragGo");

		internal static string LastError { get; private set; }

		internal static Capture Before(Container container)
		{
			try
			{
				TombStone val = (Object.op_Implicit((Object)(object)container) ? ((Component)container).GetComponent<TombStone>() : null);
				Player localPlayer = Player.m_localPlayer;
				Game instance = Game.instance;
				ZNet instance2 = ZNet.instance;
				if (!Plugin.Ready || !Plugin.AllowsUi || !Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)localPlayer) || ((Character)localPlayer).IsDead() || !Object.op_Implicit((Object)(object)instance) || !Object.op_Implicit((Object)(object)instance2) || instance2.IsDedicated() || instance2.GetWorld() == null)
				{
					return null;
				}
				long worldUID = instance2.GetWorldUID();
				long playerID = localPlayer.GetPlayerID();
				PlayerProfile playerProfile = instance.GetPlayerProfile();
				ZNetView component = ((Component)localPlayer).GetComponent<ZNetView>();
				ZNetView component2 = ((Component)val).GetComponent<ZNetView>();
				Inventory inventory = container.GetInventory();
				if (worldUID == 0L || playerID == 0L || playerProfile == null || playerProfile.GetPlayerID() != playerID || !Plugin.Session.AuthorityReady || Plugin.Session.WorldId != worldUID || !Object.op_Implicit((Object)(object)component) || !component.IsValid() || !component.IsOwner() || !Object.op_Implicit((Object)(object)component2) || !component2.IsValid() || component2.GetZDO() == null || component2.GetZDO().GetLong(ZDOVars.s_owner, 0L) != playerID || inventory == null || inventory.NrOfItems() == 0)
				{
					return null;
				}
				return new Capture
				{
					Tomb = val,
					Container = container,
					Inventory = inventory,
					Player = localPlayer,
					Game = instance,
					Zdo = component2.GetZDO(),
					World = worldUID,
					PlayerId = playerID,
					Generation = Plugin.Session.Generation
				};
			}
			catch (Exception ex)
			{
				LastError = ex.GetType().Name + ": " + ex.Message;
				return null;
			}
		}

		internal static Capture BeforeTransfer(InventoryGui gui, InventoryGrid grid, ItemData item, Modifier modifier)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Invalid comparison between Unknown and I4
			try
			{
				Capture capture = Before((Container)currentContainer.GetValue(gui));
				if (capture == null || (Object)(object)grid == (Object)null)
				{
					return null;
				}
				Inventory inventory = grid.GetInventory();
				if (Object.op_Implicit((Object)(GameObject)dragObject.GetValue(gui)))
				{
					return (dragInventory.GetValue(gui) == capture.Inventory && inventory == ((Humanoid)capture.Player).GetInventory()) ? capture : null;
				}
				return ((int)modifier == 2 && item != null && inventory == capture.Inventory) ? capture : null;
			}
			catch (Exception ex)
			{
				LastError = ex.GetType().Name + ": " + ex.Message;
				return null;
			}
		}

		internal static void Complete(Capture capture, bool original)
		{
			if (capture == null || !original)
			{
				return;
			}
			try
			{
				Player localPlayer = Player.m_localPlayer;
				ZNet instance = ZNet.instance;
				if (!Plugin.Ready || !Plugin.AllowsUi || !Object.op_Implicit((Object)(object)capture.Tomb) || !Object.op_Implicit((Object)(object)capture.Container) || (Object)(object)localPlayer != (Object)(object)capture.Player || !Object.op_Implicit((Object)(object)localPlayer) || ((Character)localPlayer).IsDead() || (Object)(object)Game.instance != (Object)(object)capture.Game || !Object.op_Implicit((Object)(object)instance) || instance.GetWorld() == null || instance.GetWorldUID() != capture.World || localPlayer.GetPlayerID() != capture.PlayerId)
				{
					return;
				}
				PlayerProfile playerProfile = Game.instance.GetPlayerProfile();
				if (((playerProfile != null) ? new long?(playerProfile.GetPlayerID()) : ((long?)null)) == capture.PlayerId && Plugin.Session.AuthorityReady && Plugin.Session.WorldId == capture.World && Plugin.Session.Generation == capture.Generation)
				{
					ZNetView component = ((Component)localPlayer).GetComponent<ZNetView>();
					ZNetView component2 = ((Component)capture.Tomb).GetComponent<ZNetView>();
					if (Object.op_Implicit((Object)(object)component) && component.IsValid() && component.IsOwner() && Object.op_Implicit((Object)(object)component2) && component2.IsValid() && component2.IsOwner() && component2.GetZDO() == capture.Zdo && capture.Zdo.GetLong(ZDOVars.s_owner, 0L) == capture.PlayerId && capture.Container.GetInventory() == capture.Inventory && capture.Inventory.NrOfItems() == 0)
					{
						FoodTopUp.Retrieved(capture.World, capture.PlayerId, ((object)Unsafe.As<ZDOID, ZDOID>(ref capture.Zdo.m_uid)/*cast due to .constrained prefix*/).ToString());
					}
				}
			}
			catch (Exception ex)
			{
				LastError = ex.GetType().Name + ": " + ex.Message;
			}
		}
	}
	internal static class FoodTopUp
	{
		[HarmonyPatch(typeof(Player), "OnDestroy")]
		private static class DestroyPatch
		{
			[HarmonyPrefix]
			private static void Before(Player __instance)
			{
				if (__instance == sessionPlayer)
				{
					ClearSession();
				}
			}
		}

		[HarmonyPatch(typeof(Player), "OnSpawned")]
		private static class SpawnPatch
		{
			[HarmonyPostfix]
			private static void After(Player __instance, bool __runOriginal)
			{
				if (__runOriginal && Plugin.AllowsUi)
				{
					Spawned(__instance);
				}
			}
		}

		internal const string RetrievalCapability = "meadworks.larder.food.retrieved";

		private static ConfigEntry<bool> enabled;

		internal static ConfigEntry<bool> AutoFromSlots;

		private static float nextAuto;

		private static Harmony patches;

		private static Player actor;

		private static Player lastSpawn;

		private static Player sessionPlayer;

		private static ZNet sessionNet;

		private static long sessionGeneration = -1L;

		private static long world;

		private static long playerId;

		private static float deadline;

		private static float nextCheck;

		private static bool pending;

		private static bool eating;

		private static FoodTopUpReason requestReason;

		private static readonly HashSet<string> graves = new HashSet<string>(StringComparer.Ordinal);

		private static string lastResult = "idle";

		private static int attempts;

		private static int consumed;

		internal static void Initialize(ConfigFile config, ProductRegistration source)
		{
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Expected O, but got Unknown
			Shutdown();
			AutoFromSlots = MeadworksConfig.Bind<bool>(config, "3 - Eating", "Eat From Food Slots Automatically", true, "Whenever food in your configured Mantle Food slots can be eaten (a free food slot, or a food low enough that the game lets you eat it again), eat it using the game's normal eating rules. Consumes actual items; does nothing without Mantle Food slots.", (ConfigScope)0, (AcceptableValueBase)null);
			enabled = MeadworksConfig.Bind<bool>(config, "3 - Eating", "Food Slots On Spawn And Retrieval", true, "After joining a world, respawning or fully retrieving your own tombstone, eat available food from your configured Mantle Food slots using the game's normal eating rules. Consumes actual items; does nothing without Mantle Food slots.", (ConfigScope)0, (AcceptableValueBase)null);
			source.AddCapability<Action<long, long, string>>("meadworks.larder.food.retrieved", new Version(1, 0), (Action<long, long, string>)Retrieved);
			patches = new Harmony("com.meadworks.larder.foodtopup");
			patches.CreateClassProcessor(typeof(SpawnPatch)).Patch();
			patches.CreateClassProcessor(typeof(DestroyPatch)).Patch();
			patches.CreateClassProcessor(typeof(FoodRetrieval.TakeAllPatch)).Patch();
			patches.CreateClassProcessor(typeof(FoodRetrieval.TransferPatch)).Patch();
		}

		private static bool Scope(Player player)
		{
			if (Object.op_Implicit((Object)(object)player) && (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return false;
			}
			if (!Object.op_Implicit((Object)(object)player) || !Object.op_Implicit((Object)(object)ZNet.instance) || ZNet.instance.GetWorld() == null)
			{
				ClearSession();
				return false;
			}
			long worldUID = ZNet.instance.GetWorldUID();
			long playerID = player.GetPlayerID();
			if (worldUID == 0L || playerID == 0L)
			{
				ClearSession();
				return false;
			}
			long generation = Plugin.Session.Generation;
			if (sessionPlayer != player || sessionNet != ZNet.instance || sessionGeneration != generation || world != worldUID || playerId != playerID)
			{
				ClearSession();
				sessionPlayer = player;
				sessionNet = ZNet.instance;
				sessionGeneration = generation;
				world = worldUID;
				playerId = playerID;
			}
			return true;
		}

		internal static void Spawned(Player player)
		{
			ConfigEntry<bool> obj = enabled;
			if (obj != null && obj.Value && Scope(player) && lastSpawn != player)
			{
				lastSpawn = player;
				Queue(player, (FoodTopUpReason)0);
			}
		}

		internal static void Retrieved(long sourceWorld, long sourcePlayer, string tombId)
		{
			Player localPlayer = Player.m_localPlayer;
			ConfigEntry<bool> obj = enabled;
			if (obj != null && obj.Value && Scope(localPlayer) && sourceWorld == world && sourcePlayer == playerId && !string.IsNullOrEmpty(tombId) && tombId.Length <= 128 && !((Character)localPlayer).IsDead() && !graves.Contains(tombId))
			{
				if (graves.Count >= 256)
				{
					lastResult = "retrieval-history-full";
					return;
				}
				graves.Add(tombId);
				Queue(localPlayer, (FoodTopUpReason)1);
			}
		}

		private unsafe static void Queue(Player player, FoodTopUpReason reason)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (!eating)
			{
				actor = player;
				requestReason = reason;
				pending = true;
				deadline = Time.realtimeSinceStartup + 180f;
				nextCheck = 0f;
				lastResult = "waiting-" + ((object)(*(FoodTopUpReason*)(&reason))/*cast due to .constrained prefix*/).ToString();
			}
		}

		private static void AutoCheck()
		{
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Invalid comparison between Unknown and I4
			Player localPlayer = Player.m_localPlayer;
			ConfigEntry<bool> autoFromSlots = AutoFromSlots;
			if (autoFromSlots == null || !autoFromSlots.Value || eating || !Plugin.Ready || !Plugin.AllowsUi || !Object.op_Implicit((Object)(object)localPlayer) || ((Character)localPlayer).IsDead())
			{
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup < nextAuto)
			{
				return;
			}
			nextAuto = realtimeSinceStartup + 2f;
			if (!Scope(localPlayer))
			{
				return;
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			if (!MantleFoodIntegration.TryFoodCells(inventory, out IReadOnlyList<Tuple<int, int>> cells))
			{
				return;
			}
			foreach (Tuple<int, int> item in cells)
			{
				ItemData itemAt = inventory.GetItemAt(item.Item1, item.Item2);
				if (itemAt?.m_shared != null && itemAt.m_stack > 0 && (int)itemAt.m_shared.m_itemType == 2 && (itemAt.m_shared.m_food > 0f || itemAt.m_shared.m_foodStamina > 0f || itemAt.m_shared.m_foodEitr > 0f) && localPlayer.CanEat(itemAt, false))
				{
					Queue(localPlayer, (FoodTopUpReason)2);
					break;
				}
			}
		}

		private static void ClearSession()
		{
			pending = false;
			actor = (lastSpawn = (sessionPlayer = null));
			sessionNet = null;
			sessionGeneration = -1L;
			world = (playerId = 0L);
			graves.Clear();
			deadline = (nextCheck = (nextAuto = 0f));
		}

		internal static void Tick()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d1: Invalid comparison between Unknown and I4
			if (eating || !Scope(Player.m_localPlayer))
			{
				return;
			}
			if (!pending)
			{
				AutoCheck();
			}
			if (!pending)
			{
				return;
			}
			if (!FoodTopUpRules.Allowed(requestReason, enabled?.Value ?? false, AutoFromSlots?.Value ?? false) || !Plugin.Ready || !Plugin.AllowsUi || !Object.op_Implicit((Object)(object)actor) || (Object)(object)actor != (Object)(object)Player.m_localPlayer || !Object.op_Implicit((Object)(object)ZNet.instance) || ZNet.instance.GetWorld() == null || ZNet.instance.GetWorldUID() != world || actor.GetPlayerID() != playerId || ((Character)actor).IsDead())
			{
				pending = false;
				lastResult = "cancelled";
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup >= deadline)
			{
				pending = false;
				lastResult = "readiness-timeout";
			}
			else
			{
				if (realtimeSinceStartup < nextCheck)
				{
					return;
				}
				nextCheck = realtimeSinceStartup + 0.1f;
				ZNetView component = ((Component)actor).GetComponent<ZNetView>();
				Game instance = Game.instance;
				PlayerProfile val = (Object.op_Implicit((Object)(object)instance) ? instance.GetPlayerProfile() : null);
				if (!Object.op_Implicit((Object)(object)component) || !component.IsValid() || !component.IsOwner() || val == null || val.GetPlayerID() != playerId || !Plugin.Session.AuthorityReady || Plugin.Session.WorldId != world || ((Character)actor).InCutscene() || ((Character)actor).IsTeleporting() || ((Character)actor).InAttack() || ((Character)actor).InDodge() || actor.m_wakeupTimer >= 0f || !Object.op_Implicit((Object)(object)((Character)actor).m_animator) || ((Character)actor).m_animator.GetBool("wakeup") || component.GetZDO() == null || component.GetZDO().GetBool(ZDOVars.s_wakeup, true))
				{
					return;
				}
				Inventory inventory = ((Humanoid)actor).GetInventory();
				if (!MantleFoodIntegration.TryFoodCells(inventory, out IReadOnlyList<Tuple<int, int>> cells))
				{
					return;
				}
				pending = false;
				eating = true;
				try
				{
					foreach (Tuple<int, int> item in cells)
					{
						if (!Object.op_Implicit((Object)(object)actor) || (Object)(object)actor != (Object)(object)Player.m_localPlayer || ((Character)actor).IsDead() || inventory != ((Humanoid)actor).GetInventory())
						{
							break;
						}
						ItemData itemAt = inventory.GetItemAt(item.Item1, item.Item2);
						if (itemAt?.m_shared != null && itemAt.m_stack > 0 && (int)itemAt.m_shared.m_itemType == 2 && (itemAt.m_shared.m_food > 0f || itemAt.m_shared.m_foodStamina > 0f || itemAt.m_shared.m_foodEitr > 0f) && actor.CanEat(itemAt, false))
						{
							int stack = itemAt.m_stack;
							attempts++;
							((Humanoid)actor).UseItem(inventory, itemAt, true);
							int num = (inventory.ContainsItem(itemAt) ? itemAt.m_stack : 0);
							if (num < stack)
							{
								consumed += stack - num;
							}
						}
					}
					lastResult = "completed";
				}
				finally
				{
					eating = false;
				}
			}
		}

		internal static object Status()
		{
			return new
			{
				enabled = (enabled?.Value ?? false),
				auto = (AutoFromSlots?.Value ?? false),
				pending = pending,
				world = world,
				playerId = playerId,
				lastResult = lastResult,
				attempts = attempts,
				consumed = consumed,
				graveHistory = graves.Count
			};
		}

		internal static void Shutdown()
		{
			Harmony obj = patches;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			patches = null;
			enabled = null;
			AutoFromSlots = null;
			nextAuto = 0f;
			pending = (eating = false);
			actor = (lastSpawn = null);
			ClearSession();
			attempts = (consumed = 0);
			lastResult = "idle";
		}
	}
	public sealed class LarderImportPreview
	{
		internal LarderReferenceImport Owner { get; }

		internal ConfigConverter Converter { get; }

		internal long Generation { get; }

		public string Source { get; }

		public ConversionPreview Preview { get; }

		public string SourceSha256 { get; }

		public string TargetSha256 { get; }

		public IReadOnlyList<string> Errors { get; }

		public IReadOnlyList<string> Normalizations { get; }

		public IReadOnlyList<ConversionValue> Values => Preview.Values;

		public IReadOnlyList<ConversionConflict> Conflicts => Preview.Conflicts;

		internal LarderImportPreview(LarderReferenceImport owner, string source, ConfigConverter converter, ConversionPreview plan, string sourceHash, string targetHash, long generation, IEnumerable<string> errors, IEnumerable<string> normalizations)
		{
			Owner = owner;
			Source = source;
			Converter = converter;
			Preview = plan;
			SourceSha256 = sourceHash;
			TargetSha256 = targetHash;
			Generation = generation;
			Errors = Array.AsReadOnly(errors.Concat(plan.Errors).ToArray());
			Normalizations = Array.AsReadOnly(normalizations.ToArray());
		}

		public override string ToString()
		{
			return string.Join("\n", new string[1] { LarderText.Format("import_review", Source) }.Concat(Values.Select((ConversionValue v) => v.SourceId + " → " + v.TargetId + " = " + Convert.ToString(v.Value, CultureInfo.InvariantCulture))).Concat(Normalizations.Select((string n) => "Normalized: " + n)).Concat(Errors.Select((string e) => "Invalid: " + e))
				.Concat(Preview.Unknown.Select((string e) => "Skipped: " + e))
				.Concat(Preview.Metadata)
				.Concat(Conflicts.Select((ConversionConflict c) => "Choose source for " + c.TargetId + ": " + string.Join(", ", c.Choices.Select((ConversionValue v) => v.SourceId))))
				.Concat(new string[2]
				{
					"Source SHA256: " + SourceSha256,
					"Target SHA256: " + TargetSha256
				}));
		}
	}
	public sealed class LarderReferenceImport : IDisposable
	{
		private sealed class Target : IValidatedConversionTarget, IConversionTarget
		{
			private readonly LarderReferenceImport owner;

			private readonly LarderImportPreview preview;

			private readonly IValidatedConversionTarget target;

			public int SchemaVersion => ((IConversionTarget)target).SchemaVersion;

			public Target(LarderReferenceImport owner, LarderImportPreview preview)
			{
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_002a: Expected O, but got Unknown
				this.owner = owner;
				this.preview = preview;
				target = (IValidatedConversionTarget)MeadworksConversion.Target(owner.config);
			}

			public void Validate(IReadOnlyDictionary<string, object> values)
			{
				owner.Admission(preview);
				target.Validate(values);
			}

			public void Backup()
			{
				owner.Admission(preview);
				if (!File.Exists(owner.config.ConfigFilePath))
				{
					throw new InvalidOperationException("Save the target before importing; backup is required.");
				}
				((IConversionTarget)target).Backup();
			}

			public void WriteAtomic(IReadOnlyDictionary<string, object> values)
			{
				owner.Admission(preview);
				((IConversionTarget)target).WriteAtomic(values);
			}
		}

		private readonly ConfigFile config;

		private readonly MeadworksLog log;

		private readonly int thread = Thread.CurrentThread.ManagedThreadId;

		private readonly List<IDisposable> registrations = new List<IDisposable>();

		private readonly ConfigEntry<bool> previewFm;

		private readonly ConfigEntry<bool> previewGs;

		private readonly ConfigEntry<bool> applyFm;

		private readonly ConfigEntry<bool> applyGs;

		private readonly ConfigEntry<string> choices;

		private bool disposed;

		private bool offered;

		private static readonly string[] FmRecovery = new string[6] { "1. Health Regeneration", "2. Stamina Regeneration", "3. Eitr Regeneration", "1. Health Regen", "2. Stamina Regen", "3. Eitr Regen" };

		private static readonly string[] FmExertion = new string[4] { "4. Stamina Costs", "5. Base Values", "6. Skills", "7. World Rates" };

		public string ReportPath => Path.Combine(Paths.ConfigPath, "Meadworks", "larder", "import-report.txt");

		public LarderReferenceImport(ConfigFile config, MeadworksLog log)
		{
			this.config = config ?? throw new ArgumentNullException("config");
			this.log = log;
			previewFm = MeadworksConfig.Bind<bool>(config, "Import", "Preview FeastMaster", false, "Preview the existing reference file; source unchanged. Resets after running.", (ConfigScope)0, (AcceptableValueBase)null);
			previewGs = MeadworksConfig.Bind<bool>(config, "Import", "Preview GrindstoneSkills", false, "Preview the existing Cooking reference settings; source unchanged. Resets after running.", (ConfigScope)0, (AcceptableValueBase)null);
			applyFm = MeadworksConfig.Bind<bool>(config, "Import", "Apply FeastMaster", false, "Applies the previewed FeastMaster import. Run it on the host; Larder backs up its settings first. The FeastMaster files are not changed. Turns itself off after running.", (ConfigScope)0, (AcceptableValueBase)null);
			applyGs = MeadworksConfig.Bind<bool>(config, "Import", "Apply GrindstoneSkills", false, "Applies the previewed GrindstoneSkills import. Run it on the host; Larder backs up its settings first. Star settings (removed from Larder) are reported, not imported. Resets after running.", (ConfigScope)0, (AcceptableValueBase)null);
			choices = MeadworksConfig.Bind<string>(config, "Import", "Source Choices", "", "Conflicts shown by Preview: target Section.Key=source id, separated by semicolons.", (ConfigScope)0, (AcceptableValueBase)null);
			Register("FeastMaster", "com.FeastMaster", "larder.feastmaster");
			Register("GrindstoneSkills", "com.GrindstoneSkills", "larder.grindstoneskills");
		}

		private void Register(string source, string guid, string id)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected O, but got Unknown
			registrations.Add(MeadworksConversion.Register(new ConversionRegistration(id, "com.meadworks.larder", guid, source, (Func<bool>)(() => !disposed && File.Exists(SourcePath(source))), (Func<object>)(() => Preview(source)), (Func<object, IReadOnlyDictionary<string, string>, bool, object>)delegate(object document, IReadOnlyDictionary<string, string> resolutions, bool backups)
			{
				if (!backups)
				{
					throw new InvalidOperationException("Larder import requires a target backup.");
				}
				return Apply((document as LarderImportPreview) ?? throw new ArgumentException("Wrong Larder preview."), resolutions);
			})));
		}

		public static string SourcePath(string source)
		{
			if (source != "FeastMaster" && source != "GrindstoneSkills")
			{
				throw new ArgumentException("Unknown reference source.");
			}
			return Path.Combine(Paths.ConfigPath, "com." + source + ".cfg");
		}

		private void Check()
		{
			if (disposed || Thread.CurrentThread.ManagedThreadId != thread)
			{
				throw new InvalidOperationException("Import requires its live owning thread.");
			}
		}

		public LarderImportPreview Preview(string source)
		{
			//IL_0343: Unknown result type (might be due to invalid IL or missing references)
			//IL_034d: Expected O, but got Unknown
			//IL_0348: Unknown result type (might be due to invalid IL or missing references)
			//IL_034f: Expected O, but got Unknown
			//IL_02d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dc: Expected O, but got Unknown
			Check();
			string text = SourcePath(source);
			string text2 = FileHash(text);
			if (text2 == "missing")
			{
				throw new FileNotFoundException("Reference configuration is unavailable.", text);
			}
			IReadOnlyList<KeyValuePair<string, string>> readOnlyList = MeadworksConversion.ReadSourceConfig(text, source);
			if (FileHash(text) != text2)
			{
				throw new InvalidOperationException("Source changed while preparing preview.");
			}
			List<ConversionRule> list = new List<ConversionRule>();
			List<KeyValuePair<string, string>> list2 = new List<KeyValuePair<string, string>>();
			List<string> list3 = new List<string>();
			List<string> list4 = new List<string>();
			foreach (KeyValuePair<string, string> item in readOnlyList)
			{
				string text3 = source + ".";
				if (!item.Key.StartsWith(text3, StringComparison.Ordinal))
				{
					continue;
				}
				string definition = item.Key.Substring(text3.Length);
				ConfigDefinition[] array = config.Keys.Where((ConfigDefinition d) => Matches(source, definition, d)).Take(2).ToArray();
				if (array.Length > 1)
				{
					list3.Add(item.Key + ": ambiguous bound target.");
					continue;
				}
				if (array.Length == 0)
				{
					list2.Add(item);
					continue;
				}
				ConfigEntryBase val = config[array[0]];
				try
				{
					string text4 = item.Value;
					if (definition.EndsWith(".Regen Per 10 Extra Stamina", StringComparison.Ordinal))
					{
						text4 = (float.Parse(text4, CultureInfo.InvariantCulture) / 10f).ToString("R", CultureInfo.InvariantCulture);
					}
					object obj = TomlTypeConverter.ConvertToValue(text4, val.SettingType);
					if (obj is float f && (float.IsNaN(f) || float.IsInfinity(f)))
					{
						throw new FormatException("Nonfinite value.");
					}
					AcceptableValueBase acceptableValues = val.Description.AcceptableValues;
					object obj2 = ((acceptableValues != null) ? acceptableValues.Clamp(obj) : null) ?? obj;
					string text5 = ((obj2 is string text6) ? text6 : Convert.ToString(obj2, CultureInfo.InvariantCulture));
					if (text4 != item.Value || !object.Equals(obj, obj2))
					{
						list4.Add(item.Key + ": " + item.Value + " → " + text5);
					}
					list.Add(new ConversionRule(item.Key, array[0].Section + "." + array[0].Key, val.SettingType.Name, val.SettingType.IsEnum ? string.Join(",", Enum.GetNames(val.SettingType)) : "", false, false));
					list2.Add(new KeyValuePair<string, string>(item.Key, text5));
				}
				catch (Exception ex)
				{
					list3.Add(item.Key + ": " + ex.GetBaseException().Message);
				}
			}
			ConfigConverter val2 = new ConfigConverter(new ConversionMapping(1, source, TargetSchema(), (IEnumerable<ConversionRule>)list));
			return new LarderImportPreview(this, source, val2, val2.Preview((IEnumerable<KeyValuePair<string, string>>)list2), text2, TargetHash(), Plugin.IsReady ? Plugin.Session.Generation : 0, list3, list4);
		}

		private static bool Matches(string source, string raw, ConfigDefinition target)
		{
			string key = target.Key;
			if (source == "GrindstoneSkills")
			{
				if (target.Section != "7 - Cooking")
				{
					return false;
				}
				if (!(raw == "4 - Kitchen." + key))
				{
					return raw == "5 - Experience." + key;
				}
				return true;
			}
			if ((target.Section == "2 - Foods" || target.Section == "3 - Eating") && raw == "0. Global Settings." + key)
			{
				return true;
			}
			if (target.Section == "3 - Eating" && raw == "8. Display." + key)
			{
				return true;
			}
			if (target.Section == "4 - Recovery" && FmRecovery.Any((string s) => raw == s + "." + key))
			{
				return true;
			}
			if (target.Section == "5 - Exertion" && FmExertion.Any((string s) => raw == s + "." + key))
			{
				return true;
			}
			if (target.Section == "6 - Kitchen" && (raw == "9. Kitchen." + key || raw == "9. Fermenter." + key))
			{
				return true;
			}
			if (target.Section.StartsWith("Food: ", StringComparison.Ordinal) || target.Section.StartsWith("Mead: ", StringComparison.Ordinal))
			{
				string text = target.Section.Substring(6);
				if (raw == text + "." + key || (target.Section.StartsWith("Mead: ", StringComparison.Ordinal) && raw == "0Meads_" + text + "." + key))
				{
					return true;
				}
			}
			if (target.Section.StartsWith("Station: ", StringComparison.Ordinal) && raw == target.Section.Substring(9) + "." + key)
			{
				return true;
			}
			if (target.Section == "4 - Recovery")
			{
				if (key == "Continuous Food Healing" && raw == "1. Health Regeneration.Steady Regeneration")
				{
					return true;
				}
				if (key == "Count Food Stamina Only" && raw == "2. Stamina Regeneration.Extra Stamina From Food Only")
				{
					return true;
				}
				if (key == "Regen Per Extra Stamina Point" && raw == "2. Stamina Regeneration.Regen Per 10 Extra Stamina")
				{
					return true;
				}
			}
			return false;
		}

		private int TargetSchema()
		{
			ConfigEntry<int> val = default(ConfigEntry<int>);
			if (!config.TryGetEntry<int>("Meadworks", "SchemaVersion", ref val))
			{
				return 0;
			}
			return val.Value;
		}

		private string TargetHash()
		{
			return Hash(string.Join("\n", from k in config.Keys.OrderBy<ConfigDefinition, string>((ConfigDefinition k) => k.Section, StringComparer.Ordinal).ThenBy<ConfigDefinition, string>((ConfigDefinition k) => k.Key, StringComparer.Ordinal)
				select k.Section.Length + ":" + k.Section + k.Key.Length + ":" + k.Key + "=" + config[k].GetSerializedValue()) + "\n" + FileHash(config.ConfigFilePath));
		}

		private void Admission(LarderImportPreview document)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Invalid comparison between Unknown and I4
			Check();
			if (document == null || document.Owner != this)
			{
				throw new ArgumentException("Preview belongs to another importer.");
			}
			if (!Plugin.IsReady || !Plugin.Ready || (int)Plugin.Session.Role == 3)
			{
				throw new InvalidOperationException("Apply imports on the live authority machine.");
			}
			if (document.Generation != Plugin.Session.Generation || document.SourceSha256 != FileHash(SourcePath(document.Source)) || document.TargetSha256 != TargetHash())
			{
				throw new InvalidOperationException("Source, target or session changed; preview again.");
			}
			ConfigEntryMetadata val = default(ConfigEntryMetadata);
			foreach (ConversionValue value in document.Values)
			{
				if (!MeadworksConfig.TryGetMetadata(config[config.Keys.Single((ConfigDefinition k) => k.Section + "." + k.Key == value.TargetId)], ref val) || val.ProductId != "com.meadworks.larder" || !val.Decision.Allowed || val.Decision.RequiresServerAmendment)
				{
					throw new InvalidOperationException("Import target is locked or has no owning amendment path: " + value.TargetId);
				}
			}
		}

		public ConversionResult Apply(LarderImportPreview document, IReadOnlyDictionary<string, string> sourceChoices)
		{
			Admission(document);
			if (document.Errors.Count != 0)
			{
				throw new ArgumentException(string.Join("; ", document.Errors));
			}
			return document.Converter.Apply(document.Preview, (IConversionTarget)(object)new Target(this, document), sourceChoices, (IReadOnlyDictionary<string, string>)null);
		}

		public void Tick()
		{
			Check();
			if (!offered && (Object)(object)ObjectDB.instance != (Object)null && ObjectDB.instance.m_items.Count != 0)
			{
				offered = true;
				if (File.Exists(SourcePath("FeastMaster")) || File.Exists(SourcePath("GrindstoneSkills")))
				{
					log.Info("Reference settings are available. Preview Larder Import settings or use Almanac; source files remain intact.");
				}
			}
			if (previewFm.Value || applyFm.Value)
			{
				Run("FeastMaster", applyFm.Value);
			}
			if (previewGs.Value || applyGs.Value)
			{
				Run("GrindstoneSkills", applyGs.Value);
			}
		}

		private void Run(string source, bool write)
		{
			List<string> list = new List<string>();
			try
			{
				LarderImportPreview larderImportPreview = Preview(source);
				list.Add(larderImportPreview.ToString());
				if (!write)
				{
					return;
				}
				Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
				string[] array = choices.Value.Split(';');
				foreach (string text in array)
				{
					int num = text.IndexOf('=');
					if (num > 0)
					{
						dictionary.Add(text.Substring(0, num).Trim(), text.Substring(num + 1).Trim());
					}
				}
				ConversionResult val = Apply(larderImportPreview, dictionary);
				list.Add("Applied " + val.Written + "; target backup: " + val.BackedUp);
			}
			catch (Exception ex)
			{
				list.Add("Import stopped: " + ex.GetType().Name + ": " + ex.GetBaseException().Message);
			}
			finally
			{
				ConfigEntry<bool> obj = previewFm;
				ConfigEntry<bool> obj2 = applyFm;
				ConfigEntry<bool> obj3 = previewGs;
				bool flag = (applyGs.Value = false);
				bool flag3 = (obj3.Value = flag);
				bool value = (obj2.Value = flag3);
				obj.Value = value;
				Directory.CreateDirectory(Path.GetDirectoryName(ReportPath));
				File.WriteAllLines(ReportPath, list);
			}
		}

		private static string FileHash(string path)
		{
			if (!File.Exists(path))
			{
				return "missing";
			}
			if (new FileInfo(path).Length > 1048576)
			{
				throw new InvalidDataException("Configuration exceeds 1 MiB.");
			}
			using FileStream inputStream = File.OpenRead(path);
			using SHA256 sHA = SHA256.Create();
			return string.Concat(from b in sHA.ComputeHash(inputStream)
				select b.ToString("X2", CultureInfo.InvariantCulture));
		}

		private static string Hash(string text)
		{
			using SHA256 sHA = SHA256.Create();
			return string.Concat(from b in sHA.ComputeHash(Encoding.UTF8.GetBytes(text))
				select b.ToString("X2", CultureInfo.InvariantCulture));
		}

		public void Dispose()
		{
			if (disposed)
			{
				return;
			}
			disposed = true;
			foreach (IDisposable registration in registrations)
			{
				registration.Dispose();
			}
			registrations.Clear();
		}
	}
	internal static class LarderSettingPresentation
	{
		internal static ConfigPresentation<float> For(string kind, string key, Func<string> heading, Func<object[]> help, Func<string>? label = null)
		{
			return new ConfigPresentation<float>("com.meadworks.larder", (Func<ConfigEntry<float>, string, ConfigPresentationText>)delegate(ConfigEntry<float> _, string language)
			{
				//IL_0093: Unknown result type (might be due to invalid IL or missing references)
				//IL_0099: Expected O, but got Unknown
				ProductTranslations translations = LarderText.Translations;
				return (translations != null && !(MeadworksLanguages.Normalize(language) == "English")) ? new ConfigPresentationText((label != null) ? label() : translations.Text(SettingTemplates.NameKey(kind, key), language), translations.Format(SettingTemplates.HeadingKey(kind), language, new object[1] { heading() }), translations.Format(SettingTemplates.HelpKey(kind, key), language, help())) : ((ConfigPresentationText)null);
			});
		}
	}
	public static class LarderText
	{
		private static MeadworksTranslations? catalog;

		internal static ProductTranslations? Translations { get; private set; }

		public static void Initialize(BepInPlugin metadata, ManualLogSource log)
		{
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Expected O, but got Unknown
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			Assembly assembly = typeof(LarderText).Assembly;
			foreach (string item in MeadworksLanguages.Supported)
			{
				using Stream stream = assembly.GetManifestResourceStream("Larder.localization." + item + ".json");
				if (stream != null)
				{
					using StreamReader streamReader = new StreamReader(stream);
					dictionary.Add(item, streamReader.ReadToEnd());
				}
			}
			ProductTranslations val = new ProductTranslations("larder", (IReadOnlyDictionary<string, string>)dictionary);
			catalog = MeadworksTranslations.Register(metadata, val, (string)null, log);
			Translations = val;
		}

		public static string Text(string key)
		{
			MeadworksTranslations? obj = catalog;
			return ((obj != null) ? obj.Text(key) : null) ?? key;
		}

		public static string Format(string key, params object[] arguments)
		{
			MeadworksTranslations? obj = catalog;
			return ((obj != null) ? obj.Format(key, arguments) : null) ?? key;
		}

		public static string Token(string key)
		{
			return "$meadworks_larder_" + key;
		}

		public static void Tick()
		{
			MeadworksTranslations? obj = catalog;
			if (obj != null)
			{
				obj.Update();
			}
		}

		public static void Shutdown()
		{
			MeadworksTranslations? obj = catalog;
			if (obj != null)
			{
				obj.Dispose();
			}
			catalog = null;
			Translations = null;
		}
	}
	public static class MantleFoodIntegration
	{
		public const string CapabilityId = "meadworks.mantle.foodslots";

		public const string ProviderId = "com.meadworks.mantle";

		private static readonly Version MinimumVersion = new Version(1, 0);

		public static ItemData SelectSameFood(Player player, Inventory inventory, string sharedName)
		{
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || inventory == null || inventory != ((Humanoid)player).GetInventory() || string.IsNullOrEmpty(sharedName))
			{
				return null;
			}
			if (!TryFoodCells(inventory, out IReadOnlyList<Tuple<int, int>> cells))
			{
				return inventory.GetItem(sharedName, -1, false);
			}
			List<ItemData> allItems = inventory.GetAllItems();
			List<AutoEatCandidate> list = new List<AutoEatCandidate>(allItems.Count);
			for (int i = 0; i < allItems.Count; i++)
			{
				ItemData val = allItems[i];
				bool flag = false;
				if (val != null)
				{
					foreach (Tuple<int, int> item in cells)
					{
						if (val.m_gridPos.x == item.Item1 && val.m_gridPos.y == item.Item2)
						{
							flag = true;
							break;
						}
					}
				}
				list.Add(new AutoEatCandidate((val != null && val.m_stack > 0 && val.m_worldLevel >= Game.m_worldLevel) ? val.m_shared?.m_name : null, flag, i));
			}
			int num = AutoEat.Select(sharedName, (IReadOnlyList<AutoEatCandidate>)list);
			if (num >= 0)
			{
				return allItems[num];
			}
			return null;
		}

		internal static bool TryFoodCells(Inventory inventory, out IReadOnlyList<Tuple<int, int>> cells)
		{
			cells = null;
			if (!Plugin.IsReady || !Plugin.Session.AuthorityReady || Plugin.Session.WorldId == 0L)
			{
				return false;
			}
			try
			{
				CapabilityRegistry capabilities = Plugin.Services.Capabilities;
				IReadOnlyList<CapabilityMetadata> readOnlyList = capabilities.Providers("meadworks.mantle.foodslots");
				Func<IReadOnlyList<Tuple<int, int>>> func = default(Func<IReadOnlyList<Tuple<int, int>>>);
				if (readOnlyList.Count != 1 || readOnlyList[0].Owner != "com.meadworks.mantle" || !capabilities.TryResolve<Func<IReadOnlyList<Tuple<int, int>>>>("meadworks.mantle.foodslots", MinimumVersion, ref func))
				{
					return false;
				}
				IReadOnlyList<Tuple<int, int>> readOnlyList2 = func();
				if (readOnlyList2 == null || readOnlyList2.Count == 0 || readOnlyList2.Count > 5)
				{
					return false;
				}
				List<Tuple<int, int>> list = new List<Tuple<int, int>>(readOnlyList2.Count);
				foreach (Tuple<int, int> item in readOnlyList2)
				{
					if (item == null || item.Item1 < 0 || item.Item1 >= inventory.GetWidth() || item.Item2 < 0 || item.Item2 >= inventory.GetHeight() || list.Contains(item))
					{
						return false;
					}
					list.Add(item);
				}
				cells = list;
				return true;
			}
			catch
			{
				return false;
			}
		}
	}
	public enum SafeStaminaAction
	{
		Sprint,
		Jump,
		Build,
		Swim,
		Attacks,
		Sneak,
		Encumbered
	}
	public static class SafeStamina
	{
		private const string Section = "5 - Exertion";

		private const float ScanInterval = 0.25f;

		private static ConfigFile config;

		private static MeadworksLog log;

		private static readonly List<ConfigEntryBase> entries = new List<ConfigEntryBase>();

		private static readonly Dictionary<SafeStaminaAction, ConfigEntry<bool>> actions = new Dictionary<SafeStaminaAction, ConfigEntry<bool>>();

		private static ConfigEntry<bool> enabled;

		private static ConfigEntry<float> radius;

		private static ConfigEntry<string> ignoredCreatures;

		private static string ignoredSpecification;

		private static IgnoredCreatures ignoredNames = new IgnoredCreatures("");

		private static WeakReference<Player> scannedPlayer;

		private static Vector3 scannedPosition;

		private static float scanExpires;

		private static bool scannedSafe;

		private static bool wasEnabled;

		private static bool scanWarning;

		private static long generation;

		public static bool Enabled
		{
			get
			{
				if (enabled != null && NativeFeastMaster.Active("Exertion"))
				{
					return MeadworksConfig.Effective<bool>(enabled, "com.meadworks.larder", (ConfigScope)1, false, true);
				}
				return false;
			}
		}

		public static void Initialize(ConfigFile file, MeadworksLog logger)
		{
			Shutdown();
			config = file ?? throw new ArgumentNullException("file");
			log = logger;
			enabled = Bind("Safe Area Stamina", value: false, "Actions switched on below cost no stamina while no hostile creature is within Safe Area Radius. Off keeps the ordinary Exertion rules.");
			radius = Bind("Safe Area Radius", 25f, "Radius in meters checked for hostile creatures. 0 counts only creatures touching you. Only creatures in loaded areas are checked.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, float.MaxValue));
			ignoredCreatures = Bind("Safe Area Ignored Creatures", "", "Comma-separated creature prefab or displayed names that do not count as nearby enemies. Empty ignores nothing. Matching is case-insensitive.");
			foreach (SafeStaminaAction value in Enum.GetValues(typeof(SafeStaminaAction)))
			{
				actions.Add(value, Bind("Safe Area " + value, value != SafeStaminaAction.Encumbered, value switch
				{
					SafeStaminaAction.Sneak => "Sneaking costs no stamina while safe.", 
					SafeStaminaAction.Build => "Placing with the hammer, hoe and cultivator costs no stamina while safe.", 
					SafeStaminaAction.Jump => "Jumping costs no stamina while safe.", 
					SafeStaminaAction.Sprint => "Sprinting costs no stamina while safe.", 
					SafeStaminaAction.Swim => "Free safe swimming; this also prevents stamina-driven drowning while safe.", 
					SafeStaminaAction.Encumbered => "While safe, encumbered walking costs no stamina and blocked stamina regeneration is allowed. Off keeps the ordinary encumbrance penalty. Other safe-action switches still apply.", 
					_ => "Weapon and tool swings cost no stamina while safe. Holding a drawn bow still costs stamina.", 
				}));
			}
			config.SettingChanged += Changed;
			config.ConfigReloaded += Reloaded;
			PatchSwitch.MarkDirty();
		}

		private static ConfigEntry<T> Bind<T>(string key, T value, string description, AcceptableValueBase range = null)
		{
			ConfigEntry<T> val = MeadworksConfig.Bind<T>(config, "5 - Exertion", key, value, description, (ConfigScope)1, range);
			MeadworksBlockedOptions.Track("com.meadworks.larder", (ConfigEntryBase)(object)val, (ConfigScope)1, true);
			entries.Add((ConfigEntryBase)(object)val);
			return val;
		}

		private static bool ActionEnabled(SafeStaminaAction action)
		{
			if (actions.TryGetValue(action, out ConfigEntry<bool> value))
			{
				return MeadworksConfig.Effective<bool>(value, "com.meadworks.larder", (ConfigScope)1, true, true);
			}
			return false;
		}

		public static bool Applies(SafeStaminaAction action)
		{
			if (Enabled)
			{
				return ActionEnabled(action);
			}
			return false;
		}

		public static void Tick()
		{
			if (config != null)
			{
				bool flag = Enabled;
				long num = (Plugin.IsReady ? Plugin.Session.Generation : 0);
				if (flag != wasEnabled || num != generation)
				{
					wasEnabled = flag;
					generation = num;
					Invalidate();
				}
			}
		}

		private static void Changed(object sender, SettingChangedEventArgs args)
		{
			Invalidate();
		}

		private static void Reloaded(object sender, EventArgs args)
		{
			Invalidate();
		}

		private static void Invalidate()
		{
			scannedPlayer = null;
			scanExpires = 0f;
			PatchSwitch.MarkDirty();
		}

		private static bool LocalReady(Player player)
		{
			if (Enabled && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead() && !((Character)player).InIntro() && !((Character)player).IsTeleporting() && (Object)(object)((Character)player).m_nview != (Object)null && ((Character)player).m_nview.IsValid())
			{
				return ((Character)player).m_nview.IsOwner();
			}
			return false;
		}

		private static bool Safe(Player player)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Expected O, but got Unknown
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			if (!LocalReady(player))
			{
				return false;
			}
			Vector3 position = ((Component)player).transform.position;
			float time = Time.time;
			if (scannedPlayer != null && scannedPlayer.TryGetTarget(out Player target) && target == player && time < scanExpires)
			{
				Vector3 val = position - scannedPosition;
				if (((Vector3)(ref val)).sqrMagnitude < 0.25f)
				{
					return scannedSafe;
				}
			}
			bool flag = false;
			try
			{
				float num = MeadworksConfig.Effective<float>(radius, "com.meadworks.larder", (ConfigScope)1, 25f, true);
				if (float.IsNaN(num) || float.IsInfinity(num) || num < 0f)
				{
					return false;
				}
				string text = MeadworksConfig.Effective<string>(ignoredCreatures, "com.meadworks.larder", (ConfigScope)1, "", true);
				if (!string.Equals(text, ignoredSpecification, StringComparison.Ordinal))
				{
					ignoredNames = new IgnoredCreatures(text);
					ignoredSpecification = text;
				}
				flag = true;
				foreach (Character allCharacter in Character.GetAllCharacters())
				{
					if ((Object)(object)allCharacter != (Object)null && (Object)(object)allCharacter != (Object)(object)player && !allCharacter.IsDead() && Vector3.Distance(position, ((Component)allCharacter).transform.position) <= num && BaseAI.IsEnemy((Character)(object)player, allCharacter) && !ignoredNames.Contains(Utils.GetPrefabName(((Component)allCharacter).gameObject), allCharacter.GetHoverName()))
					{
						flag = false;
						break;
					}
				}
			}
			catch (Exception ex)
			{
				flag = false;
				if (!scanWarning)
				{
					scanWarning = true;
					MeadworksLog obj = log;
					if (obj != null)
					{
						obj.Warning("Safe-area scan unavailable; ordinary stamina costs retained: " + ex.GetType().Name);
					}
				}
			}
			scannedPlayer = new WeakReference<Player>(player);
			scannedPosition = position;
			scannedSafe = flag;
			scanExpires = time + 0.25f;
			return flag;
		}

		public static float Multiplier(Player player, SafeStaminaAction action, float value)
		{
			if (!(value > 0f) || !Applies(action) || !Safe(player))
			{
				return value;
			}
			return 0f;
		}

		public static float Cost(Player player, SafeStaminaAction action, float value)
		{
			if (!(value > 0f) || !Applies(action) || !Safe(player))
			{
				return value;
			}
			return 0f;
		}

		public static bool ProtectsFromDrowning(Player player)
		{
			if (Applies(SafeStaminaAction.Swim))
			{
				return Safe(player);
			}
			return false;
		}

		public static bool AllowsBlockedRegeneration(Player player)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: 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_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			if (!LocalReady(player))
			{
				return false;
			}
			StaminaBlock val = (StaminaBlock)0;
			if (((Character)player).IsEncumbered())
			{
				val = (StaminaBlock)(val | 0x10);
			}
			if (((Character)player).IsSwimming() && !((Character)player).IsOnGround())
			{
				val = (StaminaBlock)(val | 1);
			}
			if (((Character)player).InAttack())
			{
				val = (StaminaBlock)(val | 2);
			}
			if (((Character)player).InDodge())
			{
				val = (StaminaBlock)(val | 4);
			}
			if (((Character)player).m_wallRunning)
			{
				val = (StaminaBlock)(val | 8);
			}
			if (SafeStaminaRules.AllowsBlockedRegeneration(val, ActionEnabled(SafeStaminaAction.Swim), ActionEnabled(SafeStaminaAction.Attacks), ActionEnabled(SafeStaminaAction.Encumbered)))
			{
				return Safe(player);
			}
			return false;
		}

		public static void Shutdown()
		{
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Expected O, but got Unknown
			if (config != null)
			{
				config.SettingChanged -= Changed;
				config.ConfigReloaded -= Reloaded;
			}
			foreach (ConfigEntryBase entry in entries)
			{
				MeadworksBlockedOptions.Untrack(entry);
			}
			entries.Clear();
			actions.Clear();
			enabled = null;
			radius = null;
			config = null;
			log = null;
			ignoredCreatures = null;
			ignoredSpecification = null;
			ignoredNames = new IgnoredCreatures("");
			scannedPlayer = null;
			scanExpires = 0f;
			wasEnabled = (scanWarning = false);
			generation = 0L;
			PatchSwitch.MarkDirty();
		}
	}
	[BepInPlugin("com.meadworks.larder", "Larder", "0.2.16")]
	[BepInDependency("com.meadworks.crucible", "0.3.32")]
	[BepInDependency("com.meadworks.crucible.ui", "0.3.21")]
	[BepInDependency("com.jotunn.jotunn", "2.30.2")]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "com.meadworks.larder";

		public const string Name = "Larder";

		public const string Version = "0.2.16";

		public const string Family = "Hearth";

		private ProductRegistration? registration;

		private MeadworksLog? log;

		private LarderReferenceImport? referenceImport;

		private bool started;

		private static bool foodsAside;

		private static bool cookingAside;

		private static bool restDelayAside;

		private const string FoodSourceId = "com.meadworks.larder.food";

		private const string CookingSourceId = "com.meadworks.larder.cooking";

		private const string RestDelaySourceId = "com.meadworks.larder.rest_delay";

		public static bool Ready { get; private set; }

		public static bool AllowsUi { get; private set; }

		public static bool SteppedAside
		{
			get
			{
				if (foodsAside)
				{
					return cookingAside;
				}
				return false;
			}
		}

		public static bool RestDelaySteppedAside => restDelayAside;

		public static bool GameplayAvailable
		{
			get
			{
				ConfigBlockReason val = default(ConfigBlockReason);
				if (Ready)
				{
					return MeadworksAuthority.ProductActive("com.meadworks.larder", ref val);
				}
				return false;
			}
		}

		public static bool ModuleActive(string module)
		{
			if (!GameplayAvailable)
			{
				return false;
			}
			switch (module)
			{
			case "Eating":
			case "Exertion":
			case "Recovery":
			case "Kitchen":
			case "Foods":
				return !foodsAside;
			case "Cooking":
				return !cookingAside;
			case "Cheats":
				if (!foodsAside)
				{
					return !cookingAside;
				}
				return false;
			default:
				return false;
			}
		}

		private static IReadOnlyList<string> Describe()
		{
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			return new string[5]
			{
				"food/eating/recovery/exertion/kitchen: " + (foodsAside ? "steps aside for FeastMaster" : (ModuleActive("Foods") ? "active" : "session inactive")),
				"Cooking: " + (cookingAside ? "steps aside for GrindstoneSkills" : (ModuleActive("Cooking") ? "active" : "session inactive")),
				"Rested delay: " + (restDelayAside ? "steps aside for OpenKeep" : (RestDelayPatch.Prepare() ? "custom delay" : "native delay")),
				"cheat gate: " + ((object)MeadworksAuthority.Decide("com.meadworks.larder", (ConfigScope)2, true).Reason/*cast due to .constrained prefix*/).ToString(),
				"native patch groups: " + string.Join(", ", (from method in Harmony.GetAllPatchedMethods().Where(delegate(MethodBase method)
					{
						Patches patchInfo = Harmony.GetPatchInfo(method);
						return patchInfo != null && patchInfo.Owners.Any((string owner) => owner.StartsWith("com.meadworks.larder.", StringComparison.Ordinal));
					})
					select method.DeclaringType?.Name + "." + method.Name).OrderBy<string, string>((string name) => name, StringComparer.Ordinal))
			};
		}

		private void Awake()
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Expected O, but got Unknown
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Expected O, but got Unknown
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Expected O, but got Unknown
			log = MeadworksLog.For(((BaseUnityPlugin)this).Logger, "Larder");
			try
			{
				if (!Plugin.IsReady)
				{
					throw new InvalidOperationException("Required Crucible is not ready");
				}
				registration = Plugin.Services.Register(new ProductMetadata("com.meadworks.larder", "Larder", new Version("0.2.16"), new Version(0, 1), new Version(0, 1), (MultiplayerPolicy)2, "Hearth"));
				AllowsUi = MeadworksHeadless.AllowsUi;
				LarderText.Initialize(((BaseUnityPlugin)this).Info.Metadata, ((BaseUnityPlugin)this).Logger);
				if (Plugin.Services.StepAside.Declared("com.meadworks.larder.food").Count == 0)
				{
					MeadworksStepAside.Declare("com.meadworks.larder.food", "Larder food and kitchen rules", (IEnumerable<SourceMod>)(object)new SourceMod[1]
					{
						new SourceMod("com.FeastMaster", "FeastMaster")
					});
				}
				if (Plugin.Services.StepAside.Declared("com.meadworks.larder.cooking").Count == 0)
				{
					MeadworksStepAside.Declare("com.meadworks.larder.cooking", "Larder Cooking rules", (IEnumerable<SourceMod>)(object)new SourceMod[1]
					{
						new SourceMod("com.GrindstoneSkills", "GrindstoneSkills")
					});
				}
				if (Plugin.Services.StepAside.Declared("com.meadworks.larder.rest_delay").Count == 0)
				{
					MeadworksStepAside.Declare("com.meadworks.larder.rest_delay", "Larder Rested delay", (IEnumerable<SourceMod>)(object)new SourceMod[1]
					{
						new SourceMod("milkyteam.openkeep", "OpenKeep")
					});
				}
				if (AllowsUi)
				{
					NativeFoodHud.Register();
				}
				NativeFeastMaster.Initialize(((BaseUnityPlugin)this).Config, log, ModuleActive);
				SafeStamina.Initialize(((BaseUnityPlugin)this).Config, log);
				FoodTopUp.Initialize(((BaseUnityPlugin)this).Config, registration);
				registration.AddCapability<Func<string, Tuple<float, bool>>>("meadworks.larder.kitchen_uses", new Version(1, 0), (Func<string, Tuple<float, bool>>)((string prefab) => (!ModuleActive("Cooking")) ? Tuple.Create(0f, item2: false) : Tuple.Create(KitchenUses.BestDish(prefab), KitchenUses.IsIngredient(prefab))));
				registration.AddCapability<Func<Tuple<int, float>>>("meadworks.larder.hud.foodslots", new Version(1, 0), (Func<Tuple<int, float>>)(() => (!AllowsUi || (!ModuleActive("Eating") && !FoodSlotsHud.CurrentHudExtended)) ? Tuple.Create(0, 0f) : NativeFoodHud.CapabilityGeometry()));
				registration.AddCapability<Func<int>>("meadworks.larder.foodslots.count", new Version(1, 0), (Func<int>)(() => (!ModuleActive("Eating")) ? (-1) : NativeFeastMaster.Value<int>(ExtraPotions.Larder.Runtime.Adopted.FeastMaster.Settings.FoodSlots)));
				registration.AddCapability<Func<IReadOnlyList<string>>>("meadworks.diagnostics.com.meadworks.larder", new Version(1, 0), (Func<IReadOnlyList<string>>)Describe);
				Ready = true;
				log.Info(AllowsUi ? "Larder registered; native food and Cooking modules start after discovery." : "Larder registered headless; native world rules are available without HUD objects.");
			}
			catch (Exception ex)
			{
				Shutdown();
				log.Error((object)("Larder initialization rejected: " + ex));
			}
		}

		private void Start()
		{
			if (!Ready || log == null)
			{
				return;
			}
			try
			{
				foodsAside = MeadworksStepAside.ShouldYield("com.meadworks.larder.food");
				cookingAside = MeadworksStepAside.ShouldYield("com.meadworks.larder.cooking");
				restDelayAside = MeadworksStepAside.ShouldYield("com.meadworks.larder.rest_delay");
				PatchSwitch.MarkDirty();
				NativeCooking.Initialize(((BaseUnityPlugin)this).Config, log, () => ModuleActive("Cooking"));
				AnyQuality.Install(log);
				referenceImport = new LarderReferenceImport(((BaseUnityPlugin)this).Config, log);
				started = true;
			}
			catch (Exception ex)
			{
				log.Error((object)("Larder modules rejected: " + ex));
				Shutdown();
			}
		}

		private void Update()
		{
			if (Ready)
			{
				LarderText.Tick();
				if (started)
				{
					SafeStamina.Tick();
					NativeFeastMaster.Tick();
					FoodTopUp.Tick();
					NativeCooking.Tick();
					referenceImport?.Tick();
				}
			}
		}

		private void OnDestroy()
		{
			Shutdown();
		}

		private void Shutdown()
		{
			Ready = false;
			started = false;
			referenceImport?.Dispose();
			referenceImport = null;
			Action[] array = new Action[6]
			{
				AnyQuality.Shutdown,
				FoodTopUp.Shutdown,
				NativeCooking.Shutdown,
				SafeStamina.Shutdown,
				NativeFeastMaster.Shutdown,
				LarderText.Shutdown
			};
			foreach (Action action in array)
			{
				try
				{
					action();
				}
				catch (Exception ex)
				{
					MeadworksLog? obj = log;
					if (obj != null)
					{
						obj.Warning("Larder teardown " + action.Method.DeclaringType?.FullName + "." + action.Method.Name + ": " + ex);
					}
				}
			}
			ProductRegistration? obj2 = registration;
			if (obj2 != null)
			{
				obj2.Dispose();
			}
			registration = null;
			foodsAside = false;
			cookingAside = false;
			restDelayAside = false;
			AllowsUi = false;
		}
	}
}
namespace ExtraPotions.Larder.Runtime.Adopted.GrindstoneCooking
{
	public sealed class CookingSetting<T>
	{
		private readonly T safe;

		public ConfigEntry<T> Entry { get; }

		public ConfigScope Scope { get; }

		public T Value => MeadworksConfig.Effective<T>(Entry, "com.meadworks.larder", Scope, safe, true);

		internal CookingSetting(ConfigEntry<T> entry, ConfigScope scope, T safe)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			Entry = entry;
			Scope = scope;
			this.safe = safe;
		}
	}
	public static class CookingSettings
	{
		private static readonly List<ConfigEntryBase> entries = new List<ConfigEntryBase>();

		public static IReadOnlyList<ConfigEntryBase> Entries => entries.AsReadOnly();

		public static CookingSetting<T> Bind<T>(ConfigFile config, string section, string key, T value, string description, AcceptableValueBase acceptableValues = null, ConfigScope scope = (ConfigScope)1)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			return Bind(config, section, key, value, description, acceptableValues, scope, new object[0]);
		}

		public static CookingSetting<T> Bind<T>(ConfigFile config, string section, string key, T value, string description, AcceptableValueBase acceptableValues, ConfigScope scope, params object[] tags)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<T> val = MeadworksConfig.Bind<T>(config, section, key, value, new ConfigDescription(description, acceptableValues, tags), scope);
			MeadworksBlockedOptions.Track("com.meadworks.larder", (ConfigEntryBase)(object)val, scope, true);
			entries.Add((ConfigEntryBase)(object)val);
			return new CookingSetting<T>(val, scope, value);
		}

		public static void Initialize(ConfigFile config)
		{
			Shutdown();
			KitchenSettings.Initialize(config);
			ExperienceSettings.Initialize(config);
			DisplaySettings.Initialize(config);
		}

		public static void Shutdown()
		{
			foreach (ConfigEntryBase entry in entries)
			{
				MeadworksBlockedOptions.Untrack(entry);
			}
			entries.Clear();
		}
	}
	public static class Settings
	{
		public static AcceptableValueRange<float> UpTo(float max)
		{
			return new AcceptableValueRange<float>(0f, max);
		}
	}
	public static class CookLevel
	{
		public const SkillType Skill = (SkillType)105;

		public const float MaxLevel = 100f;

		public static float Local()
		{
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null))
			{
				return ((Character)localPlayer).GetSkillLevel((SkillType)105);
			}
			return 0f;
		}

		public static float Factor(float level)
		{
			return Mathf.Clamp01(level / 100f);
		}
	}
	public static class DisplaySettings
	{
		public static CookingSetting<bool> FermenterTimer { get; private set; }

		public static void Initialize(ConfigFile config)
		{
			FermenterTimer = CookingSettings.Bind(config, "6 - Kitchen", "Fermenter Timer", value: false, "Shows remaining brew time on an accessible, sheltered fermenter while the Cooking module is active. Your local display preference; follows the current Kitchen duration and Cooking speed.", null, (ConfigScope)0);
		}
	}
	public static class ExperienceSettings
	{
		public const string Section = "7 - Cooking";

		public static CookingSetting<float> Multiplier { get; private set; }

		public static CookingSetting<bool> TierScaling { get; private set; }

		public static CookingSetting<float> TierReferenceValue { get; private set; }

		public static CookingSetting<float> TierMaximum { get; private set; }

		public static CookingSetting<float> DiscoveryMultiplier { get; private set; }

		public static CookingSetting<float> FermenterTap { get; private set; }

		public static void Initialize(ConfigFile config)
		{
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Expected O, but got Unknown
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Expected O, but got Unknown
			Multiplier = CookingSettings.Bind(config, "7 - Cooking", "Experience Multiplier", 1f, "Multiplies all Cooking experience earned at kitchens.", (AcceptableValueBase)(object)Settings.UpTo(10f), (ConfigScope)1);
			TierScaling = CookingSettings.Bind(config, "7 - Cooking", "Tier Scaling", value: true, "Richer dishes teach more: experience is multiplied by the dish's health + stamina + eitr divided by Tier Reference Food Value, never below 1.", null, (ConfigScope)1);
			TierReferenceValue = CookingSettings.Bind(config, "7 - Cooking", "Tier Reference Food Value", 50f, "The food value (health + stamina + eitr) that earns the game's own experience.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 500f), (ConfigScope)1, (object)new ConfigDependency((Func<bool>)(() => TierScaling.Value), (Func<string>)(() => "Requires Cooking: Tier Scaling."), (Func<bool>)null));
			TierMaximum = CookingSettings.Bind(config, "7 - Cooking", "Tier Maximum Multiplier", 3f, "The most Tier Scaling can multiply experience by.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), (ConfigScope)1, (object)new ConfigDependency((Func<bool>)(() => TierScaling.Value), (Func<string>)(() => "Requires Cooking: Tier Scaling."), (Func<bool>)null));
			DiscoveryMultiplier = CookingSettings.Bind(config, "7 - Cooking", "Discovery Multiplier", 3f, "Experience multiplier the first time a character makes each dish.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), (ConfigScope)1);
			FermenterTap = CookingSettings.Bind(config, "7 - Cooking", "Fermenter Tap Experience", 1f, "Cooking experience for tapping a fermenter (the game gives none). 0 turns it off.", (AcceptableValueBase)(object)Settings.UpTo(10f), (ConfigScope)1);
		}
	}
	public static class ExtraFood
	{
		public struct State
		{
			public bool Applied;

			public float Chance;

			public int Amount;
		}

		public static State Apply()
		{
			InventoryGui instance = InventoryGui.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return default(State);
			}
			State result = new State
			{
				Applied = true,
				Chance = instance.m_craftBonusChance,
				Amount = instance.m_craftBonusAmount
			};
			instance.m_craftBonusChance = Mathf.Clamp01(KitchenSettings.ExtraFoodChance.Value / 100f);
			instance.m_craftBonusAmount = Perks.ExtraFoodAmount;
			return result;
		}

		public static void Restore(State state)
		{
			InventoryGui instance = InventoryGui.instance;
			if (state.Applied && !((Object)(object)instance == (Object)null))
			{
				instance.m_craftBonusChance = state.Chance;
				instance.m_craftBonusAmount = state.Amount;
			}
		}
	}
	public static class Keys
	{
		public const string SlotLevel = "larder_level";

		public const string BaseLevel = "larder_base_level";

		public const string RpcAddItem = "larder_AddItem";

		public const string RpcAddBase = "larder_AddBase";

		public const string MadeDishes = "larder.made";

		public const string LegacyMadeDishes = "grindstone_made";

		public static string Legacy(string key)
		{
			if (!key.StartsWith("larder_", StringComparison.Ordinal))
			{
				return key;
			}
			return "grindstone_" + key.Substring(7);
		}

		public static int ReadInt(ZDO zdo, string key, int fallback = 0)
		{
			if (zdo != null)
			{
				return zdo.GetInt(StringExtensionMethods.GetStableHashCode(key), zdo.GetInt(StringExtensionMethods.GetStableHashCode(Legacy(key)), fallback));
			}
			return fallback;
		}

		public static float ReadFloat(ZDO zdo, string key, float fallback = 0f)
		{
			if (zdo != null)
			{
				return zdo.GetFloat(StringExtensionMethods.GetStableHashCode(key), zdo.GetFloat(StringExtensionMethods.GetStableHashCode(Legacy(key)), fallback));
			}
			return fallback;
		}

		public static long ReadLong(ZDO zdo, string key, long fallback = 0L)
		{
			if (zdo != null)
			{
				return zdo.GetLong(StringExtensionMethods.GetStableHashCode(key), zdo.GetLong(StringExtensionMethods.GetStableHashCode(Legacy(key)), fallback));
			}
			return fallback;
		}
	}
	public static class Kitchen
	{
		private static readonly HashSet<string> itemNames = new HashSet<string>();

		private static readonly Dictionary<string, float> values = new Dictionary<string, float>();

		internal static int Count => itemNames.Count;

		public static bool IsKitchen(CookingStation station)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			if (NativeCooking.Active && (Object)(object)station != (Object)null)
			{
				return (int)station.m_skill == 105;
			}
			return false;
		}

		public static bool IsKitchen(CraftingStation station)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			if (NativeCooking.Active && (Object)(object)station != (Object)null)
			{
				return (int)station.m_craftingSkill == 105;
			}
			return false;
		}

		public static bool IsKitchenItem(ItemData item)
		{
			if (item?.m_shared != null)
			{
				return itemNames.Contains(item.m_shared.m_name);
			}
			return false;
		}

		public static float FoodValue(ItemData item)
		{
			if (item?.m_shared == null)
			{
				return 0f;
			}
			if (!values.TryGetValue(item.m_shared.m_name, out var value))
			{
				return Value(item.m_shared);
			}
			return value;
		}

		public static ItemDrop ItemPrefab(string prefabName)
		{
			GameObject val = (((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrEmpty(prefabName)) ? null : ObjectDB.instance.GetItemPrefab(prefabName));
			if (!((Object)(object)val == (Object)null))
			{
				return val.GetComponent<ItemDrop>();
			}
			return null;
		}

		internal static float Value(SharedData shared)
		{
			return shared.m_food + shared.m_foodStamina + shared.m_foodEitr;
		}

		internal static void AddItem(ItemDrop drop)
		{
			if (!((Object)(object)drop == (Object)null) && drop.m_itemData?.m_shared != null && !((Object)(object)((Component)drop).GetComponent<Piece>() != (Object)null) && !((Object)(object)((Component)drop).GetComponent<Feast>() != (Object)null))
			{
				SharedData shared = drop.m_itemData.m_shared;
				if (shared.m_maxStackSize > 1)
				{
					itemNames.Add(shared.m_name);
				}
			}
		}

		internal static void AddConversion(ItemDrop from, ItemDrop to)
		{
			if (from?.m_itemData?.m_shared != null && to?.m_itemData?.m_shared != null)
			{
				SharedData shared = from.m_itemData.m_shared;
				if (Value(shared) <= 0f && !values.ContainsKey(shared.m_name))
				{
					values[shared.m_name] = Value(to.m_itemData.m_shared);
				}
			}
		}

		internal static void Clear()
		{
			itemNames.Clear();
			values.Clear();
		}
	}
	public static class KitchenDiscovery
	{
		[HarmonyPatch(typeof(ZNetScene), "Awake")]
		private static class SceneAwake
		{
			[HarmonyPostfix]
			[HarmonyPriority(0)]
			private static void Postfix()
			{
				Discover();
			}
		}

		[HarmonyPatch(typeof(ObjectDB), "Awake")]
		private static class DatabaseAwake
		{
			[HarmonyPostfix]
			[HarmonyPriority(0)]
			private static void Postfix()
			{
				Discover();
			}
		}

		public static void Discover()
		{
			if (!NativeCooking.Active)
			{
				return;
			}
			int count = Kitchen.Count;
			if ((Object)(object)ZNetScene.instance != (Object)null)
			{
				foreach (PrefabIndex.Converter converter in PrefabIndex.Scene().Converters)
				{
					AddStation(converter);
				}
			}
			if ((Object)(object)ObjectDB.instance != (Object)null)
			{
				foreach (Recipe recipe in ObjectDB.instance.m_recipes)
				{
					AddRecipe(recipe);
				}
			}
			if ((Object)(object)ObjectDB.instance != (Object)null)
			{
				KitchenUses.Rebuild();
			}
			if (Kitchen.Count != count)
			{
				NativeCooking.Report($"Kitchen products: {Kitchen.Count}.");
			}
		}

		private static void AddStation(PrefabIndex.Converter converter)
		{
			CookingStation station = converter.Station;
			if (Kitchen.IsKitchen(station))
			{
				foreach (ItemConversion item in station.m_conversion)
				{
					AddConversion(item.m_from, item.m_to);
				}
			}
			Fermenter fermenter = converter.Fermenter;
			if (!((Object)(object)fermenter != (Object)null))
			{
				return;
			}
			foreach (ItemConversion item2 in fermenter.m_conversion)
			{
				AddConversion(item2.m_from, item2.m_to);
			}
		}

		private static void AddConversion(ItemDrop from, ItemDrop to)
		{
			Kitchen.AddItem(to);
			Kitchen.AddConversion(from, to);
		}

		private static void AddRecipe(Recipe recipe)
		{
			if ((Object)(object)recipe != (Object)null && (Object)(object)recipe.m_item != (Object)null && Kitchen.IsKitchen(recipe.m_craftingStation))
			{
				Kitchen.AddItem(recipe.m_item);
			}
		}
	}
	public static class KitchenSettings
	{
		public const string Section = "7 - Cooking";

		public static CookingSetting<float> CookingSpeed { get; private set; }

		public static CookingSetting<float> ExtraBurnTime { get; private set; }

		public static CookingSetting<float> FermentingSpeed { get; private set; }

		public static CookingSetting<float> ExtraFoodChance { get; private set; }

		public static CookingSetting<int> ExtraFoodAmount { get; private set; }

		public static CookingSetting<float> IngredientSaveChance { get; private set; }

		public static void Initialize(ConfigFile config)
		{
			CookingSpeed = CookingSettings.Bind(config, "7 - Cooking", "Cooking Speed At Level 100", 50f, "Percent faster cooking on cooking stations and the oven, from the level of the player who put the food on.", (AcceptableValueBase)(object)Settings.UpTo(500f), (ConfigScope)1);
			ExtraBurnTime = CookingSettings.Bind(config, "7 - Cooking", "Extra Burn Time At Level 100", 100f, "Extra time before a finished dish burns, in percent of its cook time. The game burns a dish at twice its cook time; 100 moves that to three times.", (AcceptableValueBase)(object)Settings.UpTo(1000f), (ConfigScope)1);
			FermentingSpeed = CookingSettings.Bind(config, "7 - Cooking", "Fermenting Speed At Level 100", 30f, "Percent faster fermenting, from the level of the player who put the mead base in.", (AcceptableValueBase)(object)Settings.UpTo(500f), (ConfigScope)1);
			ExtraFoodChance = CookingSettings.Bind(config, "7 - Cooking", "Extra Food Chance At Level 100", 50f, "Chance in percent of extra food from a kitchen (cooking stations, oven, cauldron, mead cauldron, prep table). Replaces the game's own bonus chance at kitchens. Grows from 0 at level 0.", (AcceptableValueBase)(object)Settings.UpTo(100f), (ConfigScope)1);
			ExtraFoodAmount = CookingSettings.Bind(config, "7 - Cooking", "Extra Food Amount", 1, "How many extra dishes a bonus gives.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), (ConfigScope)1);
			IngredientSaveChance = CookingSettings.Bind(config, "7 - Cooking", "Ingredient Save Chance At Level 100", 10f, "Chance in percent that crafting at a kitchen gives back one of the ingredients it used.", (AcceptableValueBase)(object)Settings.UpTo(100f), (ConfigScope)1);
		}
	}
	public static class KitchenUses
	{
		public struct Conversion
		{
			public string Mill;

			public ItemDrop From;

			public ItemDrop To;
		}

		private static readonly Dictionary<string, float> ingredients = new Dictionary<string, float>();

		private static readonly List<Conversion> conversions = new List<Conversion>();

		public static List<Conversion> Conversions => conversions;

		public static bool IsIngredient(string itemPrefab)
		{
			if (itemPrefab != null)
			{
				return ingredients.ContainsKey(itemPrefab);
			}
			return false;
		}

		public static float BestDish(string itemPrefab)
		{
			if (itemPrefab == null || !ingredients.TryGetValue(itemPrefab, out var value))
			{
				return 0f;
			}
			return value;
		}

		public static void Rebuild()
		{
			ingredients.Clear();
			conversions.Clear();
			foreach (Recipe recipe in ObjectDB.instance.m_recipes)
			{
				AddRecipe(recipe);
			}
			foreach (PrefabIndex.Converter converter in PrefabIndex.Scene().Converters)
			{
				AddPrefab(converter);
			}
		}

		private static void AddRecipe(Recipe recipe)
		{
			if ((Object)(object)recipe?.m_item == (Object)null || recipe.m_resources == null || !Kitchen.IsKitchen(recipe.m_craftingStation))
			{
				return;
			}
			float value = Kitchen.FoodValue(recipe.m_item.m_itemData);
			Requirement[] resources = recipe.m_resources;
			foreach (Requirement val in resources)
			{
				if ((Object)(object)val?.m_resItem != (Object)null)
				{
					Note(((Object)val.m_resItem).name, value);
				}
			}
		}

		private static void AddPrefab(PrefabIndex.Converter converter)
		{
			CookingStation station = converter.Station;
			if (Kitchen.IsKitchen(station))
			{
				foreach (ItemConversion item in station.m_conversion)
				{
					Note(item.m_from, item.m_to);
				}
			}
			Fermenter fermenter = converter.Fermenter;
			if ((Object)(object)fermenter != (Object)null)
			{
				foreach (ItemConversion item2 in fermenter.m_conversion)
				{
					Note(item2.m_from, item2.m_to);
				}
			}
			Smelter smelter = converter.Smelter;
			if (smelter?.m_conversion == null)
			{
				return;
			}
			foreach (ItemConversion item3 in smelter.m_conversion)
			{
				AddConversion(((Object)converter.Prefab).name, item3.m_from, item3.m_to);
			}
		}

		private static void Note(ItemDrop from, ItemDrop to)
		{
			if ((Object)(object)from != (Object)null)
			{
				Note(((Object)from).name, ((Object)(object)to != (Object)null) ? Kitchen.FoodValue(to.m_itemData) : 0f);
			}
		}

		private static void Note(string itemPrefab, float value)
		{
			ingredients.TryGetValue(itemPrefab, out var value2);
			ingredients[itemPrefab] = Mathf.Max(value2, value);
		}

		private static void AddConversion(string mill, ItemDrop from, ItemDrop to)
		{
			if ((Object)(object)from != (Object)null && (Object)(object)to != (Object)null)
			{
				conversions.Add(new Conversion
				{
					Mill = mill,
					From = from,
					To = to
				});
			}
		}
	}
	public static class Perks
	{
		public static int ExtraFoodAmount => Mathf.Max(1, KitchenSettings.ExtraFoodAmount.Value);

		public static float CookingSpeed(float level)
		{
			return 1f + Share(KitchenSettings.CookingSpeed.Value, level);
		}

		public static float ExtraBurnTime(float level)
		{
			return Share(KitchenSettings.ExtraBurnTime.Value, level);
		}

		public static float FermentingSpeed(float level)
		{
			return 1f + Share(KitchenSettings.FermentingSpeed.Value, level);
		}

		public static float ExtraFoodChance(float level)
		{
			return Mathf.Clamp01(Share(KitchenSettings.ExtraFoodChance.Value, level));
		}

		public static float IngredientSaveChance(float level)
		{
			return Mathf.Clamp01(Share(KitchenSettings.IngredientSaveChance.Value, level));
		}

		private static float Share(float percentAt100, float level)
		{
			return Mathf.Max(0f, percentAt100) / 100f * CookLevel.Factor(level);
		}
	}
	public static class PlayerIds
	{
		public static long Of(ZDOID character)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			if (((ZDOID)(ref character)).IsNone() || ZDOMan.instance == null)
			{
				return 0L;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(character);
			if (zDO != null)
			{
				return zDO.GetLong(ZDOVars.s_playerID, 0L);
			}
			return 0L;
		}

		public static long PeerOf(long playerId)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null && localPlayer.GetPlayerID() == playerId)
			{
				return ZNet.GetUID();
			}
			if ((Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null)
			{
				return 0L;
			}
			foreach (PlayerInfo player in ZNet.instance.GetPlayerList())
			{
				long num = OwnerOf(player.m_characterID, playerId);
				if (num != 0L)
				{
					return num;
				}
			}
			return 0L;
		}

		private static long OwnerOf(ZDOID character, long playerId)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			ZDO val = (((ZDOID)(ref character)).IsNone() ? null : ZDOMan.instance.GetZDO(character));
			if (val == null || val.GetLong(ZDOVars.s_playerID, 0L) != playerId)
			{
				return 0L;
			}
			return val.GetOwner();
		}
	}
	public static class PrefabIndex
	{
		public struct Converter
		{
			public GameObject Prefab;

			public CookingStation Station;

			public Fermenter Fermenter;

			public Smelter Smelter;
		}

		public sealed class ScenePrefabs
		{
			public readonly List<Plant> Plants = new List<Plant>();

			public readonly List<TreeBase> Trees = new List<TreeBase>();

			public readonly List<Converter> Converters = new List<Converter>();
		}

		public sealed class ItemPrefabs
		{
			public readonly List<GameObject> Fish = new List<GameObject>();

			public readonly List<ItemDrop> Eggs = new List<ItemDrop>();
		}

		private static ScenePrefabs scenePrefabs = new ScenePrefabs();

		private static List<GameObject> sceneList;

		private static int sceneCount = -1;

		private static ItemPrefabs itemPrefabs = new ItemPrefabs();

		private static List<GameObject> itemList;

		private static int itemCount = -1;

		public static ScenePrefabs Scene()
		{
			List<GameObject> list = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.m_prefabs : null);
			if (list == null || (list == sceneList && list.Count == sceneCount))
			{
				return PrefabIndex.scenePrefabs;
			}
			ScenePrefabs scenePrefabs = new ScenePrefabs();
			foreach (GameObject item in list)
			{
				if ((Object)(object)item != (Object)null)
				{
					AddScenePrefab(item, scenePrefabs);
				}
			}
			PrefabIndex.scenePrefabs = scenePrefabs;
			sceneList = list;
			sceneCount = list.Count;
			return scenePrefabs;
		}

		public static ItemPrefabs Items()
		{
			List<GameObject> list = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.m_items : null);
			if (list == null || (list == itemList && list.Count == itemCount))
			{
				return PrefabIndex.itemPrefabs;
			}
			ItemPrefabs itemPrefabs = new ItemPrefabs();
			foreach (GameObject item in list)
			{
				if ((Object)(object)item != (Object)null)
				{
					AddItemPrefab(item, itemPrefabs);
				}
			}
			PrefabIndex.itemPrefabs = itemPrefabs;
			itemList = list;
			itemCount = list.Count;
			return itemPrefabs;
		}

		private static void AddScenePrefab(GameObject prefab, ScenePrefabs found)
		{
			Plant component = prefab.GetComponent<Plant>();
			if ((Object)(object)component != (Object)null)
			{
				found.Plants.Add(component);
			}
			TreeBase component2 = prefab.GetComponent<TreeBase>();
			if ((Object)(object)component2 != (Object)null)
			{
				found.Trees.Add(component2);
			}
			Converter item = new Converter
			{
				Prefab = prefab,
				Station = prefab.GetComponent<CookingStation>(),
				Fermenter = prefab.GetComponent<Fermenter>(),
				Smelter = prefab.GetComponent<Smelter>()
			};
			if ((Object)(object)item.Station != (Object)null || (Object)(object)item.Fermenter != (Object)null || (Object)(object)item.Smelter != (Object)null)
			{
				found.Converters.Add(item);
			}
		}

		private static void AddItemPrefab(GameObject prefab, ItemPrefabs found)
		{
			if ((Object)(object)prefab.GetComponent<Fish>() != (Object)null && (Object)(object)prefab.GetComponent<ItemDrop>() != (Object)null)
			{
				found.Fish.Add(prefab);
			}
			if ((Object)(object)prefab.GetComponent<EggGrow>() != (Object)null)
			{
				found.Eggs.Add(prefab.GetComponent<ItemDrop>());
			}
		}
	}
	[HarmonyPatch(typeof(ZNetView), "InvokeRPC", new Type[]
	{
		typeof(string),
		typeof(object[])
	})]
	public static class RpcSendSwap
	{
		[HarmonyPrefix]
		private static bool Prefix(ZNetView __instance, string method, object[] parameters)
		{
			return !FermenterAddSend.Swapped(__instance, method, parameters);
		}
	}
	public static class CraftXp
	{
		[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
		private static class CraftPatch
		{
			[HarmonyPrefix]
			private static void Prefix(InventoryGui __instance, out bool __state)
			{
				__state = Open(__instance);
			}

			[HarmonyFinalizer]
			private static void Finalizer(bool __state)
			{
				XpScope.End(__state);
			}
		}

		private static bool Open(InventoryGui gui)
		{
			Player localPlayer = Player.m_localPlayer;
			ItemDrop val = (((Object)(object)gui.m_craftRecipe == (Object)null) ? null : gui.m_craftRecipe.m_item);
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)val == (Object)null || !Kitchen.IsKitchen(localPlayer.GetCurrentCraftingStation()))
			{
				return false;
			}
			int units = ((!gui.m_multiCrafting) ? 1 : gui.m_multiCraftAmount);
			return XpScope.Begin(val.m_itemData, ((Object)((Component)val).gameObject).name, units);
		}
	}
	public static class DishDiscovery
	{
		private sealed class DiscoveryCache
		{
			internal string Raw;

			internal readonly HashSet<string> Dishes = new HashSet<string>(StringComparer.Ordinal);
		}

		private const char Separator = ',';

		private static readonly ConditionalWeakTable<Player, DiscoveryCache> caches = new ConditionalWeakTable<Player, DiscoveryCache>();

		private static DiscoveryCache CacheFor(Player player, string made)
		{
			DiscoveryCache value = caches.GetValue(player, (Player _) => new DiscoveryCache());
			string text = made ?? "";
			if (value.Raw != text)
			{
				value.Raw = text;
				value.Dishes.Clear();
				string[] array = text.Split(',');
				foreach (string text2 in array)
				{
					if (!string.IsNullOrEmpty(text2))
					{
						value.Dishes.Add(text2);
					}
				}
			}
			return value;
		}

		public static bool HasMade(Player player, string dishPrefab)
		{
			if ((Object)(object)player == (Object)null || string.IsNullOrEmpty(dishPrefab))
			{
				return false;
			}
			Read(player, out string made);
			return CacheFor(player, made).Dishes.Contains(dishPrefab);
		}

		public static bool TryRecord(Player player, string dishPrefab)
		{
			if ((Object)(object)player == (Object)null || string.IsNullOrEmpty(dishPrefab) || dishPrefab.IndexOf(',') >= 0 || HasMade(player, dishPrefab))
			{
				return false;
			}
			Read(player, out string made);
			DiscoveryCache discoveryCache = CacheFor(player, made);
			string text = (string