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 ForgeOfPotentialRedux v1.2.2
ForgeOfPotentialRedux.dll
Decompiled 12 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ConditionalConfigSync; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("ForgeOfPotentialRedux")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.2.2.0")] [assembly: AssemblyInformationalVersion("1.2.2")] [assembly: AssemblyProduct("ForgeOfPotentialRedux")] [assembly: AssemblyTitle("ForgeOfPotentialRedux")] [assembly: AssemblyVersion("1.2.2.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 ForgeOfPotentialRedux { [BepInPlugin("com.dreich.fopredux", "Forge of Potential Redux", "1.2.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.dreich.fopredux"; public const string PluginName = "Forge of Potential Redux"; public const string PluginVersion = "1.2.2"; internal const string ConfigSyncGuid = "_shudnal.ConditionalConfigSync"; internal static ManualLogSource Log; internal static bool ConfigSyncAvailable; internal static ConfigEntry<bool> ResetToLevelOne; internal static ConfigEntry<int> LevelsLostOnFail; internal static ConfigEntry<bool> SpawnEnemyOnFail; internal static ConfigEntry<string> EnemyPrefabNames; internal static ConfigEntry<int> EnemyCount; internal static ConfigEntry<int> EnemyStars; internal static ConfigEntry<float> SpawnDistance; internal static ConfigEntry<bool> UseLastBossBiome; internal static ConfigEntry<bool> TrophyBoostEnabled; internal static ConfigEntry<string> TrophyBonuses; internal static ConfigEntry<bool> TrophyConsumed; internal static ConfigEntry<bool> PenaltyEnabled; internal static ConfigEntry<float> PenaltyPerSuccess; internal static ConfigEntry<float> PenaltyMax; internal static readonly Dictionary<Biome, ConfigEntry<string>> BiomeCreatures = new Dictionary<Biome, ConfigEntry<string>>(); private void Awake() { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Expected O, but got Unknown //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Expected O, but got Unknown //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Expected O, but got Unknown //IL_030c: Unknown result type (might be due to invalid IL or missing references) //IL_0316: Expected O, but got Unknown //IL_034e: Unknown result type (might be due to invalid IL or missing references) //IL_0358: Expected O, but got Unknown //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_037b: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; ConfigSyncAvailable = Chainloader.PluginInfos.ContainsKey("_shudnal.ConditionalConfigSync"); if (ConfigSyncAvailable) { ConfigSyncCompat.Initialize("com.dreich.fopredux", "Forge of Potential Redux", "1.2.2"); } else { Log.LogInfo((object)"ConditionalConfigSync not found; configuration will not be synced from the server."); } ResetToLevelOne = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Forge of Potential", "Reset to level 1 on failure", true, "If true, a failed upgrade always sets the item back to level 1 and 'Levels lost on fail' is ignored.")); LevelsLostOnFail = Synced<int>(((BaseUnityPlugin)this).Config.Bind<int>("Forge of Potential", "Levels lost on fail", 1, new ConfigDescription("How many levels an item loses when an upgrade fails (only used when 'Reset to level 1' is false). 0 = no levels lost. The item never drops below level 1.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 20), Array.Empty<object>()))); SpawnEnemyOnFail = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Enemy Spawn", "Spawn enemy on fail", true, "If true, a failed upgrade spawns enemies next to the player.")); EnemyPrefabNames = Synced<string>(((BaseUnityPlugin)this).Config.Bind<string>("Enemy Spawn", "Enemy prefabs", "Greydwarf", "Comma-separated internal prefab names, e.g. 'Greydwarf,Skeleton,Draugr'. Each spawned enemy picks one at random from the enemies to spawn. Names are case-sensitive.")); EnemyCount = Synced<int>(((BaseUnityPlugin)this).Config.Bind<int>("Enemy Spawn", "Enemy count", 1, new ConfigDescription("How many enemies spawn on a failed upgrade.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()))); EnemyStars = Synced<int>(((BaseUnityPlugin)this).Config.Bind<int>("Enemy Spawn", "Enemy stars", 1, new ConfigDescription("Star level of the spawned enemies. 0 = normal, 1 = one star, 2 = two stars (the vanilla maximum). Values above 2 are stronger than anything the game normally spawns.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 5), Array.Empty<object>()))); SpawnDistance = Synced<float>(((BaseUnityPlugin)this).Config.Bind<float>("Enemy Spawn", "Spawn distance", 5f, new ConfigDescription("How far from the player the enemy appears, in meters.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 20f), Array.Empty<object>()))); UseLastBossBiome = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Boss Biome Spawn", "Use last boss biome", false, "If true, failed upgrades spawn a random creature from the biome of the last boss you helped kill (using the per-biome lists below). If you have not killed a boss on this character, the highest boss defeated on the world is used; if none have been defeated, the regular 'Enemy prefabs' list is used.")); BindBiome((Biome)1, "Meadows (Eikthyr)", "Boar,Neck,Greyling"); BindBiome((Biome)8, "Black Forest (The Elder)", "Greydwarf,Greydwarf_Elite,Greydwarf_Shaman,Troll,Skeleton"); BindBiome((Biome)2, "Swamp (Bonemass)", "Draugr,Draugr_Elite,Blob,Leech,Wraith"); BindBiome((Biome)4, "Mountain (Moder)", "Wolf,Fenring,Hatchling,StoneGolem,Ulv"); BindBiome((Biome)16, "Plains (Yagluth)", "Goblin,GoblinArcher,GoblinBrute,GoblinShaman,Deathsquito,Lox"); BindBiome((Biome)512, "Mistlands (The Queen)", "Seeker,SeekerBrute,Gjall,Tick"); BindBiome((Biome)32, "Ashlands (Fader)", "Charred_Melee,Charred_Archer,Charred_Mage,Asksvin,Volture,Morgen"); TrophyBoostEnabled = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Trophy Boost", "Enabled", false, "If true, carrying a creature trophy adds to the success chance of Forge of Potential upgrades.")); TrophyBonuses = Synced<string>(((BaseUnityPlugin)this).Config.Bind<string>("Trophy Boost", "Trophy bonuses", "TrophyBoar=2,TrophyDeer=2,TrophyNeck=3,TrophyGreydwarf=4,TrophySkeleton=5,TrophyGreydwarfBrute=6,TrophyGreydwarfShaman=6,TrophyTroll=8,TrophyBlob=6,TrophyDraugr=7,TrophyLeech=7,TrophyDraugrElite=10,TrophySurtling=10,TrophyWraith=12,TrophyAbomination=15,TrophyWolf=10,TrophyHatchling=10,TrophyFenring=12,TrophySGolem=14,TrophyCultist=15,TrophyGoblin=14,TrophyDeathsquito=16,TrophyGoblinShaman=16,TrophyGoblinBrute=18,TrophyLox=20,TrophySeeker=20,TrophyTick=20,TrophySeekerBrute=25,TrophyGjall=28,TrophyEikthyr=10,TrophyTheElder=15,TrophyBonemass=20,TrophyDragonQueen=25,TrophyGoblinKing=30,TrophySeekerQueen=35,TrophyFader=40", "Comma-separated 'TrophyPrefab=percent' pairs. The percent is added to the upgrade's success chance (capped at 100%). If several listed trophies are carried, only the best one applies and is used up. Names are case-sensitive; unknown names are skipped with a warning.")); TrophyConsumed = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Trophy Boost", "Consumed on use", true, "If true, one trophy is used up per upgrade attempt. If false, carrying it boosts every attempt.")); PenaltyEnabled = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Success Penalty", "Enabled", false, "If true, every successful upgrade lowers the success chance of that item's next upgrade, stacking with each success in a row. A failed upgrade resets it. Tracked per item.")); PenaltyPerSuccess = Synced<float>(((BaseUnityPlugin)this).Config.Bind<float>("Success Penalty", "Penalty per success (%)", 5f, new ConfigDescription("Percentage points removed from the success chance for each successful upgrade in a row.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>()))); PenaltyMax = Synced<float>(((BaseUnityPlugin)this).Config.Bind<float>("Success Penalty", "Maximum penalty (%)", 50f, new ConfigDescription("The most percentage points the penalty can remove, no matter how long the streak. Trophy bonuses are applied on top of the penalty.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()))); Log.LogInfo((object)"Forge of Potential Redux 1.2.2 loaded, applying patches..."); Harmony val = new Harmony("com.dreich.fopredux"); val.PatchAll(); foreach (MethodBase patchedMethod in val.GetPatchedMethods()) { Log.LogInfo((object)("Patched: " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name)); } LocalizationTweaks.Apply(Localization.instance); } private static ConfigEntry<T> Synced<T>(ConfigEntry<T> entry) { if (ConfigSyncAvailable) { ConfigSyncCompat.AddServerControlled<T>(entry); } return entry; } private void BindBiome(Biome biome, string label, string defaultPrefabs) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) ConfigEntry<string> value = Synced<string>(((BaseUnityPlugin)this).Config.Bind<string>("Boss Biome Spawn", label, defaultPrefabs, "Comma-separated prefab names that can spawn when this was the last boss you helped kill. Names are case-sensitive.")); BiomeCreatures[biome] = value; } } internal static class ConfigSyncCompat { private static object configSync; [MethodImpl(MethodImplOptions.NoInlining)] internal static void Initialize(string guid, string name, string version) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown configSync = (object)new ConfigSync(guid) { DisplayName = name, CurrentVersion = version, MinimumRequiredVersion = version, ModRequired = false }; } [MethodImpl(MethodImplOptions.NoInlining)] internal static void AddServerControlled<T>(ConfigEntry<T> entry) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) ((ConditionalConfigSync)(ConfigSync)configSync).AddConfigEntry<T>(entry, (ConfigSyncMode)0); } } internal static class IlHelpers { public static int LocalIndex(this CodeInstruction instruction) { OpCode opcode = instruction.opcode; if (opcode == OpCodes.Ldloc_0 || opcode == OpCodes.Stloc_0) { return 0; } if (opcode == OpCodes.Ldloc_1 || opcode == OpCodes.Stloc_1) { return 1; } if (opcode == OpCodes.Ldloc_2 || opcode == OpCodes.Stloc_2) { return 2; } if (opcode == OpCodes.Ldloc_3 || opcode == OpCodes.Stloc_3) { return 3; } object operand = instruction.operand; if (!(operand is LocalBuilder localBuilder)) { if (operand is byte) { return (byte)operand; } if (operand is sbyte) { return (sbyte)operand; } if (operand is short) { return (short)operand; } if (operand is ushort) { return (ushort)operand; } if (operand is int) { return (int)operand; } throw new ArgumentException("Instruction does not reference a local variable: " + (object)instruction); } return localBuilder.LocalIndex; } public static CodeInstruction LoadLocal(int index) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown switch (index) { case 0: return new CodeInstruction(OpCodes.Ldloc_0, (object)null); case 1: return new CodeInstruction(OpCodes.Ldloc_1, (object)null); case 2: return new CodeInstruction(OpCodes.Ldloc_2, (object)null); case 3: return new CodeInstruction(OpCodes.Ldloc_3, (object)null); default: if (index <= 255) { return new CodeInstruction(OpCodes.Ldloc_S, (object)(byte)index); } return new CodeInstruction(OpCodes.Ldloc, (object)(short)index); } } public static CodeInstruction StoreLocal(int index) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown switch (index) { case 0: return new CodeInstruction(OpCodes.Stloc_0, (object)null); case 1: return new CodeInstruction(OpCodes.Stloc_1, (object)null); case 2: return new CodeInstruction(OpCodes.Stloc_2, (object)null); case 3: return new CodeInstruction(OpCodes.Stloc_3, (object)null); default: if (index <= 255) { return new CodeInstruction(OpCodes.Stloc_S, (object)(byte)index); } return new CodeInstruction(OpCodes.Stloc, (object)(short)index); } } } public static class BossTracker { internal const string CustomDataKey = "FoP_LastBossBiome"; private const string BossKilledRpc = "FoP_BossKilled"; private const float RecordRange = 100f; private static readonly (string globalKey, Biome biome)[] Bosses = new(string, Biome)[7] { ("defeated_eikthyr", (Biome)1), ("defeated_gdking", (Biome)8), ("defeated_bonemass", (Biome)2), ("defeated_dragon", (Biome)4), ("defeated_goblinking", (Biome)16), ("defeated_queen", (Biome)512), ("defeated_fader", (Biome)32) }; public static void RegisterRpc() { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register<string, Vector3>("FoP_BossKilled", (Action<long, string, Vector3>)OnBossKilled); } } public static void BroadcastBossKill(Character boss) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) string defeatSetGlobalKey = boss.m_defeatSetGlobalKey; if (!string.IsNullOrEmpty(defeatSetGlobalKey) && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "FoP_BossKilled", new object[2] { defeatSetGlobalKey, ((Component)boss).transform.position }); } } private static void OnBossKilled(long sender, string globalKey, Vector3 position) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || Vector3.Distance(((Component)localPlayer).transform.position, position) > 100f) { return; } (string, Biome)[] bosses = Bosses; for (int i = 0; i < bosses.Length; i++) { (string, Biome) tuple = bosses[i]; if (!(tuple.Item1 != globalKey)) { RecordBossKill(localPlayer, tuple.Item2); break; } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to record boss kill: " + ex.Message)); } } public static Biome? GetLastDefeatedBiome() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZoneSystem.instance == (Object)null) { return null; } for (int num = Bosses.Length - 1; num >= 0; num--) { if (ZoneSystem.instance.GetGlobalKey(Bosses[num].globalKey)) { return Bosses[num].biome; } } return null; } private static string CurrentWorldName() { if (!((Object)(object)ZNet.instance != (Object)null)) { return ""; } return ZNet.instance.GetWorldName() ?? ""; } public unsafe static void RecordBossKill(Player player, Biome biome) { player.m_customData["FoP_LastBossBiome"] = ((object)(*(Biome*)(&biome))/*cast due to .constrained prefix*/).ToString() + "|" + CurrentWorldName(); } public static Biome? GetLastBossBiome() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && localPlayer.m_customData.TryGetValue("FoP_LastBossBiome", out var value)) { string[] array = value.Split(new char[1] { '|' }, 2); if ((array.Length < 2 || array[1] == CurrentWorldName()) && Enum.TryParse<Biome>(array[0], out Biome result)) { return result; } } return GetLastDefeatedBiome(); } public static string GetPrefabNameList() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (Plugin.UseLastBossBiome.Value) { Biome? lastBossBiome = GetLastBossBiome(); if (lastBossBiome.HasValue && Plugin.BiomeCreatures.TryGetValue(lastBossBiome.Value, out var value)) { return value.Value; } } return Plugin.EnemyPrefabNames.Value; } } [HarmonyPatch(typeof(Game), "Start")] internal static class BossRpcRegisterPatch { private static void Postfix() { try { BossTracker.RegisterRpc(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to register boss kill RPC: " + ex.Message)); } } } [HarmonyPatch(typeof(Character), "OnDeath")] internal static class BossDeathPatch { private static void Postfix(Character __instance) { try { if ((Object)(object)__instance != (Object)null && __instance.IsBoss()) { BossTracker.BroadcastBossKill(__instance); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to announce boss kill: " + ex.Message)); } } } public static class TrophyBoost { private static string parsedFrom; private static Dictionary<string, float> bonusTable = new Dictionary<string, float>(); private static readonly HashSet<string> warned = new HashSet<string>(); public static Dictionary<string, float> GetBonusTable() { string value = Plugin.TrophyBonuses.Value; if (value == parsedFrom) { return bonusTable; } Dictionary<string, float> dictionary = new Dictionary<string, float>(); string[] array = value.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } string[] array2 = text.Split(new char[1] { '=' }); if (array2.Length != 2 || !float.TryParse(array2[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result <= 0f) { if (warned.Add("bad:" + text)) { Plugin.Log.LogWarning((object)("Ignoring invalid trophy bonus entry '" + text + "'; expected 'TrophyPrefab=percent'.")); } } else { dictionary[array2[0].Trim()] = result; } } if ((Object)(object)ObjectDB.instance != (Object)null) { foreach (string key in dictionary.Keys) { if ((Object)(object)ObjectDB.instance.GetItemPrefab(key) == (Object)null && warned.Add("missing:" + key)) { Plugin.Log.LogWarning((object)("Trophy prefab '" + key + "' not found; it will have no effect.")); } } } bonusTable = dictionary; parsedFrom = value; return dictionary; } private static string GetPrefabName(ItemData item) { GameObject val = item.m_dropPrefab; if ((Object)(object)val == (Object)null && (Object)(object)ObjectDB.instance != (Object)null) { val = ObjectDB.instance.GetItemPrefab(item.m_shared); } if (!((Object)(object)val != (Object)null)) { return null; } return Utils.GetPrefabName(val); } public static float GetBestTrophy(Inventory inventory, out ItemData bestItem) { bestItem = null; float num = 0f; Dictionary<string, float> dictionary = GetBonusTable(); foreach (ItemData allItem in inventory.GetAllItems()) { string prefabName = GetPrefabName(allItem); if (prefabName != null && dictionary.TryGetValue(prefabName, out var value) && value > num) { bestItem = allItem; num = value; } } return num; } public static float ApplyBoost(float roll) { try { if (!Plugin.TrophyBoostEnabled.Value) { return roll; } Player localPlayer = Player.m_localPlayer; Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null); if (val == null) { return roll; } ItemData bestItem; float bestTrophy = GetBestTrophy(val, out bestItem); if (bestItem == null) { return roll; } string arg = Localization.instance.Localize(bestItem.m_shared.m_name); if (Plugin.TrophyConsumed.Value) { val.RemoveItem(bestItem, 1); } UpgradeOdds.QueueNotice($"{arg}: +{bestTrophy:0.#}% upgrade chance"); return Math.Max(0f, roll - bestTrophy / 100f); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Trophy boost check failed: " + ex.Message)); return roll; } } } public static class UpgradeOdds { private const string StreakKey = "FoP_UpgradeStreak"; private static readonly FieldInfo UpgradeItemField = AccessTools.Field(typeof(InventoryGui), "m_craftUpgradeItem"); private static readonly FieldInfo SelectedRecipeField = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe"); private static bool warnedNoRecipe; private static string cachedRateText = ""; private static ItemData cachedItem; private static Recipe cachedRecipe; private static float cachedAt = -1f; private static readonly List<string> PendingNotices = new List<string>(); private static int GetStreak(ItemData item) { if (item?.m_customData != null && item.m_customData.TryGetValue("FoP_UpgradeStreak", out var value) && int.TryParse(value, out var result)) { return Math.Max(0, result); } return 0; } private static float GetPenaltyPercent(ItemData item, out int streak) { streak = 0; if (!Plugin.PenaltyEnabled.Value) { return 0f; } streak = GetStreak(item); return Math.Min((float)streak * Plugin.PenaltyPerSuccess.Value, Plugin.PenaltyMax.Value); } private static bool TryGetBaseChance(Recipe recipe, out float chance) { chance = 0f; if (recipe?.m_resources == null) { return false; } Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (val != null && val.m_upgraderResource) { SharedData val2 = val.m_resItem?.m_itemData?.m_shared; if (val2 == null) { return false; } chance = val2.m_upgradeChance; return true; } } return false; } private static Recipe GetSelectedRecipe(InventoryGui gui) { object obj = SelectedRecipeField?.GetValue(gui); if (obj == null) { return null; } Type type = obj.GetType(); object? obj2 = AccessTools.Property(type, "Recipe")?.GetValue(obj, null) ?? AccessTools.Field(type, "Recipe")?.GetValue(obj); return (Recipe)((obj2 is Recipe) ? obj2 : null); } private static float ComputeRate(Recipe recipe, ItemData item, out float basePercent, out float penalty, out int streak, out float trophy) { basePercent = 0f; penalty = 0f; streak = 0; trophy = 0f; if (!TryGetBaseChance(recipe, out var chance)) { return -1f; } basePercent = chance * 100f; penalty = GetPenaltyPercent(item, out streak); if (Plugin.TrophyBoostEnabled.Value) { Player localPlayer = Player.m_localPlayer; Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null); if (val != null) { trophy = TrophyBoost.GetBestTrophy(val, out var _); } } return Math.Max(0f, Math.Min(100f, basePercent - penalty + trophy)); } private static string BuildRateText(InventoryGui gui, ItemData item) { Recipe selectedRecipe = GetSelectedRecipe(gui); if (item == cachedItem && (Object)(object)selectedRecipe == (Object)(object)cachedRecipe && Time.unscaledTime - cachedAt < 0.25f) { return cachedRateText; } cachedItem = item; cachedRecipe = selectedRecipe; cachedAt = Time.unscaledTime; cachedRateText = ""; if ((Object)(object)selectedRecipe == (Object)null && !warnedNoRecipe) { warnedNoRecipe = true; Plugin.Log.LogWarning((object)"Could not read the selected recipe; the success rate will not be shown."); } float basePercent; float penalty; int streak; float trophy; float num = ComputeRate(selectedRecipe, item, out basePercent, out penalty, out streak, out trophy); if (num < 0f) { return cachedRateText; } string text = string.Format("\n<size={0}><color=yellow>Success rate: {1:0.#}%</color></size>", "100%", num); if (penalty > 0f || trophy > 0f) { string text2 = $"base {basePercent:0.#}%"; if (penalty > 0f) { text2 += $", {streak} in a row -{penalty:0.#}%"; } if (trophy > 0f) { text2 += $", trophy +{trophy:0.#}%"; } text = text + " <size=80%>(" + text2 + ")</size>"; } cachedRateText = text; return cachedRateText; } public static string AppendRate(string warningText, InventoryGui gui, ItemData item) { try { return warningText + BuildRateText(gui, item); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not build the success rate text: " + ex.Message)); return warningText; } } public static void QueueNotice(string notice) { PendingNotices.Add(notice); } public static string AppendNotices(string message) { try { if (PendingNotices.Count == 0) { return message; } string result = message + "\n" + string.Join("\n", PendingNotices.ToArray()); PendingNotices.Clear(); return result; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not append upgrade notices: " + ex.Message)); PendingNotices.Clear(); return message; } } public static float AdjustRoll(InventoryGui gui, float roll) { try { float num = roll; PendingNotices.Clear(); if (Plugin.PenaltyEnabled.Value) { object? obj = UpgradeItemField?.GetValue(gui); int streak; float penaltyPercent = GetPenaltyPercent((ItemData)((obj is ItemData) ? obj : null), out streak); if (penaltyPercent > 0f) { num += penaltyPercent / 100f; QueueNotice($"{streak} successful upgrades in a row: -{penaltyPercent:0.#}% success chance"); } } num = TrophyBoost.ApplyBoost(num); return Math.Min(num, 1.5f); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Upgrade odds adjustment failed: " + ex.Message)); return roll; } } public static void OnUpgradeSucceeded(InventoryGui gui, ItemData newItem) { try { if (Plugin.PenaltyEnabled.Value && newItem != null && newItem.m_customData != null) { object? obj = UpgradeItemField?.GetValue(gui); ItemData item = (ItemData)((obj is ItemData) ? obj : null); newItem.m_customData["FoP_UpgradeStreak"] = (GetStreak(item) + 1).ToString(); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not record upgrade streak: " + ex.Message)); } } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")] internal static class UpdateRecipePatch { private static readonly MethodInfo AppendRateMethod = AccessTools.Method(typeof(UpgradeOdds), "AppendRate", (Type[])null, (Type[])null); private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> originalInstructions) { //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Expected O, but got Unknown //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(originalInstructions); int num = list.FindIndex((CodeInstruction instruction) => instruction.opcode == OpCodes.Ldstr && instruction.operand is string text && text == "$inventory_upgraderwarning"); int num2 = -1; for (int num3 = 0; num3 < list.Count - 1; num3++) { if ((list[num3].operand is MethodBase { Name: "get_ItemData" } || list[num3].operand is FieldInfo { Name: "ItemData" }) && CodeInstructionExtensions.IsStloc(list[num3 + 1], (LocalBuilder)null)) { num2 = list[num3 + 1].LocalIndex(); break; } } if (num < 0 || num + 1 >= list.Count || !(list[num + 1].operand is MethodBase methodBase2) || methodBase2.Name != "Localize" || num2 < 0) { Plugin.Log.LogWarning((object)"Could not find the upgrader warning in UpdateRecipe; the success rate will not be shown."); return list; } list.InsertRange(num + 2, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[3] { new CodeInstruction(OpCodes.Ldarg_0, (object)null), IlHelpers.LoadLocal(num2), new CodeInstruction(OpCodes.Call, (object)AppendRateMethod) }); return list; } } public static class FailSpawner { private static readonly Random random = new Random(); private static readonly HashSet<string> warnedPrefabs = new HashSet<string>(); private static void WarnOnce(string key, string message) { if (warnedPrefabs.Add(key)) { Plugin.Log.LogWarning((object)message); } } public static void SpawnPunishmentEnemies() { //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) try { if (!Plugin.SpawnEnemyOnFail.Value) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNetScene.instance == (Object)null) { return; } List<GameObject> list = new List<GameObject>(); string[] array = BossTracker.GetPrefabNameList().Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { GameObject prefab = ZNetScene.instance.GetPrefab(text); if ((Object)(object)prefab == (Object)null) { WarnOnce("missing:" + text, "Creature '" + text + "' not found; skipping it."); } else if ((Object)(object)prefab.GetComponent<Character>() == (Object)null) { WarnOnce("notcreature:" + text, "'" + text + "' is not a creature; skipping it."); } else { list.Add(prefab); } } } if (list.Count == 0) { Plugin.Log.LogWarning((object)"No valid enemy prefabs configured; nothing spawned."); return; } float num2 = default(float); for (int j = 0; j < Plugin.EnemyCount.Value; j++) { GameObject obj = list[random.Next(list.Count)]; float num = (float)(random.NextDouble() * Math.PI * 2.0); float value = Plugin.SpawnDistance.Value; Vector3 val = ((Component)localPlayer).transform.position + new Vector3(Mathf.Cos(num) * value, 0f, Mathf.Sin(num) * value); float y = ((Component)localPlayer).transform.position.y; if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetSolidHeight(val, ref num2, 3) && num2 >= y - 3f && num2 <= y + 1.5f) { val.y = num2 + 0.5f; } else { val.y = y + 0.5f; } Character component = Object.Instantiate<GameObject>(obj, val, Quaternion.identity).GetComponent<Character>(); if ((Object)(object)component != (Object)null) { component.SetLevel(Plugin.EnemyStars.Value + 1); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to spawn punishment enemy: " + ex.Message)); } } } public static class LocalizationTweaks { internal const string TextSize = "100%"; private static readonly Dictionary<string, string> Translations = new Dictionary<string, string> { { "inventory_upgraderwarning", "<size=100%>Warning - Item could lose upgrades.</size>" }, { "msg_upgrader_failed", "<size=100%>$1 level was reduced!</size>" } }; private static readonly MethodInfo AddWordMethod = AccessTools.Method(typeof(Localization), "AddWord", new Type[2] { typeof(string), typeof(string) }, (Type[])null); private static readonly FieldInfo CacheField = AccessTools.Field(typeof(Localization), "m_cache"); private static bool hasWarnedAboutMissingMethod; public static void Apply(Localization localization) { if (localization == null) { return; } if (AddWordMethod == null) { if (!hasWarnedAboutMissingMethod) { hasWarnedAboutMissingMethod = true; Plugin.Log.LogWarning((object)"Localization.AddWord(string, string) not found; the crafting panel text was not changed."); } return; } try { foreach (KeyValuePair<string, string> translation in Translations) { AddWordMethod.Invoke(localization, new object[2] { translation.Key, translation.Value }); } object obj = CacheField?.GetValue(localization); if (obj != null) { AccessTools.Method(obj.GetType(), "EvictAll", (Type[])null, (Type[])null)?.Invoke(obj, null); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not add custom translations: " + ex.Message)); } } } [HarmonyPatch] internal static class SetupLanguagePatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Localization), "SetupLanguage", (Type[])null, (Type[])null); } private static bool Prepare() { return TargetMethod() != null; } private static void Postfix(Localization __instance) { LocalizationTweaks.Apply(__instance); } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] internal static class DoCraftingPatch { private static readonly FieldInfo CraftRecipeField = AccessTools.Field(typeof(InventoryGui), "m_craftRecipe"); private static readonly MethodInfo LevelAfterFailureMethod = AccessTools.Method(typeof(DoCraftingPatch), "GetLevelAfterFailure", (Type[])null, (Type[])null); private static readonly MethodInfo SpawnEnemiesMethod = AccessTools.Method(typeof(FailSpawner), "SpawnPunishmentEnemies", (Type[])null, (Type[])null); private static readonly MethodInfo BoostMethod = AccessTools.Method(typeof(UpgradeOdds), "AdjustRoll", (Type[])null, (Type[])null); private static readonly MethodInfo SuccessMethod = AccessTools.Method(typeof(UpgradeOdds), "OnUpgradeSucceeded", (Type[])null, (Type[])null); private static readonly MethodInfo AppendNoticesMethod = AccessTools.Method(typeof(UpgradeOdds), "AppendNotices", (Type[])null, (Type[])null); public static int GetLevelAfterFailure(int attemptedLevel) { if (Plugin.ResetToLevelOne.Value) { return 1; } int num = attemptedLevel - 1; return Math.Max(1, num - Plugin.LevelsLostOnFail.Value); } private static void Prefix(InventoryGui __instance) { object? obj = CraftRecipeField?.GetValue(__instance); Recipe val = (Recipe)((obj is Recipe) ? obj : null); if ((Object)(object)val == (Object)null || val.m_resources == null) { return; } Requirement[] resources = val.m_resources; foreach (Requirement val2 in resources) { if (val2 != null && val2.m_upgraderResource) { ItemDrop resItem = val2.m_resItem; if (!((Object)(object)resItem == (Object)null) && resItem.m_itemData != null && resItem.m_itemData.m_shared != null) { resItem.m_itemData.m_shared.m_breakChance = -1f; } } } } private static bool InsertBoostHook(List<CodeInstruction> instructions) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Expected O, but got Unknown for (int i = 0; i < instructions.Count - 2; i++) { if (instructions[i].operand is MethodBase methodBase && !(methodBase.Name != "Range") && !(methodBase.DeclaringType != typeof(Random)) && CodeInstructionExtensions.IsStloc(instructions[i + 1], (LocalBuilder)null)) { ParameterInfo[] parameters = methodBase.GetParameters(); if (parameters.Length == 2 && !(parameters[0].ParameterType != typeof(float))) { int index = instructions[i + 1].LocalIndex(); CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null); val.labels.AddRange(instructions[i + 2].labels); instructions[i + 2].labels.Clear(); instructions.InsertRange(i + 2, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[4] { val, IlHelpers.LoadLocal(index), new CodeInstruction(OpCodes.Call, (object)BoostMethod), IlHelpers.StoreLocal(index) }); return true; } } } return false; } private static bool InsertSuccessHook(List<CodeInstruction> instructions) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown int num = instructions.FindIndex((CodeInstruction instruction) => instruction.opcode == OpCodes.Ldstr && instruction.operand is string text && text.Contains("succeeded")); if (num < 0) { return false; } for (int num2 = num; num2 < instructions.Count - 1; num2++) { if (instructions[num2].operand is MethodBase { Name: "AddItem" } && CodeInstructionExtensions.IsStloc(instructions[num2 + 1], (LocalBuilder)null)) { int index = instructions[num2 + 1].LocalIndex(); instructions.InsertRange(num2 + 2, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[3] { new CodeInstruction(OpCodes.Ldarg_0, (object)null), IlHelpers.LoadLocal(index), new CodeInstruction(OpCodes.Call, (object)SuccessMethod) }); return true; } } return false; } private static int InsertNoticeHooks(List<CodeInstruction> instructions) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown int num = 0; for (int i = 0; i < instructions.Count; i++) { if (instructions[i].opcode != OpCodes.Ldstr || !(instructions[i].operand is string text) || (text != "$msg_upgrader_success" && text != "$msg_upgrader_failed")) { continue; } for (int j = i + 1; j < instructions.Count; j++) { if (instructions[j].operand is MethodBase { Name: "Localize" }) { instructions.Insert(j + 1, new CodeInstruction(OpCodes.Call, (object)AppendNoticesMethod)); num++; i = j + 1; break; } } } return num; } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> originalInstructions) { //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(originalInstructions); int num = list.FindIndex((CodeInstruction instruction) => instruction.opcode == OpCodes.Ldstr && instruction.operand is string text && text.Contains("FAILED")); if (num < 0) { Plugin.Log.LogError((object)"Game build differs from the one the mod was written for; transpiler skipped."); return list; } int num2 = 0; for (int num3 = num; num3 < list.Count - 2 && !(list[num3].operand is MethodBase { Name: "AddItem" }); num3++) { if (CodeInstructionExtensions.IsLdloc(list[num3], (LocalBuilder)null) && list[num3 + 1].opcode == OpCodes.Ldc_I4_2 && list[num3 + 2].opcode == OpCodes.Sub) { list[num3 + 1].opcode = OpCodes.Call; list[num3 + 1].operand = LevelAfterFailureMethod; list.RemoveAt(num3 + 2); num2++; } } if (num2 == 0) { Plugin.Log.LogWarning((object)"Could not find the level loss in DoCrafting; failed upgrades will use the vanilla level loss."); } CodeInstruction val = new CodeInstruction(OpCodes.Call, (object)SpawnEnemiesMethod); val.labels.AddRange(list[num].labels); list[num].labels.Clear(); list.Insert(num, val); if (!InsertBoostHook(list)) { Plugin.Log.LogWarning((object)"Could not find the upgrade roll in DoCrafting; trophy boosts and the success penalty will not apply."); } if (!InsertSuccessHook(list)) { Plugin.Log.LogWarning((object)"Could not find the upgrade success in DoCrafting; success streaks will not be recorded."); } if (InsertNoticeHooks(list) == 0) { Plugin.Log.LogWarning((object)"Could not find the upgrade messages in DoCrafting; trophy and streak notices will not be shown."); } return list; } } }