Decompiled source of BreathOfTheCauldron v0.1.0

plugins\BreathOfTheCauldron\BreathOfTheCauldron.dll

Decompiled 20 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BreathOfTheCauldron.Cooking;
using BreathOfTheCauldron.Cooking.Definitions;
using BreathOfTheCauldron.Cooking.Registration;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.Managers;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("BreathOfTheCauldron")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("BreathOfTheCauldron")]
[assembly: AssemblyTitle("BreathOfTheCauldron")]
[assembly: AssemblyVersion("0.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BreathOfTheCauldron
{
	internal static class ModLocalization
	{
		private const string TranslationsFolderName = "Translations";

		private static readonly string[] SupportedLanguages = new string[2] { "English", "Russian" };

		private static bool _registered;

		internal static void Register()
		{
			if (_registered)
			{
				return;
			}
			try
			{
				CustomLocalization localization = LocalizationManager.Instance.GetLocalization();
				string[] supportedLanguages = SupportedLanguages;
				foreach (string language in supportedLanguages)
				{
					LoadLanguage(localization, language);
				}
				_registered = true;
				Plugin.Log.LogInfo((object)"BOTC localization registered.");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Failed to register BOTC localization:\n" + ex));
			}
		}

		private static void LoadLanguage(CustomLocalization localization, string language)
		{
			string text = Path.Combine(GetModDirectory(), "Translations", language + ".json");
			if (!File.Exists(text))
			{
				Plugin.Log.LogWarning((object)("BOTC localization file was not found: '" + text + "'."));
				return;
			}
			string text2 = File.ReadAllText(text);
			localization.AddJsonFile(language, text2);
			Plugin.Log.LogInfo((object)("Loaded BOTC localization: " + language + "."));
		}

		private static string GetModDirectory()
		{
			string? directoryName = Path.GetDirectoryName(typeof(Plugin).Assembly.Location);
			if (string.IsNullOrWhiteSpace(directoryName))
			{
				throw new InvalidOperationException("Could not determine the BOTC plugin directory.");
			}
			return directoryName;
		}
	}
	[BepInPlugin("sunray.valheim.breathofthecauldron", "Breath of the Cauldron", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "sunray.valheim.breathofthecauldron";

		public const string PluginName = "Breath of the Cauldron";

		public const string PluginVersion = "0.1.0";

		private Harmony? _harmony;

		internal static ManualLogSource Log { get; private set; }

		internal static ConfigEntry<bool> AllowMultipleVariantsOfSameFood { get; private set; }

		private void Awake()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			RegisterConfig();
			_harmony = new Harmony("sunray.valheim.breathofthecauldron");
			_harmony.PatchAll();
			PrefabManager.OnVanillaPrefabsAvailable += OnVanillaPrefabsAvailable;
			Log.LogInfo((object)"Breath of the Cauldron 0.1.0 loaded.");
		}

		private void OnDestroy()
		{
			PrefabManager.OnVanillaPrefabsAvailable -= OnVanillaPrefabsAvailable;
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void RegisterConfig()
		{
			AllowMultipleVariantsOfSameFood = ((BaseUnityPlugin)this).Config.Bind<bool>("Food", "Allow Multiple Variants Of Same Food", false, "If enabled, the original, seasoned, and perfect versions of the same base food can be eaten at the same time. Requires a game restart.");
		}

		private static void OnVanillaPrefabsAvailable()
		{
			ModLocalization.Register();
			FoodManager.Register();
			PrefabManager.OnVanillaPrefabsAvailable -= OnVanillaPrefabsAvailable;
		}
	}
}
namespace BreathOfTheCauldron.Cooking
{
	internal static class CauldronFoodUpgradeContext
	{
		private static readonly Dictionary<string, string> BaseFoodNameByResultName = new Dictionary<string, string>(StringComparer.Ordinal);

		internal static bool IsBuildingUpgradeList { get; private set; }

		internal static void Begin()
		{
			BaseFoodNameByResultName.Clear();
			IsBuildingUpgradeList = true;
		}

		internal static void AddMapping(string resultSharedName, string baseFoodSharedName)
		{
			if (!string.IsNullOrWhiteSpace(resultSharedName) && !string.IsNullOrWhiteSpace(baseFoodSharedName))
			{
				BaseFoodNameByResultName[resultSharedName] = baseFoodSharedName;
			}
		}

		internal static bool TryGetBaseFoodSharedName(string requestedSharedName, out string baseFoodSharedName)
		{
			if (!IsBuildingUpgradeList)
			{
				baseFoodSharedName = string.Empty;
				return false;
			}
			return BaseFoodNameByResultName.TryGetValue(requestedSharedName, out baseFoodSharedName);
		}

		internal static void End()
		{
			IsBuildingUpgradeList = false;
			BaseFoodNameByResultName.Clear();
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList", new Type[] { typeof(List<Recipe>) })]
	internal static class InventoryGuiUpdateRecipeListPatch
	{
		private sealed class PatchState
		{
			internal readonly List<Recipe> AddedRecipes = new List<Recipe>();

			internal bool ContextStarted;
		}

		private const string VanillaCauldronPrefabName = "piece_cauldron";

		private static void Prefix(InventoryGui __instance, List<Recipe> recipes, out PatchState __state)
		{
			__state = new PatchState();
			if (recipes == null || __instance.InCraftTab() || !IsCauldronRecipeList(recipes))
			{
				return;
			}
			CauldronFoodUpgradeContext.Begin();
			__state.ContextStarted = true;
			foreach (FoodUpgradeDefinition item2 in FoodUpgradeRegistry.GetAll())
			{
				Recipe runtimeRecipe = item2.RuntimeRecipe;
				if ((Object)(object)runtimeRecipe == (Object)null)
				{
					continue;
				}
				GameObject prefab = PrefabManager.Instance.GetPrefab(item2.BaseFoodPrefabName);
				ItemDrop val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<ItemDrop>() : null);
				ItemDrop item = runtimeRecipe.m_item;
				if ((Object)(object)val == (Object)null)
				{
					Plugin.Log.LogWarning((object)("BOTC could not inject upgrade '" + item2.Id + "': base food prefab '" + item2.BaseFoodPrefabName + "' was not found."));
					continue;
				}
				if ((Object)(object)item == (Object)null)
				{
					Plugin.Log.LogWarning((object)("BOTC could not inject upgrade '" + item2.Id + "': runtime recipe has no result item."));
					continue;
				}
				string name = val.m_itemData.m_shared.m_name;
				CauldronFoodUpgradeContext.AddMapping(item.m_itemData.m_shared.m_name, name);
				if (!recipes.Contains(runtimeRecipe))
				{
					recipes.Add(runtimeRecipe);
					__state.AddedRecipes.Add(runtimeRecipe);
					Plugin.Log.LogDebug((object)("Injected BOTC upgrade recipe '" + item2.Id + "' into the cauldron Upgrade list."));
				}
			}
		}

		private static void Postfix(List<Recipe> recipes, PatchState __state)
		{
			Cleanup(recipes, __state);
		}

		private static Exception? Finalizer(Exception? __exception, List<Recipe> recipes, PatchState __state)
		{
			Cleanup(recipes, __state);
			return __exception;
		}

		private static void Cleanup(List<Recipe> recipes, PatchState? state)
		{
			if (state == null)
			{
				return;
			}
			if (recipes != null)
			{
				foreach (Recipe addedRecipe in state.AddedRecipes)
				{
					recipes.Remove(addedRecipe);
				}
				state.AddedRecipes.Clear();
			}
			if (state.ContextStarted)
			{
				CauldronFoodUpgradeContext.End();
				state.ContextStarted = false;
			}
		}

		private static bool IsCauldronRecipeList(List<Recipe> recipes)
		{
			foreach (Recipe recipe in recipes)
			{
				CraftingStation val = recipe?.m_craftingStation;
				if (!((Object)(object)val == (Object)null))
				{
					string text = ((Object)((Component)val).gameObject).name;
					if (text.EndsWith("(Clone)", StringComparison.Ordinal))
					{
						text = text.Substring(0, text.Length - "(Clone)".Length).TrimEnd();
					}
					if (string.Equals(text, "piece_cauldron", StringComparison.Ordinal))
					{
						return true;
					}
				}
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Inventory), "GetAllItems", new Type[]
	{
		typeof(string),
		typeof(List<ItemData>)
	})]
	internal static class InventoryGetAllItemsPatch
	{
		private static void Prefix(ref string __0, out bool __state)
		{
			__state = false;
			if (!string.IsNullOrWhiteSpace(__0) && CauldronFoodUpgradeContext.TryGetBaseFoodSharedName(__0, out string baseFoodSharedName))
			{
				Plugin.Log.LogDebug((object)("BOTC redirected inventory lookup from '" + __0 + "' to base food '" + baseFoodSharedName + "'."));
				__0 = baseFoodSharedName;
				__state = true;
			}
		}

		private static void Postfix(List<ItemData> __1, bool __state)
		{
			if (__state && __1 != null && __1.Count > 1)
			{
				int count = __1.Count;
				__1.RemoveRange(1, __1.Count - 1);
				Plugin.Log.LogDebug((object)("BOTC reduced base food candidates " + $"from {count} to 1."));
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "DoCrafting", new Type[] { typeof(Player) })]
	internal static class InventoryGuiDoCraftingPatch
	{
		private static readonly FieldInfo? CraftRecipeField = AccessTools.Field(typeof(InventoryGui), "m_craftRecipe");

		private static readonly FieldInfo? CraftUpgradeItemField = AccessTools.Field(typeof(InventoryGui), "m_craftUpgradeItem");

		private static readonly FieldInfo? CraftItemDoneEffectsField = AccessTools.Field(typeof(CraftingStation), "m_craftItemDoneEffects");

		private static readonly MethodInfo? GetCurrentCraftingStationMethod = AccessTools.Method(typeof(Player), "GetCurrentCraftingStation", Type.EmptyTypes, (Type[])null);

		private static bool Prefix(InventoryGui __instance, Player player)
		{
			//IL_021a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0225: Unknown result type (might be due to invalid IL or missing references)
			//IL_022a: Unknown result type (might be due to invalid IL or missing references)
			//IL_022f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0234: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null)
			{
				return true;
			}
			if (CraftRecipeField == null)
			{
				Plugin.Log.LogError((object)"BOTC could not find InventoryGui.m_craftRecipe.");
				return true;
			}
			if (CraftUpgradeItemField == null)
			{
				Plugin.Log.LogError((object)"BOTC could not find InventoryGui.m_craftUpgradeItem.");
				return true;
			}
			object? value = CraftRecipeField.GetValue(__instance);
			Recipe val = (Recipe)((value is Recipe) ? value : null);
			if ((Object)(object)val == (Object)null)
			{
				return true;
			}
			FoodUpgradeDefinition foodUpgradeDefinition = FoodUpgradeRegistry.FindByRecipe(val);
			if (foodUpgradeDefinition == null)
			{
				return true;
			}
			object? value2 = CraftUpgradeItemField.GetValue(__instance);
			ItemData val2 = (ItemData)((value2 is ItemData) ? value2 : null);
			if (val2 == null)
			{
				AbortCraft(__instance, player, foodUpgradeDefinition, "selected base food is missing");
				return false;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (!inventory.ContainsItem(val2))
			{
				AbortCraft(__instance, player, foodUpgradeDefinition, "selected base food is no longer in inventory");
				return false;
			}
			int num = val2.m_quality + 1;
			int num2 = default(int);
			ItemData val3 = default(ItemData);
			val.GetAmount(num, ref num2, ref val3, 1);
			if (val3 == null || num2 <= 0)
			{
				AbortCraft(__instance, player, foodUpgradeDefinition, "no suitable seasoning was selected");
				return false;
			}
			if (!inventory.ContainsItem(val3))
			{
				AbortCraft(__instance, player, foodUpgradeDefinition, "selected seasoning is no longer in inventory");
				return false;
			}
			string prefabName = GetPrefabName(val2);
			string prefabName2 = GetPrefabName(val3);
			bool flag = foodUpgradeDefinition.HasPerfect && string.Equals(prefabName2, foodUpgradeDefinition.PerfectIngredientPrefabName, StringComparison.Ordinal);
			string text;
			if (flag)
			{
				if (string.IsNullOrWhiteSpace(foodUpgradeDefinition.PerfectResultPrefabName))
				{
					AbortCraft(__instance, player, foodUpgradeDefinition, "perfect result prefab is missing");
					return false;
				}
				text = foodUpgradeDefinition.PerfectResultPrefabName;
			}
			else
			{
				text = foodUpgradeDefinition.NormalResultPrefabName;
			}
			GameObject prefab = PrefabManager.Instance.GetPrefab(text);
			ItemDrop val4 = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<ItemDrop>() : null);
			if ((Object)(object)prefab == (Object)null || (Object)(object)val4 == (Object)null)
			{
				Plugin.Log.LogError((object)("BOTC result prefab '" + text + "' was not found or has no ItemDrop component."));
				ResetCraftFields(__instance);
				return false;
			}
			ItemData val5 = val4.m_itemData.Clone();
			val5.m_stack = 1;
			val5.m_quality = 1;
			val5.m_variant = 0;
			val5.m_dropPrefab = prefab;
			inventory.RemoveItem(val2, 1);
			inventory.RemoveItem(val3.m_shared.m_name, num2, val3.m_quality, true);
			if (!inventory.AddItem(val5))
			{
				ItemDrop.DropItem(val5, 1, ((Component)player).transform.position + ((Component)player).transform.forward + Vector3.up, Quaternion.identity);
				Plugin.Log.LogWarning((object)("BOTC crafted '" + text + "', but could not place it in inventory. The result was dropped near the player."));
			}
			Plugin.Log.LogInfo((object)("BOTC crafted upgrade '" + foodUpgradeDefinition.Id + "': base='" + prefabName + "', ingredient='" + prefabName2 + "', " + $"amount={num2}, " + $"perfect={flag}, " + "result='" + text + "'."));
			PlayCraftCompletionEffects(player, val);
			string text2 = ((Localization.instance != null) ? Localization.instance.Localize(val5.m_shared.m_name) : val5.m_shared.m_name);
			((Character)player).Message((MessageType)2, text2, 0, (Sprite)null);
			ResetCraftFields(__instance);
			return false;
		}

		private static void AbortCraft(InventoryGui inventoryGui, Player player, FoodUpgradeDefinition definition, string reason)
		{
			Plugin.Log.LogWarning((object)("BOTC upgrade '" + definition.Id + "' aborted: " + reason + "."));
			((Character)player).Message((MessageType)2, "$msg_missingrequirement", 0, (Sprite)null);
			ResetCraftFields(inventoryGui);
		}

		private static void ResetCraftFields(InventoryGui inventoryGui)
		{
			CraftRecipeField?.SetValue(inventoryGui, null);
			CraftUpgradeItemField?.SetValue(inventoryGui, null);
		}

		private static string GetPrefabName(ItemData item)
		{
			if ((Object)(object)item.m_dropPrefab != (Object)null)
			{
				return ((Object)item.m_dropPrefab).name;
			}
			return item.m_shared.m_name;
		}

		private static void PlayCraftCompletionEffects(Player player, Recipe recipe)
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			CraftingStation val = TryGetCurrentCraftingStation(player);
			if (val == null)
			{
				val = recipe.m_craftingStation;
			}
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)"BOTC could not play craft completion SFX: crafting station was not found.");
				return;
			}
			if (CraftItemDoneEffectsField == null)
			{
				Plugin.Log.LogWarning((object)"BOTC could not play craft completion SFX: CraftingStation.m_craftItemDoneEffects was not found.");
				return;
			}
			object value = CraftItemDoneEffectsField.GetValue(val);
			if (value == null)
			{
				Plugin.Log.LogWarning((object)"BOTC could not play craft completion SFX: the station has no craft completion effects.");
				return;
			}
			Scene scene = ((Component)val).gameObject.scene;
			bool num = ((Scene)(ref scene)).IsValid();
			Vector3 position = (num ? ((Component)val).transform.position : ((Component)player).transform.position);
			Quaternion rotation = (num ? ((Component)val).transform.rotation : ((Component)player).transform.rotation);
			Transform parent = (num ? ((Component)val).transform : ((Component)player).transform);
			if (!TryCreateEffects(value, position, rotation, parent))
			{
				Plugin.Log.LogWarning((object)"BOTC could not invoke the cauldron craft completion effect.");
			}
			else
			{
				Plugin.Log.LogDebug((object)"BOTC played cauldron craft completion effects.");
			}
		}

		private static CraftingStation? TryGetCurrentCraftingStation(Player player)
		{
			if (GetCurrentCraftingStationMethod == null)
			{
				Plugin.Log.LogDebug((object)"BOTC could not find Player.GetCurrentCraftingStation().");
				return null;
			}
			try
			{
				object? obj = GetCurrentCraftingStationMethod.Invoke(player, null);
				return (CraftingStation?)((obj is CraftingStation) ? obj : null);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogDebug((object)("BOTC could not get current crafting station: " + ex.Message));
				return null;
			}
		}

		private static bool TryCreateEffects(object effectList, Vector3 position, Quaternion rotation, Transform parent)
		{
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			MethodInfo[] methods = effectList.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (!string.Equals(methodInfo.Name, "Create", StringComparison.Ordinal))
				{
					continue;
				}
				ParameterInfo[] parameters = methodInfo.GetParameters();
				object[] array = new object[parameters.Length];
				bool flag = true;
				for (int j = 0; j < parameters.Length; j++)
				{
					ParameterInfo parameterInfo = parameters[j];
					Type parameterType = parameterInfo.ParameterType;
					if (parameterType == typeof(Vector3))
					{
						array[j] = position;
						continue;
					}
					if (parameterType == typeof(Quaternion))
					{
						array[j] = rotation;
						continue;
					}
					if (parameterType == typeof(Transform))
					{
						array[j] = parent;
						continue;
					}
					if (parameterType == typeof(float))
					{
						array[j] = 1f;
						continue;
					}
					if (parameterType == typeof(int))
					{
						array[j] = -1;
						continue;
					}
					if (parameterType == typeof(bool))
					{
						array[j] = false;
						continue;
					}
					if (parameterInfo.HasDefaultValue)
					{
						array[j] = parameterInfo.DefaultValue;
						continue;
					}
					flag = false;
					break;
				}
				if (flag)
				{
					try
					{
						methodInfo.Invoke(effectList, array);
						return true;
					}
					catch (Exception ex)
					{
						Plugin.Log.LogDebug((object)("BOTC could not invoke effect method " + $"'{methodInfo}': {ex.Message}"));
					}
				}
			}
			return false;
		}
	}
	internal static class CauldronFoodUpgradeUi
	{
		private const string CauldronPrefabName = "piece_cauldron";

		private const string SeasonToken = "$botc_season";

		private const string SeasoningToken = "$botc_seasoning";

		private static readonly FieldInfo? TabUpgradeField = AccessTools.Field(typeof(InventoryGui), "m_tabUpgrade");

		private static readonly FieldInfo? ItemCraftTypeField = AccessTools.Field(typeof(InventoryGui), "m_itemCraftType");

		private static readonly FieldInfo? CraftButtonField = AccessTools.Field(typeof(InventoryGui), "m_craftButton");

		private static readonly FieldInfo? CraftProgressPanelField = AccessTools.Field(typeof(InventoryGui), "m_craftProgressPanel");

		private static string? _originalCraftButtonText;

		private static string? _originalProgressText;

		internal static bool IsCauldronUpgradeUi;

		internal static void UpdateStation(InventoryGui inventoryGui, List<Recipe> recipes)
		{
			IsCauldronUpgradeUi = ContainsCauldronRecipe(recipes);
			SetUpgradeTabText(inventoryGui, IsCauldronUpgradeUi);
			if (!IsCauldronUpgradeUi)
			{
				RestoreActionTexts(inventoryGui);
			}
		}

		internal static void UpdateRecipeDetails(InventoryGui inventoryGui)
		{
			if (IsCauldronUpgradeUi)
			{
				if (inventoryGui.InCraftTab())
				{
					RestoreActionTexts(inventoryGui);
					return;
				}
				ClearUpgradeStatusText(inventoryGui);
				SetSeasonButtonText(inventoryGui);
				SetSeasoningProgressText(inventoryGui);
			}
		}

		private static void SetUpgradeTabText(InventoryGui inventoryGui, bool seasonMode)
		{
			object? obj = TabUpgradeField?.GetValue(inventoryGui);
			Button val = (Button)((obj is Button) ? obj : null);
			if (!((Object)(object)val == (Object)null))
			{
				string value = (seasonMode ? GetLocalizedText("$botc_season", "Season").ToUpperInvariant() : GetVanillaUpgradeText());
				SetChildText(((Component)val).transform, "Text", value);
				SetChildText(((Component)val).transform, "Selected/Text (1)", value);
			}
		}

		private static void ClearUpgradeStatusText(InventoryGui inventoryGui)
		{
			object? obj = ItemCraftTypeField?.GetValue(inventoryGui);
			TMP_Text val = (TMP_Text)((obj is TMP_Text) ? obj : null);
			if (!((Object)(object)val == (Object)null))
			{
				SetTextIfChanged(val, string.Empty);
			}
		}

		private static void SetSeasonButtonText(InventoryGui inventoryGui)
		{
			TMP_Text craftButtonText = GetCraftButtonText(inventoryGui);
			if (!((Object)(object)craftButtonText == (Object)null))
			{
				if (_originalCraftButtonText == null)
				{
					_originalCraftButtonText = craftButtonText.text;
				}
				string localizedText = GetLocalizedText("$botc_season", "Season");
				SetTextIfChanged(craftButtonText, localizedText);
			}
		}

		private static void SetSeasoningProgressText(InventoryGui inventoryGui)
		{
			TMP_Text craftProgressText = GetCraftProgressText(inventoryGui);
			if (!((Object)(object)craftProgressText == (Object)null))
			{
				if (_originalProgressText == null)
				{
					_originalProgressText = craftProgressText.text;
				}
				string localizedText = GetLocalizedText("$botc_seasoning", "Seasoning");
				SetTextIfChanged(craftProgressText, localizedText);
			}
		}

		private static void RestoreActionTexts(InventoryGui inventoryGui)
		{
			TMP_Text craftButtonText = GetCraftButtonText(inventoryGui);
			if ((Object)(object)craftButtonText != (Object)null && !string.IsNullOrWhiteSpace(_originalCraftButtonText))
			{
				SetTextIfChanged(craftButtonText, _originalCraftButtonText);
			}
			TMP_Text craftProgressText = GetCraftProgressText(inventoryGui);
			if ((Object)(object)craftProgressText != (Object)null && !string.IsNullOrWhiteSpace(_originalProgressText))
			{
				SetTextIfChanged(craftProgressText, _originalProgressText);
			}
		}

		private static TMP_Text? GetCraftButtonText(InventoryGui inventoryGui)
		{
			object? obj = CraftButtonField?.GetValue(inventoryGui);
			Button val = (Button)((obj is Button) ? obj : null);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Transform val2 = ((Component)val).transform.Find("Text");
			if ((Object)(object)val2 == (Object)null)
			{
				return null;
			}
			return ((Component)val2).GetComponent<TMP_Text>();
		}

		private static TMP_Text? GetCraftProgressText(InventoryGui inventoryGui)
		{
			object? obj = CraftProgressPanelField?.GetValue(inventoryGui);
			Transform val = (Transform)((obj is Transform) ? obj : null);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Transform val2 = val.Find("Text");
			if ((Object)(object)val2 == (Object)null)
			{
				return null;
			}
			return ((Component)val2).GetComponent<TMP_Text>();
		}

		private static void SetChildText(Transform root, string childPath, string value)
		{
			Transform val = root.Find(childPath);
			if (!((Object)(object)val == (Object)null))
			{
				SetTextIfChanged(((Component)val).GetComponent<TMP_Text>(), value);
			}
		}

		private static void SetTextIfChanged(TMP_Text? text, string value)
		{
			if (!((Object)(object)text == (Object)null) && !string.Equals(text.text, value, StringComparison.Ordinal))
			{
				text.text = value;
			}
		}

		private static string GetLocalizedText(string token, string fallback)
		{
			if (Localization.instance == null)
			{
				return fallback;
			}
			string text = Localization.instance.Localize(token);
			if (string.IsNullOrWhiteSpace(text) || string.Equals(text, token, StringComparison.Ordinal))
			{
				return fallback;
			}
			return text;
		}

		private static string GetVanillaUpgradeText()
		{
			if (Localization.instance == null)
			{
				return "UPGRADE";
			}
			string text = Localization.instance.Localize("$inventory_upgrade");
			if (string.IsNullOrWhiteSpace(text) || string.Equals(text, "$inventory_upgrade", StringComparison.Ordinal))
			{
				return "UPGRADE";
			}
			return text.ToUpperInvariant();
		}

		private static bool ContainsCauldronRecipe(List<Recipe> recipes)
		{
			if (recipes == null)
			{
				return false;
			}
			foreach (Recipe recipe in recipes)
			{
				if (!((Object)(object)recipe == (Object)null) && !((Object)(object)recipe.m_craftingStation == (Object)null) && string.Equals(NormalizeName(((Object)((Component)recipe.m_craftingStation).gameObject).name), "piece_cauldron", StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static string NormalizeName(string objectName)
		{
			if (objectName.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				return objectName.Substring(0, objectName.Length - "(Clone)".Length).TrimEnd();
			}
			return objectName;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList", new Type[] { typeof(List<Recipe>) })]
	[HarmonyPriority(0)]
	internal static class CauldronUpgradeTabTextPatch
	{
		private static void Postfix(InventoryGui __instance, List<Recipe> recipes)
		{
			CauldronFoodUpgradeUi.UpdateStation(__instance, recipes);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipe", new Type[]
	{
		typeof(Player),
		typeof(float)
	})]
	[HarmonyPriority(0)]
	internal static class CauldronUpgradeDetailsPatch
	{
		private static void Postfix(InventoryGui __instance)
		{
			CauldronFoodUpgradeUi.UpdateRecipeDetails(__instance);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList", new Type[] { typeof(List<Recipe>) })]
	[HarmonyPriority(0)]
	internal static class CauldronSeasonRecipeIconPatch
	{
		private const string CauldronPrefabName = "piece_cauldron";

		private static readonly FieldInfo? RecipeListRootField = AccessTools.Field(typeof(InventoryGui), "m_recipeListRoot");

		private static void Postfix(InventoryGui __instance, List<Recipe> recipes)
		{
			if (!((Object)(object)__instance == (Object)null) && recipes != null && !__instance.InCraftTab() && ContainsCauldronRecipe(recipes))
			{
				object? obj = RecipeListRootField?.GetValue(__instance);
				RectTransform val = (RectTransform)((obj is RectTransform) ? obj : null);
				if (!((Object)(object)val == (Object)null))
				{
					RestoreRecipeIconColors(val);
				}
			}
		}

		private static void RestoreRecipeIconColors(RectTransform recipeListRoot)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < ((Transform)recipeListRoot).childCount; i++)
			{
				Transform child = ((Transform)recipeListRoot).GetChild(i);
				if ((Object)(object)child == (Object)null || !((Component)child).gameObject.activeSelf)
				{
					continue;
				}
				Transform val = child.Find("icon");
				if (!((Object)(object)val == (Object)null))
				{
					Image component = ((Component)val).GetComponent<Image>();
					if (!((Object)(object)component == (Object)null) && ((Graphic)component).color != Color.white)
					{
						((Graphic)component).color = Color.white;
					}
				}
			}
		}

		private static bool ContainsCauldronRecipe(List<Recipe> recipes)
		{
			foreach (Recipe recipe in recipes)
			{
				if (!((Object)(object)recipe == (Object)null) && !((Object)(object)recipe.m_craftingStation == (Object)null) && string.Equals(NormalizeName(((Object)((Component)recipe.m_craftingStation).gameObject).name), "piece_cauldron", StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static string NormalizeName(string objectName)
		{
			if (objectName.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				return objectName.Substring(0, objectName.Length - "(Clone)".Length).TrimEnd();
			}
			return objectName;
		}
	}
	[HarmonyPatch(typeof(Player), "CanEat", new Type[]
	{
		typeof(ItemData),
		typeof(bool)
	})]
	internal static class FoodFamilyPatch
	{
		private static bool Prefix(Player __instance, ItemData item, bool showMessages, ref bool __result)
		{
			if (Plugin.AllowMultipleVariantsOfSameFood.Value)
			{
				return true;
			}
			if (item == null || (Object)(object)item.m_dropPrefab == (Object)null)
			{
				return true;
			}
			string name = ((Object)item.m_dropPrefab).name;
			if (!FoodFamilyRegistry.TryGetFamilyId(name, out string familyId))
			{
				return true;
			}
			foreach (Food food in __instance.GetFoods())
			{
				if (food != null && !string.IsNullOrWhiteSpace(food.m_name) && !(food.m_name == name) && FoodFamilyRegistry.TryGetFamilyId(food.m_name, out string familyId2) && !(familyId2 != familyId))
				{
					if (showMessages)
					{
						((Character)__instance).Message((MessageType)2, Localization.instance.Localize("$msg_nomore", new string[1] { item.m_shared.m_name }), 0, (Sprite)null);
					}
					Plugin.Log.LogDebug((object)("Blocked eating '" + name + "': active food '" + food.m_name + "' belongs to the same family '" + familyId + "'."));
					__result = false;
					return false;
				}
			}
			return true;
		}
	}
	internal static class FoodFamilyRegistry
	{
		private static readonly Dictionary<string, string> FamilyByPrefabName = new Dictionary<string, string>(StringComparer.Ordinal);

		internal static void RegisterFamily(string familyId, params string[] prefabNames)
		{
			if (string.IsNullOrWhiteSpace(familyId))
			{
				throw new ArgumentException("Food family ID cannot be empty.", "familyId");
			}
			if (prefabNames == null || prefabNames.Length == 0)
			{
				throw new ArgumentException("Food family '" + familyId + "' has no items.", "prefabNames");
			}
			foreach (string text in prefabNames)
			{
				if (string.IsNullOrWhiteSpace(text))
				{
					continue;
				}
				if (FamilyByPrefabName.TryGetValue(text, out string value))
				{
					if (!string.Equals(value, familyId, StringComparison.Ordinal))
					{
						throw new InvalidOperationException("Food prefab '" + text + "' is already registered in family '" + value + "'.");
					}
				}
				else
				{
					FamilyByPrefabName.Add(text, familyId);
				}
			}
			Plugin.Log.LogInfo((object)("Registered food family '" + familyId + "': " + string.Join(", ", prefabNames)));
		}

		internal static bool TryGetFamilyId(string prefabName, out string familyId)
		{
			if (string.IsNullOrWhiteSpace(prefabName))
			{
				familyId = string.Empty;
				return false;
			}
			return FamilyByPrefabName.TryGetValue(prefabName, out familyId);
		}

		internal static bool AreInSameFamily(string firstPrefabName, string secondPrefabName)
		{
			if (!TryGetFamilyId(firstPrefabName, out string familyId))
			{
				return false;
			}
			if (!TryGetFamilyId(secondPrefabName, out string familyId2))
			{
				return false;
			}
			return string.Equals(familyId, familyId2, StringComparison.Ordinal);
		}
	}
	internal static class FoodManager
	{
		private static bool _registered;

		internal static void Register()
		{
			if (_registered)
			{
				return;
			}
			try
			{
				IngredientRegistry.Load();
				IReadOnlyList<FoodDefinition> readOnlyList = FoodDefinitionLoader.Load();
				foreach (FoodDefinition item in readOnlyList)
				{
					FoodContentRegistrar.Register(item);
				}
				_registered = true;
				Plugin.Log.LogInfo((object)$"Registered {readOnlyList.Count} BOTC base foods.");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Failed to register BOTC food content:\n" + ex));
			}
		}
	}
	internal sealed class FoodUpgradeDefinition
	{
		internal string Id { get; }

		internal string BaseFoodPrefabName { get; }

		internal IngredientClass IngredientClass { get; }

		internal string? PerfectIngredientPrefabName { get; }

		internal string NormalResultPrefabName { get; }

		internal string? PerfectResultPrefabName { get; }

		internal Recipe RuntimeRecipe { get; }

		internal bool HasPerfect
		{
			get
			{
				if (!string.IsNullOrWhiteSpace(PerfectIngredientPrefabName))
				{
					return !string.IsNullOrWhiteSpace(PerfectResultPrefabName);
				}
				return false;
			}
		}

		internal FoodUpgradeDefinition(string id, string baseFoodPrefabName, IngredientClass ingredientClass, string? perfectIngredientPrefabName, string normalResultPrefabName, string? perfectResultPrefabName, Recipe runtimeRecipe)
		{
			if ((Object)(object)runtimeRecipe == (Object)null)
			{
				throw new ArgumentNullException("runtimeRecipe");
			}
			bool flag = !string.IsNullOrWhiteSpace(perfectIngredientPrefabName);
			bool flag2 = !string.IsNullOrWhiteSpace(perfectResultPrefabName);
			if (flag != flag2)
			{
				throw new ArgumentException("Perfect ingredient and perfect result must either both be defined or both be null.");
			}
			Id = id;
			BaseFoodPrefabName = baseFoodPrefabName;
			IngredientClass = ingredientClass;
			PerfectIngredientPrefabName = (flag ? perfectIngredientPrefabName : null);
			NormalResultPrefabName = normalResultPrefabName;
			PerfectResultPrefabName = (flag2 ? perfectResultPrefabName : null);
			RuntimeRecipe = runtimeRecipe;
		}
	}
	internal static class FoodUpgradeRegistry
	{
		private static readonly List<FoodUpgradeDefinition> Definitions = new List<FoodUpgradeDefinition>();

		internal static IReadOnlyList<FoodUpgradeDefinition> GetAll()
		{
			return Definitions;
		}

		internal static void Register(FoodUpgradeDefinition definition)
		{
			foreach (FoodUpgradeDefinition definition2 in Definitions)
			{
				if (string.Equals(definition2.Id, definition.Id, StringComparison.Ordinal))
				{
					throw new InvalidOperationException("Food upgrade '" + definition.Id + "' is already registered.");
				}
			}
			Definitions.Add(definition);
			Plugin.Log.LogInfo((object)("Registered food upgrade '" + definition.Id + "'."));
		}

		internal static FoodUpgradeDefinition? FindByRecipe(Recipe recipe)
		{
			foreach (FoodUpgradeDefinition definition in Definitions)
			{
				if (definition.RuntimeRecipe == recipe)
				{
					return definition;
				}
			}
			return null;
		}
	}
	internal enum IngredientClass
	{
		Sweet,
		Spicy,
		Meaty,
		Thick
	}
	internal static class IngredientRegistry
	{
		private const string CookingDirectoryName = "Cooking";

		private const string IngredientsFileName = "Ingredients.json";

		private static readonly Dictionary<IngredientClass, IReadOnlyList<string>> IngredientsByClass = new Dictionary<IngredientClass, IReadOnlyList<string>>();

		private static bool _loaded;

		internal static void Load()
		{
			if (!_loaded)
			{
				string ingredientsFilePath = GetIngredientsFilePath();
				if (!File.Exists(ingredientsFilePath))
				{
					throw new FileNotFoundException("BOTC ingredient definition file was not found.", ingredientsFilePath);
				}
				string text = File.ReadAllText(ingredientsFilePath);
				if (string.IsNullOrWhiteSpace(text))
				{
					throw new InvalidOperationException("BOTC ingredient definition file is empty: '" + ingredientsFilePath + "'.");
				}
				Plugin.Log.LogInfo((object)("Loading BOTC ingredient definitions from '" + ingredientsFilePath + "'."));
				ValidateAndRegister(Deserialize(text, ingredientsFilePath).Classes ?? new List<IngredientClassDefinitionDto>(), ingredientsFilePath);
				_loaded = true;
				Plugin.Log.LogInfo((object)($"Loaded {IngredientsByClass.Count} " + "BOTC ingredient classes."));
			}
		}

		internal static IReadOnlyList<string> GetIngredients(IngredientClass ingredientClass)
		{
			EnsureLoaded();
			if (!IngredientsByClass.TryGetValue(ingredientClass, out IReadOnlyList<string> value))
			{
				throw new InvalidOperationException($"Ingredient class '{ingredientClass}' " + "is not registered.");
			}
			return value;
		}

		internal static bool Contains(IngredientClass ingredientClass, string prefabName)
		{
			if (string.IsNullOrWhiteSpace(prefabName))
			{
				return false;
			}
			foreach (string ingredient in GetIngredients(ingredientClass))
			{
				if (string.Equals(ingredient, prefabName, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static void EnsureLoaded()
		{
			if (!_loaded)
			{
				Load();
			}
		}

		private static IngredientDefinitionFileDto Deserialize(string json, string filePath)
		{
			DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(IngredientDefinitionFileDto));
			byte[] bytes = Encoding.UTF8.GetBytes(json);
			try
			{
				using MemoryStream stream = new MemoryStream(bytes);
				return (dataContractJsonSerializer.ReadObject(stream) as IngredientDefinitionFileDto) ?? throw new InvalidOperationException("The JSON root object could not be converted to IngredientDefinitionFileDto.");
			}
			catch (SerializationException innerException)
			{
				throw new InvalidOperationException("BOTC Ingredients.json could not be deserialized: '" + filePath + "'.", innerException);
			}
			catch (Exception innerException2)
			{
				throw new InvalidOperationException("Failed to read BOTC Ingredients.json: '" + filePath + "'.", innerException2);
			}
		}

		private static void ValidateAndRegister(IReadOnlyList<IngredientClassDefinitionDto> classDtos, string filePath)
		{
			if (classDtos.Count == 0)
			{
				throw new InvalidOperationException("BOTC ingredient definition file contains no classes: '" + filePath + "'.");
			}
			HashSet<IngredientClass> hashSet = new HashSet<IngredientClass>();
			HashSet<string> globallyRegisteredIngredients = new HashSet<string>(StringComparer.Ordinal);
			for (int i = 0; i < classDtos.Count; i++)
			{
				RegisterClass(classDtos[i] ?? throw new InvalidOperationException("Ingredient class entry at index " + $"{i} is null."), i, hashSet, globallyRegisteredIngredients);
			}
			foreach (IngredientClass value in Enum.GetValues(typeof(IngredientClass)))
			{
				if (!hashSet.Contains(value))
				{
					throw new InvalidOperationException($"Ingredient class '{value}' " + "is missing from Ingredients.json.");
				}
			}
		}

		private static void RegisterClass(IngredientClassDefinitionDto classDto, int classIndex, HashSet<IngredientClass> registeredClasses, HashSet<string> globallyRegisteredIngredients)
		{
			string text = classDto.IngredientClassName?.Trim() ?? string.Empty;
			if (!Enum.TryParse<IngredientClass>(text, ignoreCase: true, out var result))
			{
				throw new InvalidOperationException($"Ingredient class at index {classIndex} " + "has invalid name '" + text + "'.");
			}
			if (!registeredClasses.Add(result))
			{
				throw new InvalidOperationException($"Ingredient class '{result}' " + "is registered more than once.");
			}
			List<string> list = classDto.Ingredients ?? new List<string>();
			if (list.Count == 0)
			{
				throw new InvalidOperationException($"Ingredient class '{result}' " + "contains no ingredients.");
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			List<string> list2 = new List<string>();
			for (int i = 0; i < list.Count; i++)
			{
				string text2 = list[i]?.Trim() ?? string.Empty;
				if (string.IsNullOrWhiteSpace(text2))
				{
					throw new InvalidOperationException($"Ingredient class '{result}' " + "contains an empty prefab name at index " + $"{i}.");
				}
				if (!hashSet.Add(text2))
				{
					throw new InvalidOperationException("Ingredient '" + text2 + "' appears more " + $"than once in class '{result}'.");
				}
				if (!globallyRegisteredIngredients.Add(text2))
				{
					throw new InvalidOperationException("Ingredient '" + text2 + "' is registered in more than one ingredient class.");
				}
				list2.Add(text2);
			}
			IngredientsByClass.Add(result, list2);
			Plugin.Log.LogInfo((object)("Registered ingredient class " + $"'{result}': " + string.Join(", ", list2)));
		}

		private static string GetIngredientsFilePath()
		{
			string? directoryName = Path.GetDirectoryName(typeof(Plugin).Assembly.Location);
			if (string.IsNullOrWhiteSpace(directoryName))
			{
				throw new InvalidOperationException("Could not determine BOTC plugin directory.");
			}
			return Path.Combine(directoryName, "Cooking", "Ingredients.json");
		}
	}
	[DataContract]
	internal sealed class IngredientDefinitionFileDto
	{
		[DataMember(Name = "classes", IsRequired = true)]
		public List<IngredientClassDefinitionDto> Classes { get; set; } = new List<IngredientClassDefinitionDto>();
	}
	[DataContract]
	internal sealed class IngredientClassDefinitionDto
	{
		[DataMember(Name = "ingredientClass", IsRequired = true)]
		public string IngredientClassName { get; set; } = string.Empty;

		[DataMember(Name = "ingredients", IsRequired = true)]
		public List<string> Ingredients { get; set; } = new List<string>();
	}
}
namespace BreathOfTheCauldron.Cooking.Registration
{
	internal static class FoodContentRegistrar
	{
		internal static void Register(FoodDefinition food)
		{
			List<string> list = new List<string> { food.BasePrefabName };
			foreach (FoodSeasoningDefinition seasoning in food.Seasonings)
			{
				FoodPrefabFactory.RegisterVariants(food, seasoning);
				string item = FoodNaming.BuildResultPrefabName(food, seasoning, isPerfect: false);
				list.Add(item);
				if (seasoning.HasPerfect)
				{
					string item2 = FoodNaming.BuildResultPrefabName(food, seasoning, isPerfect: true);
					list.Add(item2);
				}
			}
			FoodFamilyRegistry.RegisterFamily(food.Id, list.ToArray());
			foreach (FoodSeasoningDefinition seasoning2 in food.Seasonings)
			{
				FoodRecipeFactory.Register(food, seasoning2);
			}
			Plugin.Log.LogInfo((object)("Registered BOTC food definition '" + food.Id + "'."));
		}
	}
	internal static class FoodNaming
	{
		internal static string BuildResultPrefabName(FoodDefinition food, FoodSeasoningDefinition seasoning, bool isPerfect)
		{
			string classPascalName = GetClassPascalName(seasoning.IngredientClass);
			string text = (isPerfect ? "Plus" : string.Empty);
			return "BOTC_" + classPascalName + food.BasePrefabName + text;
		}

		internal static string BuildUpgradeId(FoodDefinition food, FoodSeasoningDefinition seasoning)
		{
			return food.Id + "_" + GetClassSnakeName(seasoning.IngredientClass);
		}

		internal static string BuildNameToken(FoodDefinition food, FoodSeasoningDefinition seasoning, bool isPerfect)
		{
			string text = (isPerfect ? "_plus" : string.Empty);
			return "$botc_" + GetClassSnakeName(seasoning.IngredientClass) + "_" + food.Id + text;
		}

		internal static string BuildDescriptionToken(FoodDefinition food, FoodSeasoningDefinition seasoning, bool isPerfect)
		{
			return BuildNameToken(food, seasoning, isPerfect) + "_description";
		}

		private static string GetClassPascalName(IngredientClass ingredientClass)
		{
			return ingredientClass switch
			{
				IngredientClass.Sweet => "Sweet", 
				IngredientClass.Spicy => "Spicy", 
				IngredientClass.Meaty => "Meaty", 
				IngredientClass.Thick => "Thick", 
				_ => throw new ArgumentOutOfRangeException("ingredientClass", ingredientClass, null), 
			};
		}

		private static string GetClassSnakeName(IngredientClass ingredientClass)
		{
			return ingredientClass switch
			{
				IngredientClass.Sweet => "sweet", 
				IngredientClass.Spicy => "spicy", 
				IngredientClass.Meaty => "meaty", 
				IngredientClass.Thick => "thick", 
				_ => throw new ArgumentOutOfRangeException("ingredientClass", ingredientClass, null), 
			};
		}
	}
	internal static class FoodPrefabFactory
	{
		internal static void RegisterVariants(FoodDefinition food, FoodSeasoningDefinition seasoning)
		{
			RegisterVariant(food, seasoning, isPerfect: false);
			if (seasoning.HasPerfect)
			{
				RegisterVariant(food, seasoning, isPerfect: true);
			}
		}

		private static void RegisterVariant(FoodDefinition food, FoodSeasoningDefinition seasoning, bool isPerfect)
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			if (isPerfect && !seasoning.HasPerfect)
			{
				throw new InvalidOperationException("Cannot register perfect variant for food '" + food.Id + "', seasoning " + $"'{seasoning.IngredientClass}': " + "no perfect pairing is configured.");
			}
			string text = FoodNaming.BuildResultPrefabName(food, seasoning, isPerfect);
			string name = FoodNaming.BuildNameToken(food, seasoning, isPerfect);
			string description = FoodNaming.BuildDescriptionToken(food, seasoning, isPerfect);
			CustomItem val = new CustomItem(text, food.BasePrefabName);
			SharedData shared = val.ItemDrop.m_itemData.m_shared;
			shared.m_name = name;
			shared.m_description = description;
			ApplySeasoningIcon(shared, seasoning, isPerfect, text);
			float food2 = shared.m_food;
			float foodStamina = shared.m_foodStamina;
			float foodRegen = shared.m_foodRegen;
			float foodBurnTime = shared.m_foodBurnTime;
			ApplySeasoningBonus(shared, seasoning, isPerfect);
			shared.m_maxQuality = 2;
			ItemManager.Instance.AddItem(val);
			Plugin.Log.LogInfo((object)("Registered BOTC food prefab '" + text + "': " + $"health {food2:0.##} -> " + $"{shared.m_food:0.##}, " + $"stamina {foodStamina:0.##} -> " + $"{shared.m_foodStamina:0.##}, " + $"regen {foodRegen:0.##} -> " + $"{shared.m_foodRegen:0.##}, " + $"duration {foodBurnTime:0.##} -> " + $"{shared.m_foodBurnTime:0.##}."));
		}

		private static void ApplySeasoningIcon(SharedData sharedData, FoodSeasoningDefinition seasoning, bool isPerfect, string prefabName)
		{
			if (sharedData.m_icons == null || sharedData.m_icons.Length == 0 || (Object)(object)sharedData.m_icons[0] == (Object)null)
			{
				Plugin.Log.LogWarning((object)("BOTC could not compose icon for '" + prefabName + "': base food has no icon."));
				return;
			}
			try
			{
				Sprite val = SeasoningIconComposer.Compose(sharedData.m_icons[0], seasoning.IngredientClass, isPerfect);
				sharedData.m_icons = (Sprite[])(object)new Sprite[1] { val };
				Plugin.Log.LogInfo((object)($"Applied '{seasoning.IngredientClass}' " + "seasoning badge to '" + prefabName + "'."));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("BOTC could not compose icon for '" + prefabName + "':\n" + ex));
			}
		}

		private static void ApplySeasoningBonus(SharedData sharedData, FoodSeasoningDefinition seasoning, bool isPerfect)
		{
			float runtimeBonus = seasoning.GetRuntimeBonus(isPerfect);
			switch (seasoning.IngredientClass)
			{
			case IngredientClass.Sweet:
				sharedData.m_food += runtimeBonus;
				break;
			case IngredientClass.Spicy:
				sharedData.m_foodStamina += runtimeBonus;
				break;
			case IngredientClass.Meaty:
				sharedData.m_foodRegen += runtimeBonus;
				break;
			case IngredientClass.Thick:
				sharedData.m_foodBurnTime += runtimeBonus;
				break;
			default:
				throw new ArgumentOutOfRangeException("IngredientClass", seasoning.IngredientClass, "Unsupported seasoning class.");
			}
		}
	}
	internal static class FoodRecipeFactory
	{
		private const string CauldronPrefabName = "piece_cauldron";

		internal static void Register(FoodDefinition food, FoodSeasoningDefinition seasoning)
		{
			string text = FoodNaming.BuildUpgradeId(food, seasoning);
			string text2 = FoodNaming.BuildResultPrefabName(food, seasoning, isPerfect: false);
			string text3 = (seasoning.HasPerfect ? FoodNaming.BuildResultPrefabName(food, seasoning, isPerfect: true) : null);
			GameObject prefab = PrefabManager.Instance.GetPrefab(text2);
			if ((Object)(object)prefab == (Object)null)
			{
				throw new InvalidOperationException("BOTC result prefab '" + text2 + "' was not found.");
			}
			ItemDrop component = prefab.GetComponent<ItemDrop>();
			if ((Object)(object)component == (Object)null)
			{
				throw new InvalidOperationException("BOTC result prefab '" + text2 + "' does not contain ItemDrop.");
			}
			if (seasoning.HasPerfect)
			{
				GameObject prefab2 = PrefabManager.Instance.GetPrefab(text3);
				if ((Object)(object)prefab2 == (Object)null)
				{
					throw new InvalidOperationException("BOTC perfect result prefab '" + text3 + "' was not found.");
				}
				if ((Object)(object)prefab2.GetComponent<ItemDrop>() == (Object)null)
				{
					throw new InvalidOperationException("BOTC perfect result prefab '" + text3 + "' does not contain ItemDrop.");
				}
			}
			CraftingStation cauldron = GetCauldron();
			Recipe val = ScriptableObject.CreateInstance<Recipe>();
			((Object)val).name = "Recipe_BOTC_" + text;
			val.m_item = component;
			val.m_amount = 1;
			val.m_enabled = true;
			val.m_craftingStation = cauldron;
			val.m_minStationLevel = food.MinimumStationLevel;
			val.m_requireOnlyOneIngredient = true;
			val.m_resources = CreateRequirements(seasoning.IngredientClass);
			FoodUpgradeRegistry.Register(new FoodUpgradeDefinition(text, food.BasePrefabName, seasoning.IngredientClass, seasoning.PerfectIngredientPrefabName, text2, text3, val));
			Plugin.Log.LogInfo((object)("Registered BOTC seasoning recipe '" + text + "'" + (seasoning.HasPerfect ? (" with perfect pairing '" + seasoning.PerfectIngredientPrefabName + "'.") : " without perfect pairing.")));
		}

		private static CraftingStation GetCauldron()
		{
			GameObject prefab = PrefabManager.Instance.GetPrefab("piece_cauldron");
			if ((Object)(object)prefab == (Object)null)
			{
				throw new InvalidOperationException("Prefab 'piece_cauldron' was not found.");
			}
			CraftingStation component = prefab.GetComponent<CraftingStation>();
			if ((Object)(object)component == (Object)null)
			{
				throw new InvalidOperationException("Prefab 'piece_cauldron' does not contain CraftingStation.");
			}
			return component;
		}

		private static Requirement[] CreateRequirements(IngredientClass ingredientClass)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Expected O, but got Unknown
			IReadOnlyList<string> ingredients = IngredientRegistry.GetIngredients(ingredientClass);
			List<Requirement> list = new List<Requirement>();
			foreach (string item in ingredients)
			{
				GameObject prefab = PrefabManager.Instance.GetPrefab(item);
				ItemDrop val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<ItemDrop>() : null);
				if ((Object)(object)val == (Object)null)
				{
					Plugin.Log.LogWarning((object)("BOTC ingredient prefab '" + item + "' was not found for class " + $"'{ingredientClass}'."));
					continue;
				}
				list.Add(new Requirement
				{
					m_resItem = val,
					m_amount = 1,
					m_amountPerLevel = 1,
					m_recover = false
				});
			}
			if (list.Count == 0)
			{
				throw new InvalidOperationException("No valid ingredients were found " + $"for class '{ingredientClass}'.");
			}
			return list.ToArray();
		}
	}
	internal static class SeasoningIconComposer
	{
		private const string AssetsDirectoryName = "Assets";

		private const float BadgeSizeRatio = 0.36f;

		private const float BadgeMarginRatio = 0.025f;

		private static readonly Dictionary<IngredientClass, Texture2D> BadgeTextures = new Dictionary<IngredientClass, Texture2D>();

		private static readonly List<Texture2D> GeneratedTextures = new List<Texture2D>();

		private static readonly List<Sprite> GeneratedSprites = new List<Sprite>();

		internal static Sprite Compose(Sprite sourceIcon, IngredientClass ingredientClass, bool isPerfect)
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)sourceIcon == (Object)null)
			{
				throw new ArgumentNullException("sourceIcon");
			}
			Texture2D badgeTexture = GetBadgeTexture(ingredientClass);
			Texture2D val = CopySpriteToReadableTexture(sourceIcon);
			Texture2D val2 = OverlayBadge(val, badgeTexture, isPerfect);
			((Object)val2).name = $"BOTC_{ingredientClass}_" + ((Object)sourceIcon).name + (isPerfect ? "_Plus" : string.Empty);
			Sprite val3 = Sprite.Create(val2, new Rect(0f, 0f, (float)((Texture)val2).width, (float)((Texture)val2).height), new Vector2(0.5f, 0.5f), sourceIcon.pixelsPerUnit, 0u, (SpriteMeshType)0);
			((Object)val3).name = ((Object)val2).name;
			GeneratedTextures.Add(val);
			GeneratedTextures.Add(val2);
			GeneratedSprites.Add(val3);
			return val3;
		}

		private static Texture2D GetBadgeTexture(IngredientClass ingredientClass)
		{
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			if (BadgeTextures.TryGetValue(ingredientClass, out Texture2D value))
			{
				return value;
			}
			string badgeFilePath = GetBadgeFilePath(ingredientClass switch
			{
				IngredientClass.Sweet => "sweet.png", 
				IngredientClass.Spicy => "spicy.png", 
				IngredientClass.Meaty => "meaty.png", 
				IngredientClass.Thick => "thick.png", 
				_ => throw new ArgumentOutOfRangeException("ingredientClass", ingredientClass, "Unsupported ingredient class."), 
			});
			if (!File.Exists(badgeFilePath))
			{
				throw new FileNotFoundException("BOTC seasoning badge was not found: '" + badgeFilePath + "'.", badgeFilePath);
			}
			byte[] array = File.ReadAllBytes(badgeFilePath);
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
			((Object)val).name = $"BOTC_Badge_{ingredientClass}";
			((Texture)val).filterMode = (FilterMode)1;
			((Texture)val).wrapMode = (TextureWrapMode)1;
			if (!ImageConversion.LoadImage(val, array, false))
			{
				Object.Destroy((Object)(object)val);
				throw new InvalidOperationException("BOTC could not load seasoning badge '" + badgeFilePath + "'.");
			}
			BadgeTextures.Add(ingredientClass, val);
			Plugin.Log.LogInfo((object)("Loaded BOTC seasoning badge " + $"'{ingredientClass}' from '{badgeFilePath}'."));
			return val;
		}

		private static Texture2D CopySpriteToReadableTexture(Sprite sprite)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Expected O, but got Unknown
			Texture texture = (Texture)(object)sprite.texture;
			Rect textureRect = sprite.textureRect;
			int num = Mathf.Max(1, Mathf.RoundToInt(((Rect)(ref textureRect)).width));
			int num2 = Mathf.Max(1, Mathf.RoundToInt(((Rect)(ref textureRect)).height));
			RenderTexture temporary = RenderTexture.GetTemporary(texture.width, texture.height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)0);
			RenderTexture active = RenderTexture.active;
			try
			{
				Graphics.Blit(texture, temporary);
				RenderTexture.active = temporary;
				Texture2D val = new Texture2D(num, num2, (TextureFormat)4, false)
				{
					name = "BOTC_Readable_" + ((Object)sprite).name,
					filterMode = (FilterMode)1,
					wrapMode = (TextureWrapMode)1
				};
				val.ReadPixels(new Rect(((Rect)(ref textureRect)).x, ((Rect)(ref textureRect)).y, (float)num, (float)num2), 0, 0, false);
				val.Apply(false, false);
				return val;
			}
			finally
			{
				RenderTexture.active = active;
				RenderTexture.ReleaseTemporary(temporary);
			}
		}

		private static Texture2D OverlayBadge(Texture2D sourceTexture, Texture2D badgeTexture, bool isPerfect)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			int width = ((Texture)sourceTexture).width;
			int height = ((Texture)sourceTexture).height;
			Texture2D val = new Texture2D(width, height, (TextureFormat)4, false);
			((Texture)val).filterMode = ((Texture)sourceTexture).filterMode;
			((Texture)val).wrapMode = (TextureWrapMode)1;
			Color[] pixels = sourceTexture.GetPixels();
			val.SetPixels(pixels);
			int size = Mathf.Max(1, Mathf.RoundToInt((float)Mathf.Min(width, height) * 0.36f));
			int num = Mathf.Max(1, Mathf.RoundToInt((float)Mathf.Min(width, height) * 0.025f));
			if (isPerfect)
			{
				DrawPerfectGlow(val, num, num, size);
			}
			DrawBadge(val, badgeTexture, num, num, size);
			val.Apply(false, false);
			return val;
		}

		private static void DrawBadge(Texture2D destination, Texture2D badge, int startX, int startY, int size)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < size; i++)
			{
				for (int j = 0; j < size; j++)
				{
					float num = ((size <= 1) ? 0.5f : ((float)j / (float)(size - 1)));
					float num2 = ((size <= 1) ? 0.5f : ((float)i / (float)(size - 1)));
					Color pixelBilinear = badge.GetPixelBilinear(num, num2);
					if (!(pixelBilinear.a <= 0.001f))
					{
						int num3 = startX + j;
						int num4 = startY + i;
						if (num3 >= 0 && num3 < ((Texture)destination).width && num4 >= 0 && num4 < ((Texture)destination).height)
						{
							Color val = AlphaBlend(destination.GetPixel(num3, num4), pixelBilinear);
							destination.SetPixel(num3, num4, val);
						}
					}
				}
			}
		}

		private static void DrawPerfectGlow(Texture2D destination, int startX, int startY, int size)
		{
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			int num = Mathf.Max(1, Mathf.RoundToInt((float)size * 0.09f));
			int num2 = startX + size / 2;
			int num3 = startY + size / 2;
			float num4 = (float)size * 0.58f;
			Color val = default(Color);
			((Color)(ref val))..ctor(1f, 0.85f, 0.25f, 0.28f);
			int num5 = num2 - Mathf.RoundToInt(num4) - num;
			int num6 = num2 + Mathf.RoundToInt(num4) + num;
			int num7 = num3 - Mathf.RoundToInt(num4) - num;
			int num8 = num3 + Mathf.RoundToInt(num4) + num;
			for (int i = num7; i <= num8; i++)
			{
				for (int j = num5; j <= num6; j++)
				{
					if (j >= 0 && j < ((Texture)destination).width && i >= 0 && i < ((Texture)destination).height)
					{
						float num9 = Vector2.Distance(new Vector2((float)j, (float)i), new Vector2((float)num2, (float)num3));
						float num10 = 1f - Mathf.InverseLerp(num4 - (float)num, num4 + (float)num, num9);
						if (!(num10 <= 0f))
						{
							Color pixel = destination.GetPixel(j, i);
							Color foreground = val;
							foreground.a *= num10;
							destination.SetPixel(j, i, AlphaBlend(pixel, foreground));
						}
					}
				}
			}
		}

		private static Color AlphaBlend(Color background, Color foreground)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			float num = foreground.a + background.a * (1f - foreground.a);
			if (num <= 0.0001f)
			{
				return Color.clear;
			}
			float num2 = (foreground.r * foreground.a + background.r * background.a * (1f - foreground.a)) / num;
			float num3 = (foreground.g * foreground.a + background.g * background.a * (1f - foreground.a)) / num;
			float num4 = (foreground.b * foreground.a + background.b * background.a * (1f - foreground.a)) / num;
			return new Color(num2, num3, num4, num);
		}

		private static string GetBadgeFilePath(string fileName)
		{
			string? directoryName = Path.GetDirectoryName(typeof(Plugin).Assembly.Location);
			if (string.IsNullOrWhiteSpace(directoryName))
			{
				throw new InvalidOperationException("Could not determine BOTC plugin directory.");
			}
			return Path.Combine(directoryName, "Assets", fileName);
		}
	}
}
namespace BreathOfTheCauldron.Cooking.Definitions
{
	internal sealed class FoodDefinition
	{
		internal string Id { get; }

		internal string BasePrefabName { get; }

		internal int MinimumStationLevel { get; }

		internal IReadOnlyList<FoodSeasoningDefinition> Seasonings { get; }

		internal FoodDefinition(string id, string basePrefabName, int minimumStationLevel, IReadOnlyList<FoodSeasoningDefinition> seasonings)
		{
			Id = id;
			BasePrefabName = basePrefabName;
			MinimumStationLevel = minimumStationLevel;
			Seasonings = seasonings;
		}
	}
	internal sealed class FoodSeasoningDefinition
	{
		internal IngredientClass IngredientClass { get; }

		internal string? PerfectIngredientPrefabName { get; }

		internal float NormalBonus { get; }

		internal float? PerfectBonus { get; }

		internal bool HasPerfect
		{
			get
			{
				if (!string.IsNullOrWhiteSpace(PerfectIngredientPrefabName))
				{
					return PerfectBonus.HasValue;
				}
				return false;
			}
		}

		internal FoodSeasoningDefinition(IngredientClass ingredientClass, string? perfectIngredientPrefabName, float normalBonus, float? perfectBonus)
		{
			IngredientClass = ingredientClass;
			PerfectIngredientPrefabName = perfectIngredientPrefabName;
			NormalBonus = normalBonus;
			PerfectBonus = perfectBonus;
		}

		internal float GetRuntimeBonus(bool isPerfect)
		{
			float num;
			if (isPerfect)
			{
				if (!HasPerfect)
				{
					throw new InvalidOperationException($"Seasoning class '{IngredientClass}' " + "does not have a perfect variant.");
				}
				num = PerfectBonus.Value;
			}
			else
			{
				num = NormalBonus;
			}
			if (IngredientClass == IngredientClass.Thick)
			{
				return num * 60f;
			}
			return num;
		}
	}
	[DataContract]
	internal sealed class FoodDefinitionFileDto
	{
		[DataMember(Name = "foods", IsRequired = true)]
		public List<FoodDefinitionDto> Foods { get; set; } = new List<FoodDefinitionDto>();
	}
	[DataContract]
	internal sealed class FoodDefinitionDto
	{
		[DataMember(Name = "id", IsRequired = true)]
		public string Id { get; set; } = string.Empty;

		[DataMember(Name = "basePrefabName", IsRequired = true)]
		public string BasePrefabName { get; set; } = string.Empty;

		[DataMember(Name = "minimumStationLevel", IsRequired = true)]
		public int MinimumStationLevel { get; set; } = 1;

		[DataMember(Name = "seasonings", IsRequired = true)]
		public List<FoodSeasoningDefinitionDto> Seasonings { get; set; } = new List<FoodSeasoningDefinitionDto>();
	}
	[DataContract]
	internal sealed class FoodSeasoningDefinitionDto
	{
		[DataMember(Name = "ingredientClass", IsRequired = true)]
		public string IngredientClassName { get; set; } = string.Empty;

		[DataMember(Name = "perfectIngredientPrefabName", IsRequired = false, EmitDefaultValue = false)]
		public string? PerfectIngredientPrefabName { get; set; }

		[DataMember(Name = "normalBonus", IsRequired = true)]
		public float NormalBonus { get; set; }

		[DataMember(Name = "perfectBonus", IsRequired = false, EmitDefaultValue = false)]
		public float? PerfectBonus { get; set; }
	}
	internal static class FoodDefinitionLoader
	{
		private const string CookingDirectoryName = "Cooking";

		private const string FoodsFileName = "Foods.json";

		internal static IReadOnlyList<FoodDefinition> Load()
		{
			string foodsFilePath = GetFoodsFilePath();
			if (!File.Exists(foodsFilePath))
			{
				throw new FileNotFoundException("BOTC food definition file was not found.", foodsFilePath);
			}
			string text = File.ReadAllText(foodsFilePath);
			if (string.IsNullOrWhiteSpace(text))
			{
				throw new InvalidOperationException("BOTC food definition file is empty: '" + foodsFilePath + "'.");
			}
			Plugin.Log.LogInfo((object)("Loading BOTC food definitions from '" + foodsFilePath + "'."));
			List<FoodDefinitionDto> list = Deserialize(text, foodsFilePath).Foods ?? new List<FoodDefinitionDto>();
			Plugin.Log.LogInfo((object)("BOTC deserialized food DTOs: " + $"{list.Count}."));
			IReadOnlyList<FoodDefinition> readOnlyList = ConvertAndValidate(list, foodsFilePath);
			Plugin.Log.LogInfo((object)$"Loaded {readOnlyList.Count} BOTC food definitions.");
			return readOnlyList;
		}

		private static FoodDefinitionFileDto Deserialize(string json, string filePath)
		{
			DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(FoodDefinitionFileDto));
			byte[] bytes = Encoding.UTF8.GetBytes(json);
			try
			{
				using MemoryStream stream = new MemoryStream(bytes);
				return (dataContractJsonSerializer.ReadObject(stream) as FoodDefinitionFileDto) ?? throw new InvalidOperationException("The JSON root object could not be converted to FoodDefinitionFileDto.");
			}
			catch (SerializationException innerException)
			{
				throw new InvalidOperationException("BOTC Foods.json could not be deserialized: '" + filePath + "'.", innerException);
			}
			catch (Exception innerException2)
			{
				throw new InvalidOperationException("Failed to read BOTC Foods.json: '" + filePath + "'.", innerException2);
			}
		}

		private static string GetFoodsFilePath()
		{
			string? directoryName = Path.GetDirectoryName(typeof(Plugin).Assembly.Location);
			if (string.IsNullOrWhiteSpace(directoryName))
			{
				throw new InvalidOperationException("Could not determine BOTC plugin directory.");
			}
			return Path.Combine(directoryName, "Cooking", "Foods.json");
		}

		private static IReadOnlyList<FoodDefinition> ConvertAndValidate(IReadOnlyList<FoodDefinitionDto> foodDtos, string filePath)
		{
			if (foodDtos.Count == 0)
			{
				throw new InvalidOperationException("BOTC food definition file contains no foods: '" + filePath + "'.");
			}
			HashSet<string> registeredIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			HashSet<string> registeredBasePrefabs = new HashSet<string>(StringComparer.Ordinal);
			List<FoodDefinition> list = new List<FoodDefinition>();
			for (int i = 0; i < foodDtos.Count; i++)
			{
				FoodDefinition item = ConvertFood(foodDtos[i] ?? throw new InvalidOperationException($"Food entry at index {i} is null."), i, registeredIds, registeredBasePrefabs);
				list.Add(item);
			}
			return list;
		}

		private static FoodDefinition ConvertFood(FoodDefinitionDto foodDto, int foodIndex, HashSet<string> registeredIds, HashSet<string> registeredBasePrefabs)
		{
			string text = foodDto.Id?.Trim() ?? string.Empty;
			string text2 = foodDto.BasePrefabName?.Trim() ?? string.Empty;
			if (string.IsNullOrWhiteSpace(text))
			{
				throw new InvalidOperationException($"Food at index {foodIndex} has no ID.");
			}
			if (!registeredIds.Add(text))
			{
				throw new InvalidOperationException("Duplicate BOTC food ID '" + text + "'.");
			}
			if (string.IsNullOrWhiteSpace(text2))
			{
				throw new InvalidOperationException("Food '" + text + "' has no basePrefabName.");
			}
			if (!registeredBasePrefabs.Add(text2))
			{
				throw new InvalidOperationException("Base prefab '" + text2 + "' is registered more than once.");
			}
			if (foodDto.MinimumStationLevel < 1)
			{
				throw new InvalidOperationException("Food '" + text + "' has invalid minimumStationLevel " + $"{foodDto.MinimumStationLevel}.");
			}
			List<FoodSeasoningDefinitionDto> list = foodDto.Seasonings ?? new List<FoodSeasoningDefinitionDto>();
			if (list.Count == 0)
			{
				throw new InvalidOperationException("Food '" + text + "' has no seasonings.");
			}
			HashSet<IngredientClass> registeredClasses = new HashSet<IngredientClass>();
			List<FoodSeasoningDefinition> list2 = new List<FoodSeasoningDefinition>();
			for (int i = 0; i < list.Count; i++)
			{
				FoodSeasoningDefinitionDto foodSeasoningDefinitionDto = list[i];
				if (foodSeasoningDefinitionDto == null)
				{
					throw new InvalidOperationException("Food '" + text + "' contains a null seasoning " + $"at index {i}.");
				}
				FoodSeasoningDefinition item = ConvertSeasoning(text, foodSeasoningDefinitionDto, i, registeredClasses);
				list2.Add(item);
			}
			return new FoodDefinition(text, text2, foodDto.MinimumStationLevel, list2);
		}

		private static FoodSeasoningDefinition ConvertSeasoning(string foodId, FoodSeasoningDefinitionDto seasoningDto, int seasoningIndex, HashSet<IngredientClass> registeredClasses)
		{
			string text = seasoningDto.IngredientClassName?.Trim() ?? string.Empty;
			if (!Enum.TryParse<IngredientClass>(text, ignoreCase: true, out var result))
			{
				throw new InvalidOperationException("Food '" + foodId + "', seasoning " + $"{seasoningIndex}: invalid ingredientClass " + "'" + text + "'.");
			}
			if (!registeredClasses.Add(result))
			{
				throw new InvalidOperationException("Food '" + foodId + "' contains more than one " + $"'{result}' seasoning.");
			}
			if (seasoningDto.NormalBonus < 0f)
			{
				throw new InvalidOperationException("Food '" + foodId + "', seasoning " + $"'{result}' has a negative " + "normalBonus.");
			}
			string text2 = seasoningDto.PerfectIngredientPrefabName?.Trim();
			bool flag = !string.IsNullOrWhiteSpace(text2);
			bool hasValue = seasoningDto.PerfectBonus.HasValue;
			if (flag != hasValue)
			{
				throw new InvalidOperationException("Food '" + foodId + "', seasoning " + $"'{result}' must define both " + "perfectIngredientPrefabName and perfectBonus, or neither of them.");
			}
			if (hasValue)
			{
				float value = seasoningDto.PerfectBonus.Value;
				if (value < 0f)
				{
					throw new InvalidOperationException("Food '" + foodId + "', seasoning " + $"'{result}' has a negative " + "perfectBonus.");
				}
				if (value < seasoningDto.NormalBonus)
				{
					throw new InvalidOperationException("Food '" + foodId + "', seasoning " + $"'{result}': perfectBonus " + "cannot be lower than normalBonus.");
				}
				if (!IngredientRegistry.Contains(result, text2))
				{
					throw new InvalidOperationException("Food '" + foodId + "', seasoning " + $"'{result}': perfect ingredient " + "'" + text2 + "' is not registered in IngredientRegistry.");
				}
			}
			return new FoodSeasoningDefinition(result, flag ? text2 : null, seasoningDto.NormalBonus, hasValue ? seasoningDto.PerfectBonus : ((float?)null));
		}
	}
}