Decompiled source of OdinBet ForgeOfPotential v1.3.0

plugins/OdinBet_ForgeOfPotential.dll

Decompiled 17 hours 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 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.3.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.3.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace OdinBet_ForgeOfPotential
{
	[BepInPlugin("OdinBet.ForgeOfPotential", "OdinBet - Forge of Potential", "1.3.0")]
	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;
					}
					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);
						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)
					{
						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}.");
					}
				}
				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)
			{
				if (string.IsNullOrEmpty(prefabName))
				{
					return false;
				}
				string text = BlacklistedItems?.Value;
				if (string.IsNullOrWhiteSpace(text))
				{
					return false;
				}
				string[] array = text.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
				string[] array2 = array;
				foreach (string text2 in array2)
				{
					if (string.Equals(text2.Trim(), prefabName, StringComparison.OrdinalIgnoreCase))
					{
						return true;
					}
				}
				return false;
			}

			private static void ShowBlacklistMessage(ItemData item)
			{
				try
				{
					string text = MessageTranslations.Resolve(ItemBlacklistedMessage) ?? "";
					if (!string.IsNullOrEmpty(text))
					{
						string itemToken = ((item.m_shared != null) ? item.m_shared.m_name : "");
						string text2 = text.Replace("{ItemName}", UpgradeNotifications.LocalizeItemName(itemToken));
						MessageHud instance = MessageHud.instance;
						if (instance != null)
						{
							instance.ShowMessage((MessageType)2, text2, 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)
				{
					Debug.Log((object)$"[ForgeOfPotential] Failed refinement: the game asked for level {quality}, forced to {num3} (level before={activeUpgrade.QualityBefore}, LevelsLostOnFailure={num2}, MinimumLevelOnFailure={num}).");
				}
				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(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 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}'.");
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] Translation loading failed, falling back to English defaults: {arg}");
				}
			}

			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
				{
				}
			}
		}

		[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 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
				{
					Type typeFromHandle = typeof(InventoryGui);
					object obj = typeFromHandle.GetProperty("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
					if (obj == null)
					{
						return;
					}
					MethodInfo method = typeFromHandle.GetMethod("UpdateCraftingPanel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if (!(method == null))
					{
						ParameterInfo[] parameters = method.GetParameters();
						object[] array = new object[parameters.Length];
						for (int i = 0; i < parameters.Length; i++)
						{
							array[i] = (parameters[i].ParameterType.IsValueType ? Activator.CreateInstance(parameters[i].ParameterType) : null);
						}
						method.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 = 16;

			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 ZRoutedRpc _registeredOn;

			private static bool _appliedFromServer;

			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;

			public static void Register<T>(ConfigEntry<T> entry)
			{
				T original = entry.Value;
				T authoritative = original;
				_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);
				});
				entry.SettingChanged += delegate
				{
					if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer() && _appliedFromServer && !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 (!(value is float num2))
						{
							if (!(value is string text))
							{
								throw new NotSupportedException($"OdinBet ConfigSync: unsupported type {typeof(T)}");
							}
							pkg.Write(text ?? "");
						}
						else
						{
							pkg.Write(num2);
						}
					}
					else
					{
						pkg.Write(num);
					}
				}
				else
				{
					pkg.Write(flag);
				}
			}

			private static T ReadValue<T>(ZPackage pkg)
			{
				object obj;
				if (typeof(T) == typeof(bool))
				{
					obj = pkg.ReadBool();
				}
				else if (typeof(T) == typeof(int))
				{
					obj = pkg.ReadInt();
				}
				else if (typeof(T) == typeof(float))
				{
					obj = pkg.ReadSingle();
				}
				else
				{
					if (!(typeof(T) == typeof(string)))
					{
						throw new NotSupportedException($"OdinBet ConfigSync: unsupported type {typeof(T)}");
					}
					obj = pkg.ReadString();
				}
				return (T)obj;
			}

			private static void SetWithoutSaving<T>(ConfigEntry<T> entry, T value)
			{
				bool saveOnConfigSet = PluginConfig != null && PluginConfig.SaveOnConfigSet;
				try
				{
					if (PluginConfig != null)
					{
						PluginConfig.SaveOnConfigSet = false;
					}
					entry.Value = value;
				}
				finally
				{
					if (PluginConfig != null)
					{
						PluginConfig.SaveOnConfigSet = saveOnConfigSet;
					}
				}
			}

			public static void RegisterRPC()
			{
				try
				{
					ZRoutedRpc instance = ZRoutedRpc.instance;
					if (instance != null && instance != _registeredOn)
					{
						instance.Register<ZPackage>("OdinBet_ConfigSync", (Action<long, ZPackage>)RPC_ApplyConfig);
						_registeredOn = instance;
					}
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] ConfigSync could not register RPC: {arg}");
				}
			}

			public static void SendTo(long peerID)
			{
				//IL_0025: Unknown result type (might be due to invalid IL or missing references)
				//IL_002b: Expected O, but got Unknown
				try
				{
					if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZRoutedRpc.instance == null)
					{
						return;
					}
					ZPackage val = new ZPackage();
					val.Write(16);
					val.Write(_writers.Count);
					foreach (Action<ZPackage> writer in _writers)
					{
						writer(val);
					}
					ZRoutedRpc.instance.InvokeRoutedRPC(peerID, "OdinBet_ConfigSync", new object[1] { val });
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] ConfigSync send failed: {arg}");
				}
			}

			private static void RPC_ApplyConfig(long sender, ZPackage pkg)
			{
				try
				{
					if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
					{
						return;
					}
					ZNet instance = ZNet.instance;
					ZNetPeer val = ((instance != null) ? instance.GetServerPeer() : null);
					if (val == null || sender != val.m_uid)
					{
						Debug.LogWarning((object)$"[ForgeOfPotential] ConfigSync: ignored a config packet that did not come from the server (sender={sender}).");
						return;
					}
					SendHello(val);
					HallOfFame.RegisterClientRpc(val);
					int num = pkg.ReadInt();
					if (num != 16)
					{
						Debug.LogWarning((object)"[ForgeOfPotential] ConfigSync version mismatch (mod versions differ between client and server); ignoring server config.");
						return;
					}
					int num2 = pkg.ReadInt();
					if (num2 != _applies.Count)
					{
						Debug.LogWarning((object)$"[ForgeOfPotential] ConfigSync entry count mismatch (server sent {num2}, this client expects {_applies.Count}); ignoring server config.");
						return;
					}
					bool saveOnConfigSet = PluginConfig != null && PluginConfig.SaveOnConfigSet;
					try
					{
						if (PluginConfig != null)
						{
							PluginConfig.SaveOnConfigSet = false;
						}
						foreach (Action<ZPackage> apply in _applies)
						{
							apply(pkg);
						}
					}
					finally
					{
						if (PluginConfig != null)
						{
							PluginConfig.SaveOnConfigSet = saveOnConfigSet;
						}
					}
					_appliedFromServer = true;
					Debug.Log((object)"[ForgeOfPotential] ConfigSync: applied server config for this session.");
					if ((Object)(object)ObjectDB.instance != (Object)null)
					{
						ObjectDB_AddConfigRecipesMulti.ApplyConfigRecipes(ObjectDB.instance, removeExistingFirst: true);
					}
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] ConfigSync apply failed: {arg}");
				}
			}

			public static void RestoreLocal()
			{
				if (!_appliedFromServer)
				{
					return;
				}
				foreach (Action restore in _restores)
				{
					try
					{
						restore();
					}
					catch
					{
					}
				}
				_appliedFromServer = false;
			}

			public static void BeginHandshake(ZNetPeer peer, ZRpc rpc)
			{
				if (peer == null || peer.m_uid == 0L)
				{
					return;
				}
				try
				{
					if (peer != null && rpc != null)
					{
						rpc.Register<string, int>("OdinBet_ModHello", (Action<ZRpc, string, int>)delegate(ZRpc connection, string clientVersion, int clientProtocol)
						{
							OnHello(peer, clientVersion, clientProtocol);
						});
						if (RequireModToJoin != null && RequireModToJoin.Value)
						{
							_awaitingHello[peer] = Time.unscaledTime;
						}
					}
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] Version lock could not start for a connecting player: {arg}");
				}
				try
				{
					HallOfFame.RegisterServerRpc(peer, rpc);
				}
				catch (Exception arg2)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] Hall of Fame could not listen to a connecting player: {arg2}");
				}
				if (peer != null)
				{
					SendTo(peer.m_uid);
				}
			}

			private static void SendHello(ZNetPeer serverPeer)
			{
				try
				{
					if (serverPeer != null)
					{
						ZRpc rpc = serverPeer.m_rpc;
						if (rpc != null)
						{
							rpc.Invoke("OdinBet_ModHello", new object[2] { "1.3.0", 16 });
						}
					}
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] Could not send the mod version to the server: {arg}");
				}
			}

			private static void OnHello(ZNetPeer peer, string clientVersion, int clientProtocol)
			{
				try
				{
					if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
					{
						return;
					}
					_awaitingHello.Remove(peer);
					if (KickDifferentModVersion != null && KickDifferentModVersion.Value && (!string.Equals(clientVersion, "1.3.0", StringComparison.Ordinal) || clientProtocol != 16))
					{
						Debug.LogWarning((object)string.Format("[ForgeOfPotential] Version lock: '{0}' runs mod version {1} (protocol {2}), this server runs {3} (protocol {4}). Disconnecting them. Set KickDifferentModVersion = false to allow this.", GetPlayerName(peer), clientVersion, clientProtocol, "1.3.0", 16));
						if (!_kickQueue.Contains(peer))
						{
							_kickQueue.Add(peer);
						}
					}
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] Version lock check failed: {arg}");
				}
			}

			public static void TickServer()
			{
				if (_awaitingHello.Count == 0 && _kickQueue.Count == 0)
				{
					return;
				}
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null || !instance.IsServer())
				{
					_awaitingHello.Clear();
					_kickQueue.Clear();
					return;
				}
				try
				{
					if (_awaitingHello.Count > 0)
					{
						float unscaledTime = Time.unscaledTime;
						List<ZNetPeer> list = null;
						foreach (KeyValuePair<ZNetPeer, float> item in _awaitingHello)
						{
							if (unscaledTime - item.Value >= 30f)
							{
								if (list == null)
								{
									list = new List<ZNetPeer>();
								}
								list.Add(item.Key);
							}
						}
						if (list != null)
						{
							foreach (ZNetPeer item2 in list)
							{
								_awaitingHello.Remove(item2);
								if (RequireModToJoin != null && RequireModToJoin.Value && ZNet_RPC_PeerInfo_SendConfig.StillConnected(instance, item2))
								{
									Debug.LogWarning((object)$"[ForgeOfPotential] Version lock: '{GetPlayerName(item2)}' did not answer the mod version check within {30f:0} s (no mod, or a version older than 1.1.0). Disconnecting them because RequireModToJoin is on.");
									if (!_kickQueue.Contains(item2))
									{
										_kickQueue.Add(item2);
									}
								}
							}
						}
					}
					if (_kickQueue.Count <= 0)
					{
						return;
					}
					List<ZNetPeer> list2 = new List<ZNetPeer>(_kickQueue);
					_kickQueue.Clear();
					foreach (ZNetPeer item3 in list2)
					{
						if (ZNet_RPC_PeerInfo_SendConfig.StillConnected(instance, item3))
						{
							KickPeer(instance, item3);
						}
					}
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] Version lock tick failed: {arg}");
				}
			}

			private static void KickPeer(ZNet znet, ZNetPeer peer)
			{
				BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
				string[] array = new string[2] { "Kick", "Disconnect" };
				foreach (string b in array)
				{
					MethodInfo[] methods = typeof(ZNet).GetMethods(bindingAttr);
					foreach (MethodInfo methodInfo in methods)
					{
						if (string.Equals(methodInfo.Name, b, StringComparison.Ordinal))
						{
							ParameterInfo[] parameters = methodInfo.GetParameters();
							if (parameters.Length == 1 && parameters[0].ParameterType == typeof(ZNetPeer))
							{
								methodInfo.Invoke(znet, new object[1] { peer });
								return;
							}
						}
					}
				}
				ZRpc rpc = peer.m_rpc;
				if (rpc != null)
				{
					rpc.Invoke("Disconnect", Array.Empty<object>());
				}
			}

			private static string GetPlayerName(ZNetPeer peer)
			{
				try
				{
					FieldInfo field = typeof(ZNetPeer).GetField("m_playerName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if (field != null && peer != null)
					{
						string text = field.GetValue(peer) as string;
						if (!string.IsNullOrEmpty(text))
						{
							return text;
						}
					}
				}
				catch
				{
				}
				if (peer == null)
				{
					return "?";
				}
				return peer.m_uid.ToString();
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		private class ZNet_RPC_PeerInfo_SendConfig
		{
			private static MethodInfo _getPeerByRpc;

			private static FieldInfo _peersField;

			private static bool _lookupDone;

			private static void EnsureLookup()
			{
				if (_lookupDone)
				{
					return;
				}
				_lookupDone = true;
				BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
				MethodInfo[] methods = typeof(ZNet).GetMethods(bindingAttr);
				foreach (MethodInfo methodInfo in methods)
				{
					if (string.Equals(methodInfo.Name, "GetPeer", StringComparison.Ordinal))
					{
						ParameterInfo[] parameters = methodInfo.GetParameters();
						if (parameters.Length == 1 && parameters[0].ParameterType == typeof(ZRpc))
						{
							_getPeerByRpc = methodInfo;
							break;
						}
					}
				}
				_peersField = typeof(ZNet).GetField("m_peers", bindingAttr);
			}

			internal static bool StillConnected(ZNet znet, ZNetPeer peer)
			{
				EnsureLookup();
				if ((Object)(object)znet != (Object)null && _peersField?.GetValue(znet) is List<ZNetPeer> list)
				{
					return list.Contains(peer);
				}
				return true;
			}

			private static void Postfix(ZNet __instance, ZRpc rpc)
			{
				try
				{
					if ((Object)(object)__instance == (Object)null || rpc == null || !__instance.IsServer())
					{
						return;
					}
					EnsureLookup();
					ZNetPeer val = null;
					if (_getPeerByRpc != null)
					{
						object? obj = _getPeerByRpc.Invoke(__instance, new object[1] { rpc });
						val = (ZNetPeer)((obj is ZNetPeer) ? obj : null);
					}
					if (val == null && _peersField?.GetValue(__instance) is List<ZNetPeer> list)
					{
						foreach (ZNetPeer item in list)
						{
							if (item != null && item.m_rpc == rpc)
							{
								val = item;
								break;
							}
						}
					}
					if (val != null)
					{
						SimpleConfigSync.BeginHandshake(val, rpc);
					}
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] ConfigSync could not resolve the connecting peer: {arg}");
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_Disconnect")]
		private class ZNet_RPC_Disconnect_RestoreConfig
		{
			private static void Postfix()
			{
				SimpleConfigSync.RestoreLocal();
			}
		}

		private static class HallOfFame
		{
			internal enum Category
			{
				MasterOfTheForge,
				TirelessSmith,
				FavoriteOfTheGods,
				TheUnlucky,
				TheDestroyer,
				TheUnstoppable,
				TheCursed,
				ForgeRelic
			}

			private sealed class PlayerStats
			{
				public string Id;

				public string Name;

				public long FirstSeen;

				public long LastSeen;

				public int Attempts;

				public int Successes;

				public int Failures;

				public int Destroyed;

				public int BestLevel;

				public string BestLevelItem;

				public long BestLevelAt;

				public int BestLevelInfusion;

				public int BestLevelInfusionLevel;

				public int WinStreak;

				public int LossStreak;

				public int BestWinStreak;

				public long BestWinStreakAt;

				public int BestLossStreak;

				public long BestLossStreakAt;
			}

			private sealed class RelicRecord
			{
				public string Id;

				public string Item;

				public int Level;

				public long At;

				public int Infusion;

				public int InfusionLevel;

				public int InfusionItemLevel;

				public long InfusionAt;
			}

			internal sealed class Row
			{
				public string Id;

				public string Name;

				public long Value;

				public string ItemToken;

				public long At;

				public int InfusionCode;

				public int InfusionLevel;
			}

			internal const int CategoryCount = 8;

			internal const int TopSize = 10;

			internal const string ReportRpcName = "OdinBet_HallReport_v2";

			private const int ReportProtocol = 2;

			private const float SaveIntervalSeconds = 10f;

			private const float MinSecondsBetweenReports = 0.25f;

			private const int MaxPlausibleLevel = 1000;

			private const int MaxPlausibleInfusionLevel = 100000000;

			private const int MaxRelics = 100;

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

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

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

			private static string _loadedWorld;

			private static string _filePath;

			private static bool _dirty;

			private static float _lastSave;

			private static string _localSteamId;

			internal const string RequestRpcName = "OdinBet_HallRequest_v2";

			internal const string DataRpcName = "OdinBet_HallData_v2";

			private const int ReadProtocol = 2;

			private const float MinSecondsBetweenRequests = 0.15f;

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

			internal static void SubmitLocalResult(string playerName, long characterId, string itemToken, int attemptedLevel, int resultLevel, UpgradeOutcome outcome, int infusionCode, int infusionLevel)
			{
				//IL_0065: Unknown result type (might be due to invalid IL or missing references)
				//IL_006b: Expected O, but got Unknown
				try
				{
					ZNet instance = ZNet.instance;
					if ((Object)(object)instance == (Object)null)
					{
						return;
					}
					if (instance.IsServer())
					{
						RecordResult(GetLocalId(characterId), CleanText(playerName, 40), CleanText(itemToken, 64), attemptedLevel, resultLevel, outcome, infusionCode, infusionLevel);
					}
					else if (SimpleConfigSync.ServerHasMatchingMod)
					{
						ZNetPeer serverPeer = instance.GetServerPeer();
						if (serverPeer != null && serverPeer.m_rpc != null)
						{
							ZPackage val = new ZPackage();
							val.Write(2);
							val.Write(itemToken ?? "");
							val.Write(attemptedLevel);
							val.Write(resultLevel);
							val.Write((int)outcome);
							val.Write(infusionCode);
							val.Write(infusionLevel);
							serverPeer.m_rpc.Invoke("OdinBet_HallReport_v2", new object[1] { val });
						}
					}
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)("[ForgeOfPotential] Hall of Fame: could not submit a refinement result: " + ex.Message));
				}
			}

			internal static void RegisterServerRpc(ZNetPeer peer, ZRpc rpc)
			{
				if (peer != null && rpc != null)
				{
					rpc.Register<ZPackage>("OdinBet_HallReport_v2", (Action<ZRpc, ZPackage>)delegate(ZRpc connection, ZPackage pkg)
					{
						OnReportFromPeer(peer, pkg);
					});
					rpc.Register<ZPackage>("OdinBet_HallRequest_v2", (Action<ZRpc, ZPackage>)delegate(ZRpc connection, ZPackage pkg)
					{
						OnRequestFromPeer(peer, pkg);
					});
				}
			}

			private static void OnReportFromPeer(ZNetPeer peer, ZPackage pkg)
			{
				try
				{
					if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || peer == null || pkg == null)
					{
						return;
					}
					float unscaledTime = Time.unscaledTime;
					if (_lastReport.TryGetValue(peer, out var value) && unscaledTime - value < 0.25f)
					{
						return;
					}
					if (_lastReport.Count > 256)
					{
						_lastReport.Clear();
					}
					_lastReport[peer] = unscaledTime;
					int num = pkg.ReadInt();
					if (num != 2)
					{
						return;
					}
					string itemToken = CleanText(pkg.ReadString(), 64);
					int num2 = pkg.ReadInt();
					int num3 = pkg.ReadInt();
					int num4 = pkg.ReadInt();
					int infusionCode = pkg.ReadInt();
					int infusionLevel = pkg.ReadInt();
					if (!IsPlausible(num2, num3, num4))
					{
						Debug.LogWarning((object)$"[ForgeOfPotential] Hall of Fame: ignored an impossible report from '{peer.m_playerName}' (attempted={num2}, result={num3}, outcome={num4}).");
					}
					else
					{
						string peerId = GetPeerId(peer);
						if (string.IsNullOrEmpty(peerId))
						{
							Debug.LogWarning((object)("[ForgeOfPotential] Hall of Fame: could not tell who '" + peer.m_playerName + "' is (no usable id on their connection); result not recorded."));
						}
						else
						{
							RecordResult(peerId, CleanText(peer.m_playerName, 40), itemToken, num2, num3, (UpgradeOutcome)num4, infusionCode, infusionLevel);
						}
					}
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[ForgeOfPotential] Hall of Fame: a report could not be processed: {arg}");
				}
			}

			private static bool IsPlausible(int attempted, int result, int outcomeRaw)
			{
				if (outcomeRaw < 0 || outcomeRaw > 3)
				{
					return false;
				}
				if (attempted < 1 || attempted > 1000 || result < 0 || result > 1000)
				{
					return false;
				}
				int num = ((MaxUpgradeLevel != null) ? MaxUpgradeLevel.Value : 0);
				if (num > 0 && EnableUpgradeChanceAndCostTweaks != null && EnableUpgradeChanceAndCostTweaks.Value && attempted > num)
				{
					return false;
				}
				return (UpgradeOutcome)outcomeRaw switch
				{
					UpgradeOutcome.Success => result >= attempted, 
					UpgradeOutcome.Broke => result == 0, 
					UpgradeOutcome.ResetToLevel1 => result == 1, 
					_ => result < attempted, 
				};
			}

			private static void RecordResult(string id, string name, string itemToken, int attemptedLevel, int resultLevel, UpgradeOutcome outcome, int infusionCode, int infusionLevel)
			{
				if (string.IsNullOrEmpty(id) || !EnsureLoaded())
				{
					return;
				}
				if (outcome != UpgradeOutcome.Success || infusionCode < 1 || infusionCode > 5 || infusionLevel < 1)
				{
					infusionCode = 0;
					infusionLevel = 0;
				}
				else if (infusionLevel > 100000000)
				{
					infusionLevel = 100000000;
				}
				long num = UnixNow();
				if (!_players.TryGetValue(id, out var value))
				{
					value = new PlayerStats
					{
						Id = id,
						FirstSeen = num
					};
					_players[id] = value;
				}
				if (!string.IsNullOrEmpty(name))
				{
					value.Name = name;
				}
				value.LastSeen = num;
				value.Attempts++;
				if (outcome == UpgradeOutcome.Success)
				{
					value.Successes++;
					value.WinStreak++;
					value.LossStreak = 0;
					if (value.WinStreak > value.BestWinStreak)
					{
						value.BestWinStreak = value.WinStreak;
						value.BestWinStreakAt = num;
					}
					if (resultLevel > value.BestLevel)
					{
						value.BestLevel = resultLevel;
						value.BestLevelItem = itemToken;
						value.BestLevelAt = num;
						value.BestLevelInfusion = infusionCode;
						value.BestLevelInfusionLevel = infusionLevel;
					}
					else if (resultLevel == value.BestLevel && string.Equals(itemToken, value.BestLevelItem, StringComparison.Ordinal) && infusionLevel > value.BestLevelInfusionLevel)
					{
						value.BestLevelInfusion = infusionCode;
						value.BestLevelInfusionLevel = infusionLevel;
					}
					NoteRelic(id, itemToken, resultLevel, num, infusionCode, infusionLevel);
				}
				else
				{
					value.Failures++;
					value.LossStreak++;
					value.WinStreak = 0;
					if (value.LossStreak > value.BestLossStreak)
					{
						value.BestLossStreak = value.LossStreak;
						value.BestLossStreakAt = num;
					}
					if (outcome == UpgradeOutcome.Broke)
					{
						value.Destroyed++;
					}
				}
				_dirty = true;
				if (VerboseLogging != null && VerboseLogging.Value)
				{
					Debug.Log((object)$"[ForgeOfPotential] Hall of Fame: '{value.Name}' [{value.Id}] {outcome} '{itemToken}' {attemptedLevel - 1}->{resultLevel} | attempts={value.Attempts} wins={value.Successes} losses={value.Failures} destroyed={value.Destroyed} | streak wins={value.WinStreak} (best {value.BestWinStreak}), losses={value.LossStreak} (best {value.BestLossStreak}) | best level={value.BestLevel}");
				}
			}

			private static string RelicKey(string id, string itemToken, int infusionCode)
			{
				return id + "|" + itemToken + "|" + infusionCode;
			}

			private static void NoteRelic(string id, string itemToken, int level, long now, int infusionCode, int infusionLevel)
			{
				if (!string.IsNullOrEmpty(itemToken) && level > 0)
				{
					string key = RelicKey(id, itemToken, infusionCode);
					RelicRecord value;
					bool flag = !_relics.TryGetValue(key, out value);
					if (flag)
					{
						value = new RelicRecord
						{
							Id = id,
							Item = itemToken,
							Level = level,
							At = now
						};
						_relics[key] = value;
					}
					else if (level > value.Level)
					{
						value.Level = level;
						value.At = now;
					}
					if (infusionLevel > 0 && infusionLevel > value.InfusionLevel)
					{
						value.Infusion = infusionCode;
						value.InfusionLevel = infusionLevel;
						value.InfusionItemLevel = level;
						value.InfusionAt = now;
					}
					if (flag && _relics.Count > 100)
					{
						TrimRelics();
					}
				}
			}

			private static void TrimRelics()
			{
				List<RelicRecord> list = new List<RelicRecord>(_relics.Values);
				List<RelicRecord> list2 = new List<RelicRecord>(list);
				list2.Sort(CompareRelics);
				List<RelicRecord> list3 = new List<RelicRecord>(list);
				list3.Sort(CompareRelicsByInfusion);
				HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
				for (int i = 0; i < list2.Count && i < 100; i++)
				{
					hashSet.Add(RelicKey(list2[i].Id, list2[i].Item, list2[i].Infusion));
				}
				for (int j = 0; j < list3.Count && j < 100 && list3[j].InfusionLevel > 0; j++)
				{
					hashSet.Add(RelicKey(list3[j].Id, list3[j].Item, list3[j].Infusion));
				}
				_relics.Clear();
				foreach (RelicRecord item in list)
				{
					string text = RelicKey(item.Id, item.Item, item.Infusion);
					if (hashSet.Contains(text))
					{
						_relics[text] = item;
					}
				}
			}

			private static bool HasRelicAtLeast(string id, string itemToken, int level)
			{
				foreach (RelicRecord value in _relics.Values)
				{
					if (value.Level >= level && string.Equals(value.Id, id, StringComparison.Ordinal) && string.Equals(value.Item, itemToken, StringComparison.Ordinal))
					{
						return true;
					}
				}
				return false;
			}

			private static int CompareRelics(RelicRecord a, RelicRecord b)
			{
				int num = b.Level.CompareTo(a.Level);
				if (num != 0)
				{
					return num;
				}
				num = a.At.CompareTo(b.At);
				if (num != 0)
				{
					return num;
				}
				num = string.CompareOrdinal(a.Id, b.Id);
				if (num == 0)
				{
					return string.CompareOrdinal(a.Item, b.Item);
				}
				return num;
			}

			private static int CompareRelicsByInfusion(RelicRecord a, RelicRecord b)
			{
				int num = b.InfusionLevel.CompareTo(a.InfusionLevel);
				if (num != 0)
				{
					return num;
				}
				num = a.InfusionAt.CompareTo(b.InfusionAt);
				if (num != 0)
				{
					return num;
				}
				num = string.CompareOrdinal(a.Id, b.Id);
				if (num == 0)
				{
					return string.CompareOrdinal(a.Item, b.Item);
				}
				return num;
			}

			internal static List<Row> GetTop(Category category, int count)
			{
				List<Row> list = new List<Row>();
				if (_loadedWorld == null)
				{
					return list;
				}
				if (category == Category.ForgeRelic || category == Category.MasterOfTheForge)
				{
					bool flag = category == Category.ForgeRelic;
					foreach (RelicRecord value in _relics.Values)
					{
						if (flag ? (value.InfusionLevel > 0) : (value.Level > 0))
						{
							if (flag)
							{
								list.Add(new Row
								{
									Id = value.Id,
									Name = NameOf(value.Id),
									Value = ((value.InfusionItemLevel > 0) ? value.InfusionItemLevel : value.Level),
									ItemToken = value.Item,
									At = ((value.InfusionAt > 0) ? value.InfusionAt : value.At),
									InfusionCode = value.Infusion,
									InfusionLevel = value.InfusionLevel
								});
							}
							else
							{
								bool flag2 = value.InfusionLevel > 0 && value.InfusionItemLevel == value.Level;
								list.Add(new Row
								{
									Id = value.Id,
									Name = NameOf(value.Id),
									Value = value.Level,
									ItemToken = value.Item,
									At = value.At,
									InfusionCode = (flag2 ? value.Infusion : 0),
									InfusionLevel = (flag2 ? value.InfusionLevel : 0)
								});
							}
						}
					}
				}
				else
				{
					foreach (PlayerStats value2 in _players.Values)
					{
						long num = category switch
						{
							Category.TirelessSmith => value2.Attempts, 
							Category.FavoriteOfTheGods => value2.Successes, 
							Category.TheUnlucky => value2.Failures, 
							Category.TheDestroyer => value2.Destroyed, 
							Category.TheUnstoppable => value2.BestWinStreak, 
							Category.TheCursed => value2.BestLossStreak, 
							_ => 0L, 
						};
						long at = category switch
						{
							Category.TheCursed => value2.BestLossStreakAt, 
							Category.TheUnstoppable => value2.BestWinStreakAt, 
							_ => value2.FirstSeen, 
						};
						if (num > 0)
						{
							list.Add(new Row
							{
								Id = value2.Id,
								Name = (string.IsNullOrEmpty(value2.Name) ? value2.Id : value2.Name),
								Value = num,
								ItemToken = null,
								At = at,
								InfusionCode = 0,
								InfusionLevel = 0
							});
						}
					}
				}
				list.Sort((category == Category.ForgeRelic) ? new Comparison<Row>(CompareRelicRows) : new Comparison<Row>(CompareRows));
				if (count > 0 && list.Count > count)
				{
					list.RemoveRange(count, list.Count - count);
				}
				return list;
			}

			private static int CompareRows(Row a, Row b)
			{
				int num = b.Value.CompareTo(a.Value);
				if (num != 0)
				{
					return num;
				}
				num = a.At.CompareTo(b.At);
				if (num != 0)
				{
					return num;
				}
				num = string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
				if (num != 0)
				{
					return num;
				}
				num = string.CompareOrdinal(a.Id, b.Id);
				if (num == 0)
				{
					return string.CompareOrdinal(a.ItemToken, b.ItemToken);
				}
				return num;
			}

			private static int CompareRelicRows(Row a, Row b)
			{
				int num = b.InfusionLevel.CompareTo(a.InfusionLevel);
				if (num != 0)
				{
					return num;
				}
				num = a.At.CompareTo(b.At);
				if (num != 0)
				{
					return num;
				}
				num = string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
				if (num != 0)
				{
					return num;
				}
				num = string.CompareOrdinal(a.Id, b.Id);
				if (num == 0)
				{
					return string.CompareOrdinal(a.ItemToken, b.ItemToken);
				}
				return num;
			}

			private static string NameOf(string id)
			{
				if (_players.TryGetValue(id, out var value) && !string.IsNullOrEmpty(value.Name))
				{
					return value.Name;
				}
				return id;
			}

			internal static void RegisterClientRpc(ZNetPeer serverPeer)
			{
				try
				{
					if (serverPeer != null && serverPeer.m_rpc != null)
					{
						serverPeer.m_rpc.Register<ZPackage>("OdinBet_HallData_v2", (Action<ZRpc, ZPackage>)delegate(ZRpc connection, ZPackage pkg)
						{
							OnDataFromServer(pkg);
						});
					}
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)("[ForgeOfPotential] Hall of Fame: could not listen for rankings from the server: " + ex.Message));
				}
			}

			internal static bool RequestTop(Category category)
			{
				//IL_005d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0063: Expected O, but got Unknown
				try
				{
					ZNet instance = ZNet.instance;
					if ((Object)(object)instance == (Object)null)
					{
						return false;
					}
					if (instance.IsServer())
					{
						EnsureLoaded();
						HallUi.DeliverRows(category, GetTop(category, 10));
						return true;
					}
					if (!SimpleConfigSync.ServerHasMatchingMod)
					{
						return false;
					}
					ZNetPeer serverPeer = instance.GetServerPeer();
					if (serverPeer == null || serverPeer.m_rpc == null)
					{
						return false;
					}
					RegisterClientRpc(serverPeer);
					ZPackage val = new ZPackage();
					val.Write(2);
					val.Write((int)category);
					serverPeer.m_rpc.Invoke("OdinBet_HallRequest_v2", new object[1] { val });
					return true;
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)("[ForgeOfPotential] Hall of Fame: could not ask the server for a ranking: " + ex.Message));
					return false;
				}
			}

			private static void OnRequestFromPeer(ZNetPeer peer, ZPackage pkg)
			{
				//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00da: Expected O, but got Unknown
				try
				{
					if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || peer == null || peer.m_rpc == null || pkg == null)
					{
						return;
					}
					float unscaledTime = Time.unscaledTime;
					if (_lastRequest.TryGetValue(peer, out var value) && unscaledTime - value < 0.15f)
					{
						return;
					}
					if (_lastRequest.Count > 256)
					{
						_lastRequest.Clear();
					}
					_lastRequest[peer] = unscaledTime;
					int num = pkg.ReadInt();
					if (num != 2)
					{
						return;
					}
					int num2 = pkg.ReadInt();
					if (num2 >= 0 && num2 < 8 && (EnableHallOfFame == null || EnableHallOfFame.Value))
					{
						EnsureLoaded();
						List<Row> top = GetTop((Category)num2, 10);
						int num3 = Math.Min(top.Count, 10);
						ZPackage val = new ZPackage();
						val.Write(2);
						val.Write(num2);
						val.Write(num3);
						for (int i = 0; i < num3; i++)
						{
							Row row = top[i];
							val.Write(row.Id ?? "");
							val.Write(row.Name ?? "");
							val.Write(row.Value);
							val.Write(row.ItemToken ?? "");
							val.Write(row.InfusionCode);
							val.Write(row.InfusionLevel);
						}
						peer.m_rpc.Invoke("OdinBet_HallData_v2", new object[1] { val });
					}
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)("[ForgeOfPotential] Hall of Fame: could not answer a ranking request: " + ex.Message));
				}
			}

			private static void OnDataFromServer(ZPackage pkg)
			{
				try
				{
					if (pkg == null)
					{
						return;
					}
					ZNet instance = ZNet.instance;
					if ((Object)(object)instance == (Object)null || instance.IsServer())
					{
						return;
					}
					int num = pkg.ReadInt();
					if (num != 2)
					{
						return;
					}
					int num2 = pkg.ReadInt();
					if (num2 < 0 || num2 >= 8)
					{
						return;
					}
					int num3 = pkg.ReadInt();
					if (num3 < 0 || num3 > 10)
					{
						return;
					}
					List<Row> list = new List<Row>();
					for (int i = 0; i < num3; i++)
					{
						Row row = new Row();
						row.Id = CleanText(pkg.ReadString(), 64);
						row.Name = CleanText(pkg.ReadString(), 40);
						row.Value = pkg.ReadLong();
						row.ItemToken = CleanText(pkg.ReadString(), 64);
						int num4 = pkg.ReadInt();
						int num5 = pkg.ReadInt();
						if (num4 >= 1 && num4 <= 5 && num5 >= 1)
						{
							row.InfusionCode = num4;
							row.InfusionLevel = num5;
						}
						list.Add(row);
					}
					HallUi.DeliverRows((Category)num2, list);
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)("[ForgeOfPotential] Hall of Fame: could not read a ranking sent by the server: " + ex.Message));
				}
			}

			internal static string ProfileUrlFor(string id)
			{
				string text = ExtractSteam64(id);
				if (text != null)
				{
					return "https://steamcommunity.com/profiles/" + text;
				}
				return null;
			}

			internal static string ExtractSteam64(string text)
			{
				if (string.IsNullOrEmpty(text))
				{
					return null;
				}
				int i = 0;
				while (i < text.Length)
				{
					if (text[i] >= '0' && text[i] <= '9')
					{
						int num = i;
						for (; i < text.Length && text[i] >= '0' && text[i] <= '9'; i++)
						{
						}
						if (i - num == 17 && text[num] == '7' && text[num + 1] == '6' && text[num + 2] == '5' && text[num + 3] == '6')
						{
							return text.Substring(num, 17);
						}
					}
					else
					{
						i++;
					}
				}
				return null;
			}

			private static string GetPeerId(ZNetPeer peer)
			{
				string text = null;
				try
				{
					if (peer.m_socket != null)
					{
						text = peer.m_socket.GetHostName();
					}
				}
				catch
				{
				}
				if (string.IsNullOrEmpty(text))
				{
					try
					{
						ISocket val = ((peer.m_rpc != null) ? peer.m_rpc.GetSocket() : null);
						if (val != null)
						{
							text = val.GetHostName();
						}
					}
					catch
					{
					}
				}
				if (string.IsNullOrEmpty(text))
				{
					return null;
				}
				string text2 = ExtractSteam64(text);
				if (text2 != null)
				{
					return text2;
				}
				string text3 = CleanText(text, 64);
				if (text3.Length <= 0)
				{
					return null;
				}
				return "ext:" + text3;
			}

			private static string GetLocalId(long characterId)
			{
				string localSteamId = GetLocalSteamId();
				if (localSteamId != null)
				{
					return localSteamId;
				}
				return "local:" + characterId.ToString(CultureInfo.InvariantCulture);
			}

			private static string GetLocalSteamId()
			{
				if (_localSteamId != null)
				{
					return _localSteamId;
				}
				try
				{
					Type type = null;
					Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
					foreach (Assembly assembly in assemblies)
					{
						try
						{
							type = assembly.GetType("Steamworks.SteamUser", throwOnError: false);
						}
						catch
						{
							type = null;
						}
						if (type != null)
						{
							break;
						}
					}
					if (type == null)
					{
						return null;
					}
					MethodInfo method = type.GetMethod("GetSteamID", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null);
					if (method == null)
					{
						return null;
					}
					object obj2 = method.Invoke(null, null);
					if (obj2 == null)
					{
						return null;
					}
					string text = ExtractSteam64(obj2.ToString());
					if (text == null)
					{
						FieldInfo field = obj2.GetType().GetField("m_SteamID", BindingFlags.Instance | BindingFlags.Public);
						if (field != null)
						{
							text = ExtractSteam64(Convert.ToString(field.GetValue(obj2), CultureInfo.InvariantCulture));
						}
					}
					if (text != null)
					{
						_localSteamId = text;
					}
					return text;
				}
				catch
				{
					return null;
				}
			}

			internal static string CleanText(string value, int maxLength)
			{
				if (string.IsNullOrEmpty(value))
				{
					return "";
				}
				StringBuilder stringBuilder = new StringBuilder(Math.Min(value.Length, maxLength));
				foreach (char c in value)
				{
					if (stringBuilder.Length >= maxLength)
					{
						break;
					}
					if (c != '<' && c != '>' && !char.IsControl(c))
					{
						stringBuilder.Append(c);
					}
				}
				return stringBuilder.ToString().Trim();
			}

			private static long UnixNow()
			{
				return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
			}

			internal static void Tick()
			{
				try
				{
					ZNet instance = ZNet.instance;
					if ((Object)(object)instance == (Object)null)
					{
						if (_loadedWorld != null)
						{
							Save();
							Unload();
						}
					}
					else if (instance.IsServer())
					{
						if (_loadedWorld == null)
						{
							EnsureLoaded();
						}
						if (_dirty && Time.unscaledTime - _lastSave >= 10f)
						{
							Save();
						}
					}
				}
				catch
				{
				}
			}

			internal static void FlushNow()
			{
				try
				{
					if (_loadedWorld != null && _dirty)
					{
						Save();
					}
				}
				catch
				{
				}
			}

			private static void Unload()
			{
				_players.Clear();
				_relics.Clear();
				_lastReport.Clear();
				_loadedWorld = null;
				_filePath = null;
				_dirty = false;
			}

			private static bool EnsureLoaded()
			{
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null || !instance.IsServer())
				{
					return false;
				}
				string worldName = instance.GetWorldName();
				if (string.IsNullOrEmpty(worldName))
				{
					return false;
				}
				if (string.Equals(worldName, _loadedWorld, StringComparison.Ordinal))
				{
					return true;
				}
				if (_loadedWorld != null)
				{
					Save();
				}
				Unload();
				_loadedWorld = worldName;
				_filePath = Path.Combine(Path.Combine(Paths.ConfigPath, "OdinBet_ForgeOfPotential_HallOfFame"), "HallOfFame_" + SafeFileName(worldName) + ".json");
				Load();
				return true;
			}

			private static string SafeFileName(string name)
			{
				char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
				StringBuilder stringBuilder = new StringBuilder(name.Length);
				foreach (char c in name)
				{
					stringBuilder.Append((Array.IndexOf(invalidFileNameChars, c) >= 0) ? '_' : c);
				}
				return stringBuilder.ToString();
			}

			private static void Load()
			{
				if (!File.Exists(_filePath))
				{
					Debug.Log((object)("[ForgeOfPotential] Hall of Fame: no database yet for world '" + _loadedWorld + "', starting a new one at '" + _filePath + "'."));
				}
				else if (!TryLoadFrom(_filePath))
				{
					string text = _filePath + ".corrupt";
					try
					{
						File.Copy(_filePath, text, overwrite: true);
					}
					catch
					{
					}
					string text2 = _filePath + ".bak";
					if (File.Exists(text2) && TryLoadFrom(text2))
					{
						Debug.LogWarning((object)("[ForgeOfPotential] Hall of Fame: '" + _filePath + "' could not be read (kept as '" + text + "'); restored the previous save from '" + text2 + "'."));
						_dirty = true;
					}
					else
					{
						Debug.LogWarning((object)("[ForgeOfPotential] Hall of Fame: '" + _filePath + "' could not be read and there is no usable backup (the bad file was kept as '" + text + "'). Starting with an empty database."));
						_players.Clear();
						_relics.Clear();
					}
				}
			}

			private static bool TryLoadFrom(string path)
			{
				try
				{
					_players.Clear();
					_relics.Clear();
					if (!(MiniJson.Parse(File.ReadAllText(path, Encoding.UTF8)) is Dictionary<string, object> dictionary))
					{
						throw new FormatException("the top level of the file is not an object");
					}
					object value;
					List<object> list = (dictionary.TryGetValue("players", out value) ? (value as List<object>) : null);
					if (list != null)
					{
						foreach (object item in list)
						{
							if (!(item is Dictionary<string, object> d))
							{
								continue;
							}
							string text = JsonString(d, "id");
							if (!string.IsNullOrEmpty(text) && text.Length <= 80)
							{
								PlayerStats playerStats = new PlayerStats();
								playerStats.Id = text;
								playerStats.Name = CleanText(JsonString(d, "name"), 40);
								playerStats.FirstSeen = JsonLong(d, "firstSeen");
								playerStats.LastSeen = JsonLong(d, "lastSeen");
								playerStats.Attempts = JsonInt(d, "attempts");
								playerStats.Successes = JsonInt(d, "successes");
								playerStats.Failures = JsonInt(d, "failures");
								playerStats.Destroyed = JsonInt(d, "destroyed");
								playerStats.BestLevel = JsonInt(d, "bestLevel");
								playerStats.BestLevelItem = JsonString(d, "bestLevelItem");
								playerStats.BestLevelAt = JsonLong(d, "bestLevelAt");
								playerStats.BestLevelInfusion = JsonInt(d, "bestLevelInfusion");
								playerStats.BestLevelInfusionLevel = JsonInt(d, "bestLevelInfusionLevel");
								if (playerStats.BestLevelInfusion < 1 || playerStats.BestLevelInfusion > 5 || playerStats.BestLevelInfusionLevel < 1)
								{
									playerStats.BestLevelInfusion = 0;
									playerStats.BestLevelInfusionLevel = 0;
								}
								playerStats.WinStreak = JsonInt(d, "winStreak");
								playerStats.LossStreak = JsonInt(d, "lossStreak");
								playerStats.BestWinStreak = JsonInt(d, "bestWinStreak");
								playerStats.BestWinStreakAt = JsonLong(d, "bestWinStreakAt");
								playerStats.BestLossStreak = JsonInt(d, "bestLossStreak");
								playerStats.BestLossStreakAt = JsonLong(d, "bestLossStreakAt");
								_players[text] = playerStats;
							}
						}
					}
					object value2;
					List<object> list2 = (dictionary.TryGetValue("relics", out value2) ? (value2 as List<object>) : null);
					if (list2 != null)
					{
						foreach (object item2 in list2)
						{
							if (!(item2 is Dictionary<string, object> d2))
							{
								continue;
							}
							string text2 = JsonString(d2, "id");
							string text3 = JsonString(d2, "item");
							int num = JsonInt(d2, "level");
							if (string.IsNullOrEmpty(text2) || string.IsNullOrEmpty(text3) || num <= 0)
							{
								continue;
							}
							RelicRecord relicRecord = new RelicRecord
							{
								Id = text2,
								Item = text3,
								Level = num,
								At = JsonLong(d2, "at")
							};
							relicRecord.Infusion = JsonInt(d2, "infusion");
							relicRecord.InfusionLevel = JsonInt(d2, "infusionLevel");
							if (relicRecord.Infusion < 1 || relicRecord.Infusion > 5 || relicRecord.InfusionLevel < 1)
							{
								relicRecord.Infusion = 0;
								relicRecord.InfusionLevel = 0;
							}
							relicRecord.InfusionItemLevel = JsonInt(d2, "infusionItemLevel");
							relicRecord.InfusionAt = JsonLong(d2, "infusionAt");
							if (relicRecord.InfusionLevel > 0)
							{
								if (relicRecord.InfusionItemLevel <= 0)
								{
									relicRecord.InfusionItemLevel = relicRecord.Level;
								}
								if (relicRecord.InfusionAt <= 0)
								{
									relicRecord.InfusionAt = relicRecord.At;
								}
							}
							_relics[RelicKey(text2, text3, relicRecord.Infusion)] = relicRecord;
						}
					}
					foreach (PlayerStats value4 in _players.Values)
					{
						if (value4.BestLevel <= 0 || string.IsNullOrEmpty(value4.BestLevelItem))
						{
							continue;
						}
						string key = RelicKey(value4.Id, value4.BestLevelItem, value4.BestLevelInfusion);
						if (!_relics.TryGetValue(key, out var value3) && HasRelicAtLeast(value4.Id, value4.BestLevelItem, value4.BestLevel))
						{
							continue;
						}
						if (value3 != null)
						{
							if (value3.Level < value4.BestLevel)
							{
								value3.Level = value4.BestLevel;
								value3.At = value4.BestLevelAt;
								if (value4.BestLevelI