Decompiled source of Hearthhold v0.1.0

plugins\Hearthhold.dll

Decompiled 3 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Charter;
using ConfigReload;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PatchGuard;
using SyncedConfig;
using UnityEngine;
using YamlConfig;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Core.Tokens;
using YamlDotNet.Helpers;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.BufferedDeserialization;
using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators;
using YamlDotNet.Serialization.Callbacks;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
using YamlDotNet.Serialization.NodeTypeResolvers;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.ObjectGraphTraversalStrategies;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.Schemas;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using YamlDotNet.Serialization.Utilities;
using YamlDotNet.Serialization.ValueDeserializers;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_guiutils")]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("Hearthhold")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Hearthhold: Stardew Valley style quality stars for Valheim food")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+ad278ecf323901c3645eb1f9f6fac6833a0f8cb1")]
[assembly: AssemblyProduct("Hearthhold")]
[assembly: AssemblyTitle("Hearthhold")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Hearthhold
{
	public sealed class AgingCask : MonoBehaviour
	{
		public const float Interval = 5f;

		private Container container;

		private uint hoverRevision = uint.MaxValue;

		private float hoverAt = -1f;

		private string hoverLine = "";

		private Container Container
		{
			get
			{
				if ((Object)(object)container == (Object)null)
				{
					container = ((Component)this).GetComponent<Container>();
				}
				return container;
			}
		}

		private void Awake()
		{
			((MonoBehaviour)this).InvokeRepeating("Tick", Random.Range(1f, 5f), 5f);
		}

		private void Tick()
		{
			HookGuard.Run("aging cask", delegate(AgingCask cask)
			{
				cask.Age();
			}, this);
		}

		private void Age()
		{
			ZDO val = OwnedIdle();
			if (val == null)
			{
				return;
			}
			Inventory inventory = container.GetInventory();
			List<CaskRecord> list = CaskRecords.Read(val);
			double timeSeconds = ZNet.instance.GetTimeSeconds();
			bool raised;
			List<CaskRecord> list2 = CaskAging.Age(inventory, list, timeSeconds, CaskAging.Period(EnvMan.instance), out raised);
			if (raised || !CaskRecords.Equal(list, list2))
			{
				CaskRecords.Write(val, list2);
				if (raised)
				{
					inventory.Changed(false, false);
				}
				else
				{
					container.m_lastRevision = val.DataRevision;
				}
			}
		}

		private ZDO OwnedIdle()
		{
			if ((Object)(object)ZNet.instance == (Object)null || (Object)(object)EnvMan.instance == (Object)null || (Object)(object)Container == (Object)null)
			{
				return null;
			}
			ZNetView nview = container.m_nview;
			if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner() || container.IsInUse() || container.GetInventory() == null)
			{
				return null;
			}
			ZDO zDO = nview.GetZDO();
			if (zDO.DataRevision != container.m_lastRevision)
			{
				return null;
			}
			return zDO;
		}

		public string HoverLine()
		{
			ZNetView val = (((Object)(object)Container != (Object)null) ? container.m_nview : null);
			if ((Object)(object)val == (Object)null || !val.IsValid() || (Object)(object)ZNet.instance == (Object)null || (Object)(object)EnvMan.instance == (Object)null)
			{
				return "Meads and wines gain a star every 2 days";
			}
			ZDO zDO = val.GetZDO();
			if (zDO.DataRevision == hoverRevision && Time.time - hoverAt < 1f)
			{
				return hoverLine;
			}
			hoverRevision = zDO.DataRevision;
			hoverAt = Time.time;
			hoverLine = CaskHover.Line(CaskRecords.Read(zDO), ZNet.instance.GetTimeSeconds(), EnvMan.instance);
			return hoverLine;
		}
	}
	public static class CaskAging
	{
		public const float DaysPerStar = 2f;

		public static double Period(EnvMan env)
		{
			return 2.0 * (double)env.m_dayLengthSec;
		}

		public static List<CaskRecord> Age(Inventory inventory, List<CaskRecord> old, double now, double period, out bool raised)
		{
			raised = false;
			List<CaskRecord> list = new List<CaskRecord>();
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (Ages(allItem))
				{
					CaskRecord record = Track(allItem, old, now);
					raised |= Raise(allItem, ref record, now, period);
					list.Add(record);
				}
			}
			return list;
		}

		private static bool Ages(ItemData item)
		{
			if ((Object)(object)item?.m_dropPrefab != (Object)null && Kitchen.IsFermented(((Object)item.m_dropPrefab).name))
			{
				return Stars.IsStarItem(item);
			}
			return false;
		}

		private static CaskRecord Track(ItemData item, List<CaskRecord> old, double now)
		{
			CaskRecord result = new CaskRecord
			{
				X = item.m_gridPos.x,
				Y = item.m_gridPos.y,
				Item = ((Object)item.m_dropPrefab).name,
				Stars = Stars.Get(item),
				Stack = item.m_stack,
				Start = now
			};
			foreach (CaskRecord item2 in old)
			{
				if (item2.X == result.X && item2.Y == result.Y && item2.Item == result.Item && item2.Stars == result.Stars && result.Stack <= item2.Stack && item2.Start <= now)
				{
					result.Start = item2.Start;
				}
			}
			return result;
		}

		private static bool Raise(ItemData item, ref CaskRecord record, double now, double period)
		{
			int stars = record.Stars;
			while (record.Stars < 3 && now - record.Start >= period)
			{
				record.Stars++;
				record.Start += period;
			}
			if (record.Stars == stars)
			{
				return false;
			}
			Stars.Set(item, record.Stars);
			return true;
		}
	}
	public static class CaskHover
	{
		[HarmonyPatch(typeof(Container), "GetHoverText")]
		private static class HoverText
		{
			[HarmonyPostfix]
			private static void Postfix(Container __instance, ref string __result)
			{
				AgingCask agingCask = default(AgingCask);
				if (((Component)__instance).TryGetComponent<AgingCask>(ref agingCask))
				{
					__result = __result + "\n" + agingCask.HoverLine();
				}
			}
		}

		public const string Rule = "Meads and wines gain a star every 2 days";

		public static string Line(List<CaskRecord> records, double now, EnvMan env)
		{
			double num = CaskAging.Period(env);
			double num2 = double.MaxValue;
			foreach (CaskRecord record in records)
			{
				if (record.Stars < 3)
				{
					num2 = Math.Min(num2, record.Start + num - now);
				}
			}
			if (num2 == double.MaxValue || env.m_dayLengthSec <= 0)
			{
				return "Meads and wines gain a star every 2 days";
			}
			double num3 = Math.Max(0.0, num2) / (double)env.m_dayLengthSec;
			return string.Format("{0}\nNext star in {1:0.0} days", "Meads and wines gain a star every 2 days", num3);
		}
	}
	public static class CaskKeys
	{
		public const string Prefab = "hearthhold_aging_cask";

		public const string Aging = "hearthhold_cask_aging";
	}
	public static class CaskPiece
	{
		[HarmonyPatch(typeof(ZNetScene), "Awake")]
		private static class SceneAwake
		{
			[HarmonyPrefix]
			private static void Prefix()
			{
				ClonedPieces.Add(Piece);
			}
		}

		public const string DisplayName = "Aging Cask";

		public const string Description = "Meads and wines stored inside gain a star every 2 days, up to gold.";

		public static readonly ClonedPiece Piece = new ClonedPiece("hearthhold_aging_cask", "piece_chest_barrel", "Aging Cask", "Meads and wines stored inside gain a star every 2 days, up to gold.", ("FineWood", 10), ("BronzeNails", 10), ("Resin", 5))
		{
			Dress = DressCopy
		};

		private static void DressCopy(GameObject copy)
		{
			Container componentInChildren = copy.GetComponentInChildren<Container>(true);
			if ((Object)(object)componentInChildren == (Object)null)
			{
				Hearthhold.Log.LogWarning((object)"The game's barrel has no container: the Aging Cask ages nothing.");
				return;
			}
			componentInChildren.m_name = "Aging Cask";
			((Component)componentInChildren).gameObject.AddComponent<AgingCask>();
		}
	}
	public struct CaskRecord
	{
		public int X;

		public int Y;

		public string Item;

		public int Stars;

		public int Stack;

		public double Start;

		public bool SameAs(CaskRecord other)
		{
			if (X == other.X && Y == other.Y && Item == other.Item && Stars == other.Stars && Stack == other.Stack)
			{
				return Start == other.Start;
			}
			return false;
		}
	}
	public static class CaskRecords
	{
		private const int Version = 1;

		public static List<CaskRecord> Read(ZDO zdo)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			List<CaskRecord> list = new List<CaskRecord>();
			byte[] array = ((zdo != null) ? zdo.GetByteArray("hearthhold_cask_aging", (byte[])null) : null);
			if (array == null || array.Length == 0)
			{
				return list;
			}
			ZPackage val = new ZPackage(array);
			if (val.ReadInt() != 1)
			{
				return list;
			}
			int num = val.ReadInt();
			for (int i = 0; i < num; i++)
			{
				list.Add(ReadOne(val));
			}
			return list;
		}

		public static void Write(ZDO zdo, List<CaskRecord> records)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(records.Count);
			foreach (CaskRecord record in records)
			{
				WriteOne(val, record);
			}
			zdo.Set("hearthhold_cask_aging", val.GetArray());
		}

		public static bool Equal(List<CaskRecord> a, List<CaskRecord> b)
		{
			if (a.Count != b.Count)
			{
				return false;
			}
			for (int i = 0; i < a.Count; i++)
			{
				if (!a[i].SameAs(b[i]))
				{
					return false;
				}
			}
			return true;
		}

		private static CaskRecord ReadOne(ZPackage package)
		{
			return new CaskRecord
			{
				X = package.ReadInt(),
				Y = package.ReadInt(),
				Item = package.ReadString(),
				Stars = package.ReadInt(),
				Stack = package.ReadInt(),
				Start = package.ReadDouble()
			};
		}

		private static void WriteOne(ZPackage package, CaskRecord record)
		{
			package.Write(record.X);
			package.Write(record.Y);
			package.Write(record.Item ?? "");
			package.Write(record.Stars);
			package.Write(record.Stack);
			package.Write(record.Start);
		}
	}
	public static class Discovery
	{
		[HarmonyPatch(typeof(ZNetScene), "Awake")]
		private static class SceneAwake
		{
			[HarmonyPostfix]
			[HarmonyPriority(0)]
			private static void Postfix()
			{
				HookGuard.Run("discovery", Run);
			}
		}

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

		private static void Run()
		{
			if ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_items.Count == 0)
			{
				return;
			}
			List<GameObject> prefabs = ZNetScene.instance.m_prefabs;
			foreach (GameObject item in prefabs)
			{
				if ((Object)(object)item != (Object)null)
				{
					AddKitchenAndPlants(item);
				}
			}
			foreach (Recipe recipe in ObjectDB.instance.m_recipes)
			{
				AddRecipe(recipe);
			}
			foreach (GameObject item2 in prefabs)
			{
				if ((Object)(object)item2 != (Object)null)
				{
					AddSources(item2);
				}
			}
			StarItems.RegisterAll();
		}

		private static void AddRecipe(Recipe recipe)
		{
			if (!((Object)(object)recipe == (Object)null) && !((Object)(object)recipe.m_item == (Object)null) && Kitchen.IsKitchen(recipe.m_craftingStation) && IsFoodish(recipe.m_item))
			{
				Kitchen.AddProduct(recipe.m_item, fromFermenter: false);
				Requirement[] resources = recipe.m_resources;
				for (int i = 0; i < resources.Length; i++)
				{
					Kitchen.AddIngredient(resources[i]?.m_resItem);
				}
			}
		}

		private static void AddKitchenAndPlants(GameObject prefab)
		{
			CookingStation component = prefab.GetComponent<CookingStation>();
			if (Kitchen.IsKitchen(component))
			{
				foreach (ItemConversion item in component.m_conversion)
				{
					AddConversion(item.m_from, item.m_to, fermenter: false);
				}
			}
			Fermenter component2 = prefab.GetComponent<Fermenter>();
			if ((Object)(object)component2 != (Object)null)
			{
				foreach (ItemConversion item2 in component2.m_conversion)
				{
					AddConversion(item2.m_from, item2.m_to, fermenter: true);
				}
			}
			Plant component3 = prefab.GetComponent<Plant>();
			if (component3?.m_grownPrefabs == null)
			{
				return;
			}
			GameObject[] grownPrefabs = component3.m_grownPrefabs;
			foreach (GameObject val in grownPrefabs)
			{
				if ((Object)(object)val != (Object)null && (Object)(object)val.GetComponent<Pickable>() != (Object)null)
				{
					Sources.AddCropPickable(((Object)val).name);
				}
			}
		}

		private static void AddConversion(ItemDrop from, ItemDrop to, bool fermenter)
		{
			Kitchen.AddIngredient(from);
			Kitchen.AddProduct(to, fermenter);
			Kitchen.AddConversion(from, to);
		}

		private static void AddSources(GameObject prefab)
		{
			Pickable component = prefab.GetComponent<Pickable>();
			if ((Object)(object)component != (Object)null && (Object)(object)component.m_itemPrefab != (Object)null)
			{
				AddPick(((Object)prefab).name, component.m_itemPrefab);
			}
			Beehive component2 = prefab.GetComponent<Beehive>();
			if ((Object)(object)component2 != (Object)null && (Object)(object)component2.m_honeyItem != (Object)null)
			{
				AddProduce(((Component)component2.m_honeyItem).gameObject);
			}
			SapCollector component3 = prefab.GetComponent<SapCollector>();
			if ((Object)(object)component3 != (Object)null && (Object)(object)component3.m_spawnItem != (Object)null)
			{
				AddProduce(((Component)component3.m_spawnItem).gameObject);
			}
			AddMeat(prefab.GetComponent<CharacterDrop>());
		}

		private static void AddPick(string pickable, GameObject item)
		{
			if (Sources.IsCropPickable(pickable))
			{
				if (IsFood(item) || Kitchen.IsIngredient(((Object)item).name))
				{
					Sources.AddCrop(((Object)item).name);
				}
			}
			else if (IsFood(item) || Sources.ForageExtras.Contains(((Object)item).name))
			{
				Sources.AddForage(((Object)item).name);
			}
		}

		private static void AddProduce(GameObject item)
		{
			if (IsFood(item) || Sources.ForageExtras.Contains(((Object)item).name))
			{
				Sources.AddForage(((Object)item).name);
			}
		}

		private static void AddMeat(CharacterDrop drops)
		{
			if (drops?.m_drops == null)
			{
				return;
			}
			foreach (Drop drop in drops.m_drops)
			{
				if ((Object)(object)drop?.m_prefab != (Object)null && (IsFood(drop.m_prefab) || Sources.MeatExtras.Contains(((Object)drop.m_prefab).name)) && (Object)(object)drop.m_prefab.GetComponent<EggGrow>() == (Object)null)
				{
					Sources.AddMeat(((Object)drop.m_prefab).name);
				}
			}
		}

		private static bool IsFoodish(ItemDrop item)
		{
			SharedData val = item.m_itemData?.m_shared;
			if (val != null)
			{
				if (!(Kitchen.Value(val) > 0f))
				{
					return Kitchen.IsStationInput(((Object)((Component)item).gameObject).name);
				}
				return true;
			}
			return false;
		}

		private static bool IsFood(GameObject item)
		{
			ItemDrop component = item.GetComponent<ItemDrop>();
			if (component?.m_itemData?.m_shared == null)
			{
				return false;
			}
			if (!(Kitchen.Value(component.m_itemData.m_shared) > 0f))
			{
				return Kitchen.IsStationInput(((Object)item).name);
			}
			return true;
		}
	}
	public static class Kitchen
	{
		public const SkillType Skill = (SkillType)105;

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

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

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

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

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

		internal static int ProductCount => products.Count;

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

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

		public static bool IsProduct(string prefabName)
		{
			if (prefabName != null)
			{
				return products.Contains(prefabName);
			}
			return false;
		}

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

		public static bool IsStationInput(string prefabName)
		{
			if (prefabName != null)
			{
				return stationInputs.Contains(prefabName);
			}
			return false;
		}

		public static bool IsFermented(string prefabName)
		{
			if (prefabName != null)
			{
				return fermented.Contains(prefabName);
			}
			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 float Value(SharedData shared)
		{
			return shared.m_food + shared.m_foodStamina + shared.m_foodEitr;
		}

		internal static void AddProduct(ItemDrop item, bool fromFermenter)
		{
			if (!((Object)(object)item == (Object)null))
			{
				products.Add(((Object)((Component)item).gameObject).name);
				if (fromFermenter)
				{
					fermented.Add(((Object)((Component)item).gameObject).name);
				}
			}
		}

		internal static void AddIngredient(ItemDrop item)
		{
			if ((Object)(object)item != (Object)null)
			{
				ingredients.Add(((Object)((Component)item).gameObject).name);
			}
		}

		internal static void AddConversion(ItemDrop from, ItemDrop to)
		{
			if (from?.m_itemData?.m_shared != null && to?.m_itemData?.m_shared != null)
			{
				stationInputs.Add(((Object)((Component)from).gameObject).name);
				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);
				}
			}
		}

		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;
		}
	}
	public static class Sources
	{
		public static readonly HashSet<string> ForageExtras = new HashSet<string> { "Dandelion", "Thistle", "Fiddleheadfern", "MushroomSmokePuff", "RoyalJelly", "Sap" };

		public static readonly HashSet<string> MeatExtras = new HashSet<string> { "Entrails", "Bloodbag" };

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

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

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

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

		public static bool IsCropPickable(string pickablePrefab)
		{
			if (pickablePrefab != null)
			{
				return cropPickables.Contains(pickablePrefab);
			}
			return false;
		}

		public static bool TryPickSource(Pickable pickable, out StarSource source)
		{
			source = StarSource.Forage;
			string text = (((Object)(object)pickable != (Object)null && (Object)(object)pickable.m_itemPrefab != (Object)null) ? ((Object)pickable.m_itemPrefab).name : null);
			if (text == null)
			{
				return false;
			}
			if (IsCropPickable(Utils.GetPrefabName(((Component)pickable).gameObject)))
			{
				source = StarSource.Crop;
				return cropItems.Contains(text);
			}
			return forageItems.Contains(text);
		}

		public static bool IsForageItem(string itemPrefab)
		{
			if (itemPrefab != null)
			{
				return forageItems.Contains(itemPrefab);
			}
			return false;
		}

		public static bool IsCropItem(string itemPrefab)
		{
			if (itemPrefab != null)
			{
				return cropItems.Contains(itemPrefab);
			}
			return false;
		}

		public static bool IsMeatItem(string itemPrefab)
		{
			if (itemPrefab != null)
			{
				return meatItems.Contains(itemPrefab);
			}
			return false;
		}

		internal static void AddCropPickable(string pickablePrefab)
		{
			cropPickables.Add(pickablePrefab);
		}

		internal static void AddForage(string itemPrefab)
		{
			forageItems.Add(itemPrefab);
		}

		internal static void AddCrop(string itemPrefab)
		{
			cropItems.Add(itemPrefab);
		}

		internal static void AddMeat(string itemPrefab)
		{
			meatItems.Add(itemPrefab);
		}

		internal static IEnumerable<string> AllItems()
		{
			foreach (string forageItem in forageItems)
			{
				yield return forageItem;
			}
			foreach (string cropItem in cropItems)
			{
				yield return cropItem;
			}
			foreach (string meatItem in meatItems)
			{
				yield return meatItem;
			}
		}
	}
	public static class StarItems
	{
		private static int registered;

		internal static void RegisterAll()
		{
			int num = 0;
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				if ((Object)(object)item != (Object)null && Wanted(((Object)item).name) && Register(item))
				{
					num++;
				}
			}
			if (num != registered)
			{
				Hearthhold.Log.LogInfo((object)$"Items that carry stars: {num}.");
			}
			registered = num;
		}

		private static bool Wanted(string name)
		{
			if (!Kitchen.IsProduct(name) && !Sources.IsForageItem(name) && !Sources.IsCropItem(name))
			{
				return Sources.IsMeatItem(name);
			}
			return true;
		}

		private static bool Register(GameObject prefab)
		{
			SharedData val = prefab.GetComponent<ItemDrop>()?.m_itemData?.m_shared;
			if (val == null || val.m_maxStackSize <= 1 || (Object)(object)prefab.GetComponent<Feast>() != (Object)null)
			{
				return false;
			}
			val.m_scaleByQuality = 0f;
			val.m_scaleWeightByQuality = 0f;
			GrindstoneLink.AddStarItem(prefab);
			return true;
		}
	}
	public sealed class ClonedPiece
	{
		private const string Workbench = "piece_workbench";

		private const string Hammer = "Hammer";

		private static GameObject holder;

		private readonly string source;

		private readonly (string Item, int Amount)[] cost;

		public string PrefabName { get; }

		public int Hash { get; }

		public string DisplayName { get; }

		public string Description { get; }

		public Action<GameObject> Dress { get; set; }

		public Func<bool> Buildable { get; set; }

		public GameObject Prefab { get; private set; }

		private static GameObject Holder
		{
			get
			{
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Expected O, but got Unknown
				if ((Object)(object)holder != (Object)null)
				{
					return holder;
				}
				holder = new GameObject("Hearthhold_Prefabs");
				holder.SetActive(false);
				Object.DontDestroyOnLoad((Object)(object)holder);
				return holder;
			}
		}

		public ClonedPiece(string prefabName, string source, string displayName, string description, params (string, int)[] cost)
		{
			PrefabName = prefabName;
			Hash = StringExtensionMethods.GetStableHashCode(prefabName);
			this.source = source;
			DisplayName = displayName;
			Description = description;
			this.cost = cost;
		}

		public bool Is(GameObject instance)
		{
			if ((Object)(object)instance != (Object)null && (Object)(object)Prefab != (Object)null)
			{
				return Utils.GetPrefabName(instance) == PrefabName;
			}
			return false;
		}

		internal void Install()
		{
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			if ((Object)(object)Prefab == (Object)null)
			{
				Build(instance);
			}
			if (!((Object)(object)Prefab == (Object)null))
			{
				if (!instance.m_prefabs.Contains(Prefab))
				{
					instance.m_prefabs.Add(Prefab);
				}
				instance.m_namedPrefabs[Hash] = Prefab;
				RefreshHammer();
			}
		}

		public void RefreshHammer()
		{
			GameObject val = (((Object)(object)ObjectDB.instance != (Object)null && (Object)(object)Prefab != (Object)null) ? ObjectDB.instance.GetItemPrefab("Hammer") : null);
			PieceTable val2 = ((!((Object)(object)val != (Object)null)) ? null : val.GetComponent<ItemDrop>()?.m_itemData.m_shared.m_buildPieces);
			if (!((Object)(object)val2 == (Object)null))
			{
				bool flag = Buildable == null || Buildable();
				if (flag && !val2.m_pieces.Contains(Prefab))
				{
					val2.m_pieces.Add(Prefab);
				}
				else if (!flag)
				{
					val2.m_pieces.Remove(Prefab);
				}
			}
		}

		private void Build(ZNetScene scene)
		{
			GameObject prefab = scene.GetPrefab(source);
			if ((Object)(object)prefab == (Object)null || (Object)(object)prefab.GetComponent<Piece>() == (Object)null)
			{
				Hearthhold.Log.LogWarning((object)("The game's " + source + " was not found: there is no " + DisplayName + "."));
			}
			else
			{
				GameObject val = Object.Instantiate<GameObject>(prefab, Holder.transform, false);
				((Object)val).name = PrefabName;
				Configure(val.GetComponent<Piece>(), scene);
				Dress?.Invoke(val);
				Prefab = val;
			}
		}

		private void Configure(Piece piece, ZNetScene scene)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			piece.m_name = DisplayName;
			piece.m_description = Description;
			piece.m_category = (PieceCategory)0;
			piece.m_enabled = true;
			if ((Object)(object)piece.m_craftingStation == (Object)null)
			{
				GameObject prefab = scene.GetPrefab("piece_workbench");
				piece.m_craftingStation = ((prefab != null) ? prefab.GetComponent<CraftingStation>() : null);
			}
			piece.m_resources = (from part in cost
				select Requirement(scene, part.Item, part.Amount) into r
				where (Object)(object)r.m_resItem != (Object)null
				select r).ToArray();
		}

		private static Requirement Requirement(ZNetScene scene, string item, int amount)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			Requirement val = new Requirement();
			GameObject prefab = scene.GetPrefab(item);
			val.m_resItem = ((prefab != null) ? prefab.GetComponent<ItemDrop>() : null);
			val.m_amount = amount;
			val.m_recover = true;
			return val;
		}
	}
	public static class ClonedPieces
	{
		[HarmonyPatch(typeof(ZNetScene), "Awake")]
		private static class SceneAwake
		{
			[HarmonyPostfix]
			private static void Postfix()
			{
				InstallAll();
			}
		}

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

		private static readonly List<ClonedPiece> pieces = new List<ClonedPiece>();

		public static ClonedPiece Add(ClonedPiece piece)
		{
			if (piece != null && !pieces.Contains(piece))
			{
				pieces.Add(piece);
			}
			return piece;
		}

		private static void InstallAll()
		{
			foreach (ClonedPiece piece in pieces)
			{
				HookGuard.Run("piece " + piece.PrefabName, piece.Install);
			}
		}
	}
	public static class GrindstoneLink
	{
		public const string Guid = "com.GrindstoneSkills";

		private const string StarsType = "GrindstoneSkills.Api.StarsApi";

		private const string SkillsType = "GrindstoneSkills.Api.SkillsApi";

		private const int Required = 1;

		private static bool bound;

		private static bool ready;

		private static Action<GameObject> addStarItem;

		private static Func<ItemData, bool> isStarItem;

		private static Func<ItemData, int> getStars;

		private static Action<ItemData, int> setStars;

		private static Func<int, string> starText;

		private static Func<string, float> localLevel;

		private static Func<Player, string, float> levelOf;

		public static bool Ready => Bind();

		public static void AddStarItem(GameObject prefab)
		{
			if ((Object)(object)prefab != (Object)null && Bind())
			{
				addStarItem(prefab);
			}
		}

		public static bool IsStarItem(ItemData item)
		{
			if (item != null && Bind())
			{
				return isStarItem(item);
			}
			return false;
		}

		public static int GetStars(ItemData item)
		{
			if (item == null || item.m_quality <= 1 || !Bind())
			{
				return 0;
			}
			return getStars(item);
		}

		public static void SetStars(ItemData item, int stars)
		{
			if (item != null && Bind())
			{
				setStars(item, stars);
			}
		}

		public static string StarText(int stars)
		{
			if (stars <= 0 || !Bind())
			{
				return "";
			}
			return starText(stars);
		}

		public static float LocalLevel(string skill)
		{
			if (!Bind())
			{
				return 0f;
			}
			return localLevel(skill);
		}

		public static float LevelOf(Player player, string skill)
		{
			if (!((Object)(object)player != (Object)null) || !Bind())
			{
				return 0f;
			}
			return levelOf(player, skill);
		}

		private static bool Bind()
		{
			if (bound)
			{
				return ready;
			}
			bound = true;
			Assembly assembly = FindAssembly();
			Type type = Api(assembly, "GrindstoneSkills.Api.StarsApi");
			Type type2 = Api(assembly, "GrindstoneSkills.Api.SkillsApi");
			if (type != null && type2 != null)
			{
				BindAll(type, type2);
			}
			if (!ready)
			{
				Hearthhold.Log.LogError((object)"GrindstoneSkills 0.15.0 or later is needed: without its API nothing gets stars.");
			}
			return ready;
		}

		private static void BindAll(Type stars, Type skills)
		{
			addStarItem = Create<Action<GameObject>>(stars, "AddStarItem");
			isStarItem = Create<Func<ItemData, bool>>(stars, "IsStarItem");
			getStars = Create<Func<ItemData, int>>(stars, "GetStars");
			setStars = Create<Action<ItemData, int>>(stars, "SetStars");
			starText = Create<Func<int, string>>(stars, "GetStarText");
			localLevel = Create<Func<string, float>>(skills, "GetLocalLevel");
			levelOf = Create<Func<Player, string, float>>(skills, "GetLevel");
			ready = addStarItem != null && isStarItem != null && getStars != null && setStars != null && starText != null && localLevel != null && levelOf != null;
		}

		private static Assembly FindAssembly()
		{
			if (!Chainloader.PluginInfos.TryGetValue("com.GrindstoneSkills", out var value) || !((Object)(object)value.Instance != (Object)null))
			{
				return null;
			}
			return ((object)value.Instance).GetType().Assembly;
		}

		private static Type Api(Assembly assembly, string name)
		{
			Type type = assembly?.GetType(name);
			Func<int> func = ((type != null) ? Create<Func<int>>(type, "GetApiVersion") : null);
			if (func == null || func() < 1)
			{
				return null;
			}
			return type;
		}

		private static T Create<T>(Type api, string name) where T : Delegate
		{
			MethodInfo method = typeof(T).GetMethod("Invoke");
			Type[] types = Array.ConvertAll(method.GetParameters(), (ParameterInfo p) => p.ParameterType);
			MethodInfo method2 = api.GetMethod(name, BindingFlags.Static | BindingFlags.Public, null, types, null);
			if (!(method2 != null) || !(method2.ReturnType == method.ReturnType))
			{
				return null;
			}
			return (T)Delegate.CreateDelegate(typeof(T), method2);
		}
	}
	public static class HookGuard
	{
		public static void Run(string context, Action action)
		{
			try
			{
				action();
			}
			catch (Exception exception)
			{
				Guard.Report(exception, context);
			}
		}

		public static T Run<T>(string context, Func<T> func, T fallback)
		{
			try
			{
				return func();
			}
			catch (Exception exception)
			{
				Guard.Report(exception, context);
				return fallback;
			}
		}

		public static void Run<TArg>(string context, Action<TArg> action, TArg arg)
		{
			try
			{
				action(arg);
			}
			catch (Exception exception)
			{
				Guard.Report(exception, context);
			}
		}
	}
	public static class Keys
	{
		public const string RpcMark = "hearthhold_mark";
	}
	public static class Marks
	{
		public struct Mark
		{
			public float Level;

			public float Stars;

			public float Time;
		}

		[HarmonyPatch(typeof(ZNetView), "HandleRoutedRPC")]
		private static class Arrival
		{
			[HarmonyPrefix]
			private static void Prefix(ZNetView __instance, RoutedRPCData rpcData)
			{
				if (rpcData.m_methodHash == RpcHash && !__instance.m_functions.ContainsKey(RpcHash))
				{
					ZNetView view = __instance;
					view.Register<float, float>("hearthhold_mark", (Action<long, float, float>)delegate(long sender, float level, float stars)
					{
						Receive(view, sender, level, stars);
					});
				}
			}
		}

		private const float Lifetime = 10f;

		private static readonly int RpcHash = StringExtensionMethods.GetStableHashCode("hearthhold_mark");

		private static readonly Dictionary<(ZDOID, long), Mark> marks = new Dictionary<(ZDOID, long), Mark>();

		private static readonly List<(ZDOID, long)> stale = new List<(ZDOID, long)>();

		public static void Send(ZNetView nview, float level, float stars)
		{
			if ((Object)(object)nview != (Object)null && nview.IsValid())
			{
				nview.InvokeRPC("hearthhold_mark", new object[2] { level, stars });
			}
		}

		public static bool TryTake(ZNetView nview, long sender, out Mark mark)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			mark = default(Mark);
			ZDO val = (((Object)(object)nview != (Object)null && nview.IsValid()) ? nview.GetZDO() : null);
			if (val == null || !marks.TryGetValue((val.m_uid, sender), out mark))
			{
				return false;
			}
			marks.Remove((val.m_uid, sender));
			return Time.time - mark.Time <= 10f;
		}

		private static void Receive(ZNetView nview, long sender, float level, float stars)
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.IsOwner())
			{
				Prune();
				Mark value = new Mark
				{
					Level = StarOdds.Sane(level),
					Stars = Mathf.Clamp(StarOdds.Sane(stars), 0f, 3f),
					Time = Time.time
				};
				marks[(nview.GetZDO().m_uid, sender)] = value;
			}
		}

		private static void Prune()
		{
			foreach (KeyValuePair<(ZDOID, long), Mark> mark in marks)
			{
				if (Time.time - mark.Value.Time > 10f)
				{
					stale.Add(mark.Key);
				}
			}
			foreach (var item in stale)
			{
				marks.Remove(item);
			}
			stale.Clear();
		}
	}
	public static class Professions
	{
		public const float Level = 100f;

		public static int Floor(StarSource source, float level)
		{
			if (!Has(level))
			{
				return 0;
			}
			return FloorOf(source);
		}

		public static bool Has(float level)
		{
			return level >= 99.99f;
		}

		public static int FloorOf(StarSource source)
		{
			if (source != StarSource.Forage)
			{
				return 2;
			}
			return 3;
		}

		public static string Name(StarSource source)
		{
			return source switch
			{
				StarSource.Forage => "Botanist", 
				StarSource.Crop => "Tiller", 
				StarSource.Meat => "Rancher", 
				StarSource.Fillet => "Angler", 
				_ => "Gourmet", 
			};
		}

		public static string Description(StarSource source)
		{
			return source switch
			{
				StarSource.Forage => "Wild picks, honey and sap are always gold.", 
				StarSource.Crop => "Crops you harvest are at least silver.", 
				StarSource.Meat => "Meat from creatures you kill is at least silver.", 
				StarSource.Fillet => "Raw fish you clean is at least silver.", 
				_ => "Dishes and meads you make are at least silver.", 
			};
		}
	}
	public static class Settings
	{
		public const string General = "1 - General";

		public const string Features = "2 - Features";

		public static ConfigEntry<bool> LockConfiguration { get; private set; }

		public static ConfigEntry<bool> DailyFortune { get; private set; }

		public static ConfigEntry<bool> ShippingCrate { get; private set; }

		public static ConfigEntry<bool> TraderFriendship { get; private set; }

		public static void Initialize(SyncedConfiguration config)
		{
			LockConfiguration = config.BindLocking("1 - General", "Lock Configuration", defaultValue: true, "Server only. When on, every player uses the server's values for this file and cannot override them locally.");
			DailyFortune = config.Bind("2 - Features", "Daily Fortune", true, "Each day the Norns grant good or bad fortune, the same for everyone, which nudges every star roll up or down.", true, null);
			ShippingCrate = config.Bind("2 - Features", "Shipping Crate", true, "The Shipping Crate piece: what you leave in it is bought at dawn for coins, more for more stars. Off: the piece cannot be built and existing crates no longer pay.", true, null);
			TraderFriendship = config.Bind("2 - Features", "Trader Friendship", true, "Give a starred dish to Haldor, Hildir or the Bog Witch once a day to earn their friendship and a small discount.", true, null);
		}
	}
	public static class SpawnStars
	{
		public delegate int Roller(ItemData item);

		[HarmonyPatch(typeof(ItemDrop), "Awake")]
		private static class Woke
		{
			[HarmonyPostfix]
			private static void Postfix(ItemDrop __instance)
			{
				if (current != null)
				{
					HookGuard.Run("spawn stars", delegate(ItemDrop item)
					{
						Apply(item);
					}, __instance);
				}
			}
		}

		private static Roller current;

		public static Roller Open(Roller roller)
		{
			Roller result = current;
			current = roller;
			return result;
		}

		public static void Close(Roller previous)
		{
			current = previous;
		}

		private static void Apply(ItemDrop item)
		{
			if (!((Object)(object)item.m_nview == (Object)null) && item.m_nview.IsValid() && item.m_nview.IsOwner() && Stars.IsStarItem(item.m_itemData))
			{
				int num = current(item.m_itemData);
				if (num > 0)
				{
					Stars.SetOnDrop(item, num);
				}
			}
		}
	}
	public static class StarOdds
	{
		public const float MaxEffective = 150f;

		public const float PerIngredientStar = 20f;

		public const float PerCreatureStar = 20f;

		public const float MaxCreatureBonus = 60f;

		public const float PerFishLevel = 15f;

		private const float Step = 50f;

		private static readonly float[][] Tables = new float[4][]
		{
			new float[4] { 0.9f, 0.09f, 0.01f, 0f },
			new float[4] { 0.55f, 0.3f, 0.12f, 0.03f },
			new float[4] { 0.25f, 0.38f, 0.26f, 0.11f },
			new float[4] { 0.05f, 0.35f, 0.35f, 0.25f }
		};

		public static float IngredientBonus(float averageStars)
		{
			return Mathf.Clamp(averageStars, 0f, 3f) * 20f;
		}

		public static int IngredientFloor(float averageStars)
		{
			return Mathf.Clamp(Mathf.FloorToInt(averageStars + 0.001f), 0, 3);
		}

		public static float CreatureBonus(int creatureLevel)
		{
			return Mathf.Clamp((float)(creatureLevel - 1) * 20f, 0f, 60f);
		}

		public static float FishBonus(int fishLevel)
		{
			return (float)Mathf.Clamp(fishLevel - 1, 0, 4) * 15f;
		}

		public static float Effective(float level, float bonus)
		{
			return Mathf.Clamp(Sane(level) + Sane(bonus) + Fortune.Levels(), 0f, 150f);
		}

		public static float Chance(float effective, int stars)
		{
			float num = Mathf.Clamp(effective, 0f, 150f) / 50f;
			int num2 = Mathf.Min((int)num, Tables.Length - 2);
			float num3 = num - (float)num2;
			int num4 = Mathf.Clamp(stars, 0, 3);
			return Mathf.Lerp(Tables[num2][num4], Tables[num2 + 1][num4], num3);
		}

		public static int Roll(float level, float bonus, int floor)
		{
			float effective = Effective(level, bonus);
			float num = Random.value;
			int num2 = 3;
			for (int i = 0; i < 3; i++)
			{
				num -= Chance(effective, i);
				if (num < 0f)
				{
					num2 = i;
					break;
				}
			}
			return Mathf.Clamp(Mathf.Max(num2, floor), 0, 3);
		}

		public static float Sane(float value)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return Mathf.Max(0f, value);
			}
			return 0f;
		}
	}
	public static class Stars
	{
		public const int Max = 3;

		private static readonly string[] tierNames = new string[4] { "Plain", "Bronze", "Silver", "Gold" };

		public static int Get(ItemData item)
		{
			return GrindstoneLink.GetStars(item);
		}

		public static bool IsStarItem(ItemData item)
		{
			return GrindstoneLink.IsStarItem(item);
		}

		public static void Set(ItemData item, int stars)
		{
			GrindstoneLink.SetStars(item, Mathf.Clamp(stars, 0, 3));
		}

		public static void SetOnDrop(ItemDrop drop, int stars)
		{
			if (!((Object)(object)drop == (Object)null) && IsStarItem(drop.m_itemData))
			{
				Set(drop.m_itemData, stars);
				drop.SetQuality(drop.m_itemData.m_quality);
				if ((Object)(object)drop.m_nview != (Object)null && drop.m_nview.IsValid() && drop.m_nview.IsOwner())
				{
					drop.Save();
				}
			}
		}

		public static int ToQuality(int stars)
		{
			return Mathf.Clamp(stars, 0, 3) + 1;
		}

		public static string Text(int stars)
		{
			return GrindstoneLink.StarText(stars);
		}

		public static string TierName(int stars)
		{
			return tierNames[Mathf.Clamp(stars, 0, 3)];
		}
	}
	public enum StarSource
	{
		Forage,
		Crop,
		Meat,
		Dish,
		Fillet
	}
	public static class StarSources
	{
		public static string Skill(StarSource source)
		{
			return source switch
			{
				StarSource.Forage => "Foraging", 
				StarSource.Crop => "Farming", 
				StarSource.Meat => "Husbandry", 
				StarSource.Fillet => "Fishing", 
				_ => "Cooking", 
			};
		}
	}
	public static class EatBonus
	{
		private const float FoodPerStar = 0.1f;

		private const float DurationPerStar = 0.1f;

		private const float RestorePerStar = 0.1f;

		private const float LastingPerStar = 0.15f;

		public static float Food(int stars)
		{
			return 0.1f * (float)Clamp(stars);
		}

		public static float Duration(int stars)
		{
			return 0.1f * (float)Clamp(stars);
		}

		public static float Restore(int stars)
		{
			return 0.1f * (float)Clamp(stars);
		}

		public static float Lasting(int stars)
		{
			return 0.15f * (float)Clamp(stars);
		}

		public static int Percent(float share)
		{
			return Mathf.RoundToInt(share * 100f);
		}

		public static float Whole(float value)
		{
			if (!(value <= 0f))
			{
				return Mathf.Ceil(value - 0.5f - 0.001f);
			}
			return 0f;
		}

		public static float Boosted(float baseValue, float bonus)
		{
			return Whole(baseValue * (1f + bonus));
		}

		public static float RoundedShare(float baseValue, float bonus)
		{
			if (!(baseValue > 0f))
			{
				return 0f;
			}
			return Boosted(baseValue, bonus) / baseValue - 1f;
		}

		private static int Clamp(int stars)
		{
			return Mathf.Clamp(stars, 0, 3);
		}
	}
	public static class EatingKeys
	{
		public const string ActiveFood = "hearthhold_food_";
	}
	public static class FoodCleanup
	{
		[HarmonyPatch(typeof(Player), "UpdateFood")]
		private static class Expired
		{
			[HarmonyPrefix]
			private static void Prefix(Player __instance, out int __state)
			{
				__state = __instance.m_foods.Count;
			}

			[HarmonyPostfix]
			private static void Postfix(Player __instance, int __state)
			{
				if (__instance.m_foods.Count < __state)
				{
					FoodStars.Prune(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(Player), "ClearFood")]
		private static class Cleared
		{
			[HarmonyPostfix]
			private static void Postfix(Player __instance)
			{
				FoodStars.Prune(__instance);
			}
		}

		[HarmonyPatch(typeof(Player), "RemoveOneFood")]
		private static class RemovedOne
		{
			[HarmonyPostfix]
			private static void Postfix(Player __instance, bool __result)
			{
				if (__result)
				{
					FoodStars.Prune(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(Player), "OnDeath")]
		private static class Died
		{
			[HarmonyPostfix]
			private static void Postfix(Player __instance)
			{
				FoodStars.Prune(__instance);
			}
		}

		[HarmonyPatch(typeof(Player), "Load")]
		private static class Loaded
		{
			[HarmonyPostfix]
			private static void Postfix(Player __instance)
			{
				FoodStars.Prune(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "EatFood")]
	public static class FoodEaten
	{
		[HarmonyPostfix]
		private static void Postfix(Player __instance, ItemData item, bool __result)
		{
			if (__result && item?.m_shared != null)
			{
				HookGuard.Run("eat starred food", delegate
				{
					Record(__instance, item);
				});
			}
		}

		private static void Record(Player player, ItemData item)
		{
			Food val = Find(player, item);
			if (val != null)
			{
				int stars = Stars.Get(item);
				FoodStars.Set(player, val.m_name, stars);
				Extend(val, item, stars);
				FoodStars.Prune(player);
				FoodTotals.Refresh(player);
			}
		}

		private static void Extend(Food food, ItemData item, int stars)
		{
			float num = EatBonus.Duration(stars);
			float foodBurnTime = item.m_shared.m_foodBurnTime;
			if (num > 0f && foodBurnTime > 0f)
			{
				food.m_time = Mathf.Max(food.m_time, foodBurnTime * (1f + num));
			}
		}

		private static Food Find(Player player, ItemData item)
		{
			foreach (Food food in player.m_foods)
			{
				if (food.m_item == item)
				{
					return food;
				}
			}
			foreach (Food food2 in player.m_foods)
			{
				if (food2.m_item?.m_shared?.m_name == item.m_shared.m_name)
				{
					return food2;
				}
			}
			return null;
		}
	}
	public static class FoodMessage
	{
		[HarmonyPatch(typeof(Player), "EatFood")]
		private static class Scope
		{
			[HarmonyPrefix]
			[HarmonyPriority(800)]
			private static void Prefix(ItemData item, out ItemData __state)
			{
				__state = eating;
				eating = ((Stars.Get(item) > 0) ? item : null);
			}

			[HarmonyFinalizer]
			private static void Finalizer(ItemData __state)
			{
				eating = __state;
			}
		}

		[HarmonyPatch(typeof(Player), "Message")]
		private static class Rewrite
		{
			[HarmonyPrefix]
			private static void Prefix(MessageType type, ref string msg)
			{
				//IL_0013: Unknown result type (might be due to invalid IL or missing references)
				//IL_0015: Invalid comparison between Unknown and I4
				if (eating?.m_shared != null && (int)type == 2 && msg == Text(eating.m_shared, 0f))
				{
					msg = Text(eating.m_shared, EatBonus.Food(Stars.Get(eating)));
				}
			}
		}

		private static ItemData eating;

		private static string Text(SharedData shared, float bonus)
		{
			return Part(shared.m_food, bonus, "$item_food_health") + Part(shared.m_foodStamina, bonus, "$item_food_stamina") + Part(shared.m_foodEitr, bonus, "$item_food_eitr");
		}

		private static string Part(float value, float bonus, string key)
		{
			if (value <= 0f)
			{
				return "";
			}
			string text = ((bonus > 0f) ? EatBonus.Boosted(value, bonus).ToString("0") : value.ToString());
			return " +" + text + " " + key + " ";
		}
	}
	public static class FoodStars
	{
		private static readonly Dictionary<string, string> keys = new Dictionary<string, string>();

		private static readonly List<string> stale = new List<string>();

		public static int Get(Player player, Food food)
		{
			if (player.m_customData.Count == 0 || string.IsNullOrEmpty(food?.m_name))
			{
				return 0;
			}
			if (!player.m_customData.TryGetValue(Key(food.m_name), out var value))
			{
				return 0;
			}
			if (!int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
			{
				return 0;
			}
			return Mathf.Clamp(result, 0, 3);
		}

		public static void Set(Player player, string foodName, int stars)
		{
			if (!string.IsNullOrEmpty(foodName))
			{
				stars = Mathf.Clamp(stars, 0, 3);
				if (stars > 0)
				{
					player.m_customData[Key(foodName)] = stars.ToString(CultureInfo.InvariantCulture);
				}
				else
				{
					player.m_customData.Remove(Key(foodName));
				}
			}
		}

		public static void Prune(Player player)
		{
			if ((Object)(object)player == (Object)null || player.m_customData.Count == 0)
			{
				return;
			}
			foreach (string key in player.m_customData.Keys)
			{
				if (key.StartsWith("hearthhold_food_", StringComparison.Ordinal) && !IsActive(player, key))
				{
					stale.Add(key);
				}
			}
			foreach (string item in stale)
			{
				player.m_customData.Remove(item);
			}
			stale.Clear();
		}

		private static bool IsActive(Player player, string key)
		{
			foreach (Food food in player.m_foods)
			{
				if (!string.IsNullOrEmpty(food.m_name) && Key(food.m_name) == key)
				{
					return true;
				}
			}
			return false;
		}

		private static string Key(string foodName)
		{
			if (!keys.TryGetValue(foodName, out var value))
			{
				value = (keys[foodName] = "hearthhold_food_" + foodName);
			}
			return value;
		}
	}
	[HarmonyPatch(typeof(Player), "GetTotalFoodValue")]
	public static class FoodTotals
	{
		[HarmonyPostfix]
		private static void Postfix(Player __instance, ref float hp, ref float stamina, ref float eitr)
		{
			if (__instance.m_customData.Count == 0)
			{
				return;
			}
			foreach (Food food in __instance.m_foods)
			{
				float num = EatBonus.Food(FoodStars.Get(__instance, food));
				SharedData val = food.m_item?.m_shared;
				if (!(num <= 0f) && val != null)
				{
					hp += food.m_health * EatBonus.RoundedShare(val.m_food, num);
					stamina += food.m_stamina * EatBonus.RoundedShare(val.m_foodStamina, num);
					eitr += food.m_eitr * EatBonus.RoundedShare(val.m_foodEitr, num);
				}
			}
		}

		public static void Refresh(Player player)
		{
			float num = default(float);
			float num2 = default(float);
			float num3 = default(float);
			player.GetTotalFoodValue(ref num, ref num2, ref num3);
			player.SetMaxHealth(num, true);
			player.SetMaxStamina(num2, true);
			player.SetMaxEitr(num3, true);
		}
	}
	public static class MeadBoost
	{
		public static void Apply(StatusEffect effect, int stars)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			switch (MeadKinds.Of(effect))
			{
			case MeadKind.Restore:
				Restore((SE_Stats)effect, EatBonus.Restore(stars));
				break;
			case MeadKind.Lasting:
				effect.m_ttl *= 1f + EatBonus.Lasting(stars);
				break;
			}
		}

		private static void Restore(SE_Stats stats, float bonus)
		{
			if (!(bonus <= 0f) && !((Object)(object)((StatusEffect)stats).m_character == (Object)null))
			{
				UpFront(stats, bonus);
				if (stats.m_healthOverTime > 0f)
				{
					stats.m_healthOverTimeTickHP *= 1f + EatBonus.RoundedShare(stats.m_healthOverTime, bonus);
					stats.m_healthOverTime = EatBonus.Boosted(stats.m_healthOverTime, bonus);
				}
				if (stats.m_staminaOverTime > 0f)
				{
					stats.m_staminaOverTime *= 1f + EatBonus.RoundedShare(stats.m_staminaOverTime, bonus);
				}
				if (stats.m_eitrOverTime > 0f)
				{
					stats.m_eitrOverTime *= 1f + EatBonus.RoundedShare(stats.m_eitrOverTime, bonus);
				}
			}
		}

		private static void UpFront(SE_Stats stats, float bonus)
		{
			Character character = ((StatusEffect)stats).m_character;
			if (stats.m_healthUpFront > 0f)
			{
				character.Heal(EatBonus.Boosted(stats.m_healthUpFront, bonus) - stats.m_healthUpFront, true);
				stats.m_healthUpFront = EatBonus.Boosted(stats.m_healthUpFront, bonus);
			}
			if (stats.m_staminaUpFront > 0f)
			{
				character.AddStamina(EatBonus.Boosted(stats.m_staminaUpFront, bonus) - stats.m_staminaUpFront);
				stats.m_staminaUpFront = EatBonus.Boosted(stats.m_staminaUpFront, bonus);
			}
			if (stats.m_eitrUpFront > 0f)
			{
				character.AddEitr(EatBonus.Boosted(stats.m_eitrUpFront, bonus) - stats.m_eitrUpFront);
				stats.m_eitrUpFront = EatBonus.Boosted(stats.m_eitrUpFront, bonus);
			}
		}
	}
	public static class MeadDrink
	{
		private struct Drink
		{
			public StatusEffect Effect;

			public int Stars;
		}

		[HarmonyPatch(typeof(Player), "ConsumeItem")]
		private static class Consumed
		{
			[HarmonyPrefix]
			private static void Prefix(ItemData item, out Drink __state)
			{
				__state = Open(item);
			}

			[HarmonyFinalizer]
			private static void Finalizer(Drink __state)
			{
				current = __state;
			}
		}

		[HarmonyPatch(typeof(ItemDrop), "Eat")]
		private static class EatenFromGround
		{
			[HarmonyPrefix]
			private static void Prefix(ItemDrop __instance, out Drink __state)
			{
				__state = Open(__instance.m_itemData);
			}

			[HarmonyFinalizer]
			private static void Finalizer(Drink __state)
			{
				current = __state;
			}
		}

		[HarmonyPatch(typeof(SEMan), "AddStatusEffect", new Type[]
		{
			typeof(StatusEffect),
			typeof(bool),
			typeof(int),
			typeof(float),
			typeof(short)
		})]
		private static class Added
		{
			[HarmonyPostfix]
			private static void Postfix(SEMan __instance, StatusEffect statusEffect, StatusEffect __result)
			{
				if (current.Effect != null && statusEffect == current.Effect && !((Object)(object)__result == (Object)null) && __instance.m_character is Player)
				{
					int stars = current.Stars;
					current.Effect = null;
					HookGuard.Run("boost starred mead", delegate
					{
						MeadBoost.Apply(__result, stars);
					});
				}
			}
		}

		private static Drink current;

		private static Drink Open(ItemData item)
		{
			Drink result = current;
			StatusEffect val = item?.m_shared?.m_consumeStatusEffect;
			int num = (((Object)(object)val != (Object)null) ? Stars.Get(item) : 0);
			current = ((num > 0) ? new Drink
			{
				Effect = val,
				Stars = num
			} : default(Drink));
			return result;
		}
	}
	public enum MeadKind
	{
		None,
		Restore,
		Lasting
	}
	public static class MeadKinds
	{
		public static MeadKind Of(StatusEffect effect)
		{
			if ((Object)(object)effect == (Object)null)
			{
				return MeadKind.None;
			}
			SE_Stats val = (SE_Stats)(object)((effect is SE_Stats) ? effect : null);
			if (val != null && Restores(val))
			{
				return MeadKind.Restore;
			}
			if (!(effect.m_ttl > 0f))
			{
				return MeadKind.None;
			}
			return MeadKind.Lasting;
		}

		private static bool Restores(SE_Stats stats)
		{
			if (!(stats.m_healthUpFront > 0f) && !(stats.m_healthOverTime > 0f) && !(stats.m_staminaUpFront > 0f) && !(stats.m_staminaOverTime > 0f) && !(stats.m_eitrUpFront > 0f))
			{
				return stats.m_eitrOverTime > 0f;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[]
	{
		typeof(ItemData),
		typeof(int),
		typeof(bool),
		typeof(float),
		typeof(int),
		typeof(bool)
	})]
	public static class StarTooltip
	{
		private static readonly StringBuilder text = new StringBuilder();

		[HarmonyPostfix]
		private static void Postfix(ItemData item, bool appending, ref string __result)
		{
			if (appending || __result == null || item?.m_shared == null)
			{
				return;
			}
			int num = Stars.Get(item);
			if (num > 0)
			{
				text.Clear();
				FoodLine(item.m_shared, num);
				MeadLine(item.m_shared.m_consumeStatusEffect, num);
				if (text.Length > 0)
				{
					__result += text.ToString();
				}
			}
		}

		private static void FoodLine(SharedData shared, int stars)
		{
			if (!(Kitchen.Value(shared) <= 0f))
			{
				float num = EatBonus.Food(stars);
				float num2 = EatBonus.Duration(stars);
				Label(stars).Append('+').Append(EatBonus.Percent(num)).Append("% ");
				StatLine.Values(text, shared.m_food, shared.m_foodStamina, shared.m_foodEitr, 1f + num);
				text.Append(", lasts ").Append(EatBonus.Percent(num2)).Append("% longer (")
					.Append(ItemData.GetDurationString(shared.m_foodBurnTime * (1f + num2)))
					.Append(')');
			}
		}

		private static void MeadLine(StatusEffect effect, int stars)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			switch (MeadKinds.Of(effect))
			{
			case MeadKind.Restore:
				Restore((SE_Stats)effect, EatBonus.Restore(stars), stars);
				break;
			case MeadKind.Lasting:
				Lasting(effect.m_ttl, EatBonus.Lasting(stars), stars);
				break;
			}
		}

		private static void Restore(SE_Stats stats, float bonus, int stars)
		{
			Label(stars).Append("restores ").Append(EatBonus.Percent(bonus)).Append("% more ");
			float stamina = stats.m_staminaUpFront + (stats.m_staminaOverTimeIsFraction ? 0f : stats.m_staminaOverTime);
			float health = stats.m_healthUpFront + stats.m_healthOverTime;
			StatLine.Values(text, health, stamina, stats.m_eitrUpFront + stats.m_eitrOverTime, 1f + bonus);
		}

		private static void Lasting(float ttl, float bonus, int stars)
		{
			Label(stars).Append("lasts ").Append(EatBonus.Percent(bonus)).Append("% longer (")
				.Append(ItemData.GetDurationString(ttl * (1f + bonus)))
				.Append(')');
		}

		private static StringBuilder Label(int stars)
		{
			return text.Append("\n<color=orange>").Append(Stars.TierName(stars)).Append("</color>: ");
		}
	}
	public static class StatLine
	{
		public static void Values(StringBuilder text, float health, float stamina, float eitr, float factor)
		{
			int num = ((health > 0f) ? 1 : 0) + ((stamina > 0f) ? 1 : 0) + ((eitr > 0f) ? 1 : 0);
			if (num != 0)
			{
				int written = 0;
				Name(text, health, "health", num, ref written);
				Name(text, stamina, "stamina", num, ref written);
				Name(text, eitr, "eitr", num, ref written);
				text.Append(" (");
				written = 0;
				Number(text, health * factor, ref written);
				Number(text, stamina * factor, ref written);
				Number(text, eitr * factor, ref written);
				text.Append(')');
			}
		}

		private static void Name(StringBuilder text, float value, string name, int count, ref int written)
		{
			if (!(value <= 0f))
			{
				if (written > 0)
				{
					text.Append((written == count - 1) ? " and " : ", ");
				}
				text.Append(name);
				written++;
			}
		}

		private static void Number(StringBuilder text, float value, ref int written)
		{
			if (!(value <= 0f))
			{
				if (written++ > 0)
				{
					text.Append(" / ");
				}
				text.Append(EatBonus.Whole(value).ToString("0", CultureInfo.InvariantCulture));
			}
		}
	}
	public static class Fortune
	{
		public enum Kind
		{
			IllOmened,
			Poor,
			Fair,
			Good,
			Blessed
		}

		private static readonly float[] levels = new float[5] { -10f, -5f, 0f, 5f, 10f };

		private static readonly string[] names = new string[5] { "Ill-omened", "Poor", "Fair", "Good", "Blessed" };

		private static int cachedDay = -1;

		private static int cachedSeed;

		private static Kind cachedKind = Kind.Fair;

		public static bool On
		{
			get
			{
				if (Settings.DailyFortune != null)
				{
					return Settings.DailyFortune.Value;
				}
				return false;
			}
		}

		public static float Levels()
		{
			if (!On)
			{
				return 0f;
			}
			return levels[(int)Today()];
		}

		public static Kind Today()
		{
			if ((Object)(object)EnvMan.instance == (Object)null || WorldGenerator.instance == null)
			{
				return Kind.Fair;
			}
			return Of(EnvMan.instance.GetDay(), WorldGenerator.instance.GetSeed());
		}

		public static Kind Of(int day, int seed)
		{
			if (day == cachedDay && seed == cachedSeed)
			{
				return cachedKind;
			}
			cachedDay = day;
			cachedSeed = seed;
			int num = (StringExtensionMethods.GetStableHashCode($"hearthhold_fortune:{seed}:{day}") & 0x7FFFFFFF) % 100;
			cachedKind = ((num >= 10) ? ((num < 30) ? Kind.Poor : ((num < 70) ? Kind.Fair : ((num < 90) ? Kind.Good : Kind.Blessed))) : Kind.IllOmened);
			return cachedKind;
		}

		public static string Name(Kind kind)
		{
			return names[(int)kind];
		}

		public static float LevelsOf(Kind kind)
		{
			return levels[(int)kind];
		}

		public static string Line(Kind kind)
		{
			float num = LevelsOf(kind);
			string text = ((num == 0f) ? "star rolls as usual" : string.Format("star rolls {0}{1:0}", (num > 0f) ? "+" : "", num));
			return Name(kind) + " fortune today: " + text;
		}
	}
	public static class FortuneNotice
	{
		[HarmonyPatch(typeof(Player), "Update")]
		private static class Tick
		{
			[HarmonyPostfix]
			private static void Postfix(Player __instance)
			{
				if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
				{
					return;
				}
				timer += Time.deltaTime;
				if (!(timer < 3f))
				{
					timer = 0f;
					HookGuard.Run("fortune notice", delegate(Player player)
					{
						Check(player);
					}, __instance);
				}
			}
		}

		[HarmonyPatch(typeof(Terminal), "InitTerminal")]
		private static class Command
		{
			[CompilerGenerated]
			private static class <>O
			{
				public static ConsoleEvent <0>__Print;
			}

			[HarmonyPostfix]
			private static void Postfix()
			{
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Unknown result type (might be due to invalid IL or missing references)
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0025: Expected O, but got Unknown
				object obj = <>O.<0>__Print;
				if (obj == null)
				{
					ConsoleEvent val = Print;
					<>O.<0>__Print = val;
					obj = (object)val;
				}
				new ConsoleCommand("fortune", "Hearthhold: today's fortune and what it does to star rolls.", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			}
		}

		private const float Interval = 3f;

		private static float timer;

		private static int toldDay = -1;

		private static Player toldPlayer;

		private static void Check(Player player)
		{
			if (Fortune.On && !((Object)(object)EnvMan.instance == (Object)null) && WorldGenerator.instance != null)
			{
				int day = EnvMan.instance.GetDay();
				if (day != toldDay || !((Object)(object)player == (Object)(object)toldPlayer))
				{
					toldDay = day;
					toldPlayer = player;
					((Character)player).Message((MessageType)1, Fortune.Line(Fortune.Today()), 0, (Sprite)null, false);
				}
			}
		}

		private static void Print(ConsoleEventArgs args)
		{
			if (!Fortune.On)
			{
				args.Context.AddString("Daily Fortune is off on this server.");
			}
			else
			{
				args.Context.AddString(Fortune.Line(Fortune.Today()));
			}
		}
	}
	public static class Friendship
	{
		public const int PointsPerHeart = 100;

		public const int MaxHearts = 10;

		public const int MaxPoints = 1000;

		public const int PercentPerHeart = 1;

		private static readonly string[] lines = BuildLines();

		public static bool On
		{
			get
			{
				if (Settings.TraderFriendship != null)
				{
					return Settings.TraderFriendship.Value;
				}
				return false;
			}
		}

		public static int Points(Player player, TraderInfo trader)
		{
			return ReadInt(player, trader.PointsKey, 0);
		}

		public static int Hearts(Player player, TraderInfo trader)
		{
			return Mathf.Clamp(Points(player, trader) / 100, 0, 10);
		}

		public static int DiscountPercent(Player player, TraderInfo trader)
		{
			if (!On || !((Object)(object)player != (Object)null))
			{
				return 0;
			}
			return Hearts(player, trader);
		}

		public static int Discounted(int price, int percent)
		{
			if (percent <= 0 || price <= 1)
			{
				return price;
			}
			return Mathf.Max(1, price * (100 - percent) / 100);
		}

		public static int LastGiftDay(Player player, TraderInfo trader)
		{
			return ReadInt(player, trader.GiftDayKey, -1);
		}

		public static int AddGift(Player player, TraderInfo trader, int points, int day)
		{
			int num = Mathf.Clamp(Points(player, trader) + points, 0, 1000);
			player.m_customData[trader.PointsKey] = num.ToString();
			player.m_customData[trader.GiftDayKey] = day.ToString();
			return num / 100;
		}

		public static string Line(int hearts)
		{
			return lines[Mathf.Clamp(hearts, 0, 10)];
		}

		private static string[] BuildLines()
		{
			string[] array = new string[11];
			for (int i = 0; i <= 10; i++)
			{
				string arg = ((i > 0) ? $", {i}% off" : "");
				array[i] = $"Friendship {i}/{10}{arg}";
			}
			return array;
		}

		private static int ReadInt(Player player, string key, int fallback)
		{
			if ((Object)(object)player == (Object)null || !player.m_customData.TryGetValue(key, out var value))
			{
				return fallback;
			}
			if (!int.TryParse(value, out var result))
			{
				return fallback;
			}
			return result;
		}
	}
	public static class FriendshipKeys
	{
		public const string PointsPrefix = "hearthhold_friendship_";

		public const string GiftDayPrefix = "hearthhold_giftday_";
	}
	public static class StoreDiscount
	{
		[HarmonyPatch(typeof(StoreGui), "FillList")]
		private static class Fill
		{
			[HarmonyPrefix]
			private static void Prefix(StoreGui __instance)
			{
				Enter(__instance);
			}

			[HarmonyFinalizer]
			private static void Finalizer()
			{
				Exit();
			}
		}

		[HarmonyPatch(typeof(StoreGui), "BuySelectedItem")]
		private static class Buy
		{
			[HarmonyPrefix]
			private static void Prefix(StoreGui __instance)
			{
				Enter(__instance);
			}

			[HarmonyFinalizer]
			private static void Finalizer()
			{
				Exit();
			}
		}

		[HarmonyPatch(typeof(StoreGui), "CanAfford")]
		private static class Afford
		{
			[HarmonyPrefix]
			private static bool Prefix(StoreGui __instance, TradeItem item, ref bool __result)
			{
				int num = ((depth <= 0 && item != null) ? Percent(__instance) : 0);
				if (num <= 0)
				{
					return true;
				}
				__result = Friendship.Discounted(item.m_price, num) <= __instance.GetPlayerCoins();
				return false;
			}
		}

		[HarmonyPatch(typeof(StoreGui), "Show")]
		private static class Open
		{
			[HarmonyPostfix]
			private static void Postfix(Trader trader)
			{
				HookGuard.Run("store discount notice", delegate(Trader t)
				{
					Tell(t);
				}, trader);
			}
		}

		private static readonly List<TradeItem> lowered = new List<TradeItem>();

		private static readonly List<int> prices = new List<int>();

		private static int depth;

		private static int Percent(StoreGui gui)
		{
			if (!((Object)(object)gui.m_trader == (Object)null))
			{
				return Friendship.DiscountPercent(Player.m_localPlayer, TraderInfo.Of(gui.m_trader));
			}
			return 0;
		}

		private static void Enter(StoreGui gui)
		{
			if (depth++ <= 0)
			{
				HookGuard.Run("store discount", delegate(StoreGui g)
				{
					Lower(g);
				}, gui);
			}
		}

		private static void Lower(StoreGui gui)
		{
			int num = Percent(gui);
			if (num <= 0)
			{
				return;
			}
			foreach (TradeItem availableItem in gui.m_trader.GetAvailableItems())
			{
				if (availableItem != null && !lowered.Contains(availableItem))
				{
					lowered.Add(availableItem);
					prices.Add(availableItem.m_price);
					availableItem.m_price = Friendship.Discounted(availableItem.m_price, num);
				}
			}
		}

		private static void Exit()
		{
			if (depth <= 0 || --depth <= 0)
			{
				for (int i = 0; i < lowered.Count; i++)
				{
					lowered[i].m_price = prices[i];
				}
				lowered.Clear();
				prices.Clear();
			}
		}

		private static void Tell(Trader trader)
		{
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)trader == (Object)null) && !((Object)(object)localPlayer == (Object)null))
			{
				TraderInfo traderInfo = TraderInfo.Of(trader);
				if (Friendship.DiscountPercent(localPlayer, traderInfo) > 0)
				{
					((Character)localPlayer).Message((MessageType)1, traderInfo.DisplayName + ": " + Friendship.Line(Friendship.Hearts(localPlayer, traderInfo)), 0, (Sprite)null, false);
				}
			}
		}
	}
	public static class TraderGift
	{
		[HarmonyPatch(typeof(Trader), "UseItem")]
		private static class Use
		{
			[HarmonyPrefix]
			private static bool Prefix(Trader __instance, Humanoid user, ItemData item, ref bool __result)
			{
				//IL_0055: Unknown result type (might be due to invalid IL or missing references)
				//IL_005f: Expected O, but got Unknown
				if (!Friendship.On || (Object)(object)user == (Object)null || (Object)(object)user != (Object)(object)Player.m_localPlayer || !IsDish(item) || TraderTakes(__instance, item))
				{
					return true;
				}
				Player player = (Player)user;
				bool num = HookGuard.Run("trader gift", () => Give(__instance, player, item), fallback: false);
				if (num)
				{
					__result = true;
				}
				return !num;
			}
		}

		public const int BasePoints = 20;

		public const int PointsPerStar = 20;

		private static bool IsDish(ItemData item)
		{
			if (item?.m_shared != null && (Object)(object)item.m_dropPrefab != (Object)null)
			{
				return Kitchen.IsProduct(((Object)item.m_dropPrefab).name);
			}
			return false;
		}

		private static bool TraderTakes(Trader trader, ItemData item)
		{
			foreach (TraderUseItem useItem in trader.m_useItems)
			{
				if ((Object)(object)useItem?.m_prefab != (Object)null && useItem.m_prefab.m_itemData.m_shared.m_name == item.m_shared.m_name)
				{
					return true;
				}
			}
			return false;
		}

		private static bool Give(Trader trader, Player player, ItemData item)
		{
			if ((Object)(object)EnvMan.instance == (Object)null)
			{
				return false;
			}
			TraderInfo traderInfo = TraderInfo.Of(trader);
			int num = Stars.Get(item);
			int day = EnvMan.instance.GetDay();
			if (num < 1)
			{
				((Character)player).Message((MessageType)2, traderInfo.DisplayName + " only accepts starred food as a gift.", 0, (Sprite)null, false);
			}
			else if (Friendship.LastGiftDay(player, traderInfo) == day)
			{
				((Character)player).Message((MessageType)2, "Come back tomorrow.", 0, (Sprite)null, false);
			}
			else
			{
				Accept(traderInfo, player, item, num, day);
			}
			return true;
		}

		private static void Accept(TraderInfo info, Player player, ItemData item, int stars, int day)
		{
			string text = Stars.TierName(stars) + " " + Localization.instance.Localize(item.m_shared.m_name);
			if (((Humanoid)player).GetInventory().RemoveItem(item, 1))
			{
				int hearts = Friendship.AddGift(player, info, 20 + 20 * stars, day);
				string text2 = info.DisplayName + " likes the " + text + ". " + Friendship.Line(hearts);
				((Character)player).Message((MessageType)2, text2, 0, (Sprite)null, false);
			}
		}
	}
	public static class TraderHover
	{
		[HarmonyPatch(typeof(Trader), "GetHoverText")]
		private static class Hover
		{
			[HarmonyPostfix]
			private static void Postfix(Trader __instance, ref string __result)
			{
				if (!Friendship.On || (Object)(object)Player.m_localPlayer == (Object)null || string.IsNullOrEmpty(__result))
				{
					return;
				}
				try
				{
					__result = WithLine(__instance, __result);
				}
				catch (Exception exception)
				{
					Guard.Report(exception, "trader hover");
				}
			}
		}

		private static string WithLine(Trader trader, string text)
		{
			TraderInfo traderInfo = TraderInfo.Of(trader);
			int num = Friendship.Hearts(Player.m_localPlayer, traderInfo);
			if (num != traderInfo.HoverHearts || text != traderInfo.HoverBase)
			{
				traderInfo.HoverHearts = num;
				traderInfo.HoverBase = text;
				traderInfo.HoverText = text + "\n" + Friendship.Line(num);
			}
			return traderInfo.HoverText;
		}
	}
	public sealed class TraderInfo
	{
		private const int PruneAt = 32;

		private static readonly Dictionary<Trader, TraderInfo> cache = new Dictionary<Trader, TraderInfo>();

		private static readonly List<Trader> gone = new List<Trader>();

		public readonly string Prefab;

		public readonly string PointsKey;

		public readonly string GiftDayKey;

		public readonly string DisplayName;

		internal string HoverBase;

		internal string HoverText;

		internal int HoverHearts = -1;

		private TraderInfo(Trader trader)
		{
			Prefab = Utils.GetPrefabName(((Component)trader).gameObject);
			PointsKey = "hearthhold_friendship_" + Prefab;
			GiftDayKey = "hearthhold_giftday_" + Prefab;
			DisplayName = ((Localization.instance != null) ? Localization.instance.Localize(trader.m_name) : trader.m_name);
		}

		public static TraderInfo Of(Trader trader)
		{
			if (cache.TryGetValue(trader, out var value))
			{
				return value;
			}
			if (cache.Count >= 32)
			{
				Prune();
			}
			value = new TraderInfo(trader);
			cache[trader] = value;
			return value;
		}

		private static void Prune()
		{
			gone.Clear();
			foreach (Trader key in cache.Keys)
			{
				if ((Object)(object)key == (Object)null)
				{
					gone.Add(key);
				}
			}
			foreach (Trader item in gone)
			{
				cache.Remove(item);
			}
			gone.Clear();
		}
	}
	[BepInPlugin("com.Hearthhold", "Hearthhold", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Hearthhold : BaseUnityPlugin
	{
		public const string PluginGuid = "com.Hearthhold";

		public const string PluginName = "Hearthhold";

		public const string PluginVersion = "0.1.0";

		public static ManualLogSource Log { get; private set; }

		public static SyncedConfiguration Synced { get; private set; }

		private void Awake()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Synced = new SyncedConfiguration((BaseUnityPlugin)(object)this, ((BaseUnityPlugin)this).Logger, "Hearthhold", "0.1.0");
			Settings.Initialize(Synced);
			Harmony val = new Harmony("com.Hearthhold");
			val.PatchAll(Assembly.GetExecutingAssembly());
			Synced.Finish(val);
			Guard.Install(val, ((BaseUnityPlugin)this).Logger, Assembly.GetExecutingAssembly());
		}
	}
	public static class KitchenCraft
	{
		[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
		private static class Crafting
		{
			[HarmonyPrefix]
			private static void Prefix(InventoryGui __instance, Player player)
			{
				HookGuard.Run("kitchen craft", delegate((InventoryGui __instance, Player player) args)
				{
					Begin(args.__instance, args.player);
				}, (__instance, player));
			}

			[HarmonyPostfix]
			[HarmonyPriority(0)]
			[HarmonyAfter(new string[] { "com.GrindstoneSkills" })]
			private static void Postfix()
			{
				if (current != null)
				{
					HookGuard.Run("kitchen craft stars", Finish);
				}
			}

			[HarmonyFinalizer]
			private static void Finalizer()
			{
				HookGuard.Run("kitchen craft", End);
			}
		}

		private static KitchenCraftContext current;

		private static void Begin(InventoryGui gui, Player player)
		{
			End();
			CraftingStation station = (((Object)(object)player == (Object)null) ? null : player.GetCurrentCraftingStation());
			Recipe craftRecipe = gui.m_craftRecipe;
			if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && Kitchen.IsKitchen(station) && !((Object)(object)craftRecipe?.m_item == (Object)null) && gui.m_craftUpgradeItem == null)
			{
				current = new KitchenCraftContext(gui, player, station);
				KitchenIngredientOrder.BestFirst(((Humanoid)player).GetInventory());
				KitchenIngredientRecord.Start();
			}
		}

		private static void Finish()
		{
			KitchenCraftContext kitchenCraftContext = current;
			KitchenIngredientRecord.Stop();
			if (kitchenCraftContext != null && kitchenCraftContext.Grades)
			{
				KitchenCraftSplit.Apply(kitchenCraftContext);
			}
		}

		private static void End()
		{
			KitchenIngredientRecord.Clear();
			KitchenIngredientOrder.Restore();
			current = null;
		}
	}
	public sealed class KitchenCraftContext
	{
		public readonly Player Player;

		public readonly ItemDrop Product;

		public readonly string Prefab;

		public readonly SharedData Shared;

		public readonly int Variant;

		public readonly bool Cheated;

		public readonly int PlainBefore;

		public readonly float CookLevel;

		public readonly float FishingLevel;

		public bool Grades => Stars.IsStarItem(Product.m_itemData);

		public KitchenCraftContext(InventoryGui gui, Player player, CraftingStation station)
		{
			Player = player;
			Product = gui.m_craftRecipe.m_item;
			Prefab = ((Object)((Component)Product).gameObject).name;
			Shared = Product.m_itemData.m_shared;
			Variant = gui.m_craftVariant;
			Cheated = WillBeCheated(player, station, gui.m_craftRecipe);
			PlainBefore = PlainCount(((Humanoid)player).GetInventory(), Shared.m_name);
			CookLevel = GrindstoneLink.LocalLevel(StarSources.Skill(StarSource.Dish));
			FishingLevel = GrindstoneLink.LocalLevel(StarSources.Skill(StarSource.Fillet));
		}

		public static int PlainCount(Inventory inventory, string name)
		{
			int num = 0;
			foreach (ItemData item in inventory.m_inventory)
			{
				if (item.m_shared.m_name == name && item.m_quality == Stars.ToQuality(0))
				{
					num += item.m_stack;
				}
			}
			return num;
		}

		private static bool WillBeCheated(Player player, CraftingStation station, Recipe recipe)
		{
			bool num = ((Humanoid)player).GetInventory().ItemCheated(recipe.m_resources, -1, true) || player.NoCostCheat();
			ZNetView val = (((Object)(object)station == (Object)null) ? null : station.m_nview);
			bool flag = (Object)(object)val != (Object)null && val.IsValid() && val.GetZDO().GetBool(ZDOVars.s_cheated, false);
			if (num || flag)
			{
				return !PlayerProfile.s_bypassCheatChecks;
			}
			return false;
		}
	}
	public static class KitchenCraftSplit
	{
		public static void Apply(KitchenCraftContext craft)
		{
			Inventory inventory = ((Humanoid)craft.Player).GetInventory();
			int num = KitchenCraftContext.PlainCount(inventory, craft.Shared.m_name) - craft.PlainBefore;
			if (num <= 0)
			{
				return;
			}
			int[] array = Roll(craft, num);
			int num2 = num - array[0];
			if (num2 > 0)
			{
				inventory.RemoveItem(craft.Shared.m_name, num2, Stars.ToQuality(0), true);
				for (int i = 1; i <= 3; i++)
				{
					Give(craft, i, array[i]);
				}
			}
		}

		private static int[] Roll(KitchenCraftContext craft, int units)
		{
			int fishLevel = KitchenIngredientRecord.FishLevel;
			float bonus = StarOdds.IngredientBonus(KitchenIngredientRecord.AverageStars) + ((fishLevel > 0) ? StarOdds.FishBonus(fishLevel) : 0f);
			int num = Mathf.Max(Professions.Floor(StarSource.Dish, craft.CookLevel), StarOdds.IngredientFloor(KitchenIngredientRecord.AverageStars));
			if (fishLevel > 0)
			{
				num = Mathf.Max(num, Professions.Floor(StarSource.Fillet, craft.FishingLevel));
			}
			int[] array = new int[4];
			for (int i = 0; i < units; i++)
			{
				array[StarOdds.Roll(craft.CookLevel, bonus, num)]++;
			}
			return array;
		}

		private static void Give(KitchenCraftContext craft, int stars, int count)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			if (count > 0)
			{
				int num = Stars.ToQuality(stars);
				Player player = craft.Player;
				Inventory inventory = ((Humanoid)player).GetInventory();
				int num2 = Mathf.Min(count, Room(inventory, craft.Shared, num));
				if (num2 > 0)
				{
					inventory.AddItem(craft.Prefab, num2, num, craft.Variant, player.GetPlayerID(), player.GetPlayerName(), new Vector2i(-1, -1), craft.Cheated, false, true);
				}
				if (count > num2)
				{
					AtFeet(player, NewItem(craft, num), count - num2);
				}
			}
		}

		private static int Room(Inventory inventory, SharedData shared, int quality)
		{
			int num = (byte)Game.m_worldLevel;
			int num2 = inventory.GetEmptySlots() * Mathf.Max(1, shared.m_maxStackSize);
			foreach (ItemData item in inventory.m_inventory)
			{
				if (item.m_shared.m_name == shared.m_name && item.m_quality == quality && item.m_worldLevel == num)
				{
					num2 += Mathf.Max(0, item.m_shared.m_maxStackSize - item.m_stack);
				}
			}
			return num2;
		}

		private static ItemData NewItem(KitchenCraftContext craft, int quality)
		{
			ItemData obj = craft.Product.m_itemData.Clone();
			obj.m_dropPrefab = ((Component)craft.Product).gameObject;
			obj.m_quality = quality;
			obj.m_variant = craft.Variant;
			obj.m_durability = obj.GetMaxDurability();
			obj.m_crafterID = craft.Player.GetPlayerID();
			obj.m_crafterName = craft.Player.GetPlayerName();
			obj.m_worldLevel = (byte)Game.m_worldLevel;
			obj.m_cheated = craft.Cheated;
			return obj;
		}

		private static void AtFeet(Player player, ItemData item, int amount)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			Transform transform = ((Component)player).transform;
			Vector3 val = transform.position + transform.forward + transform.up;
			int num = Mathf.Max(1, item.m_shared.m_maxStackSize);
			for (int num2 = amount; num2 > 0; num2 -= num)
			{
				ItemDrop.DropItem(item, Mathf.Min(num2, num), val, transform.rotation);
			}
		}
	}
	public static class KitchenIngredientOrder
	{
		private static readonly List<ItemData> original = new List<ItemData>();

		private static Inventory sorted;

		public static void BestFirst(Inventory inventory)
		{
			Restore();
			List<ItemData> list = inventory?.m_inventory;
			if (list != null && list.Any((ItemData item) => Stars.Get(item) > 0))
			{
				original.AddRange(list);
				List<ItemData> collection = list.OrderByDescending(Stars.Get).ToList();
				list.Clear();
				list.AddRange(collection);
				sorted = inventory;
			}
		}

		public static void Restore()
		{
			Inventory val = sorted;
			sorted = null;
			if (val != null)
			{
				Reorder(val.m_inventory);
			}
			original.Clear();
		}

		private static void Reorder(List<ItemData> items)
		{
			HashSet<ItemData> hashSet = new HashSet<ItemData>(items);
			HashSet<ItemData> before = new HashSet<ItemData>(original);
			List<ItemData> collection = original.Where(hashSet.Contains).Concat(items.Where((ItemData item) => !before.Contains(item))).ToList();
			items.Clear();
			items.AddRange(collection);
		}
	}
	public static class KitchenIngredientRecord
	{
		[HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[]
		{
			typeof(string),
			typeof(int),
			typeof(int),
			typeof(bool)
		})]
		private static class NameTaken
		{
			[HarmonyPrefix]
			[HarmonyPriority(200)]
			private static void Prefix(Inventory __instance, string name, int amount, int itemQuality, bool worldLevelBased, bool __runOriginal)
			{
				if (Recording && __runOriginal)
				{
					HookGuard.Run("kitchen ingredients", delegate((Inventory __instance, string name, int amount, int itemQuality, bool worldLevelBased) args)
					{
						NoteByName(args.__instance, args.name, args.amount, args.itemQuality, args.worldLevelBased);
					}, (__instance, name, amount, itemQuality, worldLevelBased));
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[]
		{
			typeof(ItemData),
			typeof(int)
		})]
		private static class StackTaken
		{
			[HarmonyPrefix]
			[HarmonyPriority(200)]
			private static void Prefix(Inventory __instance, ItemData item, int amount, bool __runOriginal)
			{
				if (Recording && __runOriginal && item != null && __instance.ContainsItem(item))
				{
					HookGuard.Run("kitchen ingredients", delegate((ItemData item, int amount) args)
					{
						Note(args.item, Mathf.Min(args.item.m_stack, args.amount));
					}, (item, amount));
				}
			}
		}

		private static int starUnits;

		private static int starSum;

		public static bool Recording { get; private set; }

		public static int FishLevel { get; private set; }

		public static float AverageStars
		{
			get
			{
				if (starUnits != 0)
				{
					return (float)starSum / (float)starUnits;
				}
				return 0f;
			}
		}

		public static void Start()
		{
			Clear();
			Recording = true;
		}

		public static void Stop()
		{
			Recording = false;
		}

		public static void Clear()
		{
			Recording = false;
			starUnits = 0;
			starSum = 0;
			FishLevel = 0;
		}

		private static void Note(ItemData stack, int count)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Invalid comparison between Unknown and I4
			if (stack?.m_shared != null && count > 0)
			{
				if ((int)stack.m_shared.m_itemType == 21)
				{
					FishLevel = Mathf.Max(FishLevel, stack.m_quality);
				}
				if (Stars.IsStarItem(stack))
				{
					starUnits += count;
					starSum += Stars.Get(stack) * count;
				}
			}
		}

		private static void NoteByName(Inventory inventory, string name, int amount, int quality, bool worldLevelBased)
		{
			foreach (ItemData item in inventory.m_inventory)
			{
				if (amount <= 0)
				{
					break;
				}
				if (!(item.m_shared.m_name != name) && (quality < 0 || item.m_quality == quality) && (!worldLevelBased || item.m_worldLevel >= Game.m_worldLevel))
				{
					int num = Mathf.Min(item.m_stack, amount);
					Note(item, num);
					amount -= num;
				}
			}
		}
	}
	public static class KitchenBarrel
	{
		public struct Pending
		{
			public bool Empty;

			public int Stars;
		}

		[HarmonyPatch(typeof(Fermenter), "AddItem")]
		private static class Adding
		{
			[HarmonyPrefix]
			[HarmonyPriority(800)]
			private static void Prefix(Fermenter __instance, Humanoid user, ItemData item)
			{
				HookGuard.Run("barrel mark", delegate((Fermenter __instance, Humanoid user, ItemData item) args)
				{
					SendMark(args.__instance, args.user, args.item);
				}, (__instance, user, item));
			}
		}

		[HarmonyPatch(typeof(Fermenter), "RPC_AddItem")]
		private static class Added
		{
			[HarmonyPrefix]
			private static void Prefix(Fermenter __instance, long sender, out Pending __state)
			{
				__state = HookGuard.Run("barrel add", () => Before(__instance, sender), default(Pending));
			}

			[HarmonyPostfix]
			private static void Postfix(Fermenter __instance, int nameHash, Pending __state)
			{
				if (__state.Empty)
				{
					HookGuard.Run("barrel add", delegate((Fermenter __instance, int nameHash, int Stars) args)
					{
						After(args.__instance, args.nameHash, args.Stars);
					}, (__instance, nameHash, __state.Stars));
				}
			}
		}

		private static readonly int StarsKey = StringExtensionMethods.GetStableHashCode("hearthhold_base_stars");

		public static int Get(ZDO zdo)
		{
			if (zdo != null)
			{
				return Mathf.Clamp(zdo.GetInt(StarsKey, 0), 0, 3);
			}
			return 0;
		}

		public static void Write(ZDO zdo, int stars)
		{
			stars = Mathf.Clamp(stars, 0, 3);
			if (zdo != null && zdo.GetInt(StarsKey, 0) != stars)
			{
				zdo.Set(StarsKey, stars, false);
			}
		}

		public static void Clear(ZDO zdo)
		{
			Write(zdo, 0);
		}

		private static void SendMark(Fermenter fermenter, Humanoid user, ItemData item)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			ZNetView nview = fermenter.m_nview;
			if (!((Object)(object)item?.m_dropPrefab == (Object)null) && !((Object)(object)user == (Object)null) && !((Object)(object)user != (Object)(object)Player.m_localPlayer) && !((Object)(object)nview == (Object)null) && nview.IsValid() && (int)fermenter.GetStatus() == 0 && fermenter.IsItemAllowed(item))
			{
				Marks.Send(nview, GrindstoneLink.LocalLevel(StarSources.Skill(StarSource.Dish)), Stars.Get(item));
			}
		}

		private static Pending Before(Fermenter fermenter, long sender)
		{
			ZNetView nview = fermenter.m_nview;
			if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner() || fermenter.GetContent() != 0)
			{
				return default(Pending);
			}
			Marks.Mark mark;
			int stars = (Marks.TryTake(nview, sender, out mark) ? Mathf.RoundToInt(mark.Stars) : 0);
			return new Pending
			{
				Empty = true,
				Stars = stars
			};
		}

		private static void After(Fermenter fermenter, int nameHash, int stars)
		{
			if (nameHash != 0 && fermenter.m_nview.IsValid() && fermenter.GetContent() == nameHash)
			{
				Write(fermenter.m_nview.GetZDO(), stars);
			}
		}
	}
	public static class KitchenBarrelTap
	{
		[HarmonyPatch(typeof(Fermenter), "RPC_Tap")]
		private static class Tap
		{
			[HarmonyPrefix]
			private static void Prefix(Fermenter __instance, out int __state)
			{
				__state = HookGuard.Run("barrel tap", () => __instance.GetContent(), 0);
			}

			[HarmonyPostfix]
			private static void Postfix(Fermenter __instance, int __state)
			{
				if (__state != 0)
				{
					HookGuard.Run("barrel tap", delegate(Fermenter fermenter)
					{
						Tapped(fermenter);
					}, __instance);
				}
			}
		}

		[HarmonyPatch(typeof(Fermenter), "DelayedTap")]
		private static class Spawn
		{
			[HarmonyPrefix]
			private static void Prefix(Fermenter __instance, out bool __state)
			{
				__state = HookGuard.Run("barrel spawn", () => Open(TakeTapped(__instance)), fallback: false);
			}

			[HarmonyFinalizer]
			private static void Finalizer(bool __state)
			{
				Close(__state);
			}
		}

		[HarmonyPatch(typeof(Fermenter), "DropAllItems")]
		private static class Drop
		{
			[HarmonyPrefix]
			private static void Prefix(Fermenter __instance, out bool __state)
			{
				__state = HookGuard.Run("barrel drops", () => __instance.GetContent() != 0 && Open(KitchenBarrel.Get(__instance.m_nview.GetZDO())), fallback: false);
			}

			[HarmonyFinalizer]
			private static void Finalizer(Fermenter __instance, bool __state)
			{
				Close(__state);
				HookGuard.Run("barrel drops", delegate(Fermenter fermenter)
				{
					ClearWhenEmpty(fermenter);
				}, __instance);
			}
		}

		private static readonly Dictionary<Fermenter, int> tapped = new Dictionary<Fermenter, int>();

		private static readonly List<Fermenter> dead = new List<Fermenter>();

		private static readonly SpawnStars.Roller roller = Give;

		private static SpawnStars.Roller previous;

		private static int giving;

		private static void Tapped(Fermenter fermenter)
		{
			ZNetView nview = fermenter.m_nview;
			if (!((Object)(object)nview == (Object)null) && nview.IsValid() && fermenter.GetContent() == 0)
			{
				ZDO zDO = nview.GetZDO();
				int num = KitchenBarrel.Get(zDO);
				KitchenBarrel.Clear(zDO);
				Prune();
				if (num > 0)
				{
					tapped[fermenter] = num;
				}
				else
				{
					tapped.Remove(fermenter);
				}
			}
		}

		private static int TakeTapped(Fermenter fermenter)
		{
			if (!tapped.TryGetValue(fermenter, out var value))
			{
				return 0;
			}
			tapped.Remove(fermenter);
			return value;
		}

		private static void ClearWhenEmpty(Fermenter fermenter)
		{
			ZNetView nview = fermenter.m_nview;
			if ((Object)(object)nview != (Object)null && nview.IsValid() && fermenter.GetContent() == 0)
			{
				KitchenBarrel.Clear(nview.GetZDO());
			}
		}

		private static void Prune()
		{
			foreach (Fermenter key in tapped.Keys)
			{
				if ((Object)(object)key == (Object)null)
				{
					dead.Add(key);
				}
			}
			foreach (Fermenter item in dead)
			{
				tapped.Remove(item);
			}
			dead.Clear();
		}

		private static bool Open(int stars)
		{
			if (stars <= 0)
			{
				return false;
			}
			giving = stars;
			previous = SpawnStars.Open(roller);
			return true;
		}

		private static void Close(bool opened)
		{
			if (opened)
			{
				SpawnStars.Close(previous);
				previous = null;
				giving = 0;
			}
		}

		private static int Give(ItemData item)
		{
			return giving;
		}
	}
	public static class KitchenKeys
	{
		public const string SlotLevel = "hearthhold_slot_level";

		public const string SlotInput = "hearthhold_slot_input";

		public const string SlotStars = "hearthhold_slot_stars";

		public const string BaseStars = "hearthhold_base_stars";
	}
	public static class KitchenSlots
	{
		[HarmonyPatch(typeof(CookingStation), "SetSlot")]
		private static class Emptied
		{
			[HarmonyPostfix]
			private static void Postfix(CookingStation __instance, int slot, string itemName)
			{
				if (string.IsNullOrEmpty(itemName) && Kitchen.IsKitchen(__instance))
				{
					HookGuard.Run("kitchen slot reset", delegate((CookingStation __instance, int slot) args)
					{
						ResetSlot(args.__instance, args.slot);
					}, (__instance, slot));
				}
			}
		}

		private const int Hashed = 16;

		private const string GameName = "slot";

		private const string GameStatus = "slotstatus";

		private static readonly int[] NameKeys = Hashes("slot");

		private static readonly int[] StatusKeys = Hashes("slotstatus");

		private static readonly int[] LevelKeys = Hashes("hearthhold_slot_level");

		private static readonly int[] InputKeys = Hashes("hearthhold_slot_input");

		private static readonly int[] StarKeys = Hashes("hearthhold_slot_stars");

		public static string Name(ZDO zdo, int slot)
		{
			return zdo.GetString(Key(NameKeys, "slot", slot), "");
		}

		public static Status Status(ZDO zdo, int slot)
		{
			return (Status)zdo.GetInt(Key(StatusKeys, "slotstatus", slot), 0);
		}

		public static float Level(ZDO zdo, int slot)
		{
			return StarOdds.Sane(zdo.GetFloat(Key(LevelKeys, "hearthhold_slot_level", slot), 0f));
		}

		public static float InputStars(ZDO zdo, int slot)
		{
			return Mathf.Clamp(StarOdds.Sane(zdo.GetFloat(Key(InputKeys, "hearthhold_slot_input", slot), 0f)), 0f, 3f);
		}

		public static int DishStars(ZDO zdo, int slot)
		{
			return Mathf.Clamp(zdo.GetInt(Key(StarKeys, "hearthhold_slot_stars", slot), 0), 0, 3);
		}

		public static int RawStars(ZDO zdo, int slot)
		{
			return Mathf.Clamp(Mathf.RoundToInt(InputStars(zdo, slot)), 0, 3);
		}

		public static void Fill(ZDO zdo, int slot, float level, float inputStars)
		{
			Write(zdo, Key(LevelKeys, "hearthhold_slot_level", slot), StarOdds.Sane(level));
			Write(zdo, Key(InputKeys, "hearthhold_slot_input", slot), Mathf.Clamp(StarOdds.Sane(inputStars), 0f, 3f));
			Write(zdo, Key(StarKeys, "hearthhold_slot_stars", slot), 0);
		}

		public static void SetStars(ZDO zdo, int slot, int stars)
		{
			Write(zdo, Key(StarKeys, "hearthhold_slot_stars", slot), Mathf.Clamp(stars, 0, 3));
		}

		public static void Reset(ZDO zdo, int slot)
		{
			Fill(zdo, slot, 0f, 0f);
		}

		private static void Write(ZDO zdo, int key, float value)
		{
			if (zdo.GetFloat(key, 0f) != value)
			{
				zdo.Set(key, value);
			}
		}

		private static void Write(ZDO zdo, int key, int value)
		{
			if (zdo.GetInt(key, 0) != value)
			{
				zdo.Set(key, value, false);
			}
		}

		private static int[] Hashes(string key)
		{
			int[] array = new int[16];
			for (int i = 0; i < 16; i++)
			{
				array[i] = StringExtensionMethods.GetStableHashCode(key + i);
			}
			return array;
		}

		private static int Key(int[] hashes, string key, int slot)
		{
			if (slot < 0 || slot >= hashes.Length)
			{
				return StringExtensionMethods.GetStableHashCode(key + slot);
			}
			return hashes[slot];
		}

		private static void ResetSlot(CookingStation station, int slot)
		{
			ZNetView nview = station.m_nview;
			if ((Object)(object)nview != (Object)null && nview.IsValid())
			{
				Reset(nview.GetZDO(), slot);
			}
		}
	}
	public static class KitchenStationAdd
	{
		public struct Pending
		{
			public int Slot;

			public float Level;

			public float Stars;
		}

		[HarmonyPatch(typeof(CookingStation), "CookItem")]
		private static class Cook
		{
			[HarmonyPrefix]
			[HarmonyPriority(800)]
			private static void Prefix(CookingStation __instance, Humanoid user, ItemData item)
			{
				if (Kitchen.IsKitchen(__instance))
				{
					HookGuard.Run("station mark", delegate((CookingStation __instance, Humanoid user, ItemData item) args)
					{
						SendMark(args.__instance, args.user, args.item);
					}, (__instance, user, item));
				}
			}
		}

		[HarmonyPatch(typeof(CookingStation), "RPC_AddItem")]
		private static class Add
		{
			[HarmonyPrefix]
			private static void Prefix(CookingStation __instance, long sender, out Pending __state)
			{
				Pending pending = new Pending
				{
					Slot = -1
				};
				__state = (Kitchen.IsKitchen(__instance) ? HookGuard.Run("station add", () => Before(__instance, sender), pending) : pending);
			}

			[HarmonyPostfix]
			private static void Postfix(CookingStation __instance, string itemName, Pending __state)
			{
				if (__state.Slot >= 0)
				{
					HookGuard.Run("station add", delegate((CookingStation __instance, string itemName, Pending __state) args)
					{
						After(args.__instance, args.itemName, args.__state);
					}, (__instance, itemName, __state));
				}
			}
		}

		private static void SendMark(CookingStation station, Humanoid user, ItemData item)
		{
			ZNetView nview = station.m_nview;
			if (!((Object)(object)item?.m_dropPrefab == (Object)null) && !((Object)(object)user == (Object)null) && !((Object)(object)user != (Object)(object)Player.m_localPlayer) && !((Object)(object)nview == (Object)null) && nview.IsValid())
			{
				if (!nview.HasOwner())
				{
					nview.ClaimOwnership();
				}
				if (!Incompatible(station, item) && station.IsItemAllowed(item) && station.GetFreeSlot() != -1)
				{
					Marks.Send(nview, GrindstoneLink.LocalLevel(StarSources.Skill(StarSource.Dish)), Stars.Get(item));
				}
			}
		}

		private static bool Incompatible(CookingStation station, ItemData item)
		{
			foreach (ItemMessage incompatibleItem in station.m_incompatibleItems)
			{
				if ((Object)(object)incompatibleItem.m_item != (Object)null && incompatibleItem.m_item.m_itemData.m_shared.m_name == item.m_shared.m_name)
				{
					return true;
				}
			}
			return false;
		}

		private static Pending Before(CookingStation station, long sender)
		{
			ZNetView nview = station.m_nview;
			if ((Object)(object)nview == (Object)null || !nview.IsValid())
			{
				return new Pending
				{
					Slot = -1
				};
			}
			if (!Marks.TryTake(nview, sender, out var mark))
			{
				mark = default(Marks.Mark);
			}
			return new Pending
			{
				Slot = station.GetFreeSlot(),
				Level = mark.Level,
				Stars = mark.Stars
			};
		}

		private static void After(CookingStation station, string itemName, Pending pending)
		{
			ZNetView nview = station.m_nview;
			if (nview.IsValid() && !string.IsNullOrEmpty(itemName))
			{
				ZDO zDO = nview.GetZDO();
				if (KitchenSlots.Name(zDO, pending.Slot) == itemName)
				{
					KitchenSlots.Fill(zDO, pending.Slot, pending.Level, pending.Stars);
				}
			}
		}
	}
	[HarmonyPatch(typeof(CookingStation), "UpdateCooking")]
	public static class KitchenStationRoll
	{
		private const int MaskSlots = 32;

		private static int cooking;

		[HarmonyPrefix]
		private static void Prefix(CookingStation __instance, out int __state)
		{
			cooking = 0;
			if (Kitchen.IsKitchen(__instance))
			{
				HookGuard.Run("station cooking", delegate(CookingStation station)
				{
					cooking = Cooking(station);
				}, __instance);
			}
			__state = cooking;
		}

		[HarmonyPostfix]
		private static void Postfix(CookingStation __instance, int __state)
		{
			if (__state != 0)
			{
				HookGuard.Run("station roll", delegate((CookingStation __instance, int __state) args)
				{
					RollDone(args.__instance, args.__state);
				}, (__instance, __state));
			}
		}

		private static int Cooking(CookingStation station)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			ZNetView nview = station.m_nview;
			if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner())
			{
				return 0;
			}
			ZDO zDO = nview.GetZDO();
			int num = 0;
			int num2 = Math.Min(station.m_slots.Length, 32);
			for (int i = 0; i < num2; i++)
			{
				if ((int)KitchenSlots.Status(zDO, i) == 0 && KitchenSlots.Name(zDO, i).Length > 0)
				{
					num |= 1 << i;
				}
			}
			return num;
		}

		private static void RollDone(CookingStation station, int cookingMask)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Invalid comparison between Unknown and I4
			ZNetView nview = station.m_nview;
			if (!nview.IsValid() || !nview.IsOwner())
			{
				return;
			}
			ZDO zDO = nview.GetZDO();
			int num = Math.Min(station.m_slots.Length, 32);
			for (int i = 0; i < num; i++)
			{
				if ((cookingMask & (1 << i)) != 0 && (int)KitchenSlots.Status(zDO, i) == 1 && Kitchen.IsProduct(KitchenSlots.Name(zDO, i)))
				{
					KitchenSlots.SetStars(zDO, i, Roll(zDO, i));
				}
			}
		}

		private static int Roll(ZDO zdo, int slot)
		{
			float level = KitchenSlots.Level(zdo, slot);
			float averageStars =