Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of OdinBet ForgeOfPotential v1.3.0
plugins/OdinBet_ForgeOfPotential.dll
Decompiled 17 hours ago
The result has been truncated due to the large size, download it to view full contents!
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