Decompiled source of OdinBet ForgeOfPotential v1.4.8

plugins/OdinBet_ForgeOfPotential.dll

Decompiled 2 weeks 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.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("OdinBet_ForgeOfPotential")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("OdinBet")]
[assembly: AssemblyProduct("OdinBet_ForgeOfPotential")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("9a350246-6f38-4f7e-9a17-7ae82ec995d7")]
[assembly: AssemblyFileVersion("1.4.8.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.4.8.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;
		}
	}
}
public static class SCMPanel
{
	public static bool Register(string Command, bool AdminOnly = false)
	{
		if (string.IsNullOrWhiteSpace(Command))
		{
			throw new ArgumentException("Panel command cannot be empty.", "Command");
		}
		try
		{
			Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => string.Equals(a.GetName().Name, "Ultimate_SCM", StringComparison.OrdinalIgnoreCase));
			if (assembly == null)
			{
				return false;
			}
			Type type = assembly.GetType("UltimateSCM.ExternalPanelRegistry", throwOnError: false, ignoreCase: false);
			if (type == null)
			{
				return false;
			}
			MethodInfo method = type.GetMethod("Register", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[3]
			{
				typeof(string),
				typeof(bool),
				typeof(Assembly)
			}, null);
			if (method == null)
			{
				return false;
			}
			method.Invoke(null, new object[3]
			{
				Command.Trim(),
				AdminOnly,
				typeof(SCMPanel).Assembly
			});
			return true;
		}
		catch
		{
			return false;
		}
	}
}
namespace OdinBet_ForgeOfPotential
{
	[BepInPlugin("OdinBet.ForgeOfPotential", "OdinBet - Forge of Potential", "1.4.8")]
	public class OdinBet_ForgeOfPotential : BaseUnityPlugin
	{
		internal enum UpgradeOutcome
		{
			Broke,
			LevelLost,
			Success,
			ResetToLevel1
		}

		[HarmonyPatch(typeof(InventoryGui), "SetupCrafting")]
		private class InventoryGuiCraftSpeedPatches
		{
			[HarmonyPrefix]
			private static void SetCraftSpeed(ref float ___m_upgraderDuration, ref float ___m_upgraderDurationPerLevel)
			{
				if (EnableCraftDurationTweaks.Value)
				{
					___m_upgraderDuration = UpgradeBaseDuration.Value;
					___m_upgraderDurationPerLevel = UpgradeDurationIncreasePerLevel.Value;
				}
			}
		}

		private class UpgradeAttempt
		{
			public ItemData OriginalItem;

			public int QualityBefore;

			public string ItemToken;

			public string PlayerName;

			public long PlayerID;

			public int Variant;

			public string RecipePrefabName;

			public Recipe Recipe;

			public bool HasUpgraderResource;

			public float BreakReturnFraction;

			public bool ItemGivenBack;

			public int QualityGivenBack;

			public bool ExpectedLevelSet;

			public int ExpectedLevel;

			public bool DestroyedByMod;

			public bool GameMessageSuppressed;

			public string SuppressedText;

			public ItemData NewItem;
		}

		private class SavedChances
		{
			public SharedData Shared;

			public float UpgradeChance;

			public float BreakChance;

			public float BreakReturn;
		}

		[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
		private class UpgradeCraftPatch
		{
			private const string CraftUpgradeItemFieldName = "m_craftUpgradeItem";

			private const string CraftRecipeFieldName = "m_craftRecipe";

			private static FieldInfo _craftUpgradeItemField;

			private static FieldInfo _craftRecipeField;

			private static bool _fieldLookupDone;

			private static void EnsureFields()
			{
				if (!_fieldLookupDone)
				{
					_fieldLookupDone = true;
					BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
					_craftUpgradeItemField = typeof(InventoryGui).GetField("m_craftUpgradeItem", bindingAttr);
					_craftRecipeField = typeof(InventoryGui).GetField("m_craftRecipe", bindingAttr);
					if (_craftUpgradeItemField == null || _craftRecipeField == null)
					{
						Debug.LogWarning((object)"[ForgeOfPotential] Could not find InventoryGui.m_craftUpgradeItem / m_craftRecipe in this game build; upgrade chance control and upgrade notifications are disabled.");
					}
				}
			}

			private static bool Prefix(InventoryGui __instance, Player player, out object __state)
			{
				__state = null;
				_activeUpgrade = null;
				try
				{
					if ((Object)(object)__instance == (Object)null || (Object)(object)player == (Object)null)
					{
						return true;
					}
					EnsureFields();
					if (_craftUpgradeItemField == null || _craftRecipeField == null)
					{
						return true;
					}
					object? value = _craftUpgradeItemField.GetValue(__instance);
					ItemData val = (ItemData)((value is ItemData) ? value : null);
					if (val == null)
					{
						return true;
					}
					CraftingStation currentCraftingStation = player.GetCurrentCraftingStation();
					if ((Object)(object)currentCraftingStation == (Object)null || !currentCraftingStation.m_upgrader)
					{
						return true;
					}
					if (SimpleConfigSync.ServerRulesNotApplied)
					{
						ShowBlacklistMessage(val, ServerVersionMismatchMessage);
						return false;
					}
					if (SimpleConfigSync.SyncGraceActive)
					{
						return false;
					}
					object? value2 = _craftRecipeField.GetValue(__instance);
					Recipe val2 = (Recipe)((value2 is Recipe) ? value2 : null);
					if ((Object)(object)val2 == (Object)null || (Object)(object)val2.m_item == (Object)null)
					{
						return true;
					}
					string prefabName = (((Object)(object)((Component)val2.m_item).gameObject != (Object)null) ? ((Object)((Component)val2.m_item).gameObject).name : null);
					if (IsItemBlacklisted(prefabName))
					{
						ShowBlacklistMessage(val, ItemBlacklistedMessage);
						return false;
					}
					if (!ModdedItemMayBeRefined(prefabName))
					{
						ShowBlacklistMessage(val, ModdedItemBlockedMessage);
						return false;
					}
					int value3 = MaxUpgradeLevel.Value;
					if (EnableUpgradeChanceAndCostTweaks.Value && value3 > 0 && val.m_quality >= value3)
					{
						ShowCapMessage(val, value3);
						return false;
					}
					UpgradeAttempt upgradeAttempt = (_activeUpgrade = new UpgradeAttempt
					{
						OriginalItem = val,
						QualityBefore = val.m_quality,
						ItemToken = ((val.m_shared != null) ? val.m_shared.m_name : null),
						PlayerName = (player.GetPlayerName() ?? "Unknown"),
						PlayerID = player.GetPlayerID(),
						Variant = val.m_variant,
						Recipe = val2,
						RecipePrefabName = (((Object)(object)((Component)val2.m_item).gameObject != (Object)null) ? ((Object)((Component)val2.m_item).gameObject).name : null)
					});
					__state = PrepareUpgraderResources(val2, upgradeAttempt);
					if (VerboseLogging.Value)
					{
						if (MayLogServerRules())
						{
							Debug.Log((object)$"[ForgeOfPotential] Refine start: '{upgradeAttempt.RecipePrefabName}' level {upgradeAttempt.QualityBefore} -> {upgradeAttempt.QualityBefore + 1}, upgraderResource={upgradeAttempt.HasUpgraderResource}, successChance={UpgradeSuccessChance.Value}%, breakShareOfFailures={FailureBreaksItemChance.Value}%, levelsLostOnFailure={LevelsLostOnFailure.Value}, minLevel={MinimumLevelOnFailure.Value}.");
						}
						else
						{
							Debug.Log((object)$"[ForgeOfPotential] Refine start: '{upgradeAttempt.RecipePrefabName}' level {upgradeAttempt.QualityBefore} -> {upgradeAttempt.QualityBefore + 1}, upgraderResource={upgradeAttempt.HasUpgraderResource} (the server's odds are only logged for an admin with devcommands on).");
						}
					}
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] DoCrafting prefix failed, upgrade left untouched: {arg}");
					__state = null;
				}
				return true;
			}

			private static void ShowCapMessage(ItemData item, int cap)
			{
				try
				{
					string text = MessageTranslations.Resolve(MaxLevelReachedMessage) ?? "";
					if (!string.IsNullOrEmpty(text))
					{
						string itemToken = ((item.m_shared != null) ? item.m_shared.m_name : "");
						string text2 = text.Replace("{ItemName}", UpgradeNotifications.LocalizeItemName(itemToken)).Replace("{Level}", item.m_quality.ToString()).Replace("{MaxLevel}", cap.ToString());
						MessageHud instance = MessageHud.instance;
						if (instance != null)
						{
							instance.ShowMessage((MessageType)2, text2, 0, (Sprite)null, false, true);
						}
					}
				}
				catch
				{
				}
			}

			private static bool IsItemBlacklisted(string prefabName)
			{
				return ItemListContains(BlacklistedItems?.Value, prefabName);
			}

			private static bool ModdedItemMayBeRefined(string prefabName)
			{
				if (AllowRefiningModdedItems == null || AllowRefiningModdedItems.Value || VanillaItems.IsVanilla(prefabName))
				{
					return true;
				}
				return ItemListContains(RefinableModdedItems?.Value, prefabName);
			}

			private static void ShowBlacklistMessage(ItemData item, MessageText text)
			{
				try
				{
					string text2 = MessageTranslations.Resolve(text) ?? "";
					if (!string.IsNullOrEmpty(text2))
					{
						string itemToken = ((item.m_shared != null) ? item.m_shared.m_name : "");
						string text3 = text2.Replace("{ItemName}", UpgradeNotifications.LocalizeItemName(itemToken));
						MessageHud instance = MessageHud.instance;
						if (instance != null)
						{
							instance.ShowMessage((MessageType)2, text3, 0, (Sprite)null, false, true);
						}
					}
				}
				catch
				{
				}
			}

			private static List<SavedChances> PrepareUpgraderResources(Recipe recipe, UpgradeAttempt attempt)
			{
				attempt.BreakReturnFraction = 0f;
				if (recipe.m_resources == null)
				{
					return null;
				}
				bool value = EnableUpgradeChanceAndCostTweaks.Value;
				float value2 = UpgradeSuccessChance.Value;
				float value3 = FailureBreaksItemChance.Value;
				float value4 = BreakReturnIngredientsPercent.Value;
				bool value5 = BreakReturnsItemAtLevel1.Value;
				bool flag = value && value2 >= 0f;
				bool flag2 = value && value3 >= 0f;
				bool flag3 = value && (value5 || value4 >= 0f);
				List<SavedChances> list = new List<SavedChances>();
				Requirement[] resources = recipe.m_resources;
				foreach (Requirement val in resources)
				{
					if (val == null || !val.m_upgraderResource || (Object)(object)val.m_resItem == (Object)null)
					{
						continue;
					}
					ItemData itemData = val.m_resItem.m_itemData;
					if (itemData == null || itemData.m_shared == null)
					{
						continue;
					}
					attempt.HasUpgraderResource = true;
					SharedData shared = itemData.m_shared;
					if (!flag && !flag2 && !flag3)
					{
						attempt.BreakReturnFraction = Mathf.Clamp01(shared.m_breakReturnIngreientsAmount);
						continue;
					}
					list.Add(new SavedChances
					{
						Shared = shared,
						UpgradeChance = shared.m_upgradeChance,
						BreakChance = shared.m_breakChance,
						BreakReturn = shared.m_breakReturnIngreientsAmount
					});
					float num = (flag ? Mathf.Clamp01(value2 / 100f) : Mathf.Clamp01(shared.m_upgradeChance));
					if (flag)
					{
						shared.m_upgradeChance = num;
					}
					if (flag2)
					{
						shared.m_breakChance = (1f - num) * Mathf.Clamp01(value3 / 100f);
					}
					if (flag3)
					{
						shared.m_breakReturnIngreientsAmount = (value5 ? 0f : Mathf.Clamp01(value4 / 100f));
					}
					attempt.BreakReturnFraction = (value5 ? 0f : ((value4 >= 0f) ? Mathf.Clamp01(value4 / 100f) : Mathf.Clamp01(list[list.Count - 1].BreakReturn)));
				}
				if (list.Count <= 0)
				{
					return null;
				}
				return list;
			}

			private static void Postfix(Player player, object __state)
			{
				RestoreChances(__state);
				UpgradeAttempt activeUpgrade = _activeUpgrade;
				_activeUpgrade = null;
				try
				{
					if (activeUpgrade == null || !Inventory_AddItem_UpgradeQualityPatch.HookAvailable || !activeUpgrade.HasUpgraderResource)
					{
						return;
					}
					Inventory val = (((Object)(object)player != (Object)null) ? ((Humanoid)player).GetInventory() : null);
					if (val != null && activeUpgrade.OriginalItem != null && val.ContainsItem(activeUpgrade.OriginalItem))
					{
						return;
					}
					int num = activeUpgrade.QualityGivenBack;
					UpgradeOutcome upgradeOutcome;
					if (!activeUpgrade.ItemGivenBack)
					{
						if (EnableUpgradeChanceAndCostTweaks.Value && BreakReturnsItemAtLevel1.Value && GiveBackAtLevel1(player, activeUpgrade))
						{
							upgradeOutcome = UpgradeOutcome.ResetToLevel1;
							num = 1;
						}
						else
						{
							if (activeUpgrade.DestroyedByMod)
							{
								RefundIngredients(player, activeUpgrade);
							}
							upgradeOutcome = UpgradeOutcome.Broke;
							num = 0;
						}
					}
					else if (activeUpgrade.QualityGivenBack > activeUpgrade.QualityBefore)
					{
						upgradeOutcome = UpgradeOutcome.Success;
					}
					else
					{
						upgradeOutcome = UpgradeOutcome.LevelLost;
						num = EnforceFailureLevel(val, activeUpgrade, num);
					}
					if (VerboseLogging.Value)
					{
						Debug.Log((object)string.Format("[ForgeOfPotential] Refine result: '{0}' {1} -> {2} ({3}){4}", activeUpgrade.RecipePrefabName, activeUpgrade.QualityBefore, num, upgradeOutcome, activeUpgrade.GameMessageSuppressed ? (" [game text replaced: \"" + activeUpgrade.SuppressedText + "\"]") : ""));
					}
					ShowLocalOutcomeMessage(activeUpgrade, upgradeOutcome, num);
					if (EnableGlobalNotifications.Value)
					{
						UpgradeNotifications.Broadcast(activeUpgrade.PlayerName, activeUpgrade.ItemToken, activeUpgrade.QualityBefore + 1, num, upgradeOutcome);
					}
					if (upgradeOutcome == UpgradeOutcome.Success)
					{
						try
						{
							Infusion.OnRefinementSucceeded(activeUpgrade, val, num);
						}
						catch (Exception ex)
						{
							if (VerboseLogging.Value)
							{
								Debug.LogWarning((object)("[ForgeOfPotential] Thunder Night Infusion failed: " + ex));
							}
						}
					}
					try
					{
						int code = 0;
						int level = 0;
						if (upgradeOutcome == UpgradeOutcome.Success)
						{
							Infusion.ReadForRecord(activeUpgrade.NewItem, out code, out level);
						}
						HallOfFame.SubmitLocalResult(activeUpgrade.PlayerName, activeUpgrade.PlayerID, activeUpgrade.ItemToken, activeUpgrade.QualityBefore + 1, num, upgradeOutcome, code, level);
					}
					catch (Exception ex2)
					{
						if (VerboseLogging.Value)
						{
							Debug.LogWarning((object)("[ForgeOfPotential] Hall of Fame submit failed: " + ex2));
						}
					}
				}
				catch
				{
				}
			}

			private static Exception Finalizer(Exception __exception, object __state)
			{
				if (__exception != null)
				{
					RestoreChances(__state);
					_activeUpgrade = null;
				}
				return __exception;
			}

			private static void RestoreChances(object state)
			{
				if (!(state is List<SavedChances> list))
				{
					return;
				}
				for (int num = list.Count - 1; num >= 0; num--)
				{
					SavedChances savedChances = list[num];
					try
					{
						if (savedChances?.Shared != null)
						{
							savedChances.Shared.m_upgradeChance = savedChances.UpgradeChance;
							savedChances.Shared.m_breakChance = savedChances.BreakChance;
							savedChances.Shared.m_breakReturnIngreientsAmount = savedChances.BreakReturn;
						}
					}
					catch
					{
					}
				}
			}

			private static void ShowLocalOutcomeMessage(UpgradeAttempt attempt, UpgradeOutcome outcome, int resultLevel)
			{
				try
				{
					if (!attempt.GameMessageSuppressed)
					{
						return;
					}
					string text = outcome switch
					{
						UpgradeOutcome.Success => MessageTranslations.Resolve(LocalSuccessMessage), 
						UpgradeOutcome.Broke => MessageTranslations.Resolve(LocalBrokenMessage), 
						UpgradeOutcome.ResetToLevel1 => MessageTranslations.Resolve(LocalResetMessage), 
						_ => MessageTranslations.Resolve(LocalFailedMessage), 
					};
					if (!string.IsNullOrEmpty(text))
					{
						string text2 = text.Replace("{ItemName}", UpgradeNotifications.LocalizeItemName(attempt.ItemToken)).Replace("{Level}", (attempt.QualityBefore + 1).ToString()).Replace("{PreviousLevel}", attempt.QualityBefore.ToString())
							.Replace("{ResultLevel}", resultLevel.ToString());
						MessageHud instance = MessageHud.instance;
						if (instance != null)
						{
							instance.ShowMessage((MessageType)2, text2, 0, (Sprite)null, false, true);
						}
					}
				}
				catch
				{
				}
			}

			private static int EnforceFailureLevel(Inventory inventory, UpgradeAttempt attempt, int fallbackLevel)
			{
				try
				{
					ItemData newItem = attempt.NewItem;
					if (!attempt.ExpectedLevelSet || newItem == null || inventory == null || !inventory.ContainsItem(newItem))
					{
						return fallbackLevel;
					}
					if (newItem.m_quality == attempt.ExpectedLevel)
					{
						return attempt.ExpectedLevel;
					}
					Debug.LogWarning((object)string.Format("[ForgeOfPotential] '{0}' came out of a failed refinement at level {1}, but LevelsLostOnFailure={2} / MinimumLevelOnFailure={3} say level {4} (it was level {5}). Corrected to level {4}.", attempt.RecipePrefabName, newItem.m_quality, LevelsLostOnFailure.Value, MinimumLevelOnFailure.Value, attempt.ExpectedLevel, attempt.QualityBefore));
					newItem.m_quality = attempt.ExpectedLevel;
					attempt.QualityGivenBack = attempt.ExpectedLevel;
					MethodInfo methodInfo = null;
					MethodInfo[] methods = typeof(Inventory).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (MethodInfo methodInfo2 in methods)
					{
						if (string.Equals(methodInfo2.Name, "Changed", StringComparison.Ordinal))
						{
							methodInfo = methodInfo2;
							break;
						}
					}
					if (methodInfo != null)
					{
						ParameterInfo[] parameters = methodInfo.GetParameters();
						object[] array = new object[parameters.Length];
						for (int j = 0; j < parameters.Length; j++)
						{
							array[j] = (parameters[j].ParameterType.IsValueType ? Activator.CreateInstance(parameters[j].ParameterType) : null);
						}
						methodInfo.Invoke(inventory, array);
					}
					return attempt.ExpectedLevel;
				}
				catch (Exception ex)
				{
					if (VerboseLogging.Value)
					{
						Debug.LogWarning((object)("[ForgeOfPotential] Could not verify the level after a failed refinement: " + ex));
					}
					return fallbackLevel;
				}
			}

			private static bool GiveBackAtLevel1(Player player, UpgradeAttempt attempt)
			{
				try
				{
					if ((Object)(object)player == (Object)null || string.IsNullOrEmpty(attempt.RecipePrefabName))
					{
						return false;
					}
					Inventory inventory = ((Humanoid)player).GetInventory();
					if (inventory == null)
					{
						return false;
					}
					ItemData val = inventory.AddItem(attempt.RecipePrefabName, 1, 1, attempt.Variant, attempt.PlayerID, attempt.PlayerName ?? "", false, false);
					if (val == null)
					{
						Debug.LogWarning((object)("[ForgeOfPotential] BreakReturnsItemAtLevel1: could not give '" + attempt.RecipePrefabName + "' back (inventory full?)."));
						return false;
					}
					return true;
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] BreakReturnsItemAtLevel1 failed: {arg}");
					return false;
				}
			}

			private static void RefundIngredients(Player player, UpgradeAttempt attempt)
			{
				try
				{
					if ((Object)(object)player == (Object)null || attempt.Recipe?.m_resources == null || attempt.BreakReturnFraction <= 0f)
					{
						return;
					}
					Inventory inventory = ((Humanoid)player).GetInventory();
					if (inventory == null)
					{
						return;
					}
					Requirement[] resources = attempt.Recipe.m_resources;
					foreach (Requirement val in resources)
					{
						if (val == null || !val.m_recover || (Object)(object)val.m_resItem == (Object)null)
						{
							continue;
						}
						int num = Mathf.CeilToInt((float)(val.GetAmount(1) + val.GetAmount(attempt.QualityBefore)) * attempt.BreakReturnFraction);
						if (num > 0)
						{
							ItemData itemData = val.m_resItem.m_itemData;
							inventory.AddItem(((Object)((Component)val.m_resItem).gameObject).name, num, itemData?.m_quality ?? 1, itemData?.m_variant ?? 0, attempt.PlayerID, attempt.PlayerName ?? "", false, false);
							if (VerboseLogging.Value)
							{
								Debug.Log((object)("[ForgeOfPotential] Refunded " + ((Object)((Component)val.m_resItem).gameObject).name + " x" + num + "."));
							}
						}
					}
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] Ingredient refund failed: {arg}");
				}
			}
		}

		[HarmonyPatch]
		private class Inventory_AddItem_UpgradeQualityPatch
		{
			internal static bool HookAvailable;

			private static IEnumerable<MethodBase> TargetMethods()
			{
				List<MethodBase> list = new List<MethodBase>();
				MethodInfo[] methods = typeof(Inventory).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo in methods)
				{
					if (!string.Equals(methodInfo.Name, "AddItem", StringComparison.Ordinal) || methodInfo.ReturnType != typeof(ItemData))
					{
						continue;
					}
					ParameterInfo[] parameters = methodInfo.GetParameters();
					if (parameters.Length == 0 || parameters[0].ParameterType != typeof(string) || !string.Equals(parameters[0].Name, "name", StringComparison.Ordinal))
					{
						continue;
					}
					bool flag = false;
					ParameterInfo[] array = parameters;
					foreach (ParameterInfo parameterInfo in array)
					{
						if (parameterInfo.ParameterType == typeof(int) && string.Equals(parameterInfo.Name, "quality", StringComparison.Ordinal))
						{
							flag = true;
							break;
						}
					}
					if (flag)
					{
						list.Add(methodInfo);
					}
				}
				if (list.Count == 0)
				{
					throw new Exception("no Inventory.AddItem(string name, ..., int quality, ...) overload found in this game build");
				}
				HookAvailable = true;
				return list;
			}

			private static bool Prefix(string name, ref int quality, ref ItemData __result)
			{
				UpgradeAttempt activeUpgrade = _activeUpgrade;
				if (activeUpgrade == null || name == null || activeUpgrade.RecipePrefabName == null)
				{
					return true;
				}
				if (!string.Equals(name, activeUpgrade.RecipePrefabName, StringComparison.Ordinal))
				{
					return true;
				}
				if (quality > activeUpgrade.QualityBefore)
				{
					activeUpgrade.ItemGivenBack = true;
					activeUpgrade.QualityGivenBack = quality;
					return true;
				}
				if (!EnableUpgradeChanceAndCostTweaks.Value)
				{
					if (quality < 1)
					{
						quality = 1;
					}
					activeUpgrade.ItemGivenBack = true;
					activeUpgrade.QualityGivenBack = quality;
					return true;
				}
				int num = Math.Max(1, MinimumLevelOnFailure.Value);
				int num2 = Math.Max(0, LevelsLostOnFailure.Value);
				if (num2 > 0 && activeUpgrade.QualityBefore - num2 < num && FailureAtMinimumLevelBreaksItem.Value)
				{
					activeUpgrade.DestroyedByMod = true;
					activeUpgrade.ItemGivenBack = false;
					activeUpgrade.QualityGivenBack = 0;
					__result = null;
					return false;
				}
				int num3 = activeUpgrade.QualityBefore - num2;
				if (num3 < num)
				{
					num3 = num;
				}
				if (num3 > activeUpgrade.QualityBefore)
				{
					num3 = activeUpgrade.QualityBefore;
				}
				if (VerboseLogging.Value && quality != num3)
				{
					if (MayLogServerRules())
					{
						Debug.Log((object)$"[ForgeOfPotential] Failed refinement: the game asked for level {quality}, forced to {num3} (level before={activeUpgrade.QualityBefore}, LevelsLostOnFailure={num2}, MinimumLevelOnFailure={num}).");
					}
					else
					{
						Debug.Log((object)$"[ForgeOfPotential] Failed refinement: the game asked for level {quality}, forced to {num3} (level before={activeUpgrade.QualityBefore}).");
					}
				}
				quality = num3;
				activeUpgrade.ItemGivenBack = true;
				activeUpgrade.QualityGivenBack = quality;
				activeUpgrade.ExpectedLevelSet = true;
				activeUpgrade.ExpectedLevel = num3;
				return true;
			}

			private static void Postfix(string name, ItemData __result)
			{
				try
				{
					UpgradeAttempt activeUpgrade = _activeUpgrade;
					if (activeUpgrade != null && __result != null && name != null && activeUpgrade.RecipePrefabName != null && string.Equals(name, activeUpgrade.RecipePrefabName, StringComparison.Ordinal))
					{
						activeUpgrade.NewItem = __result;
						Infusion.CarryOver(activeUpgrade.OriginalItem, __result);
					}
				}
				catch
				{
				}
			}
		}

		[HarmonyPatch]
		private class Character_Message_UpgradeTextPatch
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				List<MethodBase> list = new List<MethodBase>();
				Type[] array = new Type[3]
				{
					typeof(Player),
					typeof(Humanoid),
					typeof(Character)
				};
				Type[] array2 = array;
				foreach (Type type in array2)
				{
					MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (MethodInfo methodInfo in methods)
					{
						if (string.Equals(methodInfo.Name, "Message", StringComparison.Ordinal))
						{
							ParameterInfo[] parameters = methodInfo.GetParameters();
							if (parameters.Length >= 2 && !(parameters[0].ParameterType != typeof(MessageType)) && !(parameters[1].ParameterType != typeof(string)) && !list.Contains(methodInfo))
							{
								list.Add(methodInfo);
							}
						}
					}
				}
				if (list.Count == 0)
				{
					throw new Exception("no Message(MessageHud.MessageType, string, ...) found on Player/Humanoid/Character in this game build");
				}
				return list;
			}

			private static bool Prefix(Character __instance, MessageType type, string msg)
			{
				//IL_0029: Unknown result type (might be due to invalid IL or missing references)
				//IL_002b: Invalid comparison between Unknown and I4
				try
				{
					UpgradeAttempt activeUpgrade = _activeUpgrade;
					if (activeUpgrade == null || !EnableLocalNotifications.Value || !activeUpgrade.HasUpgraderResource || activeUpgrade.GameMessageSuppressed)
					{
						return true;
					}
					if ((int)type != 2)
					{
						return true;
					}
					Character obj = ((__instance is Humanoid) ? __instance : null);
					Inventory val = ((obj != null) ? ((Humanoid)obj).GetInventory() : null);
					if (val == null || activeUpgrade.OriginalItem == null || val.ContainsItem(activeUpgrade.OriginalItem))
					{
						return true;
					}
					activeUpgrade.GameMessageSuppressed = true;
					activeUpgrade.SuppressedText = msg;
					return false;
				}
				catch
				{
				}
				return true;
			}
		}

		[HarmonyPatch(typeof(ZoneSystem), "GetLocationIcons")]
		private class ZoneSystem_GetLocationIcons_ForgeOnMapPatch
		{
			private static FieldInfo _instancesField;

			private static FieldInfo _locationsField;

			private static void Prefix(ZoneSystem __instance, ref List<ZoneLocation> __state)
			{
				__state = null;
				try
				{
					if (ShowForgeOnMap == null || !ShowForgeOnMap.Value)
					{
						return;
					}
					ZNet instance = ZNet.instance;
					if ((Object)(object)instance == (Object)null || !instance.IsServer())
					{
						return;
					}
					__state = new List<ZoneLocation>();
					if (_instancesField == null)
					{
						_instancesField = AccessTools.Field(typeof(ZoneSystem), "m_locationInstances");
					}
					IDictionary dictionary = ((_instancesField != null) ? (_instancesField.GetValue(__instance) as IDictionary) : null);
					if (dictionary != null)
					{
						foreach (object value in dictionary.Values)
						{
							ZoneLocation location = ((LocationInstance)value).m_location;
							if (location != null && !location.m_iconAlways && IsForgeLocationName(location.m_prefab.Name))
							{
								location.m_iconAlways = true;
								__state.Add(location);
							}
						}
					}
					if (_locationsField == null)
					{
						_locationsField = AccessTools.Field(typeof(ZoneSystem), "m_locations");
					}
					List<ZoneLocation> list = ((_locationsField != null) ? (_locationsField.GetValue(__instance) as List<ZoneLocation>) : null);
					if (list == null)
					{
						return;
					}
					HashSet<ZoneLocation> hashSet = null;
					if (dictionary != null && dictionary.Count > 0)
					{
						hashSet = new HashSet<ZoneLocation>(dictionary.Count);
						foreach (object value2 in dictionary.Values)
						{
							ZoneLocation location2 = ((LocationInstance)value2).m_location;
							if (location2 != null)
							{
								hashSet.Add(location2);
							}
						}
					}
					foreach (ZoneLocation item in list)
					{
						if (item != null && !item.m_iconAlways && IsForgeLocationName(item.m_prefab.Name) && (hashSet == null || !hashSet.Contains(item)))
						{
							item.m_iconAlways = true;
							__state.Add(item);
						}
					}
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)("[ForgeOfPotential] ShowForgeOnMap: could not list the forge on the map: " + ex.Message));
				}
			}

			private static void Finalizer(List<ZoneLocation> __state)
			{
				if (__state != null)
				{
					for (int i = 0; i < __state.Count; i++)
					{
						__state[i].m_iconAlways = false;
					}
				}
			}
		}

		[HarmonyPatch(typeof(Minimap), "GetLocationIcon")]
		private class Minimap_GetLocationIcon_ForgeOnMapPatch
		{
			private static Sprite _fallback;

			private static bool _searched;

			private static void Postfix(string __0, ref Sprite __result)
			{
				if ((Object)(object)__result != (Object)null || ShowForgeOnMap == null || !ShowForgeOnMap.Value || !IsForgeLocationName(__0))
				{
					return;
				}
				try
				{
					if (!_searched)
					{
						_searched = true;
						CraftingStation[] array = Resources.FindObjectsOfTypeAll<CraftingStation>();
						for (int i = 0; i < array.Length; i++)
						{
							if ((Object)(object)array[i] != (Object)null && array[i].m_upgrader && (Object)(object)array[i].m_icon != (Object)null)
							{
								_fallback = array[i].m_icon;
								break;
							}
						}
						if ((Object)(object)_fallback == (Object)null)
						{
							Debug.LogWarning((object)"[ForgeOfPotential] ShowForgeOnMap: the map has no picture for the forge and none was found to use instead, so no icon is drawn.");
						}
					}
					if ((Object)(object)_fallback != (Object)null)
					{
						__result = _fallback;
					}
				}
				catch
				{
				}
			}
		}

		[HarmonyPatch(typeof(Location), "IsInsideNoBuildLocation")]
		private class Location_NoBuildZone_Patch
		{
			private static void Postfix(Vector3 point, ref bool __result)
			{
				//IL_0062: Unknown result type (might be due to invalid IL or missing references)
				//IL_0067: Unknown result type (might be due to invalid IL or missing references)
				//IL_0068: Unknown result type (might be due to invalid IL or missing references)
				//IL_006d: Unknown result type (might be due to invalid IL or missing references)
				if (!__result || !AllowBuildingNearRefinementForge.Value)
				{
					return;
				}
				try
				{
					float num = Mathf.Max(1f, RefinementForgeBuildRadius.Value);
					List<IMonoUpdater> instances = CraftingStation.Instances;
					if (instances == null)
					{
						return;
					}
					float num2 = num * num;
					for (int i = 0; i < instances.Count; i++)
					{
						IMonoUpdater obj = instances[i];
						CraftingStation val = (CraftingStation)(object)((obj is CraftingStation) ? obj : null);
						if (!((Object)(object)val == (Object)null) && val.m_upgrader)
						{
							Vector3 val2 = ((Component)val).transform.position - point;
							if (((Vector3)(ref val2)).sqrMagnitude <= num2)
							{
								__result = false;
								break;
							}
						}
					}
				}
				catch
				{
				}
			}
		}

		[HarmonyPatch(typeof(CraftingStation), "CheckUsable")]
		private class CraftingStation_CheckUsable_ForgeDisabledPatch
		{
			private static void Postfix(CraftingStation __instance, bool showMessage, ref bool __result)
			{
				if (__result && EnableRefinementForge != null && !EnableRefinementForge.Value && !((Object)(object)__instance == (Object)null) && __instance.m_upgrader)
				{
					__result = false;
					if (showMessage)
					{
						ShowForgeDisabledMessage();
					}
				}
			}

			private static void ShowForgeDisabledMessage()
			{
				try
				{
					string text = MessageTranslations.Resolve(ForgeDisabledMessage);
					if (!string.IsNullOrEmpty(text))
					{
						MessageHud instance = MessageHud.instance;
						if (instance != null)
						{
							instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true);
						}
					}
				}
				catch
				{
				}
			}
		}

		[HarmonyPatch(typeof(ZNetScene), "Awake")]
		private class ZNetSceneAwakePatch_UpgradeNotify
		{
			[HarmonyPostfix]
			private static void Postfix()
			{
				UpgradeNotifications.RegisterRPC();
				SimpleConfigSync.RegisterRPC();
			}
		}

		private static class MessageTranslations
		{
			private static readonly Dictionary<string, Dictionary<string, string>> _byLanguage = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);

			private static readonly Dictionary<string, Dictionary<string, string>> _panelByLanguage = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);

			private static bool _loaded;

			private static bool _localizationLookupDone;

			private static PropertyInfo _localizationInstanceProperty;

			private static MethodInfo _getSelectedLanguageMethod;

			private static MethodInfo _localizeMethod;

			private static readonly object[] _localizeArgs = new object[1];

			private static string _cachedLanguage;

			private static float _cachedLanguageAt = float.NegativeInfinity;

			public static void Load()
			{
				if (_loaded)
				{
					return;
				}
				_loaded = true;
				try
				{
					string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "";
					string text = Path.Combine(path, "Translations");
					if (!Directory.Exists(text))
					{
						Debug.LogWarning((object)("[ForgeOfPotential] Translations folder not found at '" + text + "' - messages will use the English text built into the mod."));
						return;
					}
					int num = 0;
					string[] files = Directory.GetFiles(text, "*.json");
					foreach (string text2 in files)
					{
						try
						{
							string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text2);
							Dictionary<string, string> dictionary = ParseFlatJsonObject(File.ReadAllText(text2));
							if (dictionary.Count > 0)
							{
								_byLanguage[fileNameWithoutExtension] = dictionary;
								num++;
							}
						}
						catch (Exception ex)
						{
							Debug.LogWarning((object)("[ForgeOfPotential] Could not read translation file '" + text2 + "': " + ex.Message));
						}
					}
					Debug.Log((object)$"[ForgeOfPotential] Loaded {num} translation file(s) from '{text}'.");
					LoadPanelTexts(Path.Combine(text, "ConfigPanel"));
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] Translation loading failed, falling back to English defaults: {arg}");
				}
			}

			private static void LoadPanelTexts(string dir)
			{
				try
				{
					if (!Directory.Exists(dir))
					{
						Debug.LogWarning((object)("[ForgeOfPotential] Settings panel translations not found at '" + dir + "' - the panel will be in English."));
						return;
					}
					int num = 0;
					string[] files = Directory.GetFiles(dir, "*.json");
					foreach (string text in files)
					{
						try
						{
							Dictionary<string, string> dictionary = ParseFlatJsonObject(File.ReadAllText(text));
							if (dictionary.Count > 0)
							{
								_panelByLanguage[Path.GetFileNameWithoutExtension(text)] = dictionary;
								num++;
							}
						}
						catch (Exception ex)
						{
							Debug.LogWarning((object)("[ForgeOfPotential] Could not read settings panel translation file '" + text + "': " + ex.Message));
						}
					}
					Debug.Log((object)$"[ForgeOfPotential] Loaded {num} settings panel translation file(s).");
				}
				catch (Exception ex2)
				{
					Debug.LogWarning((object)("[ForgeOfPotential] Settings panel translations could not be loaded: " + ex2.Message));
				}
			}

			public static string GetPanel(string key, string englishFallback)
			{
				try
				{
					if (_panelByLanguage.TryGetValue(GetCurrentLanguage(), out var value) && value.TryGetValue(key, out var value2) && !string.IsNullOrEmpty(value2))
					{
						return value2;
					}
					if (_panelByLanguage.TryGetValue("English", out value) && value.TryGetValue(key, out value2) && !string.IsNullOrEmpty(value2))
					{
						return value2;
					}
				}
				catch
				{
				}
				return englishFallback ?? "";
			}

			private static Dictionary<string, string> ParseFlatJsonObject(string json)
			{
				Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
				if (string.IsNullOrEmpty(json))
				{
					return dictionary;
				}
				int i = 0;
				int length = json.Length;
				SkipWhitespace(json, ref i);
				if (i >= length || json[i] != '{')
				{
					return dictionary;
				}
				i++;
				while (true)
				{
					SkipWhitespace(json, ref i);
					if (i >= length)
					{
						break;
					}
					if (json[i] == '}')
					{
						i++;
						break;
					}
					if (json[i] == ',')
					{
						i++;
						continue;
					}
					if (json[i] != '"')
					{
						break;
					}
					string key = ReadJsonString(json, ref i);
					SkipWhitespace(json, ref i);
					if (i >= length || json[i] != ':')
					{
						break;
					}
					i++;
					SkipWhitespace(json, ref i);
					if (i >= length || json[i] != '"')
					{
						break;
					}
					string value = ReadJsonString(json, ref i);
					dictionary[key] = value;
				}
				return dictionary;
			}

			private static void SkipWhitespace(string s, ref int i)
			{
				while (i < s.Length && char.IsWhiteSpace(s[i]))
				{
					i++;
				}
			}

			private static string ReadJsonString(string s, ref int i)
			{
				i++;
				StringBuilder stringBuilder = new StringBuilder();
				while (i < s.Length && s[i] != '"')
				{
					char c = s[i];
					if (c == '\\' && i + 1 < s.Length)
					{
						i++;
						char c2 = s[i];
						switch (c2)
						{
						case '"':
							stringBuilder.Append('"');
							break;
						case '\\':
							stringBuilder.Append('\\');
							break;
						case '/':
							stringBuilder.Append('/');
							break;
						case 'n':
							stringBuilder.Append('\n');
							break;
						case 't':
							stringBuilder.Append('\t');
							break;
						case 'r':
							stringBuilder.Append('\r');
							break;
						case 'b':
							stringBuilder.Append('\b');
							break;
						case 'f':
							stringBuilder.Append('\f');
							break;
						case 'u':
							if (i + 4 < s.Length)
							{
								string s2 = s.Substring(i + 1, 4);
								if (int.TryParse(s2, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
								{
									stringBuilder.Append((char)result);
								}
								i += 4;
							}
							break;
						default:
							stringBuilder.Append(c2);
							break;
						}
						i++;
					}
					else
					{
						stringBuilder.Append(c);
						i++;
					}
				}
				i++;
				return stringBuilder.ToString();
			}

			private static object LocalizationInstance()
			{
				if (!_localizationLookupDone)
				{
					_localizationLookupDone = true;
					Type type = null;
					Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
					foreach (Assembly assembly in assemblies)
					{
						Type type2 = assembly.GetType("Localization", throwOnError: false);
						if (type2 != null)
						{
							type = type2;
							break;
						}
					}
					if (type != null)
					{
						_localizationInstanceProperty = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public);
						_getSelectedLanguageMethod = type.GetMethod("GetSelectedLanguage", BindingFlags.Instance | BindingFlags.Public);
						_localizeMethod = type.GetMethod("Localize", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(string) }, null);
					}
				}
				return _localizationInstanceProperty?.GetValue(null);
			}

			public static string Localize(string text)
			{
				if (string.IsNullOrEmpty(text))
				{
					return text;
				}
				object obj = LocalizationInstance();
				if (obj == null || _localizeMethod == null)
				{
					return text;
				}
				_localizeArgs[0] = text;
				string text2 = _localizeMethod.Invoke(obj, _localizeArgs) as string;
				_localizeArgs[0] = null;
				if (!string.IsNullOrEmpty(text2))
				{
					return text2;
				}
				return text;
			}

			public static string GetCurrentLanguage()
			{
				try
				{
					if (_cachedLanguage != null && Time.unscaledTime - _cachedLanguageAt < 1f)
					{
						return _cachedLanguage;
					}
					object obj = LocalizationInstance();
					if (obj == null)
					{
						return "English";
					}
					string text = _getSelectedLanguageMethod?.Invoke(obj, null) as string;
					text = (_cachedLanguage = (string.IsNullOrEmpty(text) ? "English" : text));
					_cachedLanguageAt = Time.unscaledTime;
					return text;
				}
				catch
				{
					return "English";
				}
			}

			public static string Get(string key, string englishFallback)
			{
				try
				{
					if (string.IsNullOrEmpty(key))
					{
						return englishFallback ?? "";
					}
					if (_byLanguage.TryGetValue(GetCurrentLanguage(), out var value) && value.TryGetValue(key, out var value2) && value2 != null)
					{
						return value2;
					}
					if (_byLanguage.TryGetValue("English", out value) && value.TryGetValue(key, out value2) && value2 != null)
					{
						return value2;
					}
					return englishFallback ?? "";
				}
				catch
				{
					return englishFallback ?? "";
				}
			}

			public static string Label(string key, string englishFallback)
			{
				string text = Get(key, englishFallback);
				if (!string.IsNullOrEmpty(text))
				{
					return text;
				}
				return englishFallback ?? "";
			}

			public static string Resolve(MessageText message)
			{
				if (message != null)
				{
					return Get(message.Key, message.English);
				}
				return "";
			}
		}

		private static class UpgradeNotifications
		{
			private const string RpcName = "OdinBet_UpgradeBroadcast_v4";

			private const string InfusionRpcName = "OdinBet_InfusionBroadcast_v1";

			private static ZRoutedRpc _registeredOn;

			private const float ChatOpenSeconds = 4f;

			private static FieldInfo _chatHideTimerField;

			private static bool _chatHideTimerLookupDone;

			public static void RegisterRPC()
			{
				try
				{
					ZRoutedRpc instance = ZRoutedRpc.instance;
					if (instance != null && instance != _registeredOn)
					{
						instance.Register<string, string, int, int, int>("OdinBet_UpgradeBroadcast_v4", (Method<string, string, int, int, int>)RPC_ShowUpgradeMessage);
						instance.Register<string, string, int, int, int>("OdinBet_InfusionBroadcast_v1", (Method<string, string, int, int, int>)RPC_ShowInfusionMessage);
						_registeredOn = instance;
					}
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] Could not register RPC: {arg}");
				}
			}

			private static string Sanitize(string value, int maxLength)
			{
				return HallOfFame.CleanText(value, maxLength);
			}

			private static void RPC_ShowUpgradeMessage(long sender, string playerName, string itemToken, int attemptedLevel, int resultLevel, int outcomeRaw)
			{
				try
				{
					if (!EnableGlobalNotifications.Value)
					{
						return;
					}
					playerName = Sanitize(playerName, 40);
					itemToken = Sanitize(itemToken, 64);
					switch (outcomeRaw)
					{
					case 2:
						if (!NotifyOnSuccess.Value)
						{
							return;
						}
						break;
					case 0:
					case 3:
						if (!NotifyOnBreak.Value)
						{
							return;
						}
						break;
					default:
						if (!NotifyOnFailure.Value)
						{
							return;
						}
						break;
					}
					if (attemptedLevel < MinimumLevelToNotify.Value)
					{
						return;
					}
					string text = (string)((outcomeRaw switch
					{
						2 => MessageTranslations.Resolve(SuccessMessage), 
						0 => MessageTranslations.Resolve(BrokenMessage), 
						3 => MessageTranslations.Resolve(ResetMessage), 
						_ => MessageTranslations.Resolve(FailedMessage), 
					}) ?? "");
					if (text.Length == 0)
					{
						return;
					}
					string text2 = text.Replace("{Prefix}", NotificationPrefix.Value ?? "").Replace("{PlayerName}", playerName ?? "").Replace("{ItemName}", LocalizeItemName(itemToken))
						.Replace("{Level}", attemptedLevel.ToString())
						.Replace("{ResultLevel}", resultLevel.ToString());
					if (string.IsNullOrEmpty(text2))
					{
						return;
					}
					if ((Object)(object)Chat.instance != (Object)null)
					{
						string text3 = "<color=white>" + text.Replace("{Prefix}", "<color=yellow>" + NotificationPrefix.Value + "</color>").Replace("{PlayerName}", "<color=#FF6B5B>" + playerName + "</color>").Replace("{ItemName}", "<color=#FF6B5B>" + LocalizeItemName(itemToken) + "</color>")
							.Replace("{Level}", "<color=#FF6B5B>" + attemptedLevel + "</color>")
							.Replace("{ResultLevel}", "<color=#FF6B5B>" + resultLevel + "</color>") + "</color>";
						((Terminal)Chat.instance).AddString(text3);
						ForceOpenChatWindow(Chat.instance);
						return;
					}
					MessageHud instance = MessageHud.instance;
					if (instance != null)
					{
						instance.ShowMessage((MessageType)2, text2, 0, (Sprite)null, false, true);
					}
				}
				catch
				{
				}
			}

			private static void ForceOpenChatWindow(Chat chat)
			{
				try
				{
					if (!((Object)(object)chat == (Object)null))
					{
						if (!_chatHideTimerLookupDone)
						{
							_chatHideTimerLookupDone = true;
							_chatHideTimerField = typeof(Chat).GetField("m_hideTimer", BindingFlags.Instance | BindingFlags.NonPublic);
						}
						_chatHideTimerField?.SetValue(chat, Math.Max(0f, chat.m_hideDelay - 4f));
						if ((Object)(object)((Terminal)chat).m_chatWindow != (Object)null)
						{
							((Component)((Terminal)chat).m_chatWindow).gameObject.SetActive(true);
						}
					}
				}
				catch
				{
				}
			}

			public static string LocalizeItemName(string itemToken)
			{
				if (string.IsNullOrEmpty(itemToken))
				{
					return "Unknown Item";
				}
				try
				{
					return MessageTranslations.Localize(itemToken);
				}
				catch
				{
					return itemToken;
				}
			}

			public static void BroadcastInfusion(string playerName, string itemToken, InfusionElement element, int infusionLevel, bool strengthened)
			{
				try
				{
					if (EnableGlobalNotifications.Value && ZRoutedRpc.instance != null)
					{
						ZRoutedRpc.instance.InvokeRoutedRPC(0L, "OdinBet_InfusionBroadcast_v1", new object[5]
						{
							playerName ?? "",
							itemToken ?? "",
							(int)element,
							infusionLevel,
							strengthened ? 1 : 0
						});
					}
				}
				catch
				{
				}
			}

			private static void RPC_ShowInfusionMessage(long sender, string playerName, string itemToken, int elementRaw, int infusionLevel, int strengthenedRaw)
			{
				try
				{
					if (!EnableGlobalNotifications.Value || elementRaw < 0 || elementRaw > 4)
					{
						return;
					}
					playerName = Sanitize(playerName, 40);
					itemToken = Sanitize(itemToken, 64);
					string text = MessageTranslations.Resolve((strengthenedRaw != 0) ? InfusionStrengthenedMessage : InfusionMessage) ?? "";
					if (text.Length == 0)
					{
						return;
					}
					string text2 = LocalizeItemName(Infusion.ElementToken((InfusionElement)elementRaw));
					string text3 = LocalizeItemName(itemToken);
					string text4 = NotificationPrefix.Value ?? "";
					string text5 = infusionLevel.ToString();
					string text6 = text.Replace("{Prefix}", text4).Replace("{PlayerName}", playerName ?? "").Replace("{ItemName}", text3)
						.Replace("{Element}", text2)
						.Replace("{InfusionLevel}", text5);
					if (string.IsNullOrEmpty(text6))
					{
						return;
					}
					if ((Object)(object)Chat.instance != (Object)null)
					{
						string text7 = "<color=white>" + text.Replace("{Prefix}", "<color=yellow>" + text4 + "</color>").Replace("{PlayerName}", "<color=#FF6B5B>" + playerName + "</color>").Replace("{ItemName}", "<color=#FF6B5B>" + text3 + "</color>")
							.Replace("{Element}", "<color=#FFD54A>" + text2 + "</color>")
							.Replace("{InfusionLevel}", "<color=#FFD54A>" + text5 + "</color>") + "</color>";
						((Terminal)Chat.instance).AddString(text7);
						ForceOpenChatWindow(Chat.instance);
						return;
					}
					MessageHud instance = MessageHud.instance;
					if (instance != null)
					{
						instance.ShowMessage((MessageType)2, text6, 0, (Sprite)null, false, true);
					}
				}
				catch
				{
				}
			}

			public static void Broadcast(string playerName, string itemToken, int attemptedLevel, int resultLevel, UpgradeOutcome outcome)
			{
				try
				{
					if (EnableGlobalNotifications.Value && ZRoutedRpc.instance != null)
					{
						ZRoutedRpc.instance.InvokeRoutedRPC(0L, "OdinBet_UpgradeBroadcast_v4", new object[5]
						{
							playerName ?? "",
							itemToken ?? "",
							attemptedLevel,
							resultLevel,
							(int)outcome
						});
					}
				}
				catch
				{
				}
			}
		}

		internal static class VanillaItems
		{
			private static HashSet<string> _names;

			private static bool _warned;

			internal static bool Known
			{
				get
				{
					if (_names != null)
					{
						return _names.Count > 0;
					}
					return false;
				}
			}

			internal static int Count
			{
				get
				{
					if (_names == null)
					{
						return 0;
					}
					return _names.Count;
				}
			}

			internal static void Capture(ObjectDB db)
			{
				if ((Object)(object)db == (Object)null || db.m_items == null || (_names != null && _names.Count > 0))
				{
					return;
				}
				HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				bool flag = false;
				foreach (GameObject item in db.m_items)
				{
					if (!((Object)(object)item == (Object)null) && !string.IsNullOrEmpty(((Object)item).name))
					{
						hashSet.Add(((Object)item).name);
						if (string.Equals(((Object)item).name, "Wood", StringComparison.Ordinal))
						{
							flag = true;
						}
					}
				}
				if (flag)
				{
					_names = hashSet;
					if (VerboseLogging != null && VerboseLogging.Value)
					{
						Debug.Log((object)("[ForgeOfPotential] Recorded the game's own item list (" + hashSet.Count + " items)."));
					}
				}
			}

			internal static bool IsVanilla(string prefabName)
			{
				if (string.IsNullOrEmpty(prefabName))
				{
					return true;
				}
				HashSet<string> names = _names;
				if (names == null || names.Count == 0)
				{
					if (!_warned)
					{
						_warned = true;
						Debug.LogWarning((object)"[ForgeOfPotential] The game's own item list is not known, so every item counts as a game item (AllowRefiningModdedItems and InfusionAllowModdedItems cannot block anything).");
					}
					return true;
				}
				return names.Contains(StripClone(prefabName));
			}

			internal static string StripClone(string name)
			{
				if (string.IsNullOrEmpty(name))
				{
					return name;
				}
				int num = name.IndexOf("(Clone)", StringComparison.Ordinal);
				if (num <= 0)
				{
					return name.Trim();
				}
				return name.Substring(0, num).Trim();
			}
		}

		[HarmonyPatch(typeof(ObjectDB), "Awake")]
		private class ObjectDB_Awake_VanillaItemsPatch
		{
			[HarmonyPrefix]
			[HarmonyPriority(800)]
			private static void Prefix(ObjectDB __instance)
			{
				try
				{
					VanillaItems.Capture(__instance);
				}
				catch
				{
				}
			}
		}

		internal sealed class ModdedIdolInfo
		{
			internal string Prefab;

			internal string Token;

			internal ItemType Type;

			internal float BaseStat;

			internal string AutoIdol;

			internal string ReasonMaterial = "";

			internal bool Uncertain;

			internal bool OwnIdol;

			internal readonly List<string> Ingredients = new List<string>();
		}

		internal static class ModdedIdols
		{
			private sealed class Added
			{
				public Recipe Recipe;

				public Requirement Requirement;
			}

			private sealed class Sample
			{
				public ItemType Type;

				public float Stat;

				public int Tier;

				public bool Armor;
			}

			private const string LogPrefix = "[ForgeOfPotential] Modded idols: ";

			private const int MaxIngredientDepth = 3;

			internal const int MaxOverrides = 500;

			private static readonly List<Added> _added = new List<Added>();

			private static int _kDb;

			private static int _kRecipes = -1;

			private static int _kItems = -1;

			private static int _kVanilla = -1;

			private static bool _kAllow;

			private static string _kRefinable;

			private static string _kBlack;

			private static string _kMap;

			private static bool _kVerbose;

			private static MethodInfo _memberwiseClone;

			private static readonly List<ModdedIdolInfo> _items = new List<ModdedIdolInfo>();

			private static readonly Dictionary<string, ModdedIdolInfo> _byPrefab = new Dictionary<string, ModdedIdolInfo>(StringComparer.OrdinalIgnoreCase);

			private static int _version;

			private static int _maxTier = -1;

			private static string _lastSummary;

			internal static List<ModdedIdolInfo> Items => _items;

			internal static int Version => _version;

			internal static int MaxTier => _maxTier;

			internal static ModdedIdolInfo Find(string prefab)
			{
				if (string.IsNullOrEmpty(prefab) || !_byPrefab.TryGetValue(prefab, out var value))
				{
					return null;
				}
				return value;
			}

			internal static void EnsureApplied(ObjectDB db)
			{
				try
				{
					if (!((Object)(object)db == (Object)null) && db.m_recipes != null && db.m_items != null && !((Object)(object)db.GetItemPrefab("Wood") == (Object)null) && (!Unchanged(db) || !AllStillInPlace()))
					{
						Apply(db);
					}
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)("[ForgeOfPotential] Modded idols: failed: " + ex));
				}
			}

			private static bool Unchanged(ObjectDB db)
			{
				int instanceID = ((Object)db).GetInstanceID();
				bool flag = AllowRefiningModdedItems == null || AllowRefiningModdedItems.Value;
				string text = RefinableModdedItems?.Value ?? "";
				string text2 = BlacklistedItems?.Value ?? "";
				string text3 = ModdedItemIdols?.Value ?? "";
				bool flag2 = VerboseLogging != null && VerboseLogging.Value;
				if (instanceID == _kDb && db.m_recipes.Count == _kRecipes && db.m_items.Count == _kItems && VanillaItems.Count == _kVanilla && flag == _kAllow && string.Equals(text, _kRefinable, StringComparison.Ordinal) && string.Equals(text2, _kBlack, StringComparison.Ordinal) && string.Equals(text3, _kMap, StringComparison.Ordinal) && flag2 == _kVerbose)
				{
					return true;
				}
				_kDb = instanceID;
				_kRecipes = db.m_recipes.Count;
				_kItems = db.m_items.Count;
				_kVanilla = VanillaItems.Count;
				_kAllow = flag;
				_kRefinable = text;
				_kBlack = text2;
				_kMap = text3;
				_kVerbose = flag2;
				return false;
			}

			private static bool AllStillInPlace()
			{
				foreach (Added item in _added)
				{
					if ((Object)(object)item.Recipe == (Object)null || item.Recipe.m_resources == null || Array.IndexOf(item.Recipe.m_resources, item.Requirement) < 0)
					{
						return false;
					}
				}
				return true;
			}

			private static void RemoveAll()
			{
				foreach (Added item in _added)
				{
					if ((Object)(object)item.Recipe != (Object)null && item.Recipe.m_resources != null && Array.IndexOf(item.Recipe.m_resources, item.Requirement) >= 0)
					{
						Requirement mine = item.Requirement;
						item.Recipe.m_resources = item.Recipe.m_resources.Where((Requirement r) => r != mine).ToArray();
					}
				}
				_added.Clear();
			}

			internal static bool TryParseIdol(string prefab, out int tier, out bool armor)
			{
				tier = -1;
				armor = false;
				if (string.IsNullOrEmpty(prefab) || !prefab.StartsWith("Upgrader", StringComparison.Ordinal))
				{
					return false;
				}
				string text = prefab.Substring("Upgrader".Length);
				string text2;
				if (text.EndsWith("Armor", StringComparison.Ordinal))
				{
					armor = true;
					text2 = text.Substring(0, text.Length - "Armor".Length);
				}
				else
				{
					if (!text.EndsWith("Weapon", StringComparison.Ordinal))
					{
						return false;
					}
					text2 = text.Substring(0, text.Length - "Weapon".Length);
				}
				if (text2.Length > 0 && text2.Length <= 2 && int.TryParse(text2, NumberStyles.None, CultureInfo.InvariantCulture, out tier))
				{
					return tier >= 0;
				}
				return false;
			}

			internal static string IdolPrefab(int tier, bool armor)
			{
				return "Upgrader" + tier.ToString(CultureInfo.InvariantCulture) + (armor ? "Armor" : "Weapon");
			}

			internal static List<KeyValuePair<string, string>> ParseMap(string text)
			{
				List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();
				if (string.IsNullOrEmpty(text))
				{
					return list;
				}
				string[] array = text.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
				foreach (string text2 in array)
				{
					string text3 = text2.Trim();
					int num = text3.IndexOf(':');
					if (num <= 0 || num >= text3.Length - 1)
					{
						continue;
					}
					string text4 = text3.Substring(0, num).Trim();
					string text5 = text3.Substring(num + 1).Trim();
					if (!ConfigAdmin.IsPrefabName(text4) || !TryParseIdol(text5, out var _, out var _))
					{
						continue;
					}
					for (int num2 = list.Count - 1; num2 >= 0; num2--)
					{
						if (string.Equals(list[num2].Key, text4, StringComparison.OrdinalIgnoreCase))
						{
							list.RemoveAt(num2);
						}
					}
					list.Add(new KeyValuePair<string, string>(text4, text5));
				}
				return list;
			}

			internal static string JoinMap(List<KeyValuePair<string, string>> list)
			{
				StringBuilder stringBuilder = new StringBuilder();
				foreach (KeyValuePair<string, string> item in list)
				{
					if (stringBuilder.Length > 0)
					{
						stringBuilder.Append(',');
					}
					stringBuilder.Append(item.Key).Append(':').Append(item.Value);
				}
				return stringBuilder.ToString();
			}

			internal static string MapGet(List<KeyValuePair<string, string>> list, string item)
			{
				foreach (KeyValuePair<string, string> item2 in list)
				{
					if (string.Equals(item2.Key, item, StringComparison.OrdinalIgnoreCase))
					{
						return item2.Value;
					}
				}
				return null;
			}

			internal static void MapSet(List<KeyValuePair<string, string>> list, string item, string idol)
			{
				for (int i = 0; i < list.Count; i++)
				{
					if (string.Equals(list[i].Key, item, StringComparison.OrdinalIgnoreCase))
					{
						if (idol == null)
						{
							list.RemoveAt(i);
						}
						else
						{
							list[i] = new KeyValuePair<string, string>(list[i].Key, idol);
						}
						return;
					}
				}
				if (idol != null)
				{
					list.Add(new KeyValuePair<string, string>(item, idol));
				}
			}

			internal static string CheckMap(string text)
			{
				if (string.IsNullOrEmpty(text) || text.Trim().Length == 0)
				{
					return null;
				}
				string[] array = text.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
				if (array.Length > 500)
				{
					return "at most " + 500 + " items";
				}
				string[] array2 = array;
				foreach (string text2 in array2)
				{
					string text3 = text2.Trim();
					if (text3.Length != 0)
					{
						int num = text3.IndexOf(':');
						if (num <= 0)
						{
							return "entries must be 'ItemPrefab:UpgraderNArmor' or 'ItemPrefab:UpgraderNWeapon'";
						}
						string name = text3.Substring(0, num).Trim();
						string prefab = text3.Substring(num + 1).Trim();
						if (!ConfigAdmin.IsPrefabName(name) || !TryParseIdol(prefab, out var _, out var _))
						{
							return "entries must be 'ItemPrefab:UpgraderNArmor' or 'ItemPrefab:UpgraderNWeapon'";
						}
					}
				}
				return null;
			}

			internal static List<string> MissingReferenced(ObjectDB db)
			{
				List<string> list = new List<string>();
				if ((Object)(object)db == (Object)null || (Object)(object)db.GetItemPrefab("Wood") == (Object)null)
				{
					return list;
				}
				HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				List<string> list2 = new List<string>();
				list2.AddRange(ConfigPanelLists.Parse(BlacklistedItems?.Value));
				list2.AddRange(ConfigPanelLists.Parse(RefinableModdedItems?.Value));
				foreach (KeyValuePair<string, string> item in ParseMap(ModdedItemIdols?.Value))
				{
					list2.Add(item.Key);
				}
				foreach (string item2 in list2)
				{
					if (list.Count >= 1000)
					{
						break;
					}
					if (hashSet.Add(item2) && (Object)(object)db.GetItemPrefab(item2) == (Object)null)
					{
						list.Add(item2);
					}
				}
				return list;
			}

			private static Requirement IdolOf(Recipe recipe)
			{
				if ((Object)(object)recipe == (Object)null || recipe.m_resources == null)
				{
					return null;
				}
				Requirement[] resources = recipe.m_resources;
				foreach (Requirement val in resources)
				{
					if (val != null && val.m_upgraderResource && (Object)(object)val.m_resItem != (Object)null)
					{
						return val;
					}
				}
				return null;
			}

			private static string PrefabOf(Component c)
			{
				if (!((Object)(object)c != (Object)null) || !((Object)(object)c.gameObject != (Object)null))
				{
					return null;
				}
				return VanillaItems.StripClone(((Object)c.gameObject).name);
			}

			internal static float BaseStat(SharedData shared)
			{
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				//IL_001b: Invalid comparison between Unknown and I4
				if (shared.m_armor > 0f)
				{
					return shared.m_armor;
				}
				if ((int)shared.m_itemType == 5)
				{
					return shared.m_blockPower;
				}
				return ((DamageTypes)(ref shared.m_damages)).GetTotalDamage();
			}

			private static void Apply(ObjectDB db)
			{
				//IL_015d: Unknown result type (might be due to invalid IL or missing references)
				//IL_019c: Unknown result type (might be due to invalid IL or missing references)
				//IL_01a3: Expected I4, but got Unknown
				//IL_0176: Unknown result type (might be due to invalid IL or missing references)
				//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
				//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
				//IL_04d8: Unknown result type (might be due to invalid IL or missing references)
				//IL_04dd: Unknown result type (might be due to invalid IL or missing references)
				//IL_0575: Unknown result type (might be due to invalid IL or missing references)
				//IL_07e0: Unknown result type (might be due to invalid IL or missing references)
				RemoveAll();
				_items.Clear();
				_byPrefab.Clear();
				_version++;
				if (!VanillaItems.Known)
				{
					return;
				}
				Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
				Dictionary<ItemType, int[]> dictionary2 = new Dictionary<ItemType, int[]>();
				Dictionary<string, Requirement> dictionary3 = new Dictionary<string, Requirement>(StringComparer.Ordinal);
				Dictionary<string, Requirement> dictionary4 = new Dictionary<string, Requirement>(StringComparer.Ordinal);
				List<Sample> list = new List<Sample>();
				Dictionary<string, string> dictionary5 = new Dictionary<string, string>(StringComparer.Ordinal);
				int num = -1;
				foreach (Recipe recipe in db.m_recipes)
				{
					Requirement val = IdolOf(recipe);
					if (val == null || (Object)(object)recipe.m_item == (Object)null || recipe.m_item.m_itemData == null || recipe.m_item.m_itemData.m_shared == null)
					{
						continue;
					}
					SharedData shared = recipe.m_item.m_itemData.m_shared;
					string text = PrefabOf((Component)(object)val.m_resItem);
					if (!string.IsNullOrEmpty(shared.m_name) && !dictionary5.ContainsKey(shared.m_name))
					{
						dictionary5[shared.m_name] = text ?? "";
					}
					string prefabName = PrefabOf((Component)(object)recipe.m_item);
					if (!VanillaItems.IsVanilla(prefabName) || !TryParseIdol(text, out var tier, out var armor))
					{
						continue;
					}
					if (tier > num)
					{
						num = tier;
					}
					if (!dictionary2.TryGetValue(shared.m_itemType, out var value))
					{
						value = new int[2];
						dictionary2[shared.m_itemType] = value;
					}
					value[(!armor) ? 1u : 0u]++;
					string key = text + "|" + (int)shared.m_itemType;
					if (!dictionary3.ContainsKey(key))
					{
						dictionary3[key] = val;
					}
					if (!dictionary4.ContainsKey(text))
					{
						dictionary4[text] = val;
					}
					list.Add(new Sample
					{
						Type = shared.m_itemType,
						Stat = BaseStat(shared),
						Tier = tier,
						Armor = armor
					});
					Requirement[] resources = recipe.m_resources;
					foreach (Requirement val2 in resources)
					{
						if (val2 != null && !val2.m_upgraderResource && !((Object)(object)val2.m_resItem == (Object)null))
						{
							string text2 = PrefabOf((Component)(object)val2.m_resItem);
							if (!string.IsNullOrEmpty(text2) && (!dictionary.TryGetValue(text2, out var value2) || tier < value2))
							{
								dictionary[text2] = tier;
							}
						}
					}
				}
				for (int j = num + 1; j <= 20 && (!((Object)(object)db.GetItemPrefab(IdolPrefab(j, armor: true)) == (Object)null) || !((Object)(object)db.GetItemPrefab(IdolPrefab(j, armor: false)) == (Object)null)); j++)
				{
					num = j;
				}
				_maxTier = num;
				if (dictionary4.Count == 0)
				{
					Debug.LogWarning((object)"[ForgeOfPotential] Modded idols: no recipe of the game carries an idol, so items from other mods cannot get one.");
					return;
				}
				Dictionary<string, List<Recipe>> dictionary6 = new Dictionary<string, List<Recipe>>(StringComparer.OrdinalIgnoreCase);
				foreach (Recipe recipe2 in db.m_recipes)
				{
					string text3 = (((Object)(object)recipe2 != (Object)null) ? PrefabOf((Component)(object)recipe2.m_item) : null);
					if (!string.IsNullOrEmpty(text3))
					{
						if (!dictionary6.TryGetValue(text3, out var value3))
						{
							value3 = (dictionary6[text3] = new List<Recipe>());
						}
						value3.Add(recipe2);
					}
				}
				List<KeyValuePair<ModdedIdolInfo, Recipe>> list3 = new List<KeyValuePair<ModdedIdolInfo, Recipe>>();
				int num2 = 0;
				int num3 = 0;
				foreach (Recipe recipe3 in db.m_recipes)
				{
					if ((Object)(object)recipe3 == (Object)null || !recipe3.m_enabled || (Object)(object)recipe3.m_item == (Object)null || recipe3.m_item.m_itemData == null || recipe3.m_item.m_itemData.m_shared == null || recipe3.m_resources == null)
					{
						continue;
					}
					SharedData shared2 = recipe3.m_item.m_itemData.m_shared;
					string text4 = PrefabOf((Component)(object)recipe3.m_item);
					if (string.IsNullOrEmpty(text4) || string.IsNullOrEmpty(shared2.m_name) || VanillaItems.IsVanilla(text4) || _byPrefab.ContainsKey(text4))
					{
						continue;
					}
					if (shared2.m_maxQuality <= 1)
					{
						num3++;
						continue;
					}
					Requirement val3 = IdolOf(recipe3);
					string autoIdol = ((val3 != null) ? PrefabOf((Component)(object)val3.m_resItem) : null);
					bool flag = val3 != null;
					if (!flag && dictionary5.ContainsKey(shared2.m_name))
					{
						continue;
					}
					ModdedIdolInfo moddedIdolInfo = new ModdedIdolInfo();
					moddedIdolInfo.Prefab = text4;
					moddedIdolInfo.Token = shared2.m_name;
					moddedIdolInfo.Type = shared2.m_itemType;
					moddedIdolInfo.BaseStat = BaseStat(shared2);
					Requirement[] resources2 = recipe3.m_resources;
					foreach (Requirement val4 in resources2)
					{
						string text5 = ((val4 != null && !val4.m_upgraderResource) ? PrefabOf((Component)(object)val4.m_resItem) : null);
						if (!string.IsNullOrEmpty(text5) && !moddedIdolInfo.Ingredients.Contains(text5))
						{
							moddedIdolInfo.Ingredients.Add(text5);
						}
					}
					if (flag)
					{
						moddedIdolInfo.OwnIdol = true;
						moddedIdolInfo.AutoIdol = autoIdol;
					}
					else
					{
						if (!dictionary2.TryGetValue(shared2.m_itemType, out var value4) || value4[0] + value4[1] == 0)
						{
							num2++;
							continue;
						}
						bool armor2 = value4[0] >= value4[1];
						string bestMaterial;
						int num4 = TierFromMaterials(recipe3, dictionary, dictionary6, 0, new HashSet<string>(StringComparer.OrdinalIgnoreCase), out bestMaterial);
						if (num4 < 0)
						{
							num4 = TierFromClosestItem(list, shared2, armor2);
							moddedIdolInfo.Uncertain = true;
							bestMaterial = "";
						}
						if (num4 < 0)
						{
							num4 = 0;
						}
						moddedIdolInfo.ReasonMaterial = bestMaterial ?? "";
						moddedIdolInfo.AutoIdol = IdolPrefab(Math.Min(num4, Math.Max(0, num)), armor2);
					}
					_items.Add(moddedIdolInfo);
					_byPrefab[text4] = moddedIdolInfo;
					list3.Add(new KeyValuePair<ModdedIdolInfo, Recipe>(moddedIdolInfo, recipe3));
				}
				_items.Sort((ModdedIdolInfo a, ModdedIdolInfo b) => string.Compare(a.Prefab, b.Prefab, StringComparison.OrdinalIgnoreCase));
				bool flag2 = AllowRefiningModdedItems == null || AllowRefiningModdedItems.Value;
				List<KeyValuePair<string, string>> list4 = ParseMap(ModdedItemIdols?.Value);
				HashSet<string> hashSet = ItemListSet(BlacklistedItems?.Value);
				HashSet<string> hashSet2 = (flag2 ? null : ItemListSet(RefinableModdedItems?.Value));
				int num5 = 0;
				int num6 = 0;
				bool flag3 = VerboseLogging != null && VerboseLogging.Value;
				foreach (KeyValuePair<ModdedIdolInfo, Recipe> item2 in list3)
				{
					ModdedIdolInfo key2 = item2.Key;
					Recipe value5 = item2.Value;
					if (key2.OwnIdol)
					{
						continue;
					}
					string item = VanillaItems.StripClone(key2.Prefab);
					if (hashSet.Contains(item) || (!flag2 && !hashSet2.Contains(item)))
					{
						continue;
					}
					string prefab = key2.AutoIdol;
					string text6 = MapGet(list4, key2.Prefab);
					bool flag4 = false;
					if (text6 != null && TryParseIdol(text6, out var tier2, out var _) && tier2 <= num && (Object)(object)db.GetItemPrefab(text6) != (Object)null)
					{
						prefab = text6;
						flag4 = !string.Equals(text6, key2.AutoIdol, StringComparison.Ordinal);
					}
					if (!TryParseIdol(prefab, out var tier3, out var armor4))
					{
						continue;
					}
					Requirement val5 = FindTemplate(db, dictionary3, dictionary4, ref tier3, armor4, key2.Type);
					if (val5 != null)
					{
						Requirement val6 = CloneRequirement(val5);
						val6.m_upgraderResource = true;
						val6.m_recover = false;
						Requirement[] array = (Requirement[])(object)new Requirement[value5.m_resources.Length + 1];
						Array.Copy(value5.m_resources, array, value5.m_resources.Length);
						array[^1] = val6;
						value5.m_resources = array;
						_added.Add(new Added
						{
							Recipe = value5,
							Requirement = val6
						});
						num5++;
						if (flag4)
						{
							num6++;
						}
						if (flag3)
						{
							string text7 = (flag4 ? "chosen on the settings panel" : (key2.Uncertain ? "closest game item by base stat - worth a look" : ("material " + key2.ReasonMaterial)));
							Debug.Log((object)("[ForgeOfPotential] Modded idols: '" + key2.Prefab + "' (" + ((object)Unsafe.As<ItemType, ItemType>(ref key2.Type)/*cast due to .constrained prefix*/).ToString() + ") -> " + PrefabOf((Component)(object)val6.m_resItem) + " (" + text7 + ")."));
						}
					}
				}
				string text8 = "[ForgeOfPotential] Modded idols: " + _items.Count + " item(s) from other mods found, " + num5 + " can be refined at the forge (" + num6 + " with an idol chosen on the settings panel)" + ((num2 > 0) ? ("; " + num2 + " recipe(s) of kinds the game never refines were left alone") : "") + ((num3 > 0) ? ("; " + num3 + " item(s) with a maximum quality of 1 cannot be refined (the game never lists them)") : "") + ".";
				if (!string.Equals(text8, _lastSummary, StringComparison.Ordinal))
				{
					_lastSummary = text8;
					Debug.Log((object)text8);
				}
			}

			private static int TierFromMaterials(Recipe recipe, Dictionary<string, int> materialTier, Dictionary<string, List<Recipe>> recipesByItem, int depth, HashSet<string> visiting, out string bestMaterial)
			{
				int num = -1;
				bestMaterial = null;
				Requirement[] resources = recipe.m_resources;
				foreach (Requirement val in resources)
				{
					if (val == null || val.m_upgraderResource || (Object)(object)val.m_resItem == (Object)null)
					{
						continue;
					}
					string text = PrefabOf((Component)(object)val.m_resItem);
					if (string.IsNullOrEmpty(text))
					{
						continue;
					}
					if (!materialTier.TryGetValue(text, out var value))
					{
						value = -1;
						if (depth < 3 && visiting.Add(text) && recipesByItem.TryGetValue(text, out var value2))
						{
							foreach (Recipe item in value2)
							{
								if (!((Object)(object)item == (Object)null) && item.m_resources != null)
								{
									string bestMaterial2;
									int num2 = TierFromMaterials(item, materialTier, recipesByItem, depth + 1, visiting, out bestMaterial2);
									if (num2 > value)
									{
										value = num2;
									}
								}
							}
						}
					}
					if (value > num)
					{
						num = value;
						bestMaterial = text;
					}
				}
				return num;
			}

			private static int TierFromClosestItem(List<Sample> samples, SharedData shared, bool armor)
			{
				//IL_0023: 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)
				float num = BaseStat(shared);
				int num2 = -1;
				float num3 = float.MaxValue;
				foreach (Sample sample in samples)
				{
					if (sample.Type == shared.m_itemType && sample.Armor == armor)
					{
						float num4 = Math.Abs(sample.Stat - num);
						if (num4 < num3 || (num4 == num3 && sample.Tier > num2))
						{
							num3 = num4;
							num2 = sample.Tier;
						}
					}
				}
				return num2;
			}

			private static Requirement FindTemplate(ObjectDB db, Dictionary<string, Requirement> byType, Dictionary<string, Requirement> byIdol, ref int tier, bool armor, ItemType type)
			{
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_001c: Expected I4, but got Unknown
				for (int num = tier; num >= 0; num--)
				{
					string text = IdolPrefab(num, armor);
					int tier2 = (int)type;
					if (byType.TryGetValue(text + "|" + tier2, out var value) || byIdol.TryGetValue(text, out value))
					{
						tier = num;
						return value;
					}
					GameObject itemPrefab = db.GetItemPrefab(text);
					ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null);
					if ((Object)(object)val != (Object)null)
					{
						foreach (KeyValuePair<string, Requirement> item in byIdol)
						{
							if (TryParseIdol(item.Key, out tier2, out var armor2) && armor2 == armor)
							{
								Requirement val2 = CloneRequirement(item.Value);
								val2.m_resItem = val;
								tier = num;
								return val2;
							}
						}
					}
				}
				return null;
			}

			private static Requirement CloneRequirement(Requirement source)
			{
				//IL_0053: Unknown result type (might be due to invalid IL or missing references)
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				//IL_0064: Unknown result type (might be due to invalid IL or missing references)
				//IL_0070: Unknown result type (might be due to invalid IL or missing references)
				//IL_007c: 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_008b: Expected O, but got Unknown
				try
				{
					if (_memberwiseClone == null)
					{
						_memberwiseClone = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);
					}
					if (_memberwiseClone != null)
					{
						object? obj = _memberwiseClone.Invoke(source, null);
						Requirement val = (Requirement)((obj is Requirement) ? obj : null);
						if (val != null)
						{
							return val;
						}
					}
				}
				catch
				{
				}
				return new Requirement
				{
					m_resItem = source.m_resItem,
					m_amount = source.m_amount,
					m_amountPerLevel = source.m_amountPerLevel,
					m_recover = false,
					m_upgraderResource = true
				};
			}
		}

		internal static class ConfigPanelLists
		{
			internal static List<string> Parse(string text)
			{
				List<string> list = new List<string>();
				if (string.IsNullOrEmpty(text))
				{
					return list;
				}
				string[] array = text.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
				foreach (string text2 in array)
				{
					string text3 = text2.Trim();
					if (text3.Length == 0)
					{
						continue;
					}
					bool flag = false;
					foreach (string item in list)
					{
						if (string.Equals(item, text3, StringComparison.OrdinalIgnoreCase))
						{
							flag = true;
							break;
						}
					}
					if (!flag)
					{
						list.Add(text3);
					}
				}
				return list;
			}
		}

		[HarmonyPatch(typeof(ObjectDB), "Awake")]
		private class ObjectDB_Awake_ModdedIdolsPatch
		{
			[HarmonyPostfix]
			[HarmonyPriority(0)]
			private static void Postfix(ObjectDB __instance)
			{
				ModdedIdols.EnsureApplied(__instance);
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "UpdateCraftingPanel")]
		private class InventoryGui_UpdateCraftingPanel_ModdedIdolsPatch
		{
			private static void Prefix()
			{
				ModdedIdols.EnsureApplied(ObjectDB.instance);
			}
		}

		internal static class RefinementPower
		{
			internal static int ForgeLevels(ItemData item, int quality)
			{
				if (item == null || item.m_shared == null)
				{
					return 0;
				}
				int num = Math.Max(1, item.m_shared.m_maxQuality);
				if (quality <= num)
				{
					return 0;
				}
				return quality - num;
			}

			internal static float Extra(ConfigEntry<float> setting)
			{
				if (setting == null)
				{
					return 0f;
				}
				float value = setting.Value;
				if (float.IsNaN(value) || float.IsInfinity(value))
				{
					return 0f;
				}
				value = Math.Max(0f, Math.Min(500f, value));
				return value / 100f - 1f;
			}

			internal static bool IsArmorPiece(ItemType type)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				//IL_0004: Invalid comparison between Unknown and I4
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				//IL_000b: Invalid comparison between Unknown and I4
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Invalid comparison between Unknown and I4
				if (type - 6 <= 1 || type - 11 <= 1 || (int)type == 17)
				{
					return true;
				}
				return false;
			}

			internal static void ScaleDamage(ItemData item, int quality, ref DamageTypes damage)
			{
				//IL_0012: Unknown result type (might be due to invalid IL or missing references)
				//IL_0017: Unknown result type (might be due to invalid IL or missing references)
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Invalid comparison between Unknown and I4
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0051: Unknown result type (might be due to invalid IL or missing references)
				//IL_0056: 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_0084: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e4: 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)
				//IL_0124: Unknown result type (might be due to invalid IL or missing references)
				//IL_0144: Unknown result type (might be due to invalid IL or missing references)
				//IL_0164: Unknown result type (might be due to invalid IL or missing references)
				if (item == null || item.m_shared == null)
				{
					return;
				}
				ItemType itemType = item.m_shared.m_itemType;
				if ((int)itemType == 5 || IsArmorPiece(itemType))
				{
					return;
				}
				float num = Extra(RefinementStatMultiplierWeapons);
				if (num != 0f)
				{
					int num2 = ForgeLevels(item, quality);
					if (num2 > 0)
					{
						float num3 = num * (float)num2;
						DamageTypes damagesPerLevel = item.m_shared.m_damagesPerLevel;
						damage.m_damage = Math.Max(0f, damage.m_damage + damagesPerLevel.m_damage * num3);
						damage.m_blunt = Math.Max(0f, damage.m_blunt + damagesPerLevel.m_blunt * num3);
						damage.m_slash = Math.Max(0f, damage.m_slash + damagesPerLevel.m_slash * num3);
						damage.m_pierce = Math.Max(0f, damage.m_pierce + damagesPerLevel.m_pierce * num3);
						damage.m_fire = Math.Max(0f, damage.m_fire + damagesPerLevel.m_fire * num3);
						damage.m_frost = Math.Max(0f, damage.m_frost + damagesPerLevel.m_frost * num3);
						damage.m_lightning = Math.Max(0f, damage.m_lightning + damagesPerLevel.m_lightning * num3);
						damage.m_poison = Math.Max(0f, damage.m_poison + damagesPerLevel.m_poison * num3);
						damage.m_spirit = Math.Max(0f, damage.m_spirit + damagesPerLevel.m_spirit * num3);
					}
				}
			}

			internal static void ScaleArmor(ItemData item, int quality, ref float armor)
			{
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				if (item != null && item.m_shared != null && IsArmorPiece(item.m_shared.m_itemType))
				{
					float num = Extra(RefinementStatMultiplierArmor);
					int num2 = ForgeLevels(item, quality);
					if (num != 0f && num2 > 0)
					{
						armor = Math.Max(0f, armor + item.m_shared.m_armorPerLevel * num * (float)num2);
					}
				}
			}

			internal static void ScaleBlockPower(ItemData item, int quality, ref float blockPower)
			{
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_0017: Invalid comparison between Unknown and I4
				if (item != null && item.m_shared != null && (int)item.m_shared.m_itemType == 5)
				{
					float num = Extra(RefinementStatMultiplierShields);
					int num2 = ForgeLevels(item, quality);
					if (num != 0f && num2 > 0)
					{
						blockPower = Math.Max(0f, blockPower + item.m_shared.m_blockPowerPerLevel * num * (float)num2);
					}
				}
			}

			internal static void ScaleDeflection(ItemData item, int quality, ref float force)
			{
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_0017: Invalid comparison between Unknown and I4
				if (item != null && item.m_shared != null && (int)item.m_shared.m_itemType == 5)
				{
					float num = Extra(RefinementStatMultiplierShields);
					int num2 = ForgeLevels(item, quality);
					if (num != 0f && num2 > 0)
					{
						force = Math.Max(0f, force + item.m_shared.m_deflectionForcePerLevel * num * (float)num2);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Requirement), "GetAmount")]
		private class Requirement_GetAmount_Patch
		{
			private static void Postfix(Requirement __instance, int qualityLevel, ref int __result)
			{
				if (EnableUpgradeChanceAndCostTweaks.Value && __instance != null && __instance.m_upgraderResource)
				{
					int num = qualityLevel - 1;
					int num2 = Math.Max(1, CostScalingLevelStart.Value);
					long num3 = CostStart.Value;
					if (CostIncreaseInterval.Value > 0 && num >= num2)
					{
						num3 += (long)((num - num2) / CostIncreaseInterval.Value + 1) * (long)CostIncreasePerInterval.Value;
					}
					__result = (int)Math.Max(0L, Math.Min(num3, 2147483647L));
				}
			}
		}

		[HarmonyPatch(typeof(ObjectDB), "Awake")]
		private class ObjectDB_AddConfigRecipesMulti
		{
			private const string LogPrefix = "[ForgeOfPotential] ConfigRecipes: ";

			private const string RecipeNameSuffix = "_ConfigRecipe";

			private static PropertyInfo _inventoryGuiInstanceProp;

			private static MethodInfo _updateCraftingPanelMethod;

			private static ParameterInfo[] _updateCraftingPanelParams;

			private static bool _reflectionCached;

			private static void Postfix(ObjectDB __instance)
			{
				ApplyConfigRecipes(__instance, removeExistingFirst: false);
			}

			public static void ApplyConfigRecipes(ObjectDB __instance, bool removeExistingFirst)
			{
				try
				{
					if ((Object)(object)__instance == (Object)null)
					{
						Debug.LogWarning((object)"[ForgeOfPotential] ConfigRecipes: ObjectDB instance is null.");
					}
					else
					{
						if ((Object)(object)__instance.GetItemPrefab("Wood") == (Object)null)
						{
							return;
						}
						int num = 0;
						if (removeExistingFirst)
						{
							num = __instance.m_recipes.RemoveAll((Recipe r) => (Object)(object)r != (Object)null && ((Object)r).name != null && ((Object)r).name.EndsWith("_ConfigRecipe", StringComparison.Ordinal));
							if (num > 0)
							{
								Debug.Log((object)("[ForgeOfPotential] ConfigRecipes: " + $"Removed {num} previously-added recipe(s) to re-apply updated config."));
							}
						}
						if (!EnableCustomRecipes.Value)
						{
							Debug.Log((object)"[ForgeOfPotential] ConfigRecipes: EnableCustomRecipes is false; skipping recipe registration.");
							if (num > 0)
							{
								RefreshCraftingUI();
							}
							return;
						}
						var array = new[]
						{
							new
							{
								Name = "Upgrader0Armor",
								RecipeCfg = Recipe_Upgrader0Armor
							},
							new
							{
								Name = "Upgrader0Weapon",
								RecipeCfg = Recipe_Upgrader0Weapon
							},
							new
							{
								Name = "Upgrader1Armor",
								RecipeCfg = Recipe_Upgrader1Armor
							},
							new
							{
								Name = "Upgrader1Weapon",
								RecipeCfg = Recipe_Upgrader1Weapon
							},
							new
							{
								Name = "Upgrader2Armor",
								RecipeCfg = Recipe_Upgrader2Armor
							},
							new
							{
								Name = "Upgrader2Weapon",
								RecipeCfg = Recipe_Upgrader2Weapon
							},
							new
							{
								Name = "Upgrader3Armor",
								RecipeCfg = Recipe_Upgrader3Armor
							},
							new
							{
								Name = "Upgrader3Weapon",
								RecipeCfg = Recipe_Upgrader3Weapon
							},
							new
							{
								Name = "Upgrader4Armor",
								RecipeCfg = Recipe_Upgrader4Armor
							},
							new
							{
								Name = "Upgrader4Weapon",
								RecipeCfg = Recipe_Upgrader4Weapon
							},
							new
							{
								Name = "Upgrader5Armor",
								RecipeCfg = Recipe_Upgrader5Armor
							},
							new
							{
								Name = "Upgrader5Weapon",
								RecipeCfg = Recipe_Upgrader5Weapon
							},
							new
							{
								Name = "Upgrader6Armor",
								RecipeCfg = Recipe_Upgrader6Armor
							},
							new
							{
								Name = "Upgrader6Weapon",
								RecipeCfg = Recipe_Upgrader6Weapon
							},
							new
							{
								Name = "Upgrader7Armor",
								RecipeCfg = Recipe_Upgrader7Armor
							},
							new
							{
								Name = "Upgrader7Weapon",
								RecipeCfg = Recipe_Upgrader7Weapon
							}
						};
						CraftingStation val = ResolveCraftingStation(__instance, Station_Global?.Value?.Trim());
						if ((Object)(object)val != (Object)null)
						{
							Debug.Log((object)("[ForgeOfPotential] ConfigRecipes: Resolved global crafting station: '" + (val.m_name ?? ((Object)((Component)val).gameObject).name) + "'"));
						}
						else if (!string.IsNullOrEmpty(Station_Global?.Value))
						{
							Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Global station '" + Station_Global.Value + "' was not found; recipes will be craftable by hand."));
						}
						int num2 = 0;
						var array2 = array;
						foreach (var anon in array2)
						{
							string prefabName = anon.Name;
							ConfigEntry<string> recipeCfg = anon.RecipeCfg;
							if (recipeCfg == null)
							{
								Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: No config for " + prefabName + "; skipping."));
								continue;
							}
							string text = recipeCfg.Value?.Trim();
							if (string.IsNullOrEmpty(text))
							{
								Debug.Log((object)("[ForgeOfPotential] ConfigRecipes: Empty ingredient list for " + prefabName + "; skipping."));
								continue;
							}
							GameObject itemPrefab = __instance.GetItemPrefab(prefabName);
							if ((Object)(object)itemPrefab == (Object)null)
							{
								Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Target prefab '" + prefabName + "' not found in ObjectDB; skipping."));
								continue;
							}
							ItemDrop targetItem = itemPrefab.GetComponent<ItemDrop>();
							if ((Object)(object)targetItem == (Object)null)
							{
								Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Target prefab '" + prefabName + "' has no ItemDrop; skipping."));
								continue;
							}
							if (__instance.m_recipes.Exists((Recipe r) => (Object)(object)r != (Object)null && (Object)(object)r.m_item != (Object)null && ((Object)(object)r.m_item == (Object)(object)targetItem || string.Equals(((Object)r.m_item).name, prefabName, StringComparison.Ordinal))))
							{
								Debug.Log((object)("[ForgeOfPotential] ConfigRecipes: Recipe for '" + prefabName + "' already exists; skipping."));
								continue;
							}
							Requirement[] array3 = ParseRequirements(text, __instance).ToArray();
							if (array3 == null || array3.Length == 0)
							{
								Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: No valid ingredients parsed for '" + prefabName + "' from '" + text + "'; skipping."));
								continue;
							}
							Recipe val2 = ScriptableObject.CreateInstance<Recipe>();
							((Object)val2).name = prefabName + "_ConfigRecipe";
							val2.m_amount = 1;
							val2.m_item = targetItem;
							val2.m_enabled = true;
							val2.m_craftingStation = val;
							val2.m_minStationLevel = 0;
							val2.m_repairStation = null;
							val2.m_resources = array3;
							__instance.m_recipes.Add(val2);
							num2++;
							Debug.Log((object)("[ForgeOfPotential] ConfigRecipes: Added recipe for '" + prefabName + "' requiring " + string.Join(", ", array3.Select((Requirement req) => $"{GetReqName(req)} x{req.m_amount}")) + "."));
						}
						if (num2 <= 0)
						{
							if (num > 0)
							{
								RefreshCraftingUI();
							}
						}
						else
						{
							Debug.Log((object)string.Format("{0}Finished adding {1} recipe(s). Total recipes now: {2}", "[ForgeOfPotential] ConfigRecipes: ", num2, __instance.m_recipes.Count));
							RefreshCraftingUI();
						}
					}
				}
				catch (Exception arg)
				{
					Debug.LogError((object)string.Format("{0}Exception: {1}", "[ForgeOfPotential] ConfigRecipes: ", arg));
				}
			}

			private static void RefreshCraftingUI()
			{
				try
				{
					if (!_reflectionCached)
					{
						Type typeFromHandle = typeof(InventoryGui);
						_inventoryGuiInstanceProp = typeFromHandle.GetProperty("instance", BindingFlags.Static | BindingFlags.Public);
						_updateCraftingPanelMethod = typeFromHandle.GetMethod("UpdateCraftingPanel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
						_updateCraftingPanelParams = _updateCraftingPanelMethod?.GetParameters() ?? Array.Empty<ParameterInfo>();
						_reflectionCached = true;
					}
					object obj = _inventoryGuiInstanceProp?.GetValue(null);
					if (obj != null && !(_updateCraftingPanelMethod == null))
					{
						object[] array = new object[_updateCraftingPanelParams.Length];
						for (int i = 0; i < _updateCraftingPanelParams.Length; i++)
						{
							array[i] = (_updateCraftingPanelParams[i].ParameterType.IsValueType ? Activator.CreateInstance(_updateCraftingPanelParams[i].ParameterType) : null);
						}
						_updateCraftingPanelMethod.Invoke(obj, array);
						Debug.Log((object)"[ForgeOfPotential] ConfigRecipes: Requested InventoryGui.UpdateCraftingPanel()");
					}
				}
				catch
				{
				}
			}

			private static List<Requirement> ParseRequirements(string cfgValue, ObjectDB odb)
			{
				//IL_011d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0122: 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_0132: Unknown result type (might be due to invalid IL or missing references)
				//IL_0139: 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_014c: Expected O, but got Unknown
				List<Requirement> list = new List<Requirement>();
				if (string.IsNullOrEmpty(cfgValue))
				{
					return list;
				}
				string[] array = cfgValue.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
				string[] array2 = array;
				foreach (string text in array2)
				{
					string text2 = text.Trim();
					if (string.IsNullOrEmpty(text2))
					{
						continue;
					}
					string[] array3 = text2.Split(new char[1] { ':' }, StringSplitOptions.RemoveEmptyEntries);
					if (array3.Length != 2)
					{
						Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Invalid ingredient token '" + text2 + "'. Use 'PrefabName:Amount'."));
						continue;
					}
					string text3 = array3[0].Trim();
					if (!int.TryParse(array3[1].Trim(), out var result) || result <= 0)
					{
						Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Invalid amount in token '" + text2 + "'. Must be positive integer."));
						continue;
					}
					GameObject val = odb.GetItemPrefab(text3) ?? GetPrefabFromZNet(text3);
					if ((Object)(object)val == (Object)null)
					{
						Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Ingredient prefab '" + text3 + "' not found in ObjectDB or ZNetScene."));
						continue;
					}
					ItemDrop component = val.GetComponent<ItemDrop>();
					if ((Object)(object)component == (Object)null)
					{
						Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Ingredient prefab '" + text3 + "' missing ItemDrop component; skipping."));
						continue;
					}
					list.Add(new Requirement
					{
						m_amount = result,
						m_resItem = component,
						m_amountPerLevel = 0,
						m_upgraderResource = false,
						m_recover = false
					});
				}
				return list;
			}

			private static GameObject GetPrefabFromZNet(string name)
			{
				try
				{
					Type typeFromHandle = typeof(ZNetScene);
					object obj = typeFromHandle.GetProperty("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
					if (typeFromHandle.GetField("m_prefabs", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj) is List<GameObject> list)
					{
						return list.Find((GameObject g) => string.Equals(((Object)(object)g != (Object)null) ? ((Object)g).name : null, name, StringComparison.Ordinal));
					}
				}
				catch
				{
				}
				return null;
			}

			private static CraftingStation ResolveCraftingStation(ObjectDB odb, string stationName)
			{
				if (string.IsNullOrEmpty(stationName))
				{
					return null;
				}
				try
				{
					foreach (Recipe recipe in odb.m_recipes)
					{
						if ((Object)(object)recipe?.m_craftingStation != (Object)null)
						{
							CraftingStation craftingStation = recipe.m_craftingStation;
							if (string.Equals(craftingStation.m_name, stationName, StringComparison.Ordinal) || string.Equals(((Object)((Component)craftingStation).gameObject).name, stationName, StringComparison.Ordinal))
							{
								return craftingStation;
							}
						}
					}
					foreach (GameObject item in odb.m_items)
					{
						if (!((Object)(object)item == (Object)null))
						{
							CraftingStation component = item.GetComponent<CraftingStation>();
							if ((Object)(object)component != (Object)null && (string.Equals(component.m_name, stationName, StringComparison.Ordinal) || string.Equals(((Object)item).name, stationName, StringComparison.Ordinal)))
							{
								return component;
							}
						}
					}
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)string.Format("{0}ResolveCraftingStation exception: {1}", "[ForgeOfPotential] ConfigRecipes: ", arg));
				}
				return null;
			}

			private static string GetReqName(Requirement req)
			{
				if (req == null)
				{
					return "<null>";
				}
				try
				{
					ItemDrop resItem = req.m_resItem;
					if ((Object)(object)resItem != (Object)null)
					{
						return ((Object)((Component)resItem).gameObject).name;
					}
				}
				catch
				{
				}
				return "<unknown>";
			}
		}

		private static class SimpleConfigSync
		{
			private const string RpcName = "OdinBet_ConfigSync";

			private const int ProtocolVersion = 19;

			private static readonly List<Action<ZPackage>> _writers = new List<Action<ZPackage>>();

			private static readonly List<Action<ZPackage>> _applies = new List<Action<ZPackage>>();

			private static readonly List<Action> _restores = new List<Action>();

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

			private static ZRoutedRpc _registeredOn;

			private static bool _appliedFromServer;

			private static readonly List<Action<bool>> _ownSwaps = new List<Action<bool>>();

			private static bool _swapping;

			private static bool _ownSaveNeeded;

			private static bool _saveOnSetBeforeSession = true;

			private static long _mismatchServerUid;

			private const float SyncGraceSeconds = 30f;

			private static float? _syncGraceStart;

			private const string HelloRpcName = "OdinBet_ModHello";

			private const float HelloWaitSeconds = 30f;

			private static readonly Dictionary<ZNetPeer, float> _awaitingHello = new Dictionary<ZNetPeer, float>();

			private static readonly List<ZNetPeer> _kickQueue = new List<ZNetPeer>();

			internal static bool ServerHasMatchingMod => _appliedFromServer;

			internal static bool SyncGraceActive
			{
				get
				{
					if (_appliedFromServer || !_syncGraceStart.HasValue)
					{
						return false;
					}
					ZNet instance = ZNet.instance;
					if ((Object)(object)instance != (Object)null && !instance.IsServer())
					{
						return Time.unscaledTime - _syncGraceStart.Value < 30f;
					}
					return false;
				}
			}

			internal static bool ServerRulesNotApplied
			{
				get
				{
					if (_mismatchServerUid == 0L || _appliedFromServer)
					{
						return false;
					}
					ZNet instance = ZNet.instance;
					if ((Object)(object)instance == (Object)null || instance.IsServer())
					{
						return false;
					}
					ZNetPeer serverPeer = instance.GetServerPeer();
					if (serverPeer != null)
					{
						return serverPeer.m_uid == _mismatchServerUid;
					}
					return false;
				}
			}

			internal static bool IsSynced(ConfigEntryBase entry)
			{
				if (entry != null)
				{
					return _entries.Contains(entry);
				}
				return false;
			}

			internal static IEnumerable<ConfigEntryBase> SyncedEntries()
			{
				return _entries.AsReadOnly();
			}

			public static void Register<T>(ConfigEntry<T> entry)
			{
				T original = entry.Value;
				T authoritative = original;
				_entries.Add((ConfigEntryBase)(object)entry);
				_writers.Add(delegate(ZPackage pkg)
				{
					WriteValue(pkg, entry.Value);
				});
				_applies.Add(delegate(ZPackage pkg)
				{
					authoritative = ReadValue<T>(pkg);
					entry.Value = authoritative;
				});
				_restores.Add(delegate
				{
					authoritative = original;
					SetWithoutSaving(entry, original);
				});
				_ownSwaps.Add(delegate(bool own)
				{
					SetWithoutSaving(entry, own ? original : authoritative);
				});
				entry.SettingChanged += delegate
				{
					if (!_swapping)
					{
						if (!_appliedFromServer)
						{
							original = entry.Value;
							authoritative = original;
						}
						else if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer() && !EqualityComparer<T>.Default.Equals(entry.Value, authoritative))
						{
							Debug.LogWarning((object)("[ForgeOfPotential] ConfigSync: '" + ((ConfigEntryBase)entry).Definition.Section + "/" + ((ConfigEntryBase)entry).Definition.Key + "' was changed locally while connected to a server (Configuration Manager, a reloaded .cfg, or another mod); reverting to the server's value."));
							SetWithoutSaving(entry, authoritative);
						}
					}
				};
			}

			private static void WriteValue<T>(ZPackage pkg, T value)
			{
				if (!(value is bool flag))
				{
					if (!(value is int num))
					{
						if (!