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.4.8
plugins/OdinBet_ForgeOfPotential.dll
Decompiled 2 weeks 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 System.Threading; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("OdinBet_ForgeOfPotential")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("OdinBet")] [assembly: AssemblyProduct("OdinBet_ForgeOfPotential")] [assembly: AssemblyCopyright("")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("9a350246-6f38-4f7e-9a17-7ae82ec995d7")] [assembly: AssemblyFileVersion("1.4.8.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.4.8.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } public static class SCMPanel { public static bool Register(string Command, bool AdminOnly = false) { if (string.IsNullOrWhiteSpace(Command)) { throw new ArgumentException("Panel command cannot be empty.", "Command"); } try { Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => string.Equals(a.GetName().Name, "Ultimate_SCM", StringComparison.OrdinalIgnoreCase)); if (assembly == null) { return false; } Type type = assembly.GetType("UltimateSCM.ExternalPanelRegistry", throwOnError: false, ignoreCase: false); if (type == null) { return false; } MethodInfo method = type.GetMethod("Register", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[3] { typeof(string), typeof(bool), typeof(Assembly) }, null); if (method == null) { return false; } method.Invoke(null, new object[3] { Command.Trim(), AdminOnly, typeof(SCMPanel).Assembly }); return true; } catch { return false; } } } namespace OdinBet_ForgeOfPotential { [BepInPlugin("OdinBet.ForgeOfPotential", "OdinBet - Forge of Potential", "1.4.8")] public class OdinBet_ForgeOfPotential : BaseUnityPlugin { internal enum UpgradeOutcome { Broke, LevelLost, Success, ResetToLevel1 } [HarmonyPatch(typeof(InventoryGui), "SetupCrafting")] private class InventoryGuiCraftSpeedPatches { [HarmonyPrefix] private static void SetCraftSpeed(ref float ___m_upgraderDuration, ref float ___m_upgraderDurationPerLevel) { if (EnableCraftDurationTweaks.Value) { ___m_upgraderDuration = UpgradeBaseDuration.Value; ___m_upgraderDurationPerLevel = UpgradeDurationIncreasePerLevel.Value; } } } private class UpgradeAttempt { public ItemData OriginalItem; public int QualityBefore; public string ItemToken; public string PlayerName; public long PlayerID; public int Variant; public string RecipePrefabName; public Recipe Recipe; public bool HasUpgraderResource; public float BreakReturnFraction; public bool ItemGivenBack; public int QualityGivenBack; public bool ExpectedLevelSet; public int ExpectedLevel; public bool DestroyedByMod; public bool GameMessageSuppressed; public string SuppressedText; public ItemData NewItem; } private class SavedChances { public SharedData Shared; public float UpgradeChance; public float BreakChance; public float BreakReturn; } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] private class UpgradeCraftPatch { private const string CraftUpgradeItemFieldName = "m_craftUpgradeItem"; private const string CraftRecipeFieldName = "m_craftRecipe"; private static FieldInfo _craftUpgradeItemField; private static FieldInfo _craftRecipeField; private static bool _fieldLookupDone; private static void EnsureFields() { if (!_fieldLookupDone) { _fieldLookupDone = true; BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; _craftUpgradeItemField = typeof(InventoryGui).GetField("m_craftUpgradeItem", bindingAttr); _craftRecipeField = typeof(InventoryGui).GetField("m_craftRecipe", bindingAttr); if (_craftUpgradeItemField == null || _craftRecipeField == null) { Debug.LogWarning((object)"[ForgeOfPotential] Could not find InventoryGui.m_craftUpgradeItem / m_craftRecipe in this game build; upgrade chance control and upgrade notifications are disabled."); } } } private static bool Prefix(InventoryGui __instance, Player player, out object __state) { __state = null; _activeUpgrade = null; try { if ((Object)(object)__instance == (Object)null || (Object)(object)player == (Object)null) { return true; } EnsureFields(); if (_craftUpgradeItemField == null || _craftRecipeField == null) { return true; } object? value = _craftUpgradeItemField.GetValue(__instance); ItemData val = (ItemData)((value is ItemData) ? value : null); if (val == null) { return true; } CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation == (Object)null || !currentCraftingStation.m_upgrader) { return true; } if (SimpleConfigSync.ServerRulesNotApplied) { ShowBlacklistMessage(val, ServerVersionMismatchMessage); return false; } if (SimpleConfigSync.SyncGraceActive) { return false; } object? value2 = _craftRecipeField.GetValue(__instance); Recipe val2 = (Recipe)((value2 is Recipe) ? value2 : null); if ((Object)(object)val2 == (Object)null || (Object)(object)val2.m_item == (Object)null) { return true; } string prefabName = (((Object)(object)((Component)val2.m_item).gameObject != (Object)null) ? ((Object)((Component)val2.m_item).gameObject).name : null); if (IsItemBlacklisted(prefabName)) { ShowBlacklistMessage(val, ItemBlacklistedMessage); return false; } if (!ModdedItemMayBeRefined(prefabName)) { ShowBlacklistMessage(val, ModdedItemBlockedMessage); return false; } int value3 = MaxUpgradeLevel.Value; if (EnableUpgradeChanceAndCostTweaks.Value && value3 > 0 && val.m_quality >= value3) { ShowCapMessage(val, value3); return false; } UpgradeAttempt upgradeAttempt = (_activeUpgrade = new UpgradeAttempt { OriginalItem = val, QualityBefore = val.m_quality, ItemToken = ((val.m_shared != null) ? val.m_shared.m_name : null), PlayerName = (player.GetPlayerName() ?? "Unknown"), PlayerID = player.GetPlayerID(), Variant = val.m_variant, Recipe = val2, RecipePrefabName = (((Object)(object)((Component)val2.m_item).gameObject != (Object)null) ? ((Object)((Component)val2.m_item).gameObject).name : null) }); __state = PrepareUpgraderResources(val2, upgradeAttempt); if (VerboseLogging.Value) { if (MayLogServerRules()) { Debug.Log((object)$"[ForgeOfPotential] Refine start: '{upgradeAttempt.RecipePrefabName}' level {upgradeAttempt.QualityBefore} -> {upgradeAttempt.QualityBefore + 1}, upgraderResource={upgradeAttempt.HasUpgraderResource}, successChance={UpgradeSuccessChance.Value}%, breakShareOfFailures={FailureBreaksItemChance.Value}%, levelsLostOnFailure={LevelsLostOnFailure.Value}, minLevel={MinimumLevelOnFailure.Value}."); } else { Debug.Log((object)$"[ForgeOfPotential] Refine start: '{upgradeAttempt.RecipePrefabName}' level {upgradeAttempt.QualityBefore} -> {upgradeAttempt.QualityBefore + 1}, upgraderResource={upgradeAttempt.HasUpgraderResource} (the server's odds are only logged for an admin with devcommands on)."); } } } catch (Exception arg) { Debug.LogWarning((object)$"[ForgeOfPotential] DoCrafting prefix failed, upgrade left untouched: {arg}"); __state = null; } return true; } private static void ShowCapMessage(ItemData item, int cap) { try { string text = MessageTranslations.Resolve(MaxLevelReachedMessage) ?? ""; if (!string.IsNullOrEmpty(text)) { string itemToken = ((item.m_shared != null) ? item.m_shared.m_name : ""); string text2 = text.Replace("{ItemName}", UpgradeNotifications.LocalizeItemName(itemToken)).Replace("{Level}", item.m_quality.ToString()).Replace("{MaxLevel}", cap.ToString()); MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)2, text2, 0, (Sprite)null, false, true); } } } catch { } } private static bool IsItemBlacklisted(string prefabName) { return ItemListContains(BlacklistedItems?.Value, prefabName); } private static bool ModdedItemMayBeRefined(string prefabName) { if (AllowRefiningModdedItems == null || AllowRefiningModdedItems.Value || VanillaItems.IsVanilla(prefabName)) { return true; } return ItemListContains(RefinableModdedItems?.Value, prefabName); } private static void ShowBlacklistMessage(ItemData item, MessageText text) { try { string text2 = MessageTranslations.Resolve(text) ?? ""; if (!string.IsNullOrEmpty(text2)) { string itemToken = ((item.m_shared != null) ? item.m_shared.m_name : ""); string text3 = text2.Replace("{ItemName}", UpgradeNotifications.LocalizeItemName(itemToken)); MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)2, text3, 0, (Sprite)null, false, true); } } } catch { } } private static List<SavedChances> PrepareUpgraderResources(Recipe recipe, UpgradeAttempt attempt) { attempt.BreakReturnFraction = 0f; if (recipe.m_resources == null) { return null; } bool value = EnableUpgradeChanceAndCostTweaks.Value; float value2 = UpgradeSuccessChance.Value; float value3 = FailureBreaksItemChance.Value; float value4 = BreakReturnIngredientsPercent.Value; bool value5 = BreakReturnsItemAtLevel1.Value; bool flag = value && value2 >= 0f; bool flag2 = value && value3 >= 0f; bool flag3 = value && (value5 || value4 >= 0f); List<SavedChances> list = new List<SavedChances>(); Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (val == null || !val.m_upgraderResource || (Object)(object)val.m_resItem == (Object)null) { continue; } ItemData itemData = val.m_resItem.m_itemData; if (itemData == null || itemData.m_shared == null) { continue; } attempt.HasUpgraderResource = true; SharedData shared = itemData.m_shared; if (!flag && !flag2 && !flag3) { attempt.BreakReturnFraction = Mathf.Clamp01(shared.m_breakReturnIngreientsAmount); continue; } list.Add(new SavedChances { Shared = shared, UpgradeChance = shared.m_upgradeChance, BreakChance = shared.m_breakChance, BreakReturn = shared.m_breakReturnIngreientsAmount }); float num = (flag ? Mathf.Clamp01(value2 / 100f) : Mathf.Clamp01(shared.m_upgradeChance)); if (flag) { shared.m_upgradeChance = num; } if (flag2) { shared.m_breakChance = (1f - num) * Mathf.Clamp01(value3 / 100f); } if (flag3) { shared.m_breakReturnIngreientsAmount = (value5 ? 0f : Mathf.Clamp01(value4 / 100f)); } attempt.BreakReturnFraction = (value5 ? 0f : ((value4 >= 0f) ? Mathf.Clamp01(value4 / 100f) : Mathf.Clamp01(list[list.Count - 1].BreakReturn))); } if (list.Count <= 0) { return null; } return list; } private static void Postfix(Player player, object __state) { RestoreChances(__state); UpgradeAttempt activeUpgrade = _activeUpgrade; _activeUpgrade = null; try { if (activeUpgrade == null || !Inventory_AddItem_UpgradeQualityPatch.HookAvailable || !activeUpgrade.HasUpgraderResource) { return; } Inventory val = (((Object)(object)player != (Object)null) ? ((Humanoid)player).GetInventory() : null); if (val != null && activeUpgrade.OriginalItem != null && val.ContainsItem(activeUpgrade.OriginalItem)) { return; } int num = activeUpgrade.QualityGivenBack; UpgradeOutcome upgradeOutcome; if (!activeUpgrade.ItemGivenBack) { if (EnableUpgradeChanceAndCostTweaks.Value && BreakReturnsItemAtLevel1.Value && GiveBackAtLevel1(player, activeUpgrade)) { upgradeOutcome = UpgradeOutcome.ResetToLevel1; num = 1; } else { if (activeUpgrade.DestroyedByMod) { RefundIngredients(player, activeUpgrade); } upgradeOutcome = UpgradeOutcome.Broke; num = 0; } } else if (activeUpgrade.QualityGivenBack > activeUpgrade.QualityBefore) { upgradeOutcome = UpgradeOutcome.Success; } else { upgradeOutcome = UpgradeOutcome.LevelLost; num = EnforceFailureLevel(val, activeUpgrade, num); } if (VerboseLogging.Value) { Debug.Log((object)string.Format("[ForgeOfPotential] Refine result: '{0}' {1} -> {2} ({3}){4}", activeUpgrade.RecipePrefabName, activeUpgrade.QualityBefore, num, upgradeOutcome, activeUpgrade.GameMessageSuppressed ? (" [game text replaced: \"" + activeUpgrade.SuppressedText + "\"]") : "")); } ShowLocalOutcomeMessage(activeUpgrade, upgradeOutcome, num); if (EnableGlobalNotifications.Value) { UpgradeNotifications.Broadcast(activeUpgrade.PlayerName, activeUpgrade.ItemToken, activeUpgrade.QualityBefore + 1, num, upgradeOutcome); } if (upgradeOutcome == UpgradeOutcome.Success) { try { Infusion.OnRefinementSucceeded(activeUpgrade, val, num); } catch (Exception ex) { if (VerboseLogging.Value) { Debug.LogWarning((object)("[ForgeOfPotential] Thunder Night Infusion failed: " + ex)); } } } try { int code = 0; int level = 0; if (upgradeOutcome == UpgradeOutcome.Success) { Infusion.ReadForRecord(activeUpgrade.NewItem, out code, out level); } HallOfFame.SubmitLocalResult(activeUpgrade.PlayerName, activeUpgrade.PlayerID, activeUpgrade.ItemToken, activeUpgrade.QualityBefore + 1, num, upgradeOutcome, code, level); } catch (Exception ex2) { if (VerboseLogging.Value) { Debug.LogWarning((object)("[ForgeOfPotential] Hall of Fame submit failed: " + ex2)); } } } catch { } } private static Exception Finalizer(Exception __exception, object __state) { if (__exception != null) { RestoreChances(__state); _activeUpgrade = null; } return __exception; } private static void RestoreChances(object state) { if (!(state is List<SavedChances> list)) { return; } for (int num = list.Count - 1; num >= 0; num--) { SavedChances savedChances = list[num]; try { if (savedChances?.Shared != null) { savedChances.Shared.m_upgradeChance = savedChances.UpgradeChance; savedChances.Shared.m_breakChance = savedChances.BreakChance; savedChances.Shared.m_breakReturnIngreientsAmount = savedChances.BreakReturn; } } catch { } } } private static void ShowLocalOutcomeMessage(UpgradeAttempt attempt, UpgradeOutcome outcome, int resultLevel) { try { if (!attempt.GameMessageSuppressed) { return; } string text = outcome switch { UpgradeOutcome.Success => MessageTranslations.Resolve(LocalSuccessMessage), UpgradeOutcome.Broke => MessageTranslations.Resolve(LocalBrokenMessage), UpgradeOutcome.ResetToLevel1 => MessageTranslations.Resolve(LocalResetMessage), _ => MessageTranslations.Resolve(LocalFailedMessage), }; if (!string.IsNullOrEmpty(text)) { string text2 = text.Replace("{ItemName}", UpgradeNotifications.LocalizeItemName(attempt.ItemToken)).Replace("{Level}", (attempt.QualityBefore + 1).ToString()).Replace("{PreviousLevel}", attempt.QualityBefore.ToString()) .Replace("{ResultLevel}", resultLevel.ToString()); MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)2, text2, 0, (Sprite)null, false, true); } } } catch { } } private static int EnforceFailureLevel(Inventory inventory, UpgradeAttempt attempt, int fallbackLevel) { try { ItemData newItem = attempt.NewItem; if (!attempt.ExpectedLevelSet || newItem == null || inventory == null || !inventory.ContainsItem(newItem)) { return fallbackLevel; } if (newItem.m_quality == attempt.ExpectedLevel) { return attempt.ExpectedLevel; } Debug.LogWarning((object)string.Format("[ForgeOfPotential] '{0}' came out of a failed refinement at level {1}, but LevelsLostOnFailure={2} / MinimumLevelOnFailure={3} say level {4} (it was level {5}). Corrected to level {4}.", attempt.RecipePrefabName, newItem.m_quality, LevelsLostOnFailure.Value, MinimumLevelOnFailure.Value, attempt.ExpectedLevel, attempt.QualityBefore)); newItem.m_quality = attempt.ExpectedLevel; attempt.QualityGivenBack = attempt.ExpectedLevel; MethodInfo methodInfo = null; MethodInfo[] methods = typeof(Inventory).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (string.Equals(methodInfo2.Name, "Changed", StringComparison.Ordinal)) { methodInfo = methodInfo2; break; } } if (methodInfo != null) { ParameterInfo[] parameters = methodInfo.GetParameters(); object[] array = new object[parameters.Length]; for (int j = 0; j < parameters.Length; j++) { array[j] = (parameters[j].ParameterType.IsValueType ? Activator.CreateInstance(parameters[j].ParameterType) : null); } methodInfo.Invoke(inventory, array); } return attempt.ExpectedLevel; } catch (Exception ex) { if (VerboseLogging.Value) { Debug.LogWarning((object)("[ForgeOfPotential] Could not verify the level after a failed refinement: " + ex)); } return fallbackLevel; } } private static bool GiveBackAtLevel1(Player player, UpgradeAttempt attempt) { try { if ((Object)(object)player == (Object)null || string.IsNullOrEmpty(attempt.RecipePrefabName)) { return false; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return false; } ItemData val = inventory.AddItem(attempt.RecipePrefabName, 1, 1, attempt.Variant, attempt.PlayerID, attempt.PlayerName ?? "", false, false); if (val == null) { Debug.LogWarning((object)("[ForgeOfPotential] BreakReturnsItemAtLevel1: could not give '" + attempt.RecipePrefabName + "' back (inventory full?).")); return false; } return true; } catch (Exception arg) { Debug.LogWarning((object)$"[ForgeOfPotential] BreakReturnsItemAtLevel1 failed: {arg}"); return false; } } private static void RefundIngredients(Player player, UpgradeAttempt attempt) { try { if ((Object)(object)player == (Object)null || attempt.Recipe?.m_resources == null || attempt.BreakReturnFraction <= 0f) { return; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return; } Requirement[] resources = attempt.Recipe.m_resources; foreach (Requirement val in resources) { if (val == null || !val.m_recover || (Object)(object)val.m_resItem == (Object)null) { continue; } int num = Mathf.CeilToInt((float)(val.GetAmount(1) + val.GetAmount(attempt.QualityBefore)) * attempt.BreakReturnFraction); if (num > 0) { ItemData itemData = val.m_resItem.m_itemData; inventory.AddItem(((Object)((Component)val.m_resItem).gameObject).name, num, itemData?.m_quality ?? 1, itemData?.m_variant ?? 0, attempt.PlayerID, attempt.PlayerName ?? "", false, false); if (VerboseLogging.Value) { Debug.Log((object)("[ForgeOfPotential] Refunded " + ((Object)((Component)val.m_resItem).gameObject).name + " x" + num + ".")); } } } } catch (Exception arg) { Debug.LogWarning((object)$"[ForgeOfPotential] Ingredient refund failed: {arg}"); } } } [HarmonyPatch] private class Inventory_AddItem_UpgradeQualityPatch { internal static bool HookAvailable; private static IEnumerable<MethodBase> TargetMethods() { List<MethodBase> list = new List<MethodBase>(); MethodInfo[] methods = typeof(Inventory).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!string.Equals(methodInfo.Name, "AddItem", StringComparison.Ordinal) || methodInfo.ReturnType != typeof(ItemData)) { continue; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 0 || parameters[0].ParameterType != typeof(string) || !string.Equals(parameters[0].Name, "name", StringComparison.Ordinal)) { continue; } bool flag = false; ParameterInfo[] array = parameters; foreach (ParameterInfo parameterInfo in array) { if (parameterInfo.ParameterType == typeof(int) && string.Equals(parameterInfo.Name, "quality", StringComparison.Ordinal)) { flag = true; break; } } if (flag) { list.Add(methodInfo); } } if (list.Count == 0) { throw new Exception("no Inventory.AddItem(string name, ..., int quality, ...) overload found in this game build"); } HookAvailable = true; return list; } private static bool Prefix(string name, ref int quality, ref ItemData __result) { UpgradeAttempt activeUpgrade = _activeUpgrade; if (activeUpgrade == null || name == null || activeUpgrade.RecipePrefabName == null) { return true; } if (!string.Equals(name, activeUpgrade.RecipePrefabName, StringComparison.Ordinal)) { return true; } if (quality > activeUpgrade.QualityBefore) { activeUpgrade.ItemGivenBack = true; activeUpgrade.QualityGivenBack = quality; return true; } if (!EnableUpgradeChanceAndCostTweaks.Value) { if (quality < 1) { quality = 1; } activeUpgrade.ItemGivenBack = true; activeUpgrade.QualityGivenBack = quality; return true; } int num = Math.Max(1, MinimumLevelOnFailure.Value); int num2 = Math.Max(0, LevelsLostOnFailure.Value); if (num2 > 0 && activeUpgrade.QualityBefore - num2 < num && FailureAtMinimumLevelBreaksItem.Value) { activeUpgrade.DestroyedByMod = true; activeUpgrade.ItemGivenBack = false; activeUpgrade.QualityGivenBack = 0; __result = null; return false; } int num3 = activeUpgrade.QualityBefore - num2; if (num3 < num) { num3 = num; } if (num3 > activeUpgrade.QualityBefore) { num3 = activeUpgrade.QualityBefore; } if (VerboseLogging.Value && quality != num3) { if (MayLogServerRules()) { Debug.Log((object)$"[ForgeOfPotential] Failed refinement: the game asked for level {quality}, forced to {num3} (level before={activeUpgrade.QualityBefore}, LevelsLostOnFailure={num2}, MinimumLevelOnFailure={num})."); } else { Debug.Log((object)$"[ForgeOfPotential] Failed refinement: the game asked for level {quality}, forced to {num3} (level before={activeUpgrade.QualityBefore})."); } } quality = num3; activeUpgrade.ItemGivenBack = true; activeUpgrade.QualityGivenBack = quality; activeUpgrade.ExpectedLevelSet = true; activeUpgrade.ExpectedLevel = num3; return true; } private static void Postfix(string name, ItemData __result) { try { UpgradeAttempt activeUpgrade = _activeUpgrade; if (activeUpgrade != null && __result != null && name != null && activeUpgrade.RecipePrefabName != null && string.Equals(name, activeUpgrade.RecipePrefabName, StringComparison.Ordinal)) { activeUpgrade.NewItem = __result; Infusion.CarryOver(activeUpgrade.OriginalItem, __result); } } catch { } } } [HarmonyPatch] private class Character_Message_UpgradeTextPatch { private static IEnumerable<MethodBase> TargetMethods() { List<MethodBase> list = new List<MethodBase>(); Type[] array = new Type[3] { typeof(Player), typeof(Humanoid), typeof(Character) }; Type[] array2 = array; foreach (Type type in array2) { MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (string.Equals(methodInfo.Name, "Message", StringComparison.Ordinal)) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length >= 2 && !(parameters[0].ParameterType != typeof(MessageType)) && !(parameters[1].ParameterType != typeof(string)) && !list.Contains(methodInfo)) { list.Add(methodInfo); } } } } if (list.Count == 0) { throw new Exception("no Message(MessageHud.MessageType, string, ...) found on Player/Humanoid/Character in this game build"); } return list; } private static bool Prefix(Character __instance, MessageType type, string msg) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 try { UpgradeAttempt activeUpgrade = _activeUpgrade; if (activeUpgrade == null || !EnableLocalNotifications.Value || !activeUpgrade.HasUpgraderResource || activeUpgrade.GameMessageSuppressed) { return true; } if ((int)type != 2) { return true; } Character obj = ((__instance is Humanoid) ? __instance : null); Inventory val = ((obj != null) ? ((Humanoid)obj).GetInventory() : null); if (val == null || activeUpgrade.OriginalItem == null || val.ContainsItem(activeUpgrade.OriginalItem)) { return true; } activeUpgrade.GameMessageSuppressed = true; activeUpgrade.SuppressedText = msg; return false; } catch { } return true; } } [HarmonyPatch(typeof(ZoneSystem), "GetLocationIcons")] private class ZoneSystem_GetLocationIcons_ForgeOnMapPatch { private static FieldInfo _instancesField; private static FieldInfo _locationsField; private static void Prefix(ZoneSystem __instance, ref List<ZoneLocation> __state) { __state = null; try { if (ShowForgeOnMap == null || !ShowForgeOnMap.Value) { return; } ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer()) { return; } __state = new List<ZoneLocation>(); if (_instancesField == null) { _instancesField = AccessTools.Field(typeof(ZoneSystem), "m_locationInstances"); } IDictionary dictionary = ((_instancesField != null) ? (_instancesField.GetValue(__instance) as IDictionary) : null); if (dictionary != null) { foreach (object value in dictionary.Values) { ZoneLocation location = ((LocationInstance)value).m_location; if (location != null && !location.m_iconAlways && IsForgeLocationName(location.m_prefab.Name)) { location.m_iconAlways = true; __state.Add(location); } } } if (_locationsField == null) { _locationsField = AccessTools.Field(typeof(ZoneSystem), "m_locations"); } List<ZoneLocation> list = ((_locationsField != null) ? (_locationsField.GetValue(__instance) as List<ZoneLocation>) : null); if (list == null) { return; } HashSet<ZoneLocation> hashSet = null; if (dictionary != null && dictionary.Count > 0) { hashSet = new HashSet<ZoneLocation>(dictionary.Count); foreach (object value2 in dictionary.Values) { ZoneLocation location2 = ((LocationInstance)value2).m_location; if (location2 != null) { hashSet.Add(location2); } } } foreach (ZoneLocation item in list) { if (item != null && !item.m_iconAlways && IsForgeLocationName(item.m_prefab.Name) && (hashSet == null || !hashSet.Contains(item))) { item.m_iconAlways = true; __state.Add(item); } } } catch (Exception ex) { Debug.LogWarning((object)("[ForgeOfPotential] ShowForgeOnMap: could not list the forge on the map: " + ex.Message)); } } private static void Finalizer(List<ZoneLocation> __state) { if (__state != null) { for (int i = 0; i < __state.Count; i++) { __state[i].m_iconAlways = false; } } } } [HarmonyPatch(typeof(Minimap), "GetLocationIcon")] private class Minimap_GetLocationIcon_ForgeOnMapPatch { private static Sprite _fallback; private static bool _searched; private static void Postfix(string __0, ref Sprite __result) { if ((Object)(object)__result != (Object)null || ShowForgeOnMap == null || !ShowForgeOnMap.Value || !IsForgeLocationName(__0)) { return; } try { if (!_searched) { _searched = true; CraftingStation[] array = Resources.FindObjectsOfTypeAll<CraftingStation>(); for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i] != (Object)null && array[i].m_upgrader && (Object)(object)array[i].m_icon != (Object)null) { _fallback = array[i].m_icon; break; } } if ((Object)(object)_fallback == (Object)null) { Debug.LogWarning((object)"[ForgeOfPotential] ShowForgeOnMap: the map has no picture for the forge and none was found to use instead, so no icon is drawn."); } } if ((Object)(object)_fallback != (Object)null) { __result = _fallback; } } catch { } } } [HarmonyPatch(typeof(Location), "IsInsideNoBuildLocation")] private class Location_NoBuildZone_Patch { private static void Postfix(Vector3 point, ref bool __result) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) if (!__result || !AllowBuildingNearRefinementForge.Value) { return; } try { float num = Mathf.Max(1f, RefinementForgeBuildRadius.Value); List<IMonoUpdater> instances = CraftingStation.Instances; if (instances == null) { return; } float num2 = num * num; for (int i = 0; i < instances.Count; i++) { IMonoUpdater obj = instances[i]; CraftingStation val = (CraftingStation)(object)((obj is CraftingStation) ? obj : null); if (!((Object)(object)val == (Object)null) && val.m_upgrader) { Vector3 val2 = ((Component)val).transform.position - point; if (((Vector3)(ref val2)).sqrMagnitude <= num2) { __result = false; break; } } } } catch { } } } [HarmonyPatch(typeof(CraftingStation), "CheckUsable")] private class CraftingStation_CheckUsable_ForgeDisabledPatch { private static void Postfix(CraftingStation __instance, bool showMessage, ref bool __result) { if (__result && EnableRefinementForge != null && !EnableRefinementForge.Value && !((Object)(object)__instance == (Object)null) && __instance.m_upgrader) { __result = false; if (showMessage) { ShowForgeDisabledMessage(); } } } private static void ShowForgeDisabledMessage() { try { string text = MessageTranslations.Resolve(ForgeDisabledMessage); if (!string.IsNullOrEmpty(text)) { MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true); } } } catch { } } } [HarmonyPatch(typeof(ZNetScene), "Awake")] private class ZNetSceneAwakePatch_UpgradeNotify { [HarmonyPostfix] private static void Postfix() { UpgradeNotifications.RegisterRPC(); SimpleConfigSync.RegisterRPC(); } } private static class MessageTranslations { private static readonly Dictionary<string, Dictionary<string, string>> _byLanguage = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, Dictionary<string, string>> _panelByLanguage = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); private static bool _loaded; private static bool _localizationLookupDone; private static PropertyInfo _localizationInstanceProperty; private static MethodInfo _getSelectedLanguageMethod; private static MethodInfo _localizeMethod; private static readonly object[] _localizeArgs = new object[1]; private static string _cachedLanguage; private static float _cachedLanguageAt = float.NegativeInfinity; public static void Load() { if (_loaded) { return; } _loaded = true; try { string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ""; string text = Path.Combine(path, "Translations"); if (!Directory.Exists(text)) { Debug.LogWarning((object)("[ForgeOfPotential] Translations folder not found at '" + text + "' - messages will use the English text built into the mod.")); return; } int num = 0; string[] files = Directory.GetFiles(text, "*.json"); foreach (string text2 in files) { try { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text2); Dictionary<string, string> dictionary = ParseFlatJsonObject(File.ReadAllText(text2)); if (dictionary.Count > 0) { _byLanguage[fileNameWithoutExtension] = dictionary; num++; } } catch (Exception ex) { Debug.LogWarning((object)("[ForgeOfPotential] Could not read translation file '" + text2 + "': " + ex.Message)); } } Debug.Log((object)$"[ForgeOfPotential] Loaded {num} translation file(s) from '{text}'."); LoadPanelTexts(Path.Combine(text, "ConfigPanel")); } catch (Exception arg) { Debug.LogWarning((object)$"[ForgeOfPotential] Translation loading failed, falling back to English defaults: {arg}"); } } private static void LoadPanelTexts(string dir) { try { if (!Directory.Exists(dir)) { Debug.LogWarning((object)("[ForgeOfPotential] Settings panel translations not found at '" + dir + "' - the panel will be in English.")); return; } int num = 0; string[] files = Directory.GetFiles(dir, "*.json"); foreach (string text in files) { try { Dictionary<string, string> dictionary = ParseFlatJsonObject(File.ReadAllText(text)); if (dictionary.Count > 0) { _panelByLanguage[Path.GetFileNameWithoutExtension(text)] = dictionary; num++; } } catch (Exception ex) { Debug.LogWarning((object)("[ForgeOfPotential] Could not read settings panel translation file '" + text + "': " + ex.Message)); } } Debug.Log((object)$"[ForgeOfPotential] Loaded {num} settings panel translation file(s)."); } catch (Exception ex2) { Debug.LogWarning((object)("[ForgeOfPotential] Settings panel translations could not be loaded: " + ex2.Message)); } } public static string GetPanel(string key, string englishFallback) { try { if (_panelByLanguage.TryGetValue(GetCurrentLanguage(), out var value) && value.TryGetValue(key, out var value2) && !string.IsNullOrEmpty(value2)) { return value2; } if (_panelByLanguage.TryGetValue("English", out value) && value.TryGetValue(key, out value2) && !string.IsNullOrEmpty(value2)) { return value2; } } catch { } return englishFallback ?? ""; } private static Dictionary<string, string> ParseFlatJsonObject(string json) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(json)) { return dictionary; } int i = 0; int length = json.Length; SkipWhitespace(json, ref i); if (i >= length || json[i] != '{') { return dictionary; } i++; while (true) { SkipWhitespace(json, ref i); if (i >= length) { break; } if (json[i] == '}') { i++; break; } if (json[i] == ',') { i++; continue; } if (json[i] != '"') { break; } string key = ReadJsonString(json, ref i); SkipWhitespace(json, ref i); if (i >= length || json[i] != ':') { break; } i++; SkipWhitespace(json, ref i); if (i >= length || json[i] != '"') { break; } string value = ReadJsonString(json, ref i); dictionary[key] = value; } return dictionary; } private static void SkipWhitespace(string s, ref int i) { while (i < s.Length && char.IsWhiteSpace(s[i])) { i++; } } private static string ReadJsonString(string s, ref int i) { i++; StringBuilder stringBuilder = new StringBuilder(); while (i < s.Length && s[i] != '"') { char c = s[i]; if (c == '\\' && i + 1 < s.Length) { i++; char c2 = s[i]; switch (c2) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'n': stringBuilder.Append('\n'); break; case 't': stringBuilder.Append('\t'); break; case 'r': stringBuilder.Append('\r'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'u': if (i + 4 < s.Length) { string s2 = s.Substring(i + 1, 4); if (int.TryParse(s2, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { stringBuilder.Append((char)result); } i += 4; } break; default: stringBuilder.Append(c2); break; } i++; } else { stringBuilder.Append(c); i++; } } i++; return stringBuilder.ToString(); } private static object LocalizationInstance() { if (!_localizationLookupDone) { _localizationLookupDone = true; Type type = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type2 = assembly.GetType("Localization", throwOnError: false); if (type2 != null) { type = type2; break; } } if (type != null) { _localizationInstanceProperty = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public); _getSelectedLanguageMethod = type.GetMethod("GetSelectedLanguage", BindingFlags.Instance | BindingFlags.Public); _localizeMethod = type.GetMethod("Localize", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(string) }, null); } } return _localizationInstanceProperty?.GetValue(null); } public static string Localize(string text) { if (string.IsNullOrEmpty(text)) { return text; } object obj = LocalizationInstance(); if (obj == null || _localizeMethod == null) { return text; } _localizeArgs[0] = text; string text2 = _localizeMethod.Invoke(obj, _localizeArgs) as string; _localizeArgs[0] = null; if (!string.IsNullOrEmpty(text2)) { return text2; } return text; } public static string GetCurrentLanguage() { try { if (_cachedLanguage != null && Time.unscaledTime - _cachedLanguageAt < 1f) { return _cachedLanguage; } object obj = LocalizationInstance(); if (obj == null) { return "English"; } string text = _getSelectedLanguageMethod?.Invoke(obj, null) as string; text = (_cachedLanguage = (string.IsNullOrEmpty(text) ? "English" : text)); _cachedLanguageAt = Time.unscaledTime; return text; } catch { return "English"; } } public static string Get(string key, string englishFallback) { try { if (string.IsNullOrEmpty(key)) { return englishFallback ?? ""; } if (_byLanguage.TryGetValue(GetCurrentLanguage(), out var value) && value.TryGetValue(key, out var value2) && value2 != null) { return value2; } if (_byLanguage.TryGetValue("English", out value) && value.TryGetValue(key, out value2) && value2 != null) { return value2; } return englishFallback ?? ""; } catch { return englishFallback ?? ""; } } public static string Label(string key, string englishFallback) { string text = Get(key, englishFallback); if (!string.IsNullOrEmpty(text)) { return text; } return englishFallback ?? ""; } public static string Resolve(MessageText message) { if (message != null) { return Get(message.Key, message.English); } return ""; } } private static class UpgradeNotifications { private const string RpcName = "OdinBet_UpgradeBroadcast_v4"; private const string InfusionRpcName = "OdinBet_InfusionBroadcast_v1"; private static ZRoutedRpc _registeredOn; private const float ChatOpenSeconds = 4f; private static FieldInfo _chatHideTimerField; private static bool _chatHideTimerLookupDone; public static void RegisterRPC() { try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _registeredOn) { instance.Register<string, string, int, int, int>("OdinBet_UpgradeBroadcast_v4", (Method<string, string, int, int, int>)RPC_ShowUpgradeMessage); instance.Register<string, string, int, int, int>("OdinBet_InfusionBroadcast_v1", (Method<string, string, int, int, int>)RPC_ShowInfusionMessage); _registeredOn = instance; } } catch (Exception arg) { Debug.LogWarning((object)$"[ForgeOfPotential] Could not register RPC: {arg}"); } } private static string Sanitize(string value, int maxLength) { return HallOfFame.CleanText(value, maxLength); } private static void RPC_ShowUpgradeMessage(long sender, string playerName, string itemToken, int attemptedLevel, int resultLevel, int outcomeRaw) { try { if (!EnableGlobalNotifications.Value) { return; } playerName = Sanitize(playerName, 40); itemToken = Sanitize(itemToken, 64); switch (outcomeRaw) { case 2: if (!NotifyOnSuccess.Value) { return; } break; case 0: case 3: if (!NotifyOnBreak.Value) { return; } break; default: if (!NotifyOnFailure.Value) { return; } break; } if (attemptedLevel < MinimumLevelToNotify.Value) { return; } string text = (string)((outcomeRaw switch { 2 => MessageTranslations.Resolve(SuccessMessage), 0 => MessageTranslations.Resolve(BrokenMessage), 3 => MessageTranslations.Resolve(ResetMessage), _ => MessageTranslations.Resolve(FailedMessage), }) ?? ""); if (text.Length == 0) { return; } string text2 = text.Replace("{Prefix}", NotificationPrefix.Value ?? "").Replace("{PlayerName}", playerName ?? "").Replace("{ItemName}", LocalizeItemName(itemToken)) .Replace("{Level}", attemptedLevel.ToString()) .Replace("{ResultLevel}", resultLevel.ToString()); if (string.IsNullOrEmpty(text2)) { return; } if ((Object)(object)Chat.instance != (Object)null) { string text3 = "<color=white>" + text.Replace("{Prefix}", "<color=yellow>" + NotificationPrefix.Value + "</color>").Replace("{PlayerName}", "<color=#FF6B5B>" + playerName + "</color>").Replace("{ItemName}", "<color=#FF6B5B>" + LocalizeItemName(itemToken) + "</color>") .Replace("{Level}", "<color=#FF6B5B>" + attemptedLevel + "</color>") .Replace("{ResultLevel}", "<color=#FF6B5B>" + resultLevel + "</color>") + "</color>"; ((Terminal)Chat.instance).AddString(text3); ForceOpenChatWindow(Chat.instance); return; } MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)2, text2, 0, (Sprite)null, false, true); } } catch { } } private static void ForceOpenChatWindow(Chat chat) { try { if (!((Object)(object)chat == (Object)null)) { if (!_chatHideTimerLookupDone) { _chatHideTimerLookupDone = true; _chatHideTimerField = typeof(Chat).GetField("m_hideTimer", BindingFlags.Instance | BindingFlags.NonPublic); } _chatHideTimerField?.SetValue(chat, Math.Max(0f, chat.m_hideDelay - 4f)); if ((Object)(object)((Terminal)chat).m_chatWindow != (Object)null) { ((Component)((Terminal)chat).m_chatWindow).gameObject.SetActive(true); } } } catch { } } public static string LocalizeItemName(string itemToken) { if (string.IsNullOrEmpty(itemToken)) { return "Unknown Item"; } try { return MessageTranslations.Localize(itemToken); } catch { return itemToken; } } public static void BroadcastInfusion(string playerName, string itemToken, InfusionElement element, int infusionLevel, bool strengthened) { try { if (EnableGlobalNotifications.Value && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "OdinBet_InfusionBroadcast_v1", new object[5] { playerName ?? "", itemToken ?? "", (int)element, infusionLevel, strengthened ? 1 : 0 }); } } catch { } } private static void RPC_ShowInfusionMessage(long sender, string playerName, string itemToken, int elementRaw, int infusionLevel, int strengthenedRaw) { try { if (!EnableGlobalNotifications.Value || elementRaw < 0 || elementRaw > 4) { return; } playerName = Sanitize(playerName, 40); itemToken = Sanitize(itemToken, 64); string text = MessageTranslations.Resolve((strengthenedRaw != 0) ? InfusionStrengthenedMessage : InfusionMessage) ?? ""; if (text.Length == 0) { return; } string text2 = LocalizeItemName(Infusion.ElementToken((InfusionElement)elementRaw)); string text3 = LocalizeItemName(itemToken); string text4 = NotificationPrefix.Value ?? ""; string text5 = infusionLevel.ToString(); string text6 = text.Replace("{Prefix}", text4).Replace("{PlayerName}", playerName ?? "").Replace("{ItemName}", text3) .Replace("{Element}", text2) .Replace("{InfusionLevel}", text5); if (string.IsNullOrEmpty(text6)) { return; } if ((Object)(object)Chat.instance != (Object)null) { string text7 = "<color=white>" + text.Replace("{Prefix}", "<color=yellow>" + text4 + "</color>").Replace("{PlayerName}", "<color=#FF6B5B>" + playerName + "</color>").Replace("{ItemName}", "<color=#FF6B5B>" + text3 + "</color>") .Replace("{Element}", "<color=#FFD54A>" + text2 + "</color>") .Replace("{InfusionLevel}", "<color=#FFD54A>" + text5 + "</color>") + "</color>"; ((Terminal)Chat.instance).AddString(text7); ForceOpenChatWindow(Chat.instance); return; } MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)2, text6, 0, (Sprite)null, false, true); } } catch { } } public static void Broadcast(string playerName, string itemToken, int attemptedLevel, int resultLevel, UpgradeOutcome outcome) { try { if (EnableGlobalNotifications.Value && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "OdinBet_UpgradeBroadcast_v4", new object[5] { playerName ?? "", itemToken ?? "", attemptedLevel, resultLevel, (int)outcome }); } } catch { } } } internal static class VanillaItems { private static HashSet<string> _names; private static bool _warned; internal static bool Known { get { if (_names != null) { return _names.Count > 0; } return false; } } internal static int Count { get { if (_names == null) { return 0; } return _names.Count; } } internal static void Capture(ObjectDB db) { if ((Object)(object)db == (Object)null || db.m_items == null || (_names != null && _names.Count > 0)) { return; } HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); bool flag = false; foreach (GameObject item in db.m_items) { if (!((Object)(object)item == (Object)null) && !string.IsNullOrEmpty(((Object)item).name)) { hashSet.Add(((Object)item).name); if (string.Equals(((Object)item).name, "Wood", StringComparison.Ordinal)) { flag = true; } } } if (flag) { _names = hashSet; if (VerboseLogging != null && VerboseLogging.Value) { Debug.Log((object)("[ForgeOfPotential] Recorded the game's own item list (" + hashSet.Count + " items).")); } } } internal static bool IsVanilla(string prefabName) { if (string.IsNullOrEmpty(prefabName)) { return true; } HashSet<string> names = _names; if (names == null || names.Count == 0) { if (!_warned) { _warned = true; Debug.LogWarning((object)"[ForgeOfPotential] The game's own item list is not known, so every item counts as a game item (AllowRefiningModdedItems and InfusionAllowModdedItems cannot block anything)."); } return true; } return names.Contains(StripClone(prefabName)); } internal static string StripClone(string name) { if (string.IsNullOrEmpty(name)) { return name; } int num = name.IndexOf("(Clone)", StringComparison.Ordinal); if (num <= 0) { return name.Trim(); } return name.Substring(0, num).Trim(); } } [HarmonyPatch(typeof(ObjectDB), "Awake")] private class ObjectDB_Awake_VanillaItemsPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(ObjectDB __instance) { try { VanillaItems.Capture(__instance); } catch { } } } internal sealed class ModdedIdolInfo { internal string Prefab; internal string Token; internal ItemType Type; internal float BaseStat; internal string AutoIdol; internal string ReasonMaterial = ""; internal bool Uncertain; internal bool OwnIdol; internal readonly List<string> Ingredients = new List<string>(); } internal static class ModdedIdols { private sealed class Added { public Recipe Recipe; public Requirement Requirement; } private sealed class Sample { public ItemType Type; public float Stat; public int Tier; public bool Armor; } private const string LogPrefix = "[ForgeOfPotential] Modded idols: "; private const int MaxIngredientDepth = 3; internal const int MaxOverrides = 500; private static readonly List<Added> _added = new List<Added>(); private static int _kDb; private static int _kRecipes = -1; private static int _kItems = -1; private static int _kVanilla = -1; private static bool _kAllow; private static string _kRefinable; private static string _kBlack; private static string _kMap; private static bool _kVerbose; private static MethodInfo _memberwiseClone; private static readonly List<ModdedIdolInfo> _items = new List<ModdedIdolInfo>(); private static readonly Dictionary<string, ModdedIdolInfo> _byPrefab = new Dictionary<string, ModdedIdolInfo>(StringComparer.OrdinalIgnoreCase); private static int _version; private static int _maxTier = -1; private static string _lastSummary; internal static List<ModdedIdolInfo> Items => _items; internal static int Version => _version; internal static int MaxTier => _maxTier; internal static ModdedIdolInfo Find(string prefab) { if (string.IsNullOrEmpty(prefab) || !_byPrefab.TryGetValue(prefab, out var value)) { return null; } return value; } internal static void EnsureApplied(ObjectDB db) { try { if (!((Object)(object)db == (Object)null) && db.m_recipes != null && db.m_items != null && !((Object)(object)db.GetItemPrefab("Wood") == (Object)null) && (!Unchanged(db) || !AllStillInPlace())) { Apply(db); } } catch (Exception ex) { Debug.LogWarning((object)("[ForgeOfPotential] Modded idols: failed: " + ex)); } } private static bool Unchanged(ObjectDB db) { int instanceID = ((Object)db).GetInstanceID(); bool flag = AllowRefiningModdedItems == null || AllowRefiningModdedItems.Value; string text = RefinableModdedItems?.Value ?? ""; string text2 = BlacklistedItems?.Value ?? ""; string text3 = ModdedItemIdols?.Value ?? ""; bool flag2 = VerboseLogging != null && VerboseLogging.Value; if (instanceID == _kDb && db.m_recipes.Count == _kRecipes && db.m_items.Count == _kItems && VanillaItems.Count == _kVanilla && flag == _kAllow && string.Equals(text, _kRefinable, StringComparison.Ordinal) && string.Equals(text2, _kBlack, StringComparison.Ordinal) && string.Equals(text3, _kMap, StringComparison.Ordinal) && flag2 == _kVerbose) { return true; } _kDb = instanceID; _kRecipes = db.m_recipes.Count; _kItems = db.m_items.Count; _kVanilla = VanillaItems.Count; _kAllow = flag; _kRefinable = text; _kBlack = text2; _kMap = text3; _kVerbose = flag2; return false; } private static bool AllStillInPlace() { foreach (Added item in _added) { if ((Object)(object)item.Recipe == (Object)null || item.Recipe.m_resources == null || Array.IndexOf(item.Recipe.m_resources, item.Requirement) < 0) { return false; } } return true; } private static void RemoveAll() { foreach (Added item in _added) { if ((Object)(object)item.Recipe != (Object)null && item.Recipe.m_resources != null && Array.IndexOf(item.Recipe.m_resources, item.Requirement) >= 0) { Requirement mine = item.Requirement; item.Recipe.m_resources = item.Recipe.m_resources.Where((Requirement r) => r != mine).ToArray(); } } _added.Clear(); } internal static bool TryParseIdol(string prefab, out int tier, out bool armor) { tier = -1; armor = false; if (string.IsNullOrEmpty(prefab) || !prefab.StartsWith("Upgrader", StringComparison.Ordinal)) { return false; } string text = prefab.Substring("Upgrader".Length); string text2; if (text.EndsWith("Armor", StringComparison.Ordinal)) { armor = true; text2 = text.Substring(0, text.Length - "Armor".Length); } else { if (!text.EndsWith("Weapon", StringComparison.Ordinal)) { return false; } text2 = text.Substring(0, text.Length - "Weapon".Length); } if (text2.Length > 0 && text2.Length <= 2 && int.TryParse(text2, NumberStyles.None, CultureInfo.InvariantCulture, out tier)) { return tier >= 0; } return false; } internal static string IdolPrefab(int tier, bool armor) { return "Upgrader" + tier.ToString(CultureInfo.InvariantCulture) + (armor ? "Armor" : "Weapon"); } internal static List<KeyValuePair<string, string>> ParseMap(string text) { List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); if (string.IsNullOrEmpty(text)) { return list; } string[] array = text.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text2 in array) { string text3 = text2.Trim(); int num = text3.IndexOf(':'); if (num <= 0 || num >= text3.Length - 1) { continue; } string text4 = text3.Substring(0, num).Trim(); string text5 = text3.Substring(num + 1).Trim(); if (!ConfigAdmin.IsPrefabName(text4) || !TryParseIdol(text5, out var _, out var _)) { continue; } for (int num2 = list.Count - 1; num2 >= 0; num2--) { if (string.Equals(list[num2].Key, text4, StringComparison.OrdinalIgnoreCase)) { list.RemoveAt(num2); } } list.Add(new KeyValuePair<string, string>(text4, text5)); } return list; } internal static string JoinMap(List<KeyValuePair<string, string>> list) { StringBuilder stringBuilder = new StringBuilder(); foreach (KeyValuePair<string, string> item in list) { if (stringBuilder.Length > 0) { stringBuilder.Append(','); } stringBuilder.Append(item.Key).Append(':').Append(item.Value); } return stringBuilder.ToString(); } internal static string MapGet(List<KeyValuePair<string, string>> list, string item) { foreach (KeyValuePair<string, string> item2 in list) { if (string.Equals(item2.Key, item, StringComparison.OrdinalIgnoreCase)) { return item2.Value; } } return null; } internal static void MapSet(List<KeyValuePair<string, string>> list, string item, string idol) { for (int i = 0; i < list.Count; i++) { if (string.Equals(list[i].Key, item, StringComparison.OrdinalIgnoreCase)) { if (idol == null) { list.RemoveAt(i); } else { list[i] = new KeyValuePair<string, string>(list[i].Key, idol); } return; } } if (idol != null) { list.Add(new KeyValuePair<string, string>(item, idol)); } } internal static string CheckMap(string text) { if (string.IsNullOrEmpty(text) || text.Trim().Length == 0) { return null; } string[] array = text.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length > 500) { return "at most " + 500 + " items"; } string[] array2 = array; foreach (string text2 in array2) { string text3 = text2.Trim(); if (text3.Length != 0) { int num = text3.IndexOf(':'); if (num <= 0) { return "entries must be 'ItemPrefab:UpgraderNArmor' or 'ItemPrefab:UpgraderNWeapon'"; } string name = text3.Substring(0, num).Trim(); string prefab = text3.Substring(num + 1).Trim(); if (!ConfigAdmin.IsPrefabName(name) || !TryParseIdol(prefab, out var _, out var _)) { return "entries must be 'ItemPrefab:UpgraderNArmor' or 'ItemPrefab:UpgraderNWeapon'"; } } } return null; } internal static List<string> MissingReferenced(ObjectDB db) { List<string> list = new List<string>(); if ((Object)(object)db == (Object)null || (Object)(object)db.GetItemPrefab("Wood") == (Object)null) { return list; } HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); List<string> list2 = new List<string>(); list2.AddRange(ConfigPanelLists.Parse(BlacklistedItems?.Value)); list2.AddRange(ConfigPanelLists.Parse(RefinableModdedItems?.Value)); foreach (KeyValuePair<string, string> item in ParseMap(ModdedItemIdols?.Value)) { list2.Add(item.Key); } foreach (string item2 in list2) { if (list.Count >= 1000) { break; } if (hashSet.Add(item2) && (Object)(object)db.GetItemPrefab(item2) == (Object)null) { list.Add(item2); } } return list; } private static Requirement IdolOf(Recipe recipe) { if ((Object)(object)recipe == (Object)null || recipe.m_resources == null) { return null; } Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (val != null && val.m_upgraderResource && (Object)(object)val.m_resItem != (Object)null) { return val; } } return null; } private static string PrefabOf(Component c) { if (!((Object)(object)c != (Object)null) || !((Object)(object)c.gameObject != (Object)null)) { return null; } return VanillaItems.StripClone(((Object)c.gameObject).name); } internal static float BaseStat(SharedData shared) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 if (shared.m_armor > 0f) { return shared.m_armor; } if ((int)shared.m_itemType == 5) { return shared.m_blockPower; } return ((DamageTypes)(ref shared.m_damages)).GetTotalDamage(); } private static void Apply(ObjectDB db) { //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Expected I4, but got Unknown //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_04d8: Unknown result type (might be due to invalid IL or missing references) //IL_04dd: Unknown result type (might be due to invalid IL or missing references) //IL_0575: Unknown result type (might be due to invalid IL or missing references) //IL_07e0: Unknown result type (might be due to invalid IL or missing references) RemoveAll(); _items.Clear(); _byPrefab.Clear(); _version++; if (!VanillaItems.Known) { return; } Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); Dictionary<ItemType, int[]> dictionary2 = new Dictionary<ItemType, int[]>(); Dictionary<string, Requirement> dictionary3 = new Dictionary<string, Requirement>(StringComparer.Ordinal); Dictionary<string, Requirement> dictionary4 = new Dictionary<string, Requirement>(StringComparer.Ordinal); List<Sample> list = new List<Sample>(); Dictionary<string, string> dictionary5 = new Dictionary<string, string>(StringComparer.Ordinal); int num = -1; foreach (Recipe recipe in db.m_recipes) { Requirement val = IdolOf(recipe); if (val == null || (Object)(object)recipe.m_item == (Object)null || recipe.m_item.m_itemData == null || recipe.m_item.m_itemData.m_shared == null) { continue; } SharedData shared = recipe.m_item.m_itemData.m_shared; string text = PrefabOf((Component)(object)val.m_resItem); if (!string.IsNullOrEmpty(shared.m_name) && !dictionary5.ContainsKey(shared.m_name)) { dictionary5[shared.m_name] = text ?? ""; } string prefabName = PrefabOf((Component)(object)recipe.m_item); if (!VanillaItems.IsVanilla(prefabName) || !TryParseIdol(text, out var tier, out var armor)) { continue; } if (tier > num) { num = tier; } if (!dictionary2.TryGetValue(shared.m_itemType, out var value)) { value = new int[2]; dictionary2[shared.m_itemType] = value; } value[(!armor) ? 1u : 0u]++; string key = text + "|" + (int)shared.m_itemType; if (!dictionary3.ContainsKey(key)) { dictionary3[key] = val; } if (!dictionary4.ContainsKey(text)) { dictionary4[text] = val; } list.Add(new Sample { Type = shared.m_itemType, Stat = BaseStat(shared), Tier = tier, Armor = armor }); Requirement[] resources = recipe.m_resources; foreach (Requirement val2 in resources) { if (val2 != null && !val2.m_upgraderResource && !((Object)(object)val2.m_resItem == (Object)null)) { string text2 = PrefabOf((Component)(object)val2.m_resItem); if (!string.IsNullOrEmpty(text2) && (!dictionary.TryGetValue(text2, out var value2) || tier < value2)) { dictionary[text2] = tier; } } } } for (int j = num + 1; j <= 20 && (!((Object)(object)db.GetItemPrefab(IdolPrefab(j, armor: true)) == (Object)null) || !((Object)(object)db.GetItemPrefab(IdolPrefab(j, armor: false)) == (Object)null)); j++) { num = j; } _maxTier = num; if (dictionary4.Count == 0) { Debug.LogWarning((object)"[ForgeOfPotential] Modded idols: no recipe of the game carries an idol, so items from other mods cannot get one."); return; } Dictionary<string, List<Recipe>> dictionary6 = new Dictionary<string, List<Recipe>>(StringComparer.OrdinalIgnoreCase); foreach (Recipe recipe2 in db.m_recipes) { string text3 = (((Object)(object)recipe2 != (Object)null) ? PrefabOf((Component)(object)recipe2.m_item) : null); if (!string.IsNullOrEmpty(text3)) { if (!dictionary6.TryGetValue(text3, out var value3)) { value3 = (dictionary6[text3] = new List<Recipe>()); } value3.Add(recipe2); } } List<KeyValuePair<ModdedIdolInfo, Recipe>> list3 = new List<KeyValuePair<ModdedIdolInfo, Recipe>>(); int num2 = 0; int num3 = 0; foreach (Recipe recipe3 in db.m_recipes) { if ((Object)(object)recipe3 == (Object)null || !recipe3.m_enabled || (Object)(object)recipe3.m_item == (Object)null || recipe3.m_item.m_itemData == null || recipe3.m_item.m_itemData.m_shared == null || recipe3.m_resources == null) { continue; } SharedData shared2 = recipe3.m_item.m_itemData.m_shared; string text4 = PrefabOf((Component)(object)recipe3.m_item); if (string.IsNullOrEmpty(text4) || string.IsNullOrEmpty(shared2.m_name) || VanillaItems.IsVanilla(text4) || _byPrefab.ContainsKey(text4)) { continue; } if (shared2.m_maxQuality <= 1) { num3++; continue; } Requirement val3 = IdolOf(recipe3); string autoIdol = ((val3 != null) ? PrefabOf((Component)(object)val3.m_resItem) : null); bool flag = val3 != null; if (!flag && dictionary5.ContainsKey(shared2.m_name)) { continue; } ModdedIdolInfo moddedIdolInfo = new ModdedIdolInfo(); moddedIdolInfo.Prefab = text4; moddedIdolInfo.Token = shared2.m_name; moddedIdolInfo.Type = shared2.m_itemType; moddedIdolInfo.BaseStat = BaseStat(shared2); Requirement[] resources2 = recipe3.m_resources; foreach (Requirement val4 in resources2) { string text5 = ((val4 != null && !val4.m_upgraderResource) ? PrefabOf((Component)(object)val4.m_resItem) : null); if (!string.IsNullOrEmpty(text5) && !moddedIdolInfo.Ingredients.Contains(text5)) { moddedIdolInfo.Ingredients.Add(text5); } } if (flag) { moddedIdolInfo.OwnIdol = true; moddedIdolInfo.AutoIdol = autoIdol; } else { if (!dictionary2.TryGetValue(shared2.m_itemType, out var value4) || value4[0] + value4[1] == 0) { num2++; continue; } bool armor2 = value4[0] >= value4[1]; string bestMaterial; int num4 = TierFromMaterials(recipe3, dictionary, dictionary6, 0, new HashSet<string>(StringComparer.OrdinalIgnoreCase), out bestMaterial); if (num4 < 0) { num4 = TierFromClosestItem(list, shared2, armor2); moddedIdolInfo.Uncertain = true; bestMaterial = ""; } if (num4 < 0) { num4 = 0; } moddedIdolInfo.ReasonMaterial = bestMaterial ?? ""; moddedIdolInfo.AutoIdol = IdolPrefab(Math.Min(num4, Math.Max(0, num)), armor2); } _items.Add(moddedIdolInfo); _byPrefab[text4] = moddedIdolInfo; list3.Add(new KeyValuePair<ModdedIdolInfo, Recipe>(moddedIdolInfo, recipe3)); } _items.Sort((ModdedIdolInfo a, ModdedIdolInfo b) => string.Compare(a.Prefab, b.Prefab, StringComparison.OrdinalIgnoreCase)); bool flag2 = AllowRefiningModdedItems == null || AllowRefiningModdedItems.Value; List<KeyValuePair<string, string>> list4 = ParseMap(ModdedItemIdols?.Value); HashSet<string> hashSet = ItemListSet(BlacklistedItems?.Value); HashSet<string> hashSet2 = (flag2 ? null : ItemListSet(RefinableModdedItems?.Value)); int num5 = 0; int num6 = 0; bool flag3 = VerboseLogging != null && VerboseLogging.Value; foreach (KeyValuePair<ModdedIdolInfo, Recipe> item2 in list3) { ModdedIdolInfo key2 = item2.Key; Recipe value5 = item2.Value; if (key2.OwnIdol) { continue; } string item = VanillaItems.StripClone(key2.Prefab); if (hashSet.Contains(item) || (!flag2 && !hashSet2.Contains(item))) { continue; } string prefab = key2.AutoIdol; string text6 = MapGet(list4, key2.Prefab); bool flag4 = false; if (text6 != null && TryParseIdol(text6, out var tier2, out var _) && tier2 <= num && (Object)(object)db.GetItemPrefab(text6) != (Object)null) { prefab = text6; flag4 = !string.Equals(text6, key2.AutoIdol, StringComparison.Ordinal); } if (!TryParseIdol(prefab, out var tier3, out var armor4)) { continue; } Requirement val5 = FindTemplate(db, dictionary3, dictionary4, ref tier3, armor4, key2.Type); if (val5 != null) { Requirement val6 = CloneRequirement(val5); val6.m_upgraderResource = true; val6.m_recover = false; Requirement[] array = (Requirement[])(object)new Requirement[value5.m_resources.Length + 1]; Array.Copy(value5.m_resources, array, value5.m_resources.Length); array[^1] = val6; value5.m_resources = array; _added.Add(new Added { Recipe = value5, Requirement = val6 }); num5++; if (flag4) { num6++; } if (flag3) { string text7 = (flag4 ? "chosen on the settings panel" : (key2.Uncertain ? "closest game item by base stat - worth a look" : ("material " + key2.ReasonMaterial))); Debug.Log((object)("[ForgeOfPotential] Modded idols: '" + key2.Prefab + "' (" + ((object)Unsafe.As<ItemType, ItemType>(ref key2.Type)/*cast due to .constrained prefix*/).ToString() + ") -> " + PrefabOf((Component)(object)val6.m_resItem) + " (" + text7 + ").")); } } } string text8 = "[ForgeOfPotential] Modded idols: " + _items.Count + " item(s) from other mods found, " + num5 + " can be refined at the forge (" + num6 + " with an idol chosen on the settings panel)" + ((num2 > 0) ? ("; " + num2 + " recipe(s) of kinds the game never refines were left alone") : "") + ((num3 > 0) ? ("; " + num3 + " item(s) with a maximum quality of 1 cannot be refined (the game never lists them)") : "") + "."; if (!string.Equals(text8, _lastSummary, StringComparison.Ordinal)) { _lastSummary = text8; Debug.Log((object)text8); } } private static int TierFromMaterials(Recipe recipe, Dictionary<string, int> materialTier, Dictionary<string, List<Recipe>> recipesByItem, int depth, HashSet<string> visiting, out string bestMaterial) { int num = -1; bestMaterial = null; Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (val == null || val.m_upgraderResource || (Object)(object)val.m_resItem == (Object)null) { continue; } string text = PrefabOf((Component)(object)val.m_resItem); if (string.IsNullOrEmpty(text)) { continue; } if (!materialTier.TryGetValue(text, out var value)) { value = -1; if (depth < 3 && visiting.Add(text) && recipesByItem.TryGetValue(text, out var value2)) { foreach (Recipe item in value2) { if (!((Object)(object)item == (Object)null) && item.m_resources != null) { string bestMaterial2; int num2 = TierFromMaterials(item, materialTier, recipesByItem, depth + 1, visiting, out bestMaterial2); if (num2 > value) { value = num2; } } } } } if (value > num) { num = value; bestMaterial = text; } } return num; } private static int TierFromClosestItem(List<Sample> samples, SharedData shared, bool armor) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) float num = BaseStat(shared); int num2 = -1; float num3 = float.MaxValue; foreach (Sample sample in samples) { if (sample.Type == shared.m_itemType && sample.Armor == armor) { float num4 = Math.Abs(sample.Stat - num); if (num4 < num3 || (num4 == num3 && sample.Tier > num2)) { num3 = num4; num2 = sample.Tier; } } } return num2; } private static Requirement FindTemplate(ObjectDB db, Dictionary<string, Requirement> byType, Dictionary<string, Requirement> byIdol, ref int tier, bool armor, ItemType type) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected I4, but got Unknown for (int num = tier; num >= 0; num--) { string text = IdolPrefab(num, armor); int tier2 = (int)type; if (byType.TryGetValue(text + "|" + tier2, out var value) || byIdol.TryGetValue(text, out value)) { tier = num; return value; } GameObject itemPrefab = db.GetItemPrefab(text); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val != (Object)null) { foreach (KeyValuePair<string, Requirement> item in byIdol) { if (TryParseIdol(item.Key, out tier2, out var armor2) && armor2 == armor) { Requirement val2 = CloneRequirement(item.Value); val2.m_resItem = val; tier = num; return val2; } } } } return null; } private static Requirement CloneRequirement(Requirement source) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown try { if (_memberwiseClone == null) { _memberwiseClone = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic); } if (_memberwiseClone != null) { object? obj = _memberwiseClone.Invoke(source, null); Requirement val = (Requirement)((obj is Requirement) ? obj : null); if (val != null) { return val; } } } catch { } return new Requirement { m_resItem = source.m_resItem, m_amount = source.m_amount, m_amountPerLevel = source.m_amountPerLevel, m_recover = false, m_upgraderResource = true }; } } internal static class ConfigPanelLists { internal static List<string> Parse(string text) { List<string> list = new List<string>(); if (string.IsNullOrEmpty(text)) { return list; } string[] array = text.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text2 in array) { string text3 = text2.Trim(); if (text3.Length == 0) { continue; } bool flag = false; foreach (string item in list) { if (string.Equals(item, text3, StringComparison.OrdinalIgnoreCase)) { flag = true; break; } } if (!flag) { list.Add(text3); } } return list; } } [HarmonyPatch(typeof(ObjectDB), "Awake")] private class ObjectDB_Awake_ModdedIdolsPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(ObjectDB __instance) { ModdedIdols.EnsureApplied(__instance); } } [HarmonyPatch(typeof(InventoryGui), "UpdateCraftingPanel")] private class InventoryGui_UpdateCraftingPanel_ModdedIdolsPatch { private static void Prefix() { ModdedIdols.EnsureApplied(ObjectDB.instance); } } internal static class RefinementPower { internal static int ForgeLevels(ItemData item, int quality) { if (item == null || item.m_shared == null) { return 0; } int num = Math.Max(1, item.m_shared.m_maxQuality); if (quality <= num) { return 0; } return quality - num; } internal static float Extra(ConfigEntry<float> setting) { if (setting == null) { return 0f; } float value = setting.Value; if (float.IsNaN(value) || float.IsInfinity(value)) { return 0f; } value = Math.Max(0f, Math.Min(500f, value)); return value / 100f - 1f; } internal static bool IsArmorPiece(ItemType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Invalid comparison between Unknown and I4 //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 if (type - 6 <= 1 || type - 11 <= 1 || (int)type == 17) { return true; } return false; } internal static void ScaleDamage(ItemData item, int quality, ref DamageTypes damage) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) if (item == null || item.m_shared == null) { return; } ItemType itemType = item.m_shared.m_itemType; if ((int)itemType == 5 || IsArmorPiece(itemType)) { return; } float num = Extra(RefinementStatMultiplierWeapons); if (num != 0f) { int num2 = ForgeLevels(item, quality); if (num2 > 0) { float num3 = num * (float)num2; DamageTypes damagesPerLevel = item.m_shared.m_damagesPerLevel; damage.m_damage = Math.Max(0f, damage.m_damage + damagesPerLevel.m_damage * num3); damage.m_blunt = Math.Max(0f, damage.m_blunt + damagesPerLevel.m_blunt * num3); damage.m_slash = Math.Max(0f, damage.m_slash + damagesPerLevel.m_slash * num3); damage.m_pierce = Math.Max(0f, damage.m_pierce + damagesPerLevel.m_pierce * num3); damage.m_fire = Math.Max(0f, damage.m_fire + damagesPerLevel.m_fire * num3); damage.m_frost = Math.Max(0f, damage.m_frost + damagesPerLevel.m_frost * num3); damage.m_lightning = Math.Max(0f, damage.m_lightning + damagesPerLevel.m_lightning * num3); damage.m_poison = Math.Max(0f, damage.m_poison + damagesPerLevel.m_poison * num3); damage.m_spirit = Math.Max(0f, damage.m_spirit + damagesPerLevel.m_spirit * num3); } } } internal static void ScaleArmor(ItemData item, int quality, ref float armor) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (item != null && item.m_shared != null && IsArmorPiece(item.m_shared.m_itemType)) { float num = Extra(RefinementStatMultiplierArmor); int num2 = ForgeLevels(item, quality); if (num != 0f && num2 > 0) { armor = Math.Max(0f, armor + item.m_shared.m_armorPerLevel * num * (float)num2); } } } internal static void ScaleBlockPower(ItemData item, int quality, ref float blockPower) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 if (item != null && item.m_shared != null && (int)item.m_shared.m_itemType == 5) { float num = Extra(RefinementStatMultiplierShields); int num2 = ForgeLevels(item, quality); if (num != 0f && num2 > 0) { blockPower = Math.Max(0f, blockPower + item.m_shared.m_blockPowerPerLevel * num * (float)num2); } } } internal static void ScaleDeflection(ItemData item, int quality, ref float force) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 if (item != null && item.m_shared != null && (int)item.m_shared.m_itemType == 5) { float num = Extra(RefinementStatMultiplierShields); int num2 = ForgeLevels(item, quality); if (num != 0f && num2 > 0) { force = Math.Max(0f, force + item.m_shared.m_deflectionForcePerLevel * num * (float)num2); } } } } [HarmonyPatch(typeof(Requirement), "GetAmount")] private class Requirement_GetAmount_Patch { private static void Postfix(Requirement __instance, int qualityLevel, ref int __result) { if (EnableUpgradeChanceAndCostTweaks.Value && __instance != null && __instance.m_upgraderResource) { int num = qualityLevel - 1; int num2 = Math.Max(1, CostScalingLevelStart.Value); long num3 = CostStart.Value; if (CostIncreaseInterval.Value > 0 && num >= num2) { num3 += (long)((num - num2) / CostIncreaseInterval.Value + 1) * (long)CostIncreasePerInterval.Value; } __result = (int)Math.Max(0L, Math.Min(num3, 2147483647L)); } } } [HarmonyPatch(typeof(ObjectDB), "Awake")] private class ObjectDB_AddConfigRecipesMulti { private const string LogPrefix = "[ForgeOfPotential] ConfigRecipes: "; private const string RecipeNameSuffix = "_ConfigRecipe"; private static PropertyInfo _inventoryGuiInstanceProp; private static MethodInfo _updateCraftingPanelMethod; private static ParameterInfo[] _updateCraftingPanelParams; private static bool _reflectionCached; private static void Postfix(ObjectDB __instance) { ApplyConfigRecipes(__instance, removeExistingFirst: false); } public static void ApplyConfigRecipes(ObjectDB __instance, bool removeExistingFirst) { try { if ((Object)(object)__instance == (Object)null) { Debug.LogWarning((object)"[ForgeOfPotential] ConfigRecipes: ObjectDB instance is null."); } else { if ((Object)(object)__instance.GetItemPrefab("Wood") == (Object)null) { return; } int num = 0; if (removeExistingFirst) { num = __instance.m_recipes.RemoveAll((Recipe r) => (Object)(object)r != (Object)null && ((Object)r).name != null && ((Object)r).name.EndsWith("_ConfigRecipe", StringComparison.Ordinal)); if (num > 0) { Debug.Log((object)("[ForgeOfPotential] ConfigRecipes: " + $"Removed {num} previously-added recipe(s) to re-apply updated config.")); } } if (!EnableCustomRecipes.Value) { Debug.Log((object)"[ForgeOfPotential] ConfigRecipes: EnableCustomRecipes is false; skipping recipe registration."); if (num > 0) { RefreshCraftingUI(); } return; } var array = new[] { new { Name = "Upgrader0Armor", RecipeCfg = Recipe_Upgrader0Armor }, new { Name = "Upgrader0Weapon", RecipeCfg = Recipe_Upgrader0Weapon }, new { Name = "Upgrader1Armor", RecipeCfg = Recipe_Upgrader1Armor }, new { Name = "Upgrader1Weapon", RecipeCfg = Recipe_Upgrader1Weapon }, new { Name = "Upgrader2Armor", RecipeCfg = Recipe_Upgrader2Armor }, new { Name = "Upgrader2Weapon", RecipeCfg = Recipe_Upgrader2Weapon }, new { Name = "Upgrader3Armor", RecipeCfg = Recipe_Upgrader3Armor }, new { Name = "Upgrader3Weapon", RecipeCfg = Recipe_Upgrader3Weapon }, new { Name = "Upgrader4Armor", RecipeCfg = Recipe_Upgrader4Armor }, new { Name = "Upgrader4Weapon", RecipeCfg = Recipe_Upgrader4Weapon }, new { Name = "Upgrader5Armor", RecipeCfg = Recipe_Upgrader5Armor }, new { Name = "Upgrader5Weapon", RecipeCfg = Recipe_Upgrader5Weapon }, new { Name = "Upgrader6Armor", RecipeCfg = Recipe_Upgrader6Armor }, new { Name = "Upgrader6Weapon", RecipeCfg = Recipe_Upgrader6Weapon }, new { Name = "Upgrader7Armor", RecipeCfg = Recipe_Upgrader7Armor }, new { Name = "Upgrader7Weapon", RecipeCfg = Recipe_Upgrader7Weapon } }; CraftingStation val = ResolveCraftingStation(__instance, Station_Global?.Value?.Trim()); if ((Object)(object)val != (Object)null) { Debug.Log((object)("[ForgeOfPotential] ConfigRecipes: Resolved global crafting station: '" + (val.m_name ?? ((Object)((Component)val).gameObject).name) + "'")); } else if (!string.IsNullOrEmpty(Station_Global?.Value)) { Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Global station '" + Station_Global.Value + "' was not found; recipes will be craftable by hand.")); } int num2 = 0; var array2 = array; foreach (var anon in array2) { string prefabName = anon.Name; ConfigEntry<string> recipeCfg = anon.RecipeCfg; if (recipeCfg == null) { Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: No config for " + prefabName + "; skipping.")); continue; } string text = recipeCfg.Value?.Trim(); if (string.IsNullOrEmpty(text)) { Debug.Log((object)("[ForgeOfPotential] ConfigRecipes: Empty ingredient list for " + prefabName + "; skipping.")); continue; } GameObject itemPrefab = __instance.GetItemPrefab(prefabName); if ((Object)(object)itemPrefab == (Object)null) { Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Target prefab '" + prefabName + "' not found in ObjectDB; skipping.")); continue; } ItemDrop targetItem = itemPrefab.GetComponent<ItemDrop>(); if ((Object)(object)targetItem == (Object)null) { Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Target prefab '" + prefabName + "' has no ItemDrop; skipping.")); continue; } if (__instance.m_recipes.Exists((Recipe r) => (Object)(object)r != (Object)null && (Object)(object)r.m_item != (Object)null && ((Object)(object)r.m_item == (Object)(object)targetItem || string.Equals(((Object)r.m_item).name, prefabName, StringComparison.Ordinal)))) { Debug.Log((object)("[ForgeOfPotential] ConfigRecipes: Recipe for '" + prefabName + "' already exists; skipping.")); continue; } Requirement[] array3 = ParseRequirements(text, __instance).ToArray(); if (array3 == null || array3.Length == 0) { Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: No valid ingredients parsed for '" + prefabName + "' from '" + text + "'; skipping.")); continue; } Recipe val2 = ScriptableObject.CreateInstance<Recipe>(); ((Object)val2).name = prefabName + "_ConfigRecipe"; val2.m_amount = 1; val2.m_item = targetItem; val2.m_enabled = true; val2.m_craftingStation = val; val2.m_minStationLevel = 0; val2.m_repairStation = null; val2.m_resources = array3; __instance.m_recipes.Add(val2); num2++; Debug.Log((object)("[ForgeOfPotential] ConfigRecipes: Added recipe for '" + prefabName + "' requiring " + string.Join(", ", array3.Select((Requirement req) => $"{GetReqName(req)} x{req.m_amount}")) + ".")); } if (num2 <= 0) { if (num > 0) { RefreshCraftingUI(); } } else { Debug.Log((object)string.Format("{0}Finished adding {1} recipe(s). Total recipes now: {2}", "[ForgeOfPotential] ConfigRecipes: ", num2, __instance.m_recipes.Count)); RefreshCraftingUI(); } } } catch (Exception arg) { Debug.LogError((object)string.Format("{0}Exception: {1}", "[ForgeOfPotential] ConfigRecipes: ", arg)); } } private static void RefreshCraftingUI() { try { if (!_reflectionCached) { Type typeFromHandle = typeof(InventoryGui); _inventoryGuiInstanceProp = typeFromHandle.GetProperty("instance", BindingFlags.Static | BindingFlags.Public); _updateCraftingPanelMethod = typeFromHandle.GetMethod("UpdateCraftingPanel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _updateCraftingPanelParams = _updateCraftingPanelMethod?.GetParameters() ?? Array.Empty<ParameterInfo>(); _reflectionCached = true; } object obj = _inventoryGuiInstanceProp?.GetValue(null); if (obj != null && !(_updateCraftingPanelMethod == null)) { object[] array = new object[_updateCraftingPanelParams.Length]; for (int i = 0; i < _updateCraftingPanelParams.Length; i++) { array[i] = (_updateCraftingPanelParams[i].ParameterType.IsValueType ? Activator.CreateInstance(_updateCraftingPanelParams[i].ParameterType) : null); } _updateCraftingPanelMethod.Invoke(obj, array); Debug.Log((object)"[ForgeOfPotential] ConfigRecipes: Requested InventoryGui.UpdateCraftingPanel()"); } } catch { } } private static List<Requirement> ParseRequirements(string cfgValue, ObjectDB odb) { //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Expected O, but got Unknown List<Requirement> list = new List<Requirement>(); if (string.IsNullOrEmpty(cfgValue)) { return list; } string[] array = cfgValue.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); string[] array2 = array; foreach (string text in array2) { string text2 = text.Trim(); if (string.IsNullOrEmpty(text2)) { continue; } string[] array3 = text2.Split(new char[1] { ':' }, StringSplitOptions.RemoveEmptyEntries); if (array3.Length != 2) { Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Invalid ingredient token '" + text2 + "'. Use 'PrefabName:Amount'.")); continue; } string text3 = array3[0].Trim(); if (!int.TryParse(array3[1].Trim(), out var result) || result <= 0) { Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Invalid amount in token '" + text2 + "'. Must be positive integer.")); continue; } GameObject val = odb.GetItemPrefab(text3) ?? GetPrefabFromZNet(text3); if ((Object)(object)val == (Object)null) { Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Ingredient prefab '" + text3 + "' not found in ObjectDB or ZNetScene.")); continue; } ItemDrop component = val.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null) { Debug.LogWarning((object)("[ForgeOfPotential] ConfigRecipes: Ingredient prefab '" + text3 + "' missing ItemDrop component; skipping.")); continue; } list.Add(new Requirement { m_amount = result, m_resItem = component, m_amountPerLevel = 0, m_upgraderResource = false, m_recover = false }); } return list; } private static GameObject GetPrefabFromZNet(string name) { try { Type typeFromHandle = typeof(ZNetScene); object obj = typeFromHandle.GetProperty("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (typeFromHandle.GetField("m_prefabs", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj) is List<GameObject> list) { return list.Find((GameObject g) => string.Equals(((Object)(object)g != (Object)null) ? ((Object)g).name : null, name, StringComparison.Ordinal)); } } catch { } return null; } private static CraftingStation ResolveCraftingStation(ObjectDB odb, string stationName) { if (string.IsNullOrEmpty(stationName)) { return null; } try { foreach (Recipe recipe in odb.m_recipes) { if ((Object)(object)recipe?.m_craftingStation != (Object)null) { CraftingStation craftingStation = recipe.m_craftingStation; if (string.Equals(craftingStation.m_name, stationName, StringComparison.Ordinal) || string.Equals(((Object)((Component)craftingStation).gameObject).name, stationName, StringComparison.Ordinal)) { return craftingStation; } } } foreach (GameObject item in odb.m_items) { if (!((Object)(object)item == (Object)null)) { CraftingStation component = item.GetComponent<CraftingStation>(); if ((Object)(object)component != (Object)null && (string.Equals(component.m_name, stationName, StringComparison.Ordinal) || string.Equals(((Object)item).name, stationName, StringComparison.Ordinal))) { return component; } } } } catch (Exception arg) { Debug.LogWarning((object)string.Format("{0}ResolveCraftingStation exception: {1}", "[ForgeOfPotential] ConfigRecipes: ", arg)); } return null; } private static string GetReqName(Requirement req) { if (req == null) { return "<null>"; } try { ItemDrop resItem = req.m_resItem; if ((Object)(object)resItem != (Object)null) { return ((Object)((Component)resItem).gameObject).name; } } catch { } return "<unknown>"; } } private static class SimpleConfigSync { private const string RpcName = "OdinBet_ConfigSync"; private const int ProtocolVersion = 19; private static readonly List<Action<ZPackage>> _writers = new List<Action<ZPackage>>(); private static readonly List<Action<ZPackage>> _applies = new List<Action<ZPackage>>(); private static readonly List<Action> _restores = new List<Action>(); private static readonly List<ConfigEntryBase> _entries = new List<ConfigEntryBase>(); private static ZRoutedRpc _registeredOn; private static bool _appliedFromServer; private static readonly List<Action<bool>> _ownSwaps = new List<Action<bool>>(); private static bool _swapping; private static bool _ownSaveNeeded; private static bool _saveOnSetBeforeSession = true; private static long _mismatchServerUid; private const float SyncGraceSeconds = 30f; private static float? _syncGraceStart; private const string HelloRpcName = "OdinBet_ModHello"; private const float HelloWaitSeconds = 30f; private static readonly Dictionary<ZNetPeer, float> _awaitingHello = new Dictionary<ZNetPeer, float>(); private static readonly List<ZNetPeer> _kickQueue = new List<ZNetPeer>(); internal static bool ServerHasMatchingMod => _appliedFromServer; internal static bool SyncGraceActive { get { if (_appliedFromServer || !_syncGraceStart.HasValue) { return false; } ZNet instance = ZNet.instance; if ((Object)(object)instance != (Object)null && !instance.IsServer()) { return Time.unscaledTime - _syncGraceStart.Value < 30f; } return false; } } internal static bool ServerRulesNotApplied { get { if (_mismatchServerUid == 0L || _appliedFromServer) { return false; } ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || instance.IsServer()) { return false; } ZNetPeer serverPeer = instance.GetServerPeer(); if (serverPeer != null) { return serverPeer.m_uid == _mismatchServerUid; } return false; } } internal static bool IsSynced(ConfigEntryBase entry) { if (entry != null) { return _entries.Contains(entry); } return false; } internal static IEnumerable<ConfigEntryBase> SyncedEntries() { return _entries.AsReadOnly(); } public static void Register<T>(ConfigEntry<T> entry) { T original = entry.Value; T authoritative = original; _entries.Add((ConfigEntryBase)(object)entry); _writers.Add(delegate(ZPackage pkg) { WriteValue(pkg, entry.Value); }); _applies.Add(delegate(ZPackage pkg) { authoritative = ReadValue<T>(pkg); entry.Value = authoritative; }); _restores.Add(delegate { authoritative = original; SetWithoutSaving(entry, original); }); _ownSwaps.Add(delegate(bool own) { SetWithoutSaving(entry, own ? original : authoritative); }); entry.SettingChanged += delegate { if (!_swapping) { if (!_appliedFromServer) { original = entry.Value; authoritative = original; } else if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer() && !EqualityComparer<T>.Default.Equals(entry.Value, authoritative)) { Debug.LogWarning((object)("[ForgeOfPotential] ConfigSync: '" + ((ConfigEntryBase)entry).Definition.Section + "/" + ((ConfigEntryBase)entry).Definition.Key + "' was changed locally while connected to a server (Configuration Manager, a reloaded .cfg, or another mod); reverting to the server's value.")); SetWithoutSaving(entry, authoritative); } } }; } private static void WriteValue<T>(ZPackage pkg, T value) { if (!(value is bool flag)) { if (!(value is int num)) { if (!