Decompiled source of FoodKnowledge v1.0.3

BepInEx/plugins/dannanboi_FoodKnowledge.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using ModifiersFramework;
using ModifiersFramework.Compatibility;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("Food Knowledge")]
[assembly: AssemblyDescription("YAML-defined Valheim foods, native recipe edits and temporary modifiers")]
[assembly: AssemblyCompany("dannanboi")]
[assembly: AssemblyProduct("Food Knowledge")]
[assembly: AssemblyFileVersion("1.0.3")]
[assembly: AssemblyInformationalVersion("1.0.3")]
[assembly: AssemblyVersion("1.0.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace FoodKnowledge
{
	internal sealed class Ingredient
	{
		internal string Item;

		internal int Amount;
	}
	internal sealed class FoodDefinition
	{
		internal string Item;

		internal string UseModel;

		internal string DisplayName;

		internal string Description;

		internal string EffectInfo;

		internal string NamedBuff;

		internal BuffOptions Presentation;

		internal float Hp;

		internal float HpPerTick;

		internal float Stamina;

		internal float Eitr;

		internal float DurationMinutes;

		internal float WeightPerUnit;

		internal int Amount;

		internal int StackSize;

		internal int StationLevel;

		internal string[] Stations;

		internal Ingredient[] Ingredients;

		internal ModifierSet ModifierSet = ModifierSet.Empty;

		internal string Location;

		internal string NativeBaseline;

		internal bool Synchronized;

		internal bool UsesNamedBuff;

		internal readonly Dictionary<string, string> Fields = new Dictionary<string, string>(StringComparer.Ordinal);

		internal bool NameOnly => Fields.Count == 1;

		internal bool Has(string key)
		{
			return Fields.ContainsKey(key);
		}

		internal bool Overrides(string key)
		{
			string value;
			return Fields.TryGetValue(key, out value) && value != null;
		}
	}
	internal sealed class Catalog
	{
		internal const int MaxBytes = 1048576;

		internal const int MaxFoods = 512;

		internal readonly List<FoodDefinition> Foods = new List<FoodDefinition>();

		internal string Canonical;

		internal const string FoodComments = "# Food Knowledge\n# See ModifiersFramework documentation on how to add modifiers and buffs.\n# The top-level key is the food's console name; do not add an itemName field.\n# A native food name with an empty value removes its crafting recipes; additional fields edit it.\n# Native edits inherit omitted values. New food names still require ingredients and use independent defaults.\n# Indent section headings by 2 spaces and their contents by 4.\n# In ingredients, use one '- Prefab[amount]' list row per ingredient.\n# Each food starts with PrefabName: (the colon is required), followed by basics, stats and ingredients.\n# Multiple .yaml/.yml files are supported. Server YAML controls multiplayer.\n# Leave optional values empty to use the mod's defaults.\n";

		internal static readonly CultureInfo Invariant = CultureInfo.InvariantCulture;

		private static readonly Regex PrefabName = new Regex("^[A-Za-z_][A-Za-z0-9_\\-]*$", RegexOptions.CultureInvariant);

		private static readonly Regex IngredientPattern = new Regex("^([A-Za-z_][A-Za-z0-9_\\-]*)\\s*\\[\\s*([0-9]+)\\s*\\]$", RegexOptions.CultureInvariant);

		private static readonly Regex NamedBuffStart = new Regex("^\\s*buff\\s*\\[", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);

		private static readonly Regex ModifierEntry = new Regex("^\\s*([A-Za-z_][A-Za-z0-9_\\-]*)\\s*\\[([^\\[\\]]*)\\]\\s*$", RegexOptions.CultureInvariant);

		private static readonly HashSet<string> Fields = new HashSet<string>(new string[17]
		{
			"itemName", "displayName", "description", "useModel", "craftingStation", "stationLevel", "hpGain", "hpPerTick", "staminaGain", "eitrGain",
			"modifierGain", "activeEffectText", "stackSize", "weightPerUnit", "durationMinutes", "amountProduced", "ingredientList"
		}, StringComparer.Ordinal);

		private static readonly HashSet<string> BasicFields = new HashSet<string>(new string[9] { "itemName", "displayName", "description", "useModel", "craftingStation", "stationLevel", "stackSize", "weightPerUnit", "amountProduced" }, StringComparer.Ordinal);

		internal static Catalog Parse(IEnumerable<KeyValuePair<string, string>> files)
		{
			return Parse(files, synchronized: false);
		}

		private static Catalog Parse(IEnumerable<KeyValuePair<string, string>> files, bool synchronized)
		{
			Catalog catalog = new Catalog();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			HashSet<int> hashSet2 = new HashSet<int>();
			foreach (KeyValuePair<string, string> item in files.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> x) => x.Key, StringComparer.Ordinal))
			{
				foreach (FlatYaml.Row item2 in FlatYaml.Read(item.Value, item.Key))
				{
					FoodDefinition foodDefinition = ParseRow(item2, synchronized);
					if (!hashSet.Add(foodDefinition.Item))
					{
						throw item2.Error("Duplicate item '" + foodDefinition.Item + "'.");
					}
					if (!hashSet2.Add(StableHash(foodDefinition.Item)))
					{
						throw item2.Error("Item prefab hash collision for '" + foodDefinition.Item + "'.");
					}
					catalog.Foods.Add(foodDefinition);
					if (catalog.Foods.Count > 512)
					{
						throw item2.Error("At most " + 512 + " foods are supported.");
					}
				}
			}
			catalog.Foods.Sort((FoodDefinition a, FoodDefinition b) => StringComparer.Ordinal.Compare(a.Item, b.Item));
			catalog.Canonical = Serialize(catalog.Foods);
			if (Encoding.UTF8.GetByteCount(catalog.Canonical) > 1048576)
			{
				throw new FormatException("Food definitions exceed 1 MiB.");
			}
			return catalog;
		}

		internal static Catalog Parse(string yaml)
		{
			return Parse(new KeyValuePair<string, string>[1]
			{
				new KeyValuePair<string, string>("server YAML", yaml)
			});
		}

		internal static Catalog ParseSynchronized(string yaml)
		{
			return Parse(new KeyValuePair<string, string>[1]
			{
				new KeyValuePair<string, string>("server YAML", yaml)
			}, synchronized: true);
		}

		internal static Catalog Empty()
		{
			return Parse("[]\n");
		}

		internal static string WithFoodComments(string yaml)
		{
			Match match = Regex.Match(yaml, "\\A(?:[ \\t]*#[^\\r\\n]*(?:\\r\\n|\\r|\\n|$)|[ \\t]*(?:\\r\\n|\\r|\\n))*", RegexOptions.CultureInvariant);
			return "# Food Knowledge\n# See ModifiersFramework documentation on how to add modifiers and buffs.\n# The top-level key is the food's console name; do not add an itemName field.\n# A native food name with an empty value removes its crafting recipes; additional fields edit it.\n# Native edits inherit omitted values. New food names still require ingredients and use independent defaults.\n# Indent section headings by 2 spaces and their contents by 4.\n# In ingredients, use one '- Prefab[amount]' list row per ingredient.\n# Each food starts with PrefabName: (the colon is required), followed by basics, stats and ingredients.\n# Multiple .yaml/.yml files are supported. Server YAML controls multiplayer.\n# Leave optional values empty to use the mod's defaults.\n" + yaml.Substring(match.Length);
		}

		internal static string StarterYaml()
		{
			return "# Food Knowledge\n# See ModifiersFramework documentation on how to add modifiers and buffs.\n# The top-level key is the food's console name; do not add an itemName field.\n# A native food name with an empty value removes its crafting recipes; additional fields edit it.\n# Native edits inherit omitted values. New food names still require ingredients and use independent defaults.\n# Indent section headings by 2 spaces and their contents by 4.\n# In ingredients, use one '- Prefab[amount]' list row per ingredient.\n# Each food starts with PrefabName: (the colon is required), followed by basics, stats and ingredients.\n# Multiple .yaml/.yml files are supported. Server YAML controls multiplayer.\n# Leave optional values empty to use the mod's defaults.\n{}\n";
		}

		internal static bool IsIdentity(string key)
		{
			return key == "itemName";
		}

		internal static bool IsSection(string key)
		{
			return key == "basics" || key == "stats" || key == "ingredients";
		}

		internal static bool FieldInSection(string section, string key)
		{
			return (section == "basics") ? BasicFields.Contains(key) : (section == "stats" && (key == "modifierValues" || (Fields.Contains(key) && key != "ingredientList" && !BasicFields.Contains(key))));
		}

		internal static string NestedIngredient(FlatYaml.Row row, string item, string value)
		{
			CheckPrefab(row, item, "ingredient prefab");
			if (!int.TryParse((value ?? "").Trim(), NumberStyles.None, Invariant, out var result) || result < 1 || result > 1000000)
			{
				throw row.Error("Ingredient '" + item + "' needs a whole-number amount between 1 and 1000000.");
			}
			return item + "[" + result.ToString(Invariant) + "]";
		}

		internal static string BracketIngredient(FlatYaml.Row row, string value)
		{
			Match match = IngredientPattern.Match((value ?? "").Trim());
			if (!match.Success)
			{
				throw row.Error("Invalid ingredient '" + value + "'. Use one 'Prefab[positive amount]' row, for example 'Fish1[5]'; a leading dash is optional.");
			}
			return NestedIngredient(row, match.Groups[1].Value, match.Groups[2].Value);
		}

		private static FoodDefinition ParseRow(FlatYaml.Row row, bool synchronized)
		{
			//IL_06fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0706: Expected O, but got Unknown
			//IL_067b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0768: Unknown result type (might be due to invalid IL or missing references)
			//IL_0772: Expected O, but got Unknown
			foreach (string key in row.Values.Keys)
			{
				if (!Fields.Contains(key) && (!synchronized || (!(key == "_namedBuff") && !(key == "_nativeBaseline"))))
				{
					throw row.Error("Unknown field '" + key + "'. Field names are case-sensitive.");
				}
			}
			FoodDefinition foodDefinition = new FoodDefinition
			{
				Item = Required(row, "itemName"),
				UseModel = Value(row, "useModel"),
				DisplayName = Value(row, "displayName"),
				Description = Value(row, "description"),
				EffectInfo = Value(row, "activeEffectText"),
				Hp = Number(row, "hpGain", 0f, 100000f).GetValueOrDefault(),
				Stamina = Number(row, "staminaGain", 0f, 100000f).GetValueOrDefault(),
				Eitr = Number(row, "eitrGain", 0f, 100000f).GetValueOrDefault(),
				DurationMinutes = (Number(row, "durationMinutes", 0.0001f, 100000f) ?? 20f),
				WeightPerUnit = (Number(row, "weightPerUnit", 0f, 100000f) ?? 0.1f),
				StackSize = (Integer(row, "stackSize", required: false) ?? 20),
				Amount = (Integer(row, "amountProduced", required: false) ?? 1),
				StationLevel = (string.IsNullOrWhiteSpace(Value(row, "stationLevel")) ? 1 : Integer(row, "stationLevel", required: true).Value),
				Stations = Split(Value(row, "craftingStation")),
				Location = row.Location
			};
			foodDefinition.Synchronized = synchronized;
			foodDefinition.NativeBaseline = Value(row, "_nativeBaseline");
			if (foodDefinition.NativeBaseline != null && (foodDefinition.NativeBaseline.Length != 44 || !Regex.IsMatch(foodDefinition.NativeBaseline, "^[A-Za-z0-9+/]{43}=$", RegexOptions.CultureInvariant)))
			{
				throw row.Error("Invalid native food baseline fingerprint.");
			}
			foreach (KeyValuePair<string, string> value2 in row.Values)
			{
				if (value2.Key != "_namedBuff" && value2.Key != "_nativeBaseline")
				{
					foodDefinition.Fields.Add(value2.Key, value2.Value);
				}
			}
			foodDefinition.HpPerTick = Number(row, "hpPerTick", 0f, 100000f) ?? ((foodDefinition.Hp > 0f) ? 2f : 0f);
			if (string.IsNullOrWhiteSpace(foodDefinition.EffectInfo))
			{
				foodDefinition.EffectInfo = null;
			}
			CheckPrefab(row, foodDefinition.Item, "itemName");
			if (string.IsNullOrWhiteSpace(foodDefinition.UseModel) || !PrefabName.IsMatch(foodDefinition.UseModel) || foodDefinition.Item == foodDefinition.UseModel)
			{
				foodDefinition.UseModel = "RawMeat";
			}
			if (foodDefinition.StationLevel > 1000)
			{
				throw row.Error("stationLevel must be between 1 and 1000.");
			}
			if (foodDefinition.Stations.Length > 32)
			{
				throw row.Error("At most 32 crafting stations can be listed per food.");
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			string[] stations = foodDefinition.Stations;
			foreach (string text in stations)
			{
				CheckPrefab(row, text, "craftingStation");
				if (!hashSet.Add(text))
				{
					throw row.Error("Duplicate crafting station '" + text + "'.");
				}
			}
			if (foodDefinition.DisplayName != null && foodDefinition.DisplayName.Length > 512)
			{
				throw row.Error("displayName exceeds 512 characters.");
			}
			if (foodDefinition.Description != null && foodDefinition.Description.Length > 16384)
			{
				throw row.Error("description exceeds 16384 characters.");
			}
			string value = Value(row, "ingredientList");
			if (!string.IsNullOrWhiteSpace(value))
			{
				List<Ingredient> list = new List<Ingredient>();
				HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
				string[] array = Split(value);
				foreach (string text2 in array)
				{
					Match match = IngredientPattern.Match(text2);
					if (!match.Success || !int.TryParse(match.Groups[2].Value, NumberStyles.None, Invariant, out var result) || result < 1 || result > 1000000)
					{
						throw row.Error("Invalid ingredient '" + text2 + "'. Use Prefab[positive amount], for example Fish1[5].");
					}
					if (!hashSet2.Add(match.Groups[1].Value))
					{
						throw row.Error("Duplicate ingredient '" + match.Groups[1].Value + "'.");
					}
					list.Add(new Ingredient
					{
						Item = match.Groups[1].Value,
						Amount = result
					});
				}
				if (list.Count == 0 || list.Count > 32)
				{
					throw row.Error("ingredientList needs between 1 and 32 ingredients.");
				}
				foodDefinition.Ingredients = list.ToArray();
			}
			try
			{
				ModifierSet val = FoodModifiers(row, Value(row, "modifierGain"), out foodDefinition.UsesNamedBuff);
				foodDefinition.NamedBuff = val.CustomBuffName;
				foodDefinition.ModifierSet = (ModifierSet)((val.CustomBuffName == null) ? ((object)val) : ((object)new ModifierSet((IEnumerable<SkillBonus>)val.Skills, (IEnumerable<EffectBonus>)val.Effects, (IEnumerable<AddedDamageBonus>)val.AddedDamage)));
				if (synchronized)
				{
					if (foodDefinition.NamedBuff != null)
					{
						throw row.Error("Server catalog must contain expanded named effects.");
					}
					foodDefinition.NamedBuff = Value(row, "_namedBuff");
				}
				if (foodDefinition.NamedBuff != null)
				{
					foodDefinition.EffectInfo = null;
					try
					{
						foodDefinition.Presentation = FrameworkApi.GetCustomBuff(foodDefinition.NamedBuff, true, true, true);
					}
					catch (FormatException ex)
					{
						foodDefinition.Presentation = new BuffOptions(foodDefinition.NamedBuff, (Sprite)null, (string)null, true, true, true);
						Plugin.Warn(row.Location + ": Named buff presentation is unavailable; using its calling name. " + ex.Message);
					}
				}
				else
				{
					if (Encoding.UTF8.GetByteCount(foodDefinition.EffectInfo ?? "") > 4096)
					{
						throw row.Error("activeEffectText exceeds 4096 UTF-8 bytes.");
					}
					foodDefinition.Presentation = new BuffOptions((string)null, (Sprite)null, foodDefinition.EffectInfo, true, true, true);
				}
			}
			catch (FormatException ex2)
			{
				throw row.Error(ex2.Message);
			}
			return foodDefinition;
		}

		private static ModifierSet FoodModifiers(FlatYaml.Row row, string value, out bool usesNamedBuff)
		{
			usesNamedBuff = false;
			if (Encoding.UTF8.GetByteCount(value ?? "") > 16384)
			{
				return FrameworkNames.Parse(value);
			}
			if (value != null && value.IndexOf("buff", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				ModifierSet val = null;
				string[] array = value.Split(',');
				foreach (string text in array)
				{
					if (!NamedBuffStart.IsMatch(text))
					{
						continue;
					}
					usesNamedBuff = true;
					ModifierSet val2;
					try
					{
						val2 = FrameworkNames.Parse(text.Trim());
					}
					catch (FormatException ex)
					{
						if (!ModifierEntry.IsMatch(text))
						{
							throw;
						}
						Plugin.Warn(row.Location + ": Ignoring unavailable named buff '" + text.Trim() + "'. " + ex.Message);
						continue;
					}
					if (val != null)
					{
						throw new FormatException("Use one buff[CallingName] per food; other modifierGain entries are ignored.");
					}
					val = val2;
				}
				if (usesNamedBuff)
				{
					return val ?? ModifierSet.Empty;
				}
			}
			try
			{
				return FrameworkNames.Parse(value);
			}
			catch (FormatException)
			{
				StringBuilder stringBuilder = new StringBuilder();
				string[] array2 = value.Split(',');
				string text3 = default(string);
				foreach (string text2 in array2)
				{
					Match match = ModifierEntry.Match(text2);
					if (!match.Success)
					{
						throw;
					}
					if (!FrameworkApi.TryGetModifierFullName(match.Groups[1].Value, ref text3))
					{
						Plugin.Warn(row.Location + ": Ignoring unknown modifier '" + match.Groups[1].Value + "'.");
						continue;
					}
					if (stringBuilder.Length > 0)
					{
						stringBuilder.Append(',');
					}
					stringBuilder.Append(text2);
				}
				return FrameworkNames.Parse(stringBuilder.ToString());
			}
		}

		private static string Value(FlatYaml.Row row, string key)
		{
			string value;
			return row.Values.TryGetValue(key, out value) ? value : null;
		}

		private static string Required(FlatYaml.Row row, string key)
		{
			string text = Value(row, key);
			if (string.IsNullOrWhiteSpace(text))
			{
				throw row.Error("Required field '" + key + "' is missing or empty.");
			}
			return text.Trim();
		}

		private static void CheckPrefab(FlatYaml.Row row, string value, string field)
		{
			if (value.Length > 128 || !PrefabName.IsMatch(value))
			{
				throw row.Error("Invalid prefab name in " + field + ": '" + value + "'.");
			}
		}

		private static int? Integer(FlatYaml.Row row, string key, bool required)
		{
			string text = Value(row, key);
			if (text == null && !required)
			{
				return null;
			}
			if (text == null || !int.TryParse(text, NumberStyles.Integer, Invariant, out var result) || result < 1 || result > 1000000)
			{
				throw row.Error(key + " must be a positive whole number.");
			}
			return result;
		}

		private static float? Number(FlatYaml.Row row, string key, float min, float max)
		{
			string text = Value(row, key);
			return (text == null) ? ((float?)null) : new float?(ParseNumber(text, min, max, row, key));
		}

		private static float ParseNumber(string text, float min, float max, FlatYaml.Row row, string key)
		{
			if (!float.TryParse(text, NumberStyles.Float, Invariant, out var result) || float.IsNaN(result) || float.IsInfinity(result) || result < min || result > max)
			{
				throw row.Error(key + " must be between " + min.ToString(Invariant) + " and " + max.ToString(Invariant) + ", using a decimal point.");
			}
			return result;
		}

		private static string[] Split(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return Array.Empty<string>();
			}
			string[] array = (from x in value.Split(',')
				select x.Trim()).ToArray();
			if (array.Any((string x) => x.Length == 0))
			{
				throw new FormatException("Comma-separated lists cannot contain empty entries.");
			}
			return array;
		}

		internal static int StableHash(string value)
		{
			int num = 5381;
			int num2 = 5381;
			for (int i = 0; i < value.Length && value[i] != 0; i += 2)
			{
				num = ((num << 5) + num) ^ value[i];
				if (i == value.Length - 1 || value[i + 1] == '\0')
				{
					break;
				}
				num2 = ((num2 << 5) + num2) ^ value[i + 1];
			}
			return num + num2 * 1566083941;
		}

		internal static string Quote(string value)
		{
			StringBuilder stringBuilder = new StringBuilder("\"");
			foreach (char c in value)
			{
				if (c == '"' || c == '\\')
				{
					stringBuilder.Append('\\').Append(c);
					continue;
				}
				switch (c)
				{
				case '\n':
					stringBuilder.Append("\\n");
					continue;
				case '\r':
					stringBuilder.Append("\\r");
					continue;
				case '\t':
					stringBuilder.Append("\\t");
					continue;
				}
				if (char.IsControl(c))
				{
					StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
					int num = c;
					stringBuilder2.Append(num.ToString("x4", Invariant));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.Append('"').ToString();
		}

		internal static string SerializeSynchronized(IEnumerable<FoodDefinition> definitions, Dictionary<string, string> baselines)
		{
			string text = Serialize(definitions, baselines);
			if (Encoding.UTF8.GetByteCount(text) > 1048576)
			{
				throw new FormatException("Synchronized food definitions exceed 1 MiB.");
			}
			return text;
		}

		private static string Serialize(IEnumerable<FoodDefinition> definitions, Dictionary<string, string> baselines = null)
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (FoodDefinition definition in definitions)
			{
				stringBuilder.Append("- itemName: ").Append(Quote(definition.Item)).Append('\n');
				string text = definition.NativeBaseline;
				if (baselines != null && baselines.TryGetValue(definition.Item, out var value))
				{
					text = value;
				}
				if (text != null)
				{
					Write(stringBuilder, "_nativeBaseline", text);
				}
				WriteField(stringBuilder, definition, "displayName", definition.DisplayName);
				WriteField(stringBuilder, definition, "description", definition.Description);
				WriteField(stringBuilder, definition, "useModel", definition.Fields.TryGetValue("useModel", out var value2) ? value2 : null);
				WriteField(stringBuilder, definition, "craftingStation", string.Join(",", definition.Stations));
				WriteField(stringBuilder, definition, "stationLevel", definition.StationLevel);
				WriteField(stringBuilder, definition, "hpGain", definition.Hp);
				WriteField(stringBuilder, definition, "hpPerTick", definition.HpPerTick);
				WriteField(stringBuilder, definition, "staminaGain", definition.Stamina);
				WriteField(stringBuilder, definition, "eitrGain", definition.Eitr);
				WriteField(stringBuilder, definition, "modifierGain", definition.ModifierSet.Canonical);
				if (definition.NamedBuff != null)
				{
					Write(stringBuilder, "_namedBuff", definition.NamedBuff);
				}
				if (definition.NamedBuff == null)
				{
					WriteField(stringBuilder, definition, "activeEffectText", definition.Fields.TryGetValue("activeEffectText", out var value3) ? value3 : null);
				}
				WriteField(stringBuilder, definition, "stackSize", definition.StackSize);
				WriteField(stringBuilder, definition, "weightPerUnit", definition.WeightPerUnit);
				WriteField(stringBuilder, definition, "durationMinutes", definition.DurationMinutes);
				WriteField(stringBuilder, definition, "amountProduced", definition.Amount);
				if (definition.Ingredients != null)
				{
					WritePlain(stringBuilder, "ingredientList", string.Join(",", definition.Ingredients.Select((Ingredient x) => x.Item + "[" + x.Amount.ToString(Invariant) + "]")));
				}
				else if (definition.Has("ingredientList"))
				{
					stringBuilder.Append("  ingredientList:\n");
				}
			}
			return (stringBuilder.Length == 0) ? "[]\n" : stringBuilder.ToString();
		}

		private static void WriteField(StringBuilder b, FoodDefinition f, string key, object value)
		{
			if (!f.Has(key))
			{
				return;
			}
			if (!f.Overrides(key))
			{
				b.Append("  ").Append(key).Append(":\n");
				return;
			}
			string text = ((value is float num) ? num.ToString("R", Invariant) : Convert.ToString(value, Invariant));
			if (value is float || value is int || key == "craftingStation" || key == "modifierGain")
			{
				WritePlain(b, key, text);
			}
			else
			{
				Write(b, key, text ?? "");
			}
		}

		private static void Write(StringBuilder b, string field, string value)
		{
			if (value != null)
			{
				b.Append("  ").Append(field).Append(": ")
					.Append(Quote(value))
					.Append('\n');
			}
		}

		private static void WritePlain(StringBuilder b, string field, string value)
		{
			b.Append("  ").Append(field).Append(':');
			if (!string.IsNullOrEmpty(value))
			{
				b.Append(' ').Append(value);
			}
			b.Append('\n');
		}

		private static void Write(StringBuilder b, string field, float? value)
		{
			if (value.HasValue)
			{
				b.Append("  ").Append(field).Append(": ")
					.Append(value.Value.ToString("R", Invariant))
					.Append('\n');
			}
		}
	}
	internal static class FlatYaml
	{
		internal sealed class Row
		{
			internal string Location;

			internal readonly Dictionary<string, string> Values = new Dictionary<string, string>(StringComparer.Ordinal);

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

			internal List<string> Ingredients;

			internal bool BracketIngredients;

			internal bool ItemFirst;

			internal bool InlineValue;

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

			internal FormatException Error(string message)
			{
				return new FormatException(Location + ": " + message);
			}
		}

		internal static IEnumerable<Row> Read(string text, string filename)
		{
			if (text == null || Encoding.UTF8.GetByteCount(text) > 1048576)
			{
				throw new FormatException(filename + ": YAML exceeds 1 MiB.");
			}
			string[] array = text.Replace("\r\n", "\n").Replace('\r', '\n').TrimStart('\ufeff')
				.Split('\n');
			List<Row> list = new List<Row>();
			Row row = null;
			int num = -1;
			bool flag = false;
			bool flag2 = false;
			bool? flag3 = null;
			string text2 = null;
			int num2 = -1;
			bool? flag4 = null;
			for (int i = 0; i < array.Length; i++)
			{
				string text3 = array[i];
				int num3 = Indent(text3, filename, i);
				string text4 = text3.Trim();
				if (text4.Length == 0 || text4.StartsWith("#", StringComparison.Ordinal))
				{
					continue;
				}
				if (text4 == "---" && !flag2 && num3 == 0)
				{
					flag2 = true;
					continue;
				}
				if (text4 == "..." && num3 == 0)
				{
					flag = true;
					continue;
				}
				if (flag)
				{
					throw new FormatException(filename + ":" + (i + 1) + ": Multiple YAML documents are not supported.");
				}
				if ((text4 == "[]" || text4 == "{}") && list.Count == 0 && num3 == 0)
				{
					flag = true;
					continue;
				}
				bool flag5 = num3 == 0 && text3.StartsWith("- ", StringComparison.Ordinal);
				if (num3 == 0 && !flag5)
				{
					if (flag3 == false)
					{
						throw new FormatException(filename + ":" + (i + 1) + ": Do not mix food-name mappings and the earlier itemName list in one file.");
					}
					row = new Row
					{
						Location = filename + ":" + (i + 1),
						ItemFirst = true
					};
					int num4 = text3.IndexOf(':');
					if (num4 <= 0 || (num4 + 1 < text3.Length && !char.IsWhiteSpace(text3[num4 + 1])))
					{
						throw row.Error("Start each food with 'PrefabName:'; the colon is required.");
					}
					string value = Scalar(text3.Substring(0, num4).Trim(), row);
					string text5 = Uncomment(text3.Substring(num4 + 1)).Trim();
					if (text5.Length != 0 && text5 != "{}" && Scalar(text5, row) != null)
					{
						throw row.Error("A food-name key must contain indented fields or an empty value, not an inline scalar.");
					}
					row.Values.Add("itemName", value);
					row.RootFields.Add("itemName");
					row.InlineValue = text5.Length != 0;
					list.Add(row);
					flag3 = true;
					num = 2;
					text2 = null;
					num2 = -1;
					flag4 = null;
					flag2 = true;
					continue;
				}
				bool flag6 = false;
				if (flag5)
				{
					if (flag3 == true)
					{
						throw new FormatException(filename + ":" + (i + 1) + ": Do not mix food-name mappings and the earlier itemName list in one file.");
					}
					flag3 = false;
					row = new Row
					{
						Location = filename + ":" + (i + 1)
					};
					list.Add(row);
					text3 = text3.Substring(2);
					num = -1;
					text2 = null;
					num2 = -1;
					flag4 = null;
				}
				else
				{
					if (row == null || num3 == 0)
					{
						throw new FormatException(filename + ":" + (i + 1) + ": Start each food with 'PrefabName:'. Earlier '- itemName: PrefabName' lists remain supported.");
					}
					if (row.InlineValue)
					{
						throw row.Error("An inline empty value cannot also have indented fields. Leave the food-name value empty before its sections.");
					}
					if (num < 0)
					{
						num = num3;
					}
					if (text2 != null && num3 > num)
					{
						if (num2 < 0)
						{
							num2 = num3;
						}
						if (num3 != num2)
						{
							throw row.Error("Inconsistent section indentation at line " + (i + 1) + ".");
						}
						flag6 = true;
					}
					else
					{
						if (num3 != num && (!row.ItemFirst || text2 != null || num != 4 || num3 != 2 || !IsSectionHeading(text3, num3)))
						{
							throw row.Error("Inconsistent field indentation at line " + (i + 1) + ".");
						}
						text2 = null;
						num2 = -1;
						flag4 = null;
					}
					text3 = text3.Substring(num3);
				}
				string text6 = ((flag6 && text2 == "ingredients") ? Uncomment(text3).Trim() : null);
				if (text6 != null && (text6.StartsWith("- ", StringComparison.Ordinal) || text6.IndexOf(':') < 0))
				{
					if (row.Ingredients.Count != 0 && !row.BracketIngredients)
					{
						throw row.Error("Use either Prefab[amount] rows or prefab/amount mappings in ingredients; do not mix them.");
					}
					if (row.Ingredients.Count >= 32)
					{
						throw row.Error("At most 32 ingredients can be listed per food.");
					}
					row.BracketIngredients = true;
					string text7 = (text6.StartsWith("- ", StringComparison.Ordinal) ? text6.Substring(2) : text6);
					row.Ingredients.Add(Catalog.BracketIngredient(row, Scalar(text7.Trim(), row)));
					flag2 = true;
					continue;
				}
				int num5 = (flag5 ? 2 : num3);
				if (flag6 && text2 != "ingredients")
				{
					bool flag7 = text3.StartsWith("- ", StringComparison.Ordinal);
					if (flag4.HasValue && flag4.Value != flag7)
					{
						throw row.Error("Use either dashed fields or mapping fields within '" + text2 + "'; do not mix them.");
					}
					flag4 = flag7;
					if (flag7)
					{
						text3 = text3.Substring(2);
						num5 += 2;
					}
				}
				int num6 = text3.IndexOf(':');
				if (num6 <= 0 || (num6 + 1 < text3.Length && !char.IsWhiteSpace(text3[num6 + 1])))
				{
					throw row.Error(((flag6 && text2 == "ingredients") ? "Expected an ingredient row 'Prefab[amount]'" : "Expected field: value") + " at line " + (i + 1) + ".");
				}
				string text8 = text3.Substring(0, num6).Trim();
				if (!flag6 || !(text2 == "ingredients"))
				{
					text8 = YamlMigration.Key(text8, row);
				}
				string text9 = Uncomment(text3.Substring(num6 + 1)).Trim();
				if (flag5)
				{
					row.ItemFirst = Catalog.IsIdentity(text8);
				}
				if (!flag6 && Catalog.IsSection(text8))
				{
					string text10 = text8;
					if (row.RootFields.Count != 0)
					{
						foreach (string rootField in row.RootFields)
						{
							if (text10 == "basics" && !Catalog.IsIdentity(rootField))
							{
								throw row.Error("Keep item and crafting fields inside basics when using that section.");
							}
							if (!Catalog.FieldInSection("basics", rootField))
							{
								throw row.Error("Put food stats inside stats when using sections; do not mix flat stats with sections.");
							}
						}
					}
					if (text9.Length != 0)
					{
						throw row.Error("Section '" + text8 + "' must contain indented rows, not an inline value.");
					}
					if (!row.Sections.Add(text10))
					{
						throw row.Error("Duplicate section '" + text10 + "' (including earlier section names).");
					}
					text2 = text10;
					num2 = -1;
					flag4 = null;
					num = 2;
					flag2 = true;
					if (text8 == "ingredients")
					{
						row.Ingredients = new List<string>();
					}
					continue;
				}
				if (!flag6)
				{
					if (row.Sections.Count != 0 && (row.Sections.Contains("basics") ? (!Catalog.IsIdentity(text8)) : (!Catalog.FieldInSection("basics", text8))))
					{
						throw row.Error(row.Sections.Contains("basics") ? "Put item and crafting fields inside basics when using grouped sections." : "Only item characteristics belong at the root when using stats and ingredients sections.");
					}
					row.RootFields.Add(text8);
				}
				else if (text2 != "ingredients" && !Catalog.FieldInSection(text2, text8))
				{
					throw row.Error("Field '" + text8 + "' does not belong in '" + text2 + "'.");
				}
				string value2;
				switch (text9)
				{
				default:
					if (!(text9 == ">+"))
					{
						value2 = Scalar(text9, row);
						break;
					}
					goto case "|";
				case "|":
				case "|-":
				case "|+":
				case ">":
				case ">-":
				{
					List<string> list2 = new List<string>();
					int num7 = -1;
					int num8 = num5;
					while (i + 1 < array.Length)
					{
						string text11 = array[i + 1];
						int num9 = Indent(text11, filename, i + 1);
						if (text11.Trim().Length != 0 && num9 <= num8)
						{
							break;
						}
						i++;
						if (text11.Trim().Length == 0)
						{
							list2.Add("");
							continue;
						}
						if (num7 < 0)
						{
							num7 = num9;
						}
						if (num9 < num7)
						{
							throw row.Error("Invalid block indentation at line " + (i + 1) + ".");
						}
						list2.Add(text11.Substring(num7));
					}
					StringBuilder stringBuilder = new StringBuilder();
					for (int j = 0; j < list2.Count; j++)
					{
						if (j > 0)
						{
							stringBuilder.Append((text9[0] == '>' && list2[j].Length != 0 && list2[j - 1].Length != 0) ? ' ' : '\n');
						}
						stringBuilder.Append(list2[j]);
					}
					value2 = stringBuilder.ToString();
					value2 = ((!text9.EndsWith("-", StringComparison.Ordinal)) ? (value2.TrimEnd('\n') + "\n") : value2.TrimEnd('\n'));
					break;
				}
				}
				if (flag6 && text2 == "ingredients")
				{
					if (row.BracketIngredients)
					{
						throw row.Error("Use either Prefab[amount] rows or prefab/amount mappings in ingredients; do not mix them.");
					}
					if (row.Ingredients.Count >= 32)
					{
						throw row.Error("At most 32 ingredients can be listed per food.");
					}
					row.Ingredients.Add(Catalog.NestedIngredient(row, text8, value2));
					flag2 = true;
				}
				else
				{
					if (row.Values.ContainsKey(text8))
					{
						throw row.Error("Duplicate field '" + text8 + "'.");
					}
					row.Values.Add(text8, value2);
					flag2 = true;
				}
			}
			foreach (Row item in list)
			{
				if (item.Ingredients != null)
				{
					item.Values.Add("ingredientList", string.Join(",", item.Ingredients));
				}
				YamlMigration.Finish(item);
			}
			return list;
		}

		private static int Indent(string line, string filename, int index)
		{
			int i;
			for (i = 0; i < line.Length && (line[i] == ' ' || line[i] == '\t'); i++)
			{
				if (line[i] == '\t')
				{
					throw new FormatException(filename + ":" + (index + 1) + ": Use spaces, not tabs, for YAML indentation.");
				}
			}
			return i;
		}

		private static bool IsSectionHeading(string line, int indent)
		{
			int num = line.IndexOf(':');
			return num > indent && (Catalog.IsSection(line.Substring(indent, num - indent).Trim()) || line.Substring(indent, num - indent).Trim() == "itemCharacteristics");
		}

		private static string Uncomment(string text)
		{
			bool flag = false;
			char c = '\0';
			int i;
			for (i = 0; i < text.Length && char.IsWhiteSpace(text[i]); i++)
			{
			}
			if (i < text.Length && (text[i] == '\'' || text[i] == '"'))
			{
				flag = true;
				c = text[i];
			}
			for (int j = (flag ? (i + 1) : 0); j < text.Length; j++)
			{
				if (flag)
				{
					if (c == '"' && text[j] == '\\')
					{
						j++;
					}
					else if (c == '\'' && text[j] == '\'' && j + 1 < text.Length && text[j + 1] == '\'')
					{
						j++;
					}
					else if (text[j] == c)
					{
						flag = false;
					}
				}
				else if (text[j] == '#' && (j == 0 || char.IsWhiteSpace(text[j - 1])))
				{
					return text.Substring(0, j);
				}
			}
			return text;
		}

		private static string Scalar(string text, Row row)
		{
			if (text.Length != 0)
			{
				switch (text)
				{
				default:
				{
					if (text == "NULL")
					{
						break;
					}
					char c = text[0];
					if (c != '\'' && c != '"')
					{
						if (text.IndexOf(": ", StringComparison.Ordinal) >= 0)
						{
							throw row.Error("Quote text containing a colon followed by a space.");
						}
						if ("[{&*!%@`".IndexOf(c) >= 0)
						{
							throw row.Error("Use scalar values; YAML collections, tags and anchors are not supported here.");
						}
						return text;
					}
					if (text.Length < 2 || text[text.Length - 1] != c)
					{
						throw row.Error("Unclosed YAML quote.");
					}
					StringBuilder stringBuilder = new StringBuilder();
					for (int i = 1; i < text.Length - 1; i++)
					{
						char c2 = text[i];
						if (c2 == c)
						{
							if (c == '\'' && i + 1 < text.Length - 1 && text[i + 1] == '\'')
							{
								stringBuilder.Append('\'');
								i++;
								continue;
							}
							throw row.Error("Escape quotes inside quoted text.");
						}
						if (c2 == '\\' && c == '"')
						{
							if (++i >= text.Length - 1)
							{
								throw row.Error("Incomplete YAML escape.");
							}
							c2 = text[i];
							if (c2 == 'n')
							{
								c2 = '\n';
							}
							else if (c2 == 'r')
							{
								c2 = '\r';
							}
							else if (c2 == 't')
							{
								c2 = '\t';
							}
							else if (c2 == '0')
							{
								c2 = '\0';
							}
							else if (c2 == 'b')
							{
								c2 = '\b';
							}
							else if (c2 == 'f')
							{
								c2 = '\f';
							}
							else
							{
								if (c2 == 'u' || c2 == 'x' || c2 == 'U')
								{
									int num = c2 switch
									{
										'x' => 2, 
										'u' => 4, 
										_ => 8, 
									};
									if (i + num >= text.Length - 1 || !int.TryParse(text.Substring(i + 1, num), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result) || result < 0 || result > 1114111)
									{
										throw row.Error("Invalid Unicode escape.");
									}
									if (result <= 65535)
									{
										stringBuilder.Append((char)result);
									}
									else
									{
										stringBuilder.Append(char.ConvertFromUtf32(result));
									}
									i += num;
									continue;
								}
								if (c2 != '"' && c2 != '\\' && c2 != '/')
								{
									throw row.Error("Unsupported YAML escape.");
								}
							}
						}
						stringBuilder.Append(c2);
					}
					return stringBuilder.ToString();
				}
				case "~":
				case "null":
				case "Null":
					break;
				}
			}
			return null;
		}
	}
	internal static class FoodEffects
	{
		private static string Gain(float value, string token, string fallback)
		{
			string text = ((Localization.instance == null) ? token : Localization.instance.Localize(token));
			if (text == token || text.StartsWith("[", StringComparison.Ordinal))
			{
				text = fallback;
			}
			return "+" + value.ToString("R", CultureInfo.InvariantCulture) + " " + text;
		}

		internal static BuffOptions ResolveDisplay(BuffOptions display)
		{
			if (((display != null) ? display.CustomBuffName : null) == null)
			{
				return display;
			}
			try
			{
				return FrameworkApi.GetCustomBuff(display.CustomBuffName, true, true, true);
			}
			catch (FormatException)
			{
				return display;
			}
		}

		internal static string Eating(FoodStatSnapshot food, ModifierSet modifiers = null, BuffOptions presentation = null)
		{
			List<string> list = new List<string>();
			if (food.Hp > 0f)
			{
				list.Add(Gain(food.Hp, "$item_food_health", "Health"));
			}
			if (food.Stamina > 0f)
			{
				list.Add(Gain(food.Stamina, "$item_food_stamina", "Stamina"));
			}
			if (food.Eitr > 0f)
			{
				list.Add(Gain(food.Eitr, "$item_food_eitr", "Eitr"));
			}
			string text2;
			if (((presentation != null) ? presentation.CustomBuffName : null) != null)
			{
				BuffOptions val = ResolveDisplay(presentation);
				string text = val.DisplayName ?? val.CustomBuffName;
				text2 = ((Localization.instance == null) ? text : Localization.instance.Localize(text));
			}
			else
			{
				text2 = FrameworkApi.FormatGains(modifiers ?? ModifierSet.Empty);
			}
			if (text2.Length > 0)
			{
				list.Add(text2);
			}
			if (list.Count == 0 && food.Regen > 0f)
			{
				list.Add(Gain(food.Regen, "$item_food_health", "Health") + " / 10s");
			}
			return string.Join(" and ", list);
		}
	}
	internal static class FoodMessages
	{
		internal sealed class Notice
		{
			internal MessageHud Hud;

			internal MethodInfo Method;

			internal object[] Arguments;
		}

		internal sealed class Scope
		{
			internal Scope Previous;

			internal Player Player;

			internal RegisteredFood Food;

			internal BuffOptions Presentation;

			internal readonly List<Notice> Notices = new List<Notice>();

			internal bool Managed;

			internal bool Finished;
		}

		[HarmonyPatch(typeof(Player), "EatFood")]
		private static class EatingHook
		{
			[HarmonyPrepare]
			private static bool Prepare()
			{
				return !Application.isBatchMode && show != null;
			}

			private static void Prefix(Player __instance, ItemData __0, out Scope __state)
			{
				__state = Begin(__instance, __0);
			}

			private static Exception Finalizer(Scope __state, bool __result, Exception __exception)
			{
				Plugin.Guard("eating notification", delegate
				{
					End(__state, __result, __exception);
				});
				return __exception;
			}
		}

		[HarmonyPatch]
		private static class HudHook
		{
			[HarmonyPrepare]
			private static bool Prepare()
			{
				return PrepareHud(hudTargets, Application.isBatchMode);
			}

			private static IEnumerable<MethodBase> TargetMethods()
			{
				return hudTargets;
			}

			private static bool Prefix(MessageHud __instance, MethodBase __originalMethod, object[] __args)
			{
				return Capture(__instance, (MethodInfo)__originalMethod, __args);
			}
		}

		[ThreadStatic]
		private static Scope active;

		[ThreadStatic]
		private static int replaying;

		private static readonly MethodInfo[] hudTargets = FindHudTargets(typeof(MessageHud));

		private static readonly MethodInfo show = hudTargets.OrderByDescending((MethodInfo m) => m.GetParameters().Length).FirstOrDefault();

		private static readonly FieldInfo hudInstance = AccessTools.Field(typeof(MessageHud), "m_instance");

		internal static MethodInfo[] FindHudTargets(Type hudType)
		{
			return hudType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(IsShowMessage).ToArray();
		}

		internal static bool IsShowMessage(MethodInfo method)
		{
			ParameterInfo[] parameters = method.GetParameters();
			return method.Name == "ShowMessage" && method.ReturnType == typeof(void) && !method.IsStatic && parameters.Length >= 4 && parameters[0].ParameterType == typeof(MessageType) && parameters[1].ParameterType == typeof(string) && parameters[2].ParameterType == typeof(int) && parameters[3].ParameterType == typeof(Sprite) && parameters.Skip(4).All((ParameterInfo parameter) => parameter.ParameterType == typeof(bool));
		}

		internal static bool PrepareHud(IEnumerable<MethodInfo> methods, bool headless)
		{
			if (headless)
			{
				return false;
			}
			if (methods.Any())
			{
				return true;
			}
			Plugin.Warn("No compatible MessageHud.ShowMessage method was found. Native eating notices are retained; recipes and food bonuses remain enabled.");
			return false;
		}

		private static object[] MessageArguments(MethodInfo method, Notice route)
		{
			if (route != null)
			{
				return (object[])route.Arguments.Clone();
			}
			return (from p in method.GetParameters()
				select (p.HasDefaultValue && p.DefaultValue != DBNull.Value && p.DefaultValue != Missing.Value) ? p.DefaultValue : (p.ParameterType.IsValueType ? Activator.CreateInstance(p.ParameterType) : null)).ToArray();
		}

		internal static Scope Begin(Player player, ItemData item)
		{
			RegisteredFood registeredFood = Plugin.Instance?.Registry.Find(item);
			bool flag = show != null && !Application.isBatchMode && Object.op_Implicit((Object)(object)player) && player == Player.m_localPlayer && registeredFood != null;
			if (!flag && active == null)
			{
				return null;
			}
			return active = new Scope
			{
				Previous = active,
				Player = player,
				Food = registeredFood,
				Presentation = registeredFood?.Presentation,
				Managed = flag
			};
		}

		internal static bool Capture(MessageHud hud, MethodInfo method, object[] arguments)
		{
			if (replaying != 0 || active == null || !active.Managed)
			{
				return true;
			}
			active.Notices.Add(new Notice
			{
				Hud = hud,
				Method = method,
				Arguments = (object[])arguments.Clone()
			});
			return false;
		}

		internal static void End(Scope scope, bool success, Exception error)
		{
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			if (scope == null || scope.Finished)
			{
				return;
			}
			scope.Finished = true;
			active = scope.Previous;
			if (!scope.Managed)
			{
				return;
			}
			replaying++;
			try
			{
				if (!success || error != null)
				{
					foreach (Notice notice2 in scope.Notices)
					{
						notice2.Method.Invoke(notice2.Hud, notice2.Arguments);
					}
					return;
				}
				Food val = ((IEnumerable<Food>)scope.Player.GetFoods()).FirstOrDefault((Func<Food, bool>)((Food f) => Plugin.Instance.Registry.Find(f.m_item) == scope.Food));
				FoodStatSnapshot foodStatSnapshot = ((val == null) ? null : FoodStats.FindSnapshot(val.m_item));
				SourceSnapshot val2 = ((val == null) ? null : FrameworkApi.GetFoodSnapshot(scope.Player, val.m_item));
				string text = FoodEffects.Eating(foodStatSnapshot ?? scope.Food.Snapshot(), ((val2 != null) ? val2.Modifiers : null) ?? scope.Food.ModifierSet, scope.Presentation);
				if (text.Length != 0)
				{
					Notice notice = scope.Notices.FirstOrDefault();
					MessageHud val3 = (MessageHud)(((object)notice?.Hud) ?? ((object)(MessageHud)(hudInstance?.GetValue(null))));
					MethodInfo methodInfo = notice?.Method ?? show;
					if (Object.op_Implicit((Object)(object)val3) && !(methodInfo == null))
					{
						object[] array = MessageArguments(methodInfo, notice);
						array[0] = (object)(MessageType)2;
						array[1] = text;
						array[2] = 0;
						array[3] = scope.Food.Icons[0];
						methodInfo.Invoke(val3, array);
					}
				}
			}
			finally
			{
				replaying--;
			}
		}
	}
	internal static class FoodEligibility
	{
		[HarmonyPatch]
		private static class EligibilityHooks
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				yield return AccessTools.Method(typeof(Player), "CanEat", (Type[])null, (Type[])null);
				yield return AccessTools.Method(typeof(Player), "EatFood", (Type[])null, (Type[])null);
				yield return AccessTools.Method(typeof(ItemDrop), "CanEat", (Type[])null, (Type[])null);
				yield return AccessTools.Method(typeof(Humanoid), "UseItem", (Type[])null, (Type[])null);
				yield return AccessTools.DeclaredMethod(typeof(Player), "CanConsumeItem", (Type[])null, (Type[])null);
				yield return AccessTools.DeclaredMethod(typeof(Player), "ConsumeItem", (Type[])null, (Type[])null);
				yield return AccessTools.DeclaredMethod(typeof(Humanoid), "CanConsumeItem", (Type[])null, (Type[])null);
				yield return AccessTools.DeclaredMethod(typeof(Humanoid), "ConsumeItem", (Type[])null, (Type[])null);
				yield return AccessTools.Method(typeof(InventoryGui), "UpdateRecipe", (Type[])null, (Type[])null);
				yield return AccessTools.Method(typeof(InventoryGui), "DoCrafting", (Type[])null, (Type[])null);
			}

			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
			{
				return Rewrite(instructions, __originalMethod);
			}
		}

		private static readonly FieldInfo hp = AccessTools.Field(typeof(SharedData), "m_food");

		private static readonly MethodInfo predicate = AccessTools.Method(typeof(FoodEligibility), "Eligibility", (Type[])null, (Type[])null);

		internal static float Eligibility(SharedData shared)
		{
			if (shared.m_food > 0f || (Object)(object)Plugin.Instance == (Object)null)
			{
				return shared.m_food;
			}
			RegisteredFood registeredFood = Plugin.Instance.Registry.Find(shared);
			return (registeredFood == null) ? shared.m_food : 1f;
		}

		internal static IEnumerable<CodeInstruction> Rewrite(IEnumerable<CodeInstruction> instructions, MethodBase original)
		{
			List<CodeInstruction> list = instructions.ToList();
			int num = 0;
			for (int i = 0; i + 2 < list.Count; i++)
			{
				if (list[i].opcode != OpCodes.Ldfld || !object.Equals(list[i].operand, hp))
				{
					continue;
				}
				int j;
				for (j = i + 1; j < list.Count && list[j].opcode == OpCodes.Nop; j++)
				{
				}
				if (j + 1 < list.Count && !(list[j].opcode != OpCodes.Ldc_R4) && list[j].operand is float && (float)list[j].operand == 0f)
				{
					OpCode opcode = list[j + 1].opcode;
					if (!(opcode != OpCodes.Ble_Un) || !(opcode != OpCodes.Ble_Un_S) || !(opcode != OpCodes.Ble) || !(opcode != OpCodes.Ble_S) || !(opcode != OpCodes.Bgt) || !(opcode != OpCodes.Bgt_S) || !(opcode != OpCodes.Bgt_Un) || !(opcode != OpCodes.Bgt_Un_S) || !(opcode != OpCodes.Bge) || !(opcode != OpCodes.Bge_S) || !(opcode != OpCodes.Bge_Un) || !(opcode != OpCodes.Bge_Un_S) || !(opcode != OpCodes.Blt) || !(opcode != OpCodes.Blt_S) || !(opcode != OpCodes.Blt_Un) || !(opcode != OpCodes.Blt_Un_S) || !(opcode != OpCodes.Cgt) || !(opcode != OpCodes.Cgt_Un) || !(opcode != OpCodes.Clt) || !(opcode != OpCodes.Clt_Un) || !(opcode != OpCodes.Beq) || !(opcode != OpCodes.Beq_S) || !(opcode != OpCodes.Bne_Un) || !(opcode != OpCodes.Bne_Un_S) || !(opcode != OpCodes.Ceq))
					{
						list[i].opcode = OpCodes.Call;
						list[i].operand = predicate;
						num++;
					}
				}
			}
			if (num == 0 && original.DeclaringType != typeof(InventoryGui) && original.Name != "EatFood" && list.Any((CodeInstruction x) => x.opcode == OpCodes.Ldfld && object.Equals(x.operand, hp)))
			{
				throw new NotSupportedException("Unrecognized zero-HP food eligibility check in " + original.DeclaringType.Name + "." + original.Name + ".");
			}
			return list;
		}
	}
	internal static class FoodForks
	{
		[HarmonyPatch(typeof(InventoryGrid), "UpdateGui")]
		private static class GridHook
		{
			[HarmonyPrepare]
			private static bool Prepare()
			{
				if (Application.isBatchMode)
				{
					return false;
				}
				if (Compatible)
				{
					return true;
				}
				if (!reported)
				{
					reported = true;
					Plugin.Warn("Food fork layout is unavailable; native inventory display is retained.");
				}
				return false;
			}

			[HarmonyPriority(0)]
			private static void Postfix(InventoryGrid __instance)
			{
				try
				{
					Refresh(__instance);
				}
				catch (Exception ex)
				{
					if (!reported)
					{
						reported = true;
						Plugin.Warn("Could not update the food fork: " + ex.Message);
					}
				}
			}
		}

		private static readonly FieldInfo elements = AccessTools.Field(typeof(InventoryGrid), "m_elements");

		private static readonly Type elementType;

		private static readonly FieldInfo position;

		private static readonly MethodInfo positionGetter;

		private static readonly FieldInfo fork;

		private static readonly Func<object, Vector2i> readPosition;

		private static readonly Func<InventoryGrid, Color> healthColor;

		private static readonly Func<InventoryGrid, Color> staminaColor;

		private static readonly Func<InventoryGrid, Color> eitrColor;

		private static readonly bool compatible;

		private static bool reported;

		internal static bool Compatible => compatible;

		private static Func<InventoryGrid, Color> ColorReader(string name)
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(InventoryGrid), name);
			if (fieldInfo?.FieldType != typeof(Color))
			{
				return null;
			}
			try
			{
				DynamicMethod dynamicMethod = new DynamicMethod("FoodKnowledge_" + name, typeof(Color), new Type[1] { typeof(InventoryGrid) }, typeof(FoodForks), skipVisibility: true);
				ILGenerator iLGenerator = dynamicMethod.GetILGenerator();
				iLGenerator.Emit(OpCodes.Ldarg_0);
				iLGenerator.Emit(OpCodes.Ldfld, fieldInfo);
				iLGenerator.Emit(OpCodes.Ret);
				return (Func<InventoryGrid, Color>)dynamicMethod.CreateDelegate(typeof(Func<InventoryGrid, Color>));
			}
			catch
			{
				return null;
			}
		}

		private static Func<object, Vector2i> PositionReader()
		{
			if (positionGetter?.ReturnType == typeof(Vector2i))
			{
				try
				{
					return (Func<object, Vector2i>)AccessTools.Method(typeof(FoodForks), "CreatePositionReader", (Type[])null, (Type[])null).MakeGenericMethod(elementType).Invoke(null, new object[1] { positionGetter });
				}
				catch
				{
					return (object cell) => (Vector2i)positionGetter.Invoke(cell, null);
				}
			}
			return (position?.FieldType == typeof(Vector2i)) ? ((Func<object, Vector2i>)((object cell) => (Vector2i)position.GetValue(cell))) : null;
		}

		private static Func<object, Vector2i> CreatePositionReader<T>(MethodInfo getter)
		{
			Func<T, Vector2i> read = (Func<T, Vector2i>)getter.CreateDelegate(typeof(Func<T, Vector2i>));
			return (object cell) => read((T)cell);
		}

		internal static bool ModifierOnly(ItemData item, FoodRegistry registry)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			SharedData val = item?.m_shared;
			if (val == null || (int)val.m_itemType != 2 || val.m_food != 0f || val.m_foodStamina != 0f || val.m_foodEitr != 0f)
			{
				return false;
			}
			RegisteredFood registeredFood = registry?.Find(item);
			return registeredFood != null && registeredFood.ModifierSet.HasAny;
		}

		internal static void Refresh(InventoryGrid grid)
		{
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Invalid comparison between Unknown and I4
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0274: Unknown result type (might be due to invalid IL or missing references)
			//IL_0281: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_025a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0286: Unknown result type (might be due to invalid IL or missing references)
			//IL_028a: Unknown result type (might be due to invalid IL or missing references)
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a5: Unknown result type (might be due to invalid IL or missing references)
			if (Application.isBatchMode || !Object.op_Implicit((Object)(object)grid) || !Compatible)
			{
				return;
			}
			FoodRegistry foodRegistry = Plugin.Instance?.Registry;
			Inventory val = ((foodRegistry == null) ? null : grid.GetInventory());
			if (val == null)
			{
				return;
			}
			IList list = null;
			int num = 0;
			foreach (ItemData allItem in val.GetAllItems())
			{
				SharedData val2 = allItem?.m_shared;
				if (val2 == null || (int)val2.m_itemType != 2 || (val2.m_food <= 0f && val2.m_foodStamina <= 0f && val2.m_foodEitr <= 0f && !ModifierOnly(allItem, foodRegistry)))
				{
					continue;
				}
				if (list == null)
				{
					list = elements.GetValue(grid) as IList;
					num = val.GetWidth();
				}
				if (list == null || num <= 0)
				{
					break;
				}
				Vector2i gridPos = allItem.m_gridPos;
				if (gridPos.x < 0 || gridPos.x >= num || gridPos.y < 0)
				{
					continue;
				}
				long num2 = (long)gridPos.y * (long)num + gridPos.x;
				if (num2 >= list.Count)
				{
					continue;
				}
				object obj = list[(int)num2];
				if (obj == null)
				{
					continue;
				}
				Vector2i val3 = readPosition(obj);
				if (val3.x != gridPos.x || val3.y != gridPos.y)
				{
					continue;
				}
				object? value = fork.GetValue(obj);
				Image val4 = (Image)((value is Image) ? value : null);
				if (Object.op_Implicit((Object)(object)val4) && Object.op_Implicit((Object)(object)((Component)val4).gameObject))
				{
					Color val5 = ((val2.m_foodEitr > val2.m_food && val2.m_foodEitr > val2.m_foodStamina) ? eitrColor(grid) : ((val2.m_food > val2.m_foodStamina) ? healthColor(grid) : ((val2.m_foodStamina > val2.m_food) ? staminaColor(grid) : Color.white)));
					Color color = ((Graphic)val4).color;
					if (!((Color)(ref color)).Equals(val5))
					{
						((Graphic)val4).color = val5;
					}
					if (!((Behaviour)val4).enabled)
					{
						((Behaviour)val4).enabled = true;
					}
					((Component)val4).gameObject.SetActive(true);
				}
			}
		}

		static FoodForks()
		{
			FieldInfo fieldInfo = elements;
			elementType = (((object)fieldInfo != null && fieldInfo.FieldType.IsGenericType) ? elements.FieldType.GetGenericArguments()[0] : null);
			position = ((elementType == null) ? null : AccessTools.Field(elementType, "m_pos"));
			positionGetter = ((elementType == null) ? null : AccessTools.PropertyGetter(elementType, "Position"));
			fork = ((elementType == null) ? null : AccessTools.Field(elementType, "m_food"));
			readPosition = PositionReader();
			healthColor = ColorReader("m_foodHealthColor");
			staminaColor = ColorReader("m_foodStaminaColor");
			eitrColor = ColorReader("m_foodEitrColor");
			compatible = elements != null && typeof(IList).IsAssignableFrom(elements.FieldType) && readPosition != null && fork?.FieldType == typeof(Image) && healthColor != null && staminaColor != null && eitrColor != null;
		}
	}
	internal sealed class FoodIcons : IDisposable
	{
		private sealed class Entry
		{
			internal IconFile File;

			internal Sprite Sprite;

			internal Texture2D Texture;

			internal long Pixels;
		}

		internal const int MaxBytes = 8388608;

		internal const int MaxDimension = 2048;

		private const long MaxPixels = 33554432L;

		internal readonly IconFiles Files;

		private readonly Action<string> warn;

		private readonly Dictionary<string, Entry> cache = new Dictionary<string, Entry>(StringComparer.Ordinal);

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

		private long pixels;

		private Dictionary<string, IconFile> serverFiles;

		internal bool ServerLocked => serverFiles != null;

		internal bool NeedsRetry { get; private set; }

		internal FoodIcons(string directory, Action<string> warn)
			: this(new IconFiles(directory), warn)
		{
		}

		internal FoodIcons(IconFiles files, Action<string> warn)
		{
			Files = files;
			this.warn = warn;
		}

		internal void UseServer(Dictionary<string, IconFile> files)
		{
			if (serverFiles == null)
			{
				Dispose();
			}
			serverFiles = files ?? new Dictionary<string, IconFile>(StringComparer.Ordinal);
		}

		internal void UseLocal()
		{
			Dispose();
			serverFiles = null;
		}

		internal bool Refresh(IEnumerable<RegisteredFood> registered, Sprite fallback)
		{
			//IL_02ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0309: Unknown result type (might be due to invalid IL or missing references)
			//IL_0311: Unknown result type (might be due to invalid IL or missing references)
			//IL_031b: Expected O, but got Unknown
			//IL_037d: Unknown result type (might be due to invalid IL or missing references)
			//IL_038c: Unknown result type (might be due to invalid IL or missing references)
			NeedsRetry = false;
			if (Application.isBatchMode)
			{
				return false;
			}
			RegisteredFood[] array = registered.ToArray();
			Dictionary<string, string[]> dictionary;
			try
			{
				dictionary = ((serverFiles == null) ? ((IEnumerable<string>)Files.Paths()) : ((IEnumerable<string>)serverFiles.Keys.ToArray())).GroupBy<string, string>(Path.GetFileNameWithoutExtension, StringComparer.Ordinal).ToDictionary<IGrouping<string, string>, string, string[]>((IGrouping<string, string> g) => g.Key, (IGrouping<string, string> g) => g.OrderBy<string, string>((string p) => p, StringComparer.Ordinal).ToArray(), StringComparer.Ordinal);
				errors.Remove("folder");
			}
			catch (Exception ex)
			{
				NeedsRetry = ex is IOException;
				Warn("folder", "Cannot read the icons folder: " + ex.Message);
				return false;
			}
			HashSet<string> names = new HashSet<string>(array.Select((RegisteredFood f) => f.Item), StringComparer.Ordinal);
			string[] array2 = cache.Keys.Where((string k) => !names.Contains(k)).ToArray();
			foreach (string item in array2)
			{
				Remove(item);
			}
			bool flag = false;
			RegisteredFood[] array3 = array;
			foreach (RegisteredFood registeredFood in array3)
			{
				Sprite val = (registeredFood.Created ? fallback : registeredFood.NativeIcon);
				cache.TryGetValue(registeredFood.Item, out var value);
				if (!dictionary.TryGetValue(registeredFood.Item, out var value2))
				{
					flag |= Set(registeredFood, val);
					Remove(registeredFood.Item);
					errors.Remove(registeredFood.Item);
					continue;
				}
				Texture2D val2 = null;
				Sprite val3 = null;
				try
				{
					if (value2.Length != 1)
					{
						throw new FormatException("More than one PNG matches this item. Keep one file with this exact item name.");
					}
					IconFile iconFile = ((serverFiles == null) ? Files.Read(value2[0]) : serverFiles[value2[0]]);
					int width = iconFile.Width;
					int height = iconFile.Height;
					if (value == null || value.File.Hash != iconFile.Hash)
					{
						long num3 = (long)width * (long)height;
						if (pixels - (value?.Pixels ?? 0) + num3 > 33554432)
						{
							throw new FormatException("Combined loaded icons exceed 32 million pixels. Use smaller icons.");
						}
						val2 = new Texture2D(2, 2, (TextureFormat)4, false)
						{
							name = "FoodKnowledge_Icon_" + registeredFood.Item,
							wrapMode = (TextureWrapMode)1,
							filterMode = (FilterMode)1
						};
						if (!ImageConversion.LoadImage(val2, iconFile.Bytes, true))
						{
							throw new FormatException("Unity could not decode the PNG.");
						}
						if (((Texture)val2).width != width || ((Texture)val2).height != height)
						{
							throw new FormatException("Decoded PNG dimensions do not match its header.");
						}
						val3 = Sprite.Create(val2, new Rect(0f, 0f, (float)width, (float)height), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0);
						if (!Object.op_Implicit((Object)(object)val3))
						{
							throw new FormatException("Unity could not create the icon sprite.");
						}
						((Object)val3).name = "FoodKnowledge_Icon_" + registeredFood.Item;
						flag |= Set(registeredFood, val3);
						Remove(registeredFood.Item);
						value = new Entry
						{
							File = iconFile,
							Sprite = val3,
							Texture = val2,
							Pixels = num3
						};
						cache[registeredFood.Item] = value;
						pixels += num3;
						val2 = null;
						val3 = null;
					}
					flag |= Set(registeredFood, value.Sprite);
					errors.Remove(registeredFood.Item);
				}
				catch (Exception ex2)
				{
					if (Object.op_Implicit((Object)(object)val3))
					{
						Object.Destroy((Object)(object)val3);
					}
					if (Object.op_Implicit((Object)(object)val2))
					{
						Object.Destroy((Object)(object)val2);
					}
					NeedsRetry |= ex2 is IOException;
					Warn(registeredFood.Item, "Icon for '" + registeredFood.Item + "' was not changed: " + ex2.Message);
					flag |= Set(registeredFood, Object.op_Implicit((Object)(object)value?.Sprite) ? value.Sprite : val);
				}
			}
			return flag;
		}

		private static bool Set(RegisteredFood food, Sprite sprite)
		{
			if (food.Icons[0] == sprite)
			{
				return false;
			}
			food.Icons[0] = sprite;
			return true;
		}

		private void Warn(string key, string message)
		{
			if (!errors.TryGetValue(key, out var value) || !(value == message))
			{
				errors[key] = message;
				warn?.Invoke(message);
			}
		}

		private void Remove(string item)
		{
			if (cache.TryGetValue(item, out var value))
			{
				pixels -= value.Pixels;
				cache.Remove(item);
				if (Object.op_Implicit((Object)(object)value.Sprite))
				{
					Object.Destroy((Object)(object)value.Sprite);
				}
				if (Object.op_Implicit((Object)(object)value.Texture))
				{
					Object.Destroy((Object)(object)value.Texture);
				}
			}
		}

		internal static byte[] Read(string path)
		{
			FileInfo fileInfo = new FileInfo(path);
			long length = fileInfo.Length;
			DateTime lastWriteTimeUtc = fileInfo.LastWriteTimeUtc;
			if (length < 33 || length > 8388608)
			{
				throw new FormatException("PNG must be at most 8 MiB and contain a valid image header.");
			}
			byte[] array = new byte[(int)length];
			using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
			{
				int i;
				int num;
				for (i = 0; i < array.Length; i += num)
				{
					if ((num = fileStream.Read(array, i, array.Length - i)) == 0)
					{
						break;
					}
				}
				if (i != array.Length || fileStream.ReadByte() != -1)
				{
					throw new IOException("PNG changed while being read; waiting for the save to finish.");
				}
			}
			FileInfo fileInfo2 = new FileInfo(path);
			if (!fileInfo2.Exists || fileInfo2.Length != length || fileInfo2.LastWriteTimeUtc != lastWriteTimeUtc)
			{
				throw new IOException("PNG changed while being read; waiting for the save to finish.");
			}
			return array;
		}

		internal static void Dimensions(byte[] bytes, out int width, out int height)
		{
			byte[] second = new byte[8] { 137, 80, 78, 71, 13, 10, 26, 10 };
			if (bytes == null || bytes.Length < 33 || !bytes.Take(8).SequenceEqual(second) || Number(bytes, 8) != 13 || bytes[12] != 73 || bytes[13] != 72 || bytes[14] != 68 || bytes[15] != 82)
			{
				throw new FormatException("File is not a PNG with a valid IHDR header.");
			}
			uint num = Number(bytes, 16);
			uint num2 = Number(bytes, 20);
			if (num == 0 || num2 == 0 || num > 2048 || num2 > 2048)
			{
				throw new FormatException("PNG dimensions must be between 1 and 2048 pixels per side.");
			}
			width = (int)num;
			height = (int)num2;
		}

		private static uint Number(byte[] data, int offset)
		{
			return (uint)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]);
		}

		public void Dispose()
		{
			string[] array = cache.Keys.ToArray();
			foreach (string item in array)
			{
				Remove(item);
			}
			errors.Clear();
			serverFiles = null;
			NeedsRetry = false;
		}
	}
	internal sealed class RegisteredFood
	{
		internal string Item;

		internal string UseModel;

		internal string Title;

		internal string Description;

		internal string EffectInfo;

		internal GameObject Prefab;

		internal ItemDrop Drop;

		internal ModifierSet ModifierSet = ModifierSet.Empty;

		internal BuffOptions Presentation;

		internal Recipe[] Recipes = Array.Empty<Recipe>();

		internal Sprite[] Icons = (Sprite[])(object)new Sprite[1];

		internal bool Active;

		internal bool Created = true;

		internal bool Deleted;

		internal bool ChangedRecipe;

		internal SharedData Baseline;

		internal Recipe[] OriginalRecipes = Array.Empty<Recipe>();

		internal Sprite NativeIcon => (Baseline?.m_icons != null && Baseline.m_icons.Length != 0) ? Baseline.m_icons[0] : null;

		internal FoodStatSnapshot Snapshot()
		{
			SharedData shared = Drop.m_itemData.m_shared;
			return new FoodStatSnapshot
			{
				Item = Item,
				Hp = shared.m_food,
				Stamina = shared.m_foodStamina,
				Eitr = shared.m_foodEitr,
				Seconds = shared.m_foodBurnTime,
				Regen = shared.m_foodRegen
			};
		}
	}
	internal sealed class FoodRegistry
	{
		private sealed class References<T> : IEqualityComparer<T> where T : class
		{
			internal static readonly References<T> Instance = new References<T>();

			public bool Equals(T x, T y)
			{
				return x == y;
			}

			public int GetHashCode(T value)
			{
				return RuntimeHelpers.GetHashCode(value);
			}
		}

		private sealed class Plan
		{
			internal FoodDefinition Definition;

			internal ItemDrop Template;

			internal ItemDrop BaseFood;

			internal SharedData Values;

			internal Ingredient[] Ingredients;

			internal CraftingStation[] Stations;

			internal int Amount;

			internal string Title;

			internal string Description;

			internal RegisteredFood Existing;

			internal bool Created;

			internal bool Delete;

			internal bool ChangeRecipe;

			internal Recipe[] Originals = Array.Empty<Recipe>();
		}

		private sealed class InventoryRevision
		{
			internal int Value = -1;
		}

		[HarmonyPatch(typeof(Inventory), "Changed")]
		private static class InventoryChangeHook
		{
			private static void Prefix(Inventory __instance)
			{
				if (!Application.isBatchMode)
				{
					Plugin.Instance?.Registry?.RefreshInventory(__instance, force: true, notify: false);
				}
			}
		}

		[HarmonyPatch(typeof(InventoryGrid), "UpdateGui")]
		private static class InventoryDisplayHook
		{
			private static void Prefix(InventoryGrid __instance)
			{
				if (!Application.isBatchMode && Object.op_Implicit((Object)(object)__instance))
				{
					Plugin.Instance?.Registry?.RefreshInventory(__instance.GetInventory());
				}
			}
		}

		internal ObjectDB Database;

		internal ZNetScene Scene;

		internal bool SceneAwaking;

		internal FoodIcons Icons;

		internal Action<string> Warning;

		private GameObject root;

		private readonly Dictionary<string, RegisteredFood> foods = new Dictionary<string, RegisteredFood>(StringComparer.Ordinal);

		private readonly Dictionary<string, RegisteredFood> tokens = new Dictionary<string, RegisteredFood>(StringComparer.Ordinal);

		private readonly HashSet<Recipe> ownedRecipes = new HashSet<Recipe>(References<Recipe>.Instance);

		private readonly HashSet<List<GameObject>> itemLists = new HashSet<List<GameObject>>();

		private readonly HashSet<List<Recipe>> recipeLists = new HashSet<List<Recipe>>();

		private readonly HashSet<Dictionary<int, GameObject>> itemHashes = new HashSet<Dictionary<int, GameObject>>();

		private readonly HashSet<Dictionary<SharedData, GameObject>> itemData = new HashSet<Dictionary<SharedData, GameObject>>();

		private readonly HashSet<List<GameObject>> sceneLists = new HashSet<List<GameObject>>();

		private readonly HashSet<Dictionary<int, GameObject>> sceneHashes = new HashSet<Dictionary<int, GameObject>>();

		private readonly Dictionary<Localization, Dictionary<string, string>> nativeWords = new Dictionary<Localization, Dictionary<string, string>>();

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

		private int itemRevision;

		private bool inventoryWarning;

		private readonly ConditionalWeakTable<Inventory, InventoryRevision> refreshedInventories = new ConditionalWeakTable<Inventory, InventoryRevision>();

		internal bool DatabaseReady => Object.op_Implicit((Object)(object)Database) && Database.m_items != null && Database.m_items.Count > 0 && Object.op_Implicit((Object)(object)Database.GetItemPrefab("Wood"));

		internal bool HasActiveFoods => ownedRecipes.Count != 0;

		internal IEnumerable<RegisteredFood> ActiveEntries => foods.Values.Where((RegisteredFood food) => food.Active);

		internal RegisteredFood Find(string item)
		{
			RegisteredFood value;
			return (item != null && foods.TryGetValue(item, out value)) ? value : null;
		}

		internal RegisteredFood Find(ItemData data)
		{
			if (data == null || data.m_shared == null)
			{
				return null;
			}
			RegisteredFood value;
			return (Object.op_Implicit((Object)(object)data.m_dropPrefab) && foods.TryGetValue(((Object)data.m_dropPrefab).name, out value)) ? value : (tokens.TryGetValue(data.m_shared.m_name ?? "", out value) ? value : null);
		}

		internal RegisteredFood Find(SharedData shared)
		{
			RegisteredFood value;
			return (shared != null && tokens.TryGetValue(shared.m_name ?? "", out value)) ? value : null;
		}

		internal bool IsOwned(Recipe recipe)
		{
			return Object.op_Implicit((Object)(object)recipe) && ownedRecipes.Contains(recipe);
		}

		internal static string NameKey(string item)
		{
			return "foodknowledge_name_" + item;
		}

		internal static string DescriptionKey(string item)
		{
			return "foodknowledge_description_" + item;
		}

		private List<Plan> Prepare(Catalog catalog)
		{
			//IL_03fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0403: Invalid comparison between Unknown and I4
			//IL_0a7c: Unknown result type (might be due to invalid IL or missing references)
			if (nativeFoods.Count == 0)
			{
				CaptureNativeFoods(Database);
			}
			GameObject itemPrefab = Database.GetItemPrefab("RawMeat");
			ItemDrop val = (Object.op_Implicit((Object)(object)itemPrefab) ? itemPrefab.GetComponent<ItemDrop>() : null);
			List<Plan> list = new List<Plan>();
			HashSet<string> hashSet = new HashSet<string>(catalog.Foods.Select((FoodDefinition f) => f.Item), StringComparer.Ordinal);
			Dictionary<string, CraftingStation> dictionary = new Dictionary<string, CraftingStation>(StringComparer.Ordinal);
			foreach (Recipe item in Database.m_recipes.Concat(foods.Values.SelectMany((RegisteredFood f) => f.OriginalRecipes)))
			{
				if (Object.op_Implicit((Object)(object)item) && Object.op_Implicit((Object)(object)item.m_craftingStation))
				{
					dictionary[((Object)((Component)item.m_craftingStation).gameObject).name] = item.m_craftingStation;
				}
			}
			if (Object.op_Implicit((Object)(object)Scene) && Scene.m_prefabs != null)
			{
				foreach (GameObject prefab in Scene.m_prefabs)
				{
					if (Object.op_Implicit((Object)(object)prefab))
					{
						CraftingStation component = prefab.GetComponent<CraftingStation>();
						if (Object.op_Implicit((Object)(object)component))
						{
							dictionary[((Object)prefab).name] = component;
						}
					}
				}
			}
			Dictionary<int, GameObject> dictionary2 = (Dictionary<int, GameObject>)GameApi.ItemHashes.GetValue(Database);
			Dictionary<int, GameObject> dictionary3 = (Object.op_Implicit((Object)(object)Scene) ? ((Dictionary<int, GameObject>)GameApi.SceneHashes.GetValue(Scene)) : null);
			foreach (FoodDefinition food in catalog.Foods)
			{
				RegisteredFood own = Find(food.Item);
				int hash = Catalog.StableHash(food.Item);
				GameObject itemPrefab2 = Database.GetItemPrefab(food.Item);
				if (dictionary2.TryGetValue(hash, out var value) && Object.op_Implicit((Object)(object)value) && ((Object)value).name != food.Item)
				{
					throw new FormatException(food.Location + ": prefab hash collision for '" + food.Item + "'.");
				}
				if (own != null && Object.op_Implicit((Object)(object)itemPrefab2) && (Object)(object)itemPrefab2 != (Object)(object)own.Prefab)
				{
					throw new FormatException(food.Location + ": another mod replaced the tracked prefab '" + food.Item + "'. Restart to resolve the conflict.");
				}
				bool flag = own?.Created ?? (!Object.op_Implicit((Object)(object)itemPrefab2));
				Plan plan = new Plan
				{
					Definition = food,
					Existing = own,
					Created = flag
				};
				if (!flag)
				{
					ItemDrop drop = (Object.op_Implicit((Object)(object)itemPrefab2) ? itemPrefab2.GetComponent<ItemDrop>() : null);
					SharedData val2 = own?.Baseline ?? drop?.m_itemData?.m_shared;
					if (!nativeFoods.Contains(food.Item) || !Object.op_Implicit((Object)(object)drop) || val2 == null || (int)val2.m_itemType != 2 || (val2.m_food <= 0f && val2.m_foodStamina <= 0f && val2.m_foodEitr <= 0f))
					{
						throw new FormatException(food.Location + ": existing item '" + food.Item + "' is not a native food. Only existing foods can be edited or deleted.");
					}
					if (food.Overrides("useModel") && !string.IsNullOrEmpty(food.Fields["useModel"]) && food.Fields["useModel"] != food.Item)
					{
						throw new FormatException(food.Location + ": useModel selects appearance for new foods only; existing foods keep their native model.");
					}
					plan.Template = drop;
					plan.BaseFood = drop;
					plan.Values = GameApi.Clone(val2);
					plan.Originals = own?.OriginalRecipes ?? Database.m_recipes.Where((Recipe r) => Object.op_Implicit((Object)(object)r) && (Object)(object)r.m_item == (Object)(object)drop && !ownedRecipes.Contains(r)).ToArray();
					string text = NativeSignature(val2, plan.Originals);
					if (food.Synchronized && food.NativeBaseline != text)
					{
						throw new FormatException(food.Location + ": native food/recipe baseline differs from the server for '" + food.Item + "'. Install matching game and recipe/stat mods.");
					}
					plan.Delete = food.NameOnly;
					plan.ChangeRecipe = plan.Delete || food.Has("craftingStation") || food.Overrides("stationLevel") || food.Overrides("amountProduced") || food.Ingredients != null;
					if (food.Overrides("hpGain"))
					{
						plan.Values.m_food = food.Hp;
					}
					if (food.Overrides("hpPerTick"))
					{
						plan.Values.m_foodRegen = food.HpPerTick;
					}
					if (food.Overrides("staminaGain"))
					{
						plan.Values.m_foodStamina = food.Stamina;
					}
					if (food.Overrides("eitrGain"))
					{
						plan.Values.m_foodEitr = food.Eitr;
					}
					if (food.Overrides("durationMinutes"))
					{
						plan.Values.m_foodBurnTime = food.DurationMinutes * 60f;
					}
					if (food.Overrides("weightPerUnit"))
					{
						plan.Values.m_weight = food.WeightPerUnit;
					}
					if (food.Overrides("stackSize"))
					{
						plan.Values.m_maxStackSize = food.StackSize;
					}
					plan.Title = (food.Overrides("displayName") ? (food.DisplayName ?? "") : null);
					plan.Description = (food.Overrides("description") ? (food.Description ?? "") : null);
					if (plan.Title != null && !(plan.Values.m_name ?? "").StartsWith("$", StringComparison.Ordinal))
					{
						plan.Values.m_name = "$" + NameKey(food.Item);
					}
					if (plan.Description != null && !(plan.Values.m_description ?? "").StartsWith("$", StringComparison.Ordinal))
					{
						plan.Values.m_description = "$" + DescriptionKey(food.Item);
					}
					if (plan.ChangeRecipe && !plan.Delete && plan.Originals.Length > 1)
					{
						throw new FormatException(food.Location + ": this food has several native recipes; recipe edits require one unambiguous recipe.");
					}
					goto IL_0b98;
				}
				if (food.NativeBaseline != null)
				{
					throw new FormatException(food.Location + ": a native baseline fingerprint cannot target a new food.");
				}
				if (food.Ingredients == null)
				{
					throw new FormatException(food.Location + ": a new food requires ingredientList or an ingredients list.");
				}
				if (!Object.op_Implicit((Object)(object)val) || val.m_itemData?.m_shared == null || !Object.op_Implicit((Object)(object)FallbackIcon()))
				{
					throw new FormatException("The vanilla boar-meat (RawMeat) item/icon is not available yet.");
				}
				if ((dictionary3 != null && dictionary3.TryGetValue(hash, out value) && (own == null || (Object)(object)value != (Object)(object)own.Prefab)) || (Object.op_Implicit((Object)(object)Scene) && SceneAwaking && Scene.m_prefabs.Any((GameObject x) => Object.op_Implicit((Object)(object)x) && Catalog.StableHash(((Object)x).name) == hash && (own == null || (Object)(object)x != (Object)(object)own.Prefab))))
				{
					throw new FormatException(food.Location + ": network prefab collision for '" + food.Item + "'.");
				}
				object obj;
				if (!hashSet.Contains(food.UseModel))
				{
					RegisteredFood registeredFood = Find(food.UseModel);
					if (registeredFood == null || !registeredFood.Created)
					{
						obj = Database.GetItemPrefab(food.UseModel);
						goto IL_0987;
					}
				}
				obj = null;
				goto IL_0987;
				IL_0987:
				GameObject val3 = (GameObject)obj;
				ItemDrop val4 = (Object.op_Implicit((Object)(object)val3) ? val3.GetComponent<ItemDrop>() : null);
				if (!Object.op_Implicit((Object)(object)val4) || val4.m_itemData?.m_shared == null)
				{
					val4 = val;
					Warning?.Invoke(food.Location + ": useModel '" + food.UseModel + "' is unavailable; using boar meat (RawMeat).");
				}
				if (own != null && own.UseModel != ((Object)((Component)val4).gameObject).name)
				{
					throw new FormatException(food.Location + ": changing useModel for an already registered item requires restarting the game/server.");
				}
				plan.Template = val4;
				plan.BaseFood = val;
				plan.ChangeRecipe = true;
				plan.Values = GameApi.Clone(val.m_itemData.m_shared);
				SharedData values = plan.Values;
				values.m_itemType = (ItemType)2;
				values.m_food = food.Hp;
				values.m_foodStamina = food.Stamina;
				values.m_foodEitr = food.Eitr;
				values.m_foodBurnTime = food.DurationMinutes * 60f;
				values.m_foodRegen = food.HpPerTick;
				values.m_name = "$" + NameKey(food.Item);
				values.m_description = "$" + DescriptionKey(food.Item);
				values.m_weight = food.WeightPerUnit;
				values.m_maxStackSize = food.StackSize;
				values.m_maxQuality = 1;
				values.m_teleportable = true;
				values.m_useDurability = false;
				values.m_consumeStatusEffect = null;
				values.m_equipStatusEffect = null;
				values.m_setStatusEffect = null;
				values.m_appendToolTip = null;
				plan.Title = (string.IsNullOrWhiteSpace(food.DisplayName) ? food.Item : food.DisplayName);
				plan.Description = food.Description ?? "";
				goto IL_0b98;
				IL_0b98:
				if (plan.ChangeRecipe && !plan.Delete)
				{
					Recipe val5 = plan.Originals.FirstOrDefault();
					plan.Ingredients = food.Ingredients ?? val5?.m_resources?.Select((Requirement r) => new Ingredient
					{
						Item = ((Object)((Component)r.m_resItem).gameObject).name,
						Amount = r.m_amount
					}).ToArray();
					if (plan.Ingredients == null || plan.Ingredients.Length == 0)
					{
						throw new FormatException(food.Location + ": creating a recipe requires ingredients.");
					}
					Ingredient[] ingredients = plan.Ingredients;
					foreach (Ingredient ingredient in ingredients)
					{
						if (ingredient.Item == food.Item)
						{
							throw new FormatException(food.Location + ": a food cannot require itself as an ingredient.");
						}
						if (!hashSet.Contains(ingredient.Item))
						{
							GameObject itemPrefab3 = Database.GetItemPrefab(ingredient.Item);
							if (!Object.op_Implicit((Object)(object)itemPrefab3) || !Object.op_Implicit((Object)(object)itemPrefab3.GetComponent<ItemDrop>()))
							{
								throw new FormatException(food.Location + ": unknown ingredient prefab '" + ingredient.Item + "'.");
							}
						}
					}
					List<CraftingStation> list2 = new List<CraftingStation>();
					if (!plan.Created && !food.Has("craftingStation"))
					{
						list2.Add(Object.op_Implicit((Object)(object)val5) ? val5.m_craftingStation : null);
					}
					else
					{
						string[] stations = food.Stations;
						foreach (string text2 in stations)
						{
							if (!dictionary.TryGetValue(text2, out var value2))
							{
								throw new FormatException(food.Location + ": unknown crafting-station prefab '" + text2 + "'. Use native crafting-menu stations such as piece_cauldron; cooking racks and ovens are conversion machines.");
							}
							list2.Add(value2);
						}
					}
					if (list2.Count == 0)
					{
						list2.Add(null);
					}
					plan.Stations = list2.ToArray();
					plan.Amount = ((!plan.Created && !food.Overrides("amountProduced") && Object.op_Implicit((Object)(object)val5)) ? val5.m_amount : food.Amount);
				}
				list.Add(plan);
			}
			Catalog.SerializeSynchronized(catalog.Foods, list.Where((Plan p) => !p.Created).ToDictionary<Plan, string, string>((Plan p) => p.Definition.Item, (Plan p) => NativeSignature(p.Existing?.Baseline ?? p.Template.m_itemData.m_shared, p.Originals), StringComparer.Ordinal));
			return list;
		}

		internal bool Apply(Catalog catalog)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			if (!DatabaseReady)
			{
				return false;
			}
			ObserveDatabase(Database);
			List<Plan> list = Prepare(catalog);
			if (!Object.op_Implicit((Object)(object)root))
			{
				root = new GameObject("FoodKnowledge_Prefabs");
				root.SetActive(false);
				((Object)root).hideFlags = (HideFlags)61;
				Object.DontDestroyOnLoad((Object)(object)root);
			}
			Dictionary<string, RegisteredFood> staged = new Dictionary<string, RegisteredFood>(StringComparer.Ordinal);
			Dictionary<string, Recipe[]> dictionary = new Dictionary<string, Recipe[]>(StringComparer.Ordinal);
			List<Recipe> list2 = new List<Recipe>();
			try
			{
				foreach (Plan item2 in list)
				{
					FoodDefinition definition = item2.Definition;
					RegisteredFood existing = item2.Existing;
					if (existing != null)
					{
						staged.Add(definition.Item, existing);
					}
					else if (!item2.Created)
					{
						staged.Add(definition.Item, new RegisteredFood
						{
							Item = definition.Item,
							UseModel = definition.Item,
							Prefab = ((Component)item2.Template).gameObject,
							Drop = item2.Template,
							Created = false,
							Baseline = GameApi.Clone(item2.Template.m_itemData.m_shared),
							OriginalRecipes = item2.Originals,
							Icons = (Sprite[])((item2.Template.m_itemData.m_shared.m_icons != null && item2.Template.m_itemData.m_shared.m_icons.Length != 0) ? ((Array)(Sprite[])item2.Template.m_itemData.m_shared.m_icons.Clone()) : ((Array)new Sprite[1]))
						});
					}
					else
					{
						GameObject val = Object.Instantiate<GameObject>(((Component)item2.Template).gameObject, root.transform, false);
						((Object)val).name = definition.Item;
						((Object)val).hideFlags = (HideFlags)0;
						ItemDrop component = val.GetComponent<ItemDrop>();
						component.m_itemData = item2.BaseFood.m_itemData.Clone();
						component.m_itemData.m_stack = 1;
						component.m_itemData.m_quality = 1;
						component.m_itemData.m_variant = 0;
						component.m_itemData.m_shared = GameApi.Clone(item2.Values);
						component.m_itemData.m_dropPrefab = val;
						val.SetActive(true);
						staged.Add(definition.Item, new RegisteredFood
						{
							Item = definition.Item,
							UseModel = ((Object)((Component)item2.Template).gameObject).name,
							Prefab = val,
							Drop = component
						});
					}
				}
				foreach (Plan item3 in list)
				{
					if (!item3.Delete)
					{
						FrameworkApi.ValidateFoodRegistration("dannanboi.foodknowledge", staged[item3.Definition.Item].Prefab, item3.Definition.ModifierSet, item3.Definition.Presentation);
					}
				}
				foreach (Plan item4 in list)
				{
					if (item4.Delete)
					{
						dictionary.Add(item4.Definition.Item, Array.Empty<Recipe>());
						continue;
					}
					if (!item4.ChangeRecipe)
					{
						dictionary.Add(item4.Definition.Item, item4.Originals);
						continue;
					}
					List<Recipe> list3 = new List<Recipe>();
					Recipe val2 = item4.Originals.FirstOrDefault();
					CraftingStation[] stations = item4.Stations;
					foreach (CraftingStation val3 in stations)
					{
						Recipe val4 = (Object.op_Implicit((Object)(object)val2) ? Object.Instantiate<Recipe>(val2) : ScriptableObject.CreateInstance<Recipe>());
						list3.Add(val4);
						list2.Add(val4);
						((Object)val4).name = "FoodKnowledge_Recipe_" + item4.Definition.Item + "_" + (Object.op_Implicit((Object)(object)val3) ? ((Object)((Component)val3).gameObject).name : "Inventory");
						val4.m_item = staged[item4.Definition.Item].Drop;
						val4.m_amount = item4.Amount;
						val4.m_enabled = item4.Created || !Object.op_Implicit((Object)(object)val2) || val2.m_enabled;
						val4.m_minStationLevel = ((!item4.Created && !item4.Definition.Overrides("stationLevel") && !item4.Definition.Has("craftingStation") && Object.op_Implicit((Object)(object)val2)) ? val2.m_minStationLevel : item4.Definition.StationLevel);
						val4.m_craftingStation = val3;
						if (item4.Created)
						{
							val4.m_qualityResultAmountMultiplier = 0f;
							val4.m_requireOnlyOneIngredient = false;
						}
						val4.m_resources = ((item4.Definition.Ingredients == null && Object.op_Implicit((Object)(object)val2)) ? val2.m_resources : ((IEnumerable<Ingredient>)item4.Ingredients).Select((Func<Ingredient, Requirement>)((Ingredient x) => new Requirement
						{
							m_resItem = (staged.ContainsKey(x.Item) ? staged[x.Item].Drop : Database.GetItemPrefab(x.Item).GetComponent<ItemDrop>()),
							m_amount = x.Amount,
							m_amountPerLevel = 0,
							m_recover = false
						})).ToArray());
					}
					dictionary.Add(item4.Definition.Item, list3.ToArray());
				}
			}
			catch
			{
				foreach (KeyValuePair<string, RegisteredFood> item5 in staged)
				{
					if (item5.Value.Created && Find(item5.Key) == null)
					{
						Object.Destroy((Object)(object)item5.Value.Prefab);
					}
				}
				foreach (Recipe item6 in list2)
				{
					Object.Destroy((Object)(object)item6);
				}
				throw;
			}
			if (!Application.isBatchMode && Object.op_Implicit((Object)(object)InventoryGui.instance))
			{
				Recipe crafting = default(Recipe);
				ref Recipe reference = ref crafting;
				object? value = GameApi.CraftRecipe.GetValue(InventoryGui.instance);
				reference = (Recipe)((value is Recipe) ? value : null);
				if (IsOwned(crafting) || (Object.op_Implicit((Object)(object)crafting) && list.Any((Plan p) => p.ChangeRecipe && p.Originals.Contains(crafting))))
				{
					GameApi.CancelCrafting.Invoke(InventoryGui.instance, null);
				}
			}
			foreach (List<Recipe> recipeList in recipeLists)
			{
				recipeList.RemoveAll(ownedRecipes.Contains);
			}
			foreach (Recipe ownedRecipe in ownedRecipes)
			{
				Object.Destroy((Object)(object)ownedRecipe);
			}
			FoodStats.PreserveNative(Player.m_localPlayer, staged.Values);
			ownedRecipes.Clear();
			RestoreNative();
			tokens.Clear();
			foreach (RegisteredFood value2 in foods.Values)
			{
				value2.Active = false;
				value2.Deleted = false;
				value2.ChangedRecipe = false;
				value2.Recipes = (value2.Created ? Array.Empty<Recipe>() : value2.OriginalRecipes);
			}
			foreach (Plan item7 in list)
			{
				FoodDefinition definition2 = item7.Definition;
				RegisteredFood registeredFood = staged[definition2.Item];
				SharedData shared = registeredFood.Drop.m_itemData.m_shared;
				ApplyValues(shared, item7.Values);
				if (registeredFood.Created || !item7.Delete)
				{
					shared.m_icons = registeredFood.Icons;
					if (!Object.op_Implicit((Object)(object)registeredFood.Icons[0]))
					{
						registeredFood.Icons[0] = (registeredFood.Created ? FallbackIcon() : registeredFood.NativeIcon);
					}
				}
				registeredFood.Title = item7.Title;
				registeredFood.Description = item7.Description;
				registeredFood.ModifierSet = (item7.Delete ? ModifierSet.Empty : definition2.ModifierSet);
				registeredFood.EffectInfo = definition2.EffectInfo;
				registeredFood.Presentation = definition2.Presentation;
				registeredFood.Recipes = dictionary[definition2.Item];
				registeredFood.Active = !item7.Delete;
				registeredFood.Deleted = item7.Delete;
				registeredFood.ChangedRecipe = item7.ChangeRecipe;
				foods[definition2.Item] = registeredFood;
				if (item7.ChangeRecipe)
				{
					foreach (List<Recipe> recipeList2 in recipeLists)
					{
						recipeList2.RemoveAll(((IEnumerable<Recipe>)registeredFood.OriginalRecipes).Contains<Recipe>);
					}
				}
				if (item7.Created || item7.ChangeRecipe)
				{
					Recipe[] recipes = registeredFood.Recipes;
					foreach (Recipe item in recipes)
					{
						ownedRecipes.Add(item);
					}
				}
			}
			foreach (RegisteredFood value3 in foods.Values)
			{
				tokens[value3.Drop.m_itemData.m_shared.m_name] = value3;
			}
			RegisterDatabase(Database);
			RegisterScene(Scene, SceneAwaking);
			Localize(Localization.instance);
			RefreshIcons();
			foreach (RegisteredFood value4 in foods.Values)
			{
				FrameworkApi.RegisterFood("dannanboi.foodknowledge", value4.Prefab, value4.ModifierSet, value4.Presentation);
			}
			LiveItemsChanged();
			return true;
		}

		private Sprite FallbackIcon()
		{
			GameObject val = (Object.op_Implicit((Object)(object)Database) ? Database.GetItemPrefab("RawMeat") : null);
			Sprite[] array = Find("RawMeat")?.Baseline?.m_icons ?? ((!Object.op_Implicit((Object)(object)val)) ? null : val.GetComponent<ItemDrop>()?.m_itemData?.m_shared?.m_icons);
			return (array != null && array.Length != 0) ? array[0] : null;
		}

		internal bool RefreshIcons()
		{
			Sprite val = FallbackIcon();
			if (!Object.op_Implicit((Object)(object)val))
			{
				return false;
			}
			if (Icons != null)
			{
				return Icons.Refresh(foods.Values.Where((RegisteredFood f) => f.Created || f.Active), val);
			}
			bool result = false;
			foreach (RegisteredFood value in foods.Values)
			{
				if (value.Created || value.Active)
				{
					Sprite val2 = (value.Created ? val : value.NativeIcon);
					if (value.Icons[0] != val2)
					{
						value.Icons[0] = val2;
						result = true;
					}
				}
			}
			return result;
		}

		private void ObserveDatabase(ObjectDB db)
		{
			if (Object.op_Implicit((Object)(object)db))
			{
				if (db.m_items != null)
				{
					itemLists.Add(db.m_items);
				}
				if (db.m_recipes != null)
				{
					recipeLists.Add(db.m_recipes);
				}
				if (GameApi.ItemHashes.GetValue(db) is Dictionary<int, GameObject> item)
				{
					itemHashes.Add(item);
				}
				if (GameApi.ItemDataIndex?.GetValue(db) is Dictionary<SharedData, GameObject> item2)
				{
					itemData.Add(item2);
				}
			}
		}

		internal void PrepareDatabaseRebuild(ObjectDB db)
		{
			if (Object.op_Implicit((Object)(object)db))
			{
				ObserveDatabase(db);
				db.m_items?.RemoveAll((GameObject prefab) => !Object.op_Implicit((Object)(object)prefab));
				db.m_recipes?.RemoveAll((Recipe recipe) => !Object.op_Implicit((Object)(object)recipe));
			}
		}

		internal void PrepareDatabaseCopy(ObjectDB destination, ObjectDB source)
		{
			ObserveDatabase(destination);
			PrepareDatabaseRebuild(source);
		}

		internal void RegisterDatabase(ObjectDB db)
		{
			if (!Object.op_Implicit((Object)(object)db) || db.m_items == null)
			{
				return;
			}
			Database = db;
			ObserveDatabase(db);
			Dictionary<int, GameObject> dictionary = (Dictionary<int, GameObject>)GameApi.ItemHashes.GetValue(db);
			Dictionary<SharedData, GameObject> dictionary2 = GameApi.ItemDataIndex?.GetValue(db) as Dictionary<SharedData, GameObject>;
			foreach (RegisteredFood value2 in foods.Values)
			{
				int key = Catalog.StableHash(value2.Item);
				if (dictionary.TryGetValue(key, out var value) && (Object)(object)value != (Object)(object)value2.Prefab)
				{
					throw new FormatException("Prefab registration collision: " + value2.Item);
				}
				if (!db.m_items.Contains(value2.Prefab))
				{
					db.m_items.Add(value2.Prefab);
				}
				dictionary[key] = value2.Prefab;
				if (dictionary2 != null)
				{
					dictionary2[value2.Drop.m_itemData.m_shared] = value2.Prefab;
				}
				if (value2.ChangedRecipe)
				{
					db.m_recipes.RemoveAll(((IEnumerable<Recipe>)value2.OriginalRecipes).Contains<Recipe>);
				}
				Recipe[] recipes = value2.Recipes;
				foreach (Recipe val in recipes)
				{
					if (Object.op_Implicit((Object)(object)val) && !db.m_recipes.Contains(val))
					{
						db.m_recipes.Add(val);
					}
				}
			}
		}

		internal void RegisterScene(ZNetScene scene, bool beforeAwake)
		{
			if (!Object.op_Implicit((Object)(object)scene) || scene.m_prefabs == null)
			{
				return;
			}
			Scene = scene;
			Dictionary<int, GameObject> dictionary = (Dictionary<int, GameObject>)GameApi.SceneHashes.GetValue(scene);
			sceneLists.Add(scene.m_prefabs);
			if (dictionary != null)
			{
				sceneHashes.Add(dictionary);
			}
			foreach (RegisteredFood item in foods.Values.Where((RegisteredFood f) => f.Created))
			{
				int key = Catalog.StableHash(item.Item);
				if (!beforeAwake && dictionary.TryGetValue(key, out var value) && (Object)(object)value != (Object)(object)item.Prefab)
				{
					throw new FormatException("Network prefab registration collision: " + item.Item);
				}
				if (!scene.m_prefabs.Contains(item.Prefab))
				{
					scene.m_prefabs.Add(item.Prefab);
				}
				if (!beforeAwake)
				{
					dictionary[key] = item.Prefab;
				}
			}
		}

		internal void Localize(Localization locale)
		{
			if (locale == null)
			{
				return;
			}
			RestoreWords(locale);
			foreach (RegisteredFood item in foods.Values.Where((RegisteredFood f) => f.Created || f.Active))
			{
				if (item.Title != null)
				{
					AddWord(locale, item.Drop.m_itemData.m_shared.m_name, item.Title, !item.Created);
				}
				if (item.Description != null)
				{
					AddWord(locale, item.Drop.m_itemData.m_shared.m_description, item.Description, !item.Created);
				}
			}
			object obj = GameApi.LocaleCache?.GetValue(locale);
			if (obj != null)
			{
				GameApi.EvictLocale?.Invoke(obj, null);
			}
		}

		internal void ClearSession()
		{
			FrameworkApi.UnregisterFoods("dannanboi.foodknowledge");
			RestoreNative();
			nativeWords.Clear();
			LiveItemsChanged();
			HashSet<GameObject> prefabs = new HashSet<GameObject>(from food in foods.Values
				where food.Created
				select food.Prefab, References<GameObject>.Instance);
			foreach (List<GameObject> itemList in itemLists)
			{
				itemList.RemoveAll(prefabs.Contains);
			}
			foreach (List<Recipe> recipeList in recipeLists)
			{
				recipeList.RemoveAll(ownedRecipes.Contains);
			}
			foreach (Dictionary<int, GameObject> itemHash in itemHashes)
			{
				int[] array = (from pair in itemHash
					where prefabs.Contains(pair.Value)
					select pair.Key).ToArray();
				foreach (int key in array)
				{
					itemHash.Remove(key);
				}
			}
			foreach (Dictionary<SharedData, GameObject> itemDatum in itemData)
			{
				SharedData[] array2 = (from pair in itemDatum
					where prefabs.Contains(pair.Value)
					select pair.Key).ToArray();
				foreach (SharedData key2 in array2)
				{
					itemDatum.Remove(key2);
				}
			}
			foreach (List<GameObject> sceneList in sceneLists)
			{
				sceneList.RemoveAll(prefabs.Contains);
			}
			foreach (Dictionary<int, GameObject> sceneHash in sceneHashes)
			{
				int[] array3 = (from pair in sceneHash
					where prefabs.Contains(pair.Value)
					select pair.Key).ToArray();
				foreach (int key3 in array3)
				{
					sceneHash.Remove(key3);
				}
			}
			foreach (RegisteredFood value in foods.Values)
			{
				if (value.Created)
				{
					Object.Destroy((Object)(object)value.Prefab);
				}
			}
			foreach (Recipe ownedRecipe in ownedRecipes)
			{
				Object.Destroy((Object)(object)ownedRecipe);
			}
			ownedRecipes.Clear();
			foods.Clear();
			tokens.Clear();
			Scene = null;
			SceneAwaking = false;
			itemLists.Clear();
			recipeLists.Clear();
			itemHashes.Clear();
			itemData.Clear();
			sceneLists.Clear();
			sceneHashes.Clear();
			ObserveDatabase(Database);
		}

		internal void RefreshItem(ItemData item)
		{
			SharedData val = Find(item)?.Drop?.m_itemData?.m_shared;
			if (val != null && val != item.m_shared)
			{
				ApplyValues(item.m_shared, val);
				item.m_shared.m_icons = val.m_icons;
			}
		}

		internal void RefreshInventory(Inventory inventory, bool force = false, bool notify = true)
		{
			if (Application.isBatchMode || inventory == null || foods.Count == 0)
			{
				return;
			}
			InventoryRevision orCreateValue = refreshedInventories.GetOrCreateValue(inventory);
			if (!force && orCreateValue.Value == itemRevision)
			{
				return;
			}
			orCreateValue.Value = itemRevision;
			bool flag = false;
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (Find(allItem) != null)
				{
					flag = true;
					RefreshItem(allItem);
				}
			}
			if (!(notify && flag))
			{
				return;
			}
			try
			{
				GameApi.InventoryChanged.Invoke(inventory, new object[2] { false, false });
			}
			catch (Exception ex)
			{
				if (!inventoryWarning)