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 TheGodRunTheoryCompat v2.0.2
plugins/TheGodRunTheoryCompat/TheGodRunTheoryCompat.dll
Decompiled 3 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 HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("TheGodRunTheoryCompat")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.0.2.0")] [assembly: AssemblyInformationalVersion("2.0.2+225a7ea07af57655f16663f5d88f42a5b0427f82")] [assembly: AssemblyProduct("TheGodRunTheoryCompat")] [assembly: AssemblyTitle("TheGodRunTheoryCompat")] [assembly: AssemblyVersion("2.0.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 TheGodRunTheoryCompat { internal static class ClassicItemsPatch { private const string Clover = "ClassicItemsReturns.Items.Uncommon.ElitesDropItems"; private const string ArmsRace = "ClassicItemsReturns.Items.Uncommon.ArmsRace"; private const string EnergyCell = "ClassicItemsReturns.Items.Uncommon.EnergyCell"; private const string FireShield = "ClassicItemsReturns.Items.Common.FireShield"; private const string Jewel = "ClassicItemsReturns.Items.Uncommon.Jewel"; private const string Medallion = "ClassicItemsReturns.Items.Uncommon.RoyalMedallion"; private const string RustyJetpack = "ClassicItemsReturns.Items.Uncommon.RustyJetpack"; private const string SmartShopper = "ClassicItemsReturns.Items.Uncommon.SmartShopper"; private const string SnakeEyes = "ClassicItemsReturns.Items.Common.SnakeEyes"; private const string SnakeTracker = "ClassicItemsReturns.Items.Common.SnakeEyes+MasterSnakeEyesTracker"; private const string Thallium = "ClassicItemsReturns.Items.Rare.Thallium"; private const string ToughTimes = "ClassicItemsReturns.Items.Uncommon.BearArmor"; private static int cloverConstantReplacements; private static int energyCellConstantReplacements; private static int fireShieldThresholdReplacements; private static int fireShieldStackInjections; private static int rustyJetpackConstantReplacements; private static int snakeEyesCapReplacements; internal static void Install(Harmony harmony) { ApplyFieldsAndLanguage(); cloverConstantReplacements = 0; Patch(harmony, "ClassicItemsReturns.Items.Uncommon.ElitesDropItems", "RollForMaster", "CloverTranspiler"); if (cloverConstantReplacements != 2) { throw new InvalidOperationException($"56 Leaf Clover gameplay patch expected 2 constants but replaced {cloverConstantReplacements}."); } energyCellConstantReplacements = 0; Patch(harmony, "ClassicItemsReturns.Items.Uncommon.EnergyCell", "RecalculateStatsAPI_GetStatCoefficients", "EnergyCellTranspiler"); Patch(harmony, "ClassicItemsReturns.Items.Uncommon.EnergyCell+EnergyCellBehavior", "FixedUpdate", "EnergyCellTranspiler"); RequireCount("Energy Cell gameplay", energyCellConstantReplacements, 6); fireShieldThresholdReplacements = 0; fireShieldStackInjections = 0; Patch(harmony, "ClassicItemsReturns.Items.Common.FireShield", "ProcFireShield", "FireShieldTranspiler"); RequireCount("Fire Shield threshold", fireShieldThresholdReplacements, 1); RequireCount("Fire Shield stack scaling", fireShieldStackInjections, 1); rustyJetpackConstantReplacements = 0; Patch(harmony, "ClassicItemsReturns.Items.Uncommon.RustyJetpack", "RecalculateStatsAPI_GetStatCoefficients", "RustyJetpackTranspiler"); RequireCount("Rusty Jetpack gameplay", rustyJetpackConstantReplacements, 2); snakeEyesCapReplacements = 0; Patch(harmony, "ClassicItemsReturns.Items.Common.SnakeEyes+MasterSnakeEyesTracker", "Increment", "SnakeEyesCapTranspiler"); RequireCount("Snake Eyes stage cap", snakeEyesCapReplacements, 1); } internal static void ApplyFieldsAndLanguage() { BalanceConfig balance = Plugin.Balance; if (balance != null && !(ReflectionUtil.FindType("ClassicItemsReturns.Items.Uncommon.ArmsRace", "com.RiskySleeps.ClassicItemsReturns") == null)) { Set("ClassicItemsReturns.Items.Uncommon.ArmsRace", "cooldown", balance.ArmsRaceCooldown.Value); Set("ClassicItemsReturns.Items.Uncommon.ArmsRace", "missileCount", balance.ArmsRaceMissiles.Value); Set("ClassicItemsReturns.Items.Uncommon.ArmsRace", "missileCountPerStack", balance.ArmsRaceMissilesPerStack.Value); Set("ClassicItemsReturns.Items.Uncommon.ArmsRace", "damageCoeff", balance.ArmsRaceDamage.Value); ReflectionUtil.SetStaticField("ClassicItemsReturns.Items.Common.FireShield", "explosionDamageCoefficient", balance.FireShieldExplosion.Value, "com.RiskySleeps.ClassicItemsReturns"); ReflectionUtil.SetStaticField("ClassicItemsReturns.Items.Common.FireShield", "igniteDamageCoefficient", balance.FireShieldBurnBase.Value, "com.RiskySleeps.ClassicItemsReturns"); Set("ClassicItemsReturns.Items.Uncommon.Jewel", "moneyToGrant", balance.JewelGold.Value); Set("ClassicItemsReturns.Items.Uncommon.Jewel", "barrierInitial", balance.JewelBarrier.Value); Set("ClassicItemsReturns.Items.Uncommon.Jewel", "barrierStack", balance.JewelBarrierPerStack.Value); Set("ClassicItemsReturns.Items.Uncommon.RoyalMedallion", "procChance", balance.MedallionChance.Value); Set("ClassicItemsReturns.Items.Uncommon.RoyalMedallion", "buffDuration", balance.MedallionDuration.Value); Set("ClassicItemsReturns.Items.Uncommon.RoyalMedallion", "buffDurationStack", balance.MedallionDurationPerStack.Value); Set("ClassicItemsReturns.Items.Uncommon.SmartShopper", "moneyMult", balance.ShopperGold.Value); Set("ClassicItemsReturns.Items.Uncommon.SmartShopper", "moneyMultStack", balance.ShopperGold.Value); Set("ClassicItemsReturns.Items.Common.SnakeEyes", "critChance", balance.SnakeCrit.Value); Set("ClassicItemsReturns.Items.Common.SnakeEyes", "critDamageStack", balance.SnakeCritDamagePerStack.Value); Set("ClassicItemsReturns.Items.Rare.Thallium", "chance", balance.ThalliumChance.Value); Set("ClassicItemsReturns.Items.Rare.Thallium", "enemyAttackDamageCoef", balance.ThalliumDamage.Value); Set("ClassicItemsReturns.Items.Rare.Thallium", "enemyMoveSpeedCoef", balance.ThalliumSlow.Value); Set("ClassicItemsReturns.Items.Rare.Thallium", "duration", balance.ThalliumDuration.Value); Set("ClassicItemsReturns.Items.Rare.Thallium", "durationPerStack", balance.ThalliumDurationPerStack.Value); Set("ClassicItemsReturns.Items.Uncommon.BearArmor", "armor", balance.ToughTimesArmor.Value); Set("ClassicItemsReturns.Items.Uncommon.BearArmor", "armorPerStack", balance.ToughTimesArmor.Value); ReflectionUtil.AddLanguage("CLASSICITEMSRETURNS_ITEM_56LEAFCLOVER_DESCRIPTION", $"Elite enemies have a <style=cIsUtility>{balance.CloverBaseChance.Value:0.##}% chance</style> <style=cStack>(+{balance.CloverStackChance.Value:0.##}% chance per stack)</style> to <style=cIsUtility>drop an item</style> on death. <style=cIsUtility>Unaffected by luck</style>."); ReflectionUtil.AddLanguage("CLASSICITEMSRETURNS_ITEM_ARMSRACE_DESCRIPTION", $"Drones periodically fire a barrage of <style=cIsDamage>{balance.ArmsRaceMissiles.Value}</style> <style=cStack>(+{balance.ArmsRaceMissilesPerStack.Value} per stack)</style> missiles that deal <style=cIsDamage>{balance.ArmsRaceDamage.Value * 100f:0.#}%</style> damage. Recharges every <style=cIsUtility>{balance.ArmsRaceCooldown.Value:0.##}</style> seconds."); ReflectionUtil.AddLanguage("CLASSICITEMSRETURNS_ITEM_ENERGYCELL_DESCRIPTION", $"Increase attack speed by <style=cIsDamage>{balance.EnergyCellHealthy.Value * 100f:0.#}% per stack</style>, increasing by up to another <style=cIsDamage>{balance.EnergyCellLowHealthExtra.Value * 100f:0.#}% per stack</style> as health falls."); ReflectionUtil.AddLanguage("CLASSICITEMSRETURNS_ITEM_FIRESHIELD_DESCRIPTION", $"After taking more than <style=cIsHealth>{balance.FireShieldThreshold.Value * 100f:0.#}% of your maximum health</style> as damage, explode for <style=cIsDamage>{balance.FireShieldExplosion.Value * 100f:0.#}%</style> base damage. Additionally, enemies burn for <style=cIsDamage>{balance.FireShieldBurnBase.Value * 100f:0.#}%</style> <style=cStack>(+{balance.FireShieldBurnPerStack.Value * 100f:0.#}% per extra stack)</style> base damage."); ReflectionUtil.AddLanguage("CLASSICITEMSRETURNS_ITEM_JEWEL_DESCRIPTION", $"Activating an interactable grants a temporary barrier for <style=cIsHealing>{balance.JewelBarrier.Value * 100f:0.#}%</style> <style=cStack>(+{balance.JewelBarrierPerStack.Value * 100f:0.#}% per stack)</style> of maximum health and <style=cIsUtility>${balance.JewelGold.Value}</style>. Scales with time."); ReflectionUtil.AddLanguage("CLASSICITEMSRETURNS_ITEM_ROYALMEDALLION_DESCRIPTION", $"<style=cIsDamage>{balance.MedallionChance.Value:0.##}%</style> chance on hitting a boss to drop a buffing orb that improves health regeneration, attack speed, movement speed, and base damage for <style=cIsDamage>{balance.MedallionDuration.Value:0.##}s</style> <style=cStack>(+{balance.MedallionDurationPerStack.Value:0.##}s per stack)</style>."); ReflectionUtil.AddLanguage("CLASSICITEMSRETURNS_ITEM_RUSTYJETPACK_DESCRIPTION", $"Increase jump height by <style=cIsUtility>{balance.RustyJump.Value * 100f:0.#}%</style> <style=cStack>(+{balance.RustyJump.Value * 100f:0.#}% per stack)</style> and gain a short airhop."); ReflectionUtil.AddLanguage("CLASSICITEMSRETURNS_ITEM_SMARTSHOPPER_DESCRIPTION", $"Enemies drop an extra <style=cIsUtility>{balance.ShopperGold.Value * 100f:0.#}%</style> <style=cStack>(+{balance.ShopperGold.Value * 100f:0.#}% per stack)</style> gold on kill."); ReflectionUtil.AddLanguage("CLASSICITEMSRETURNS_ITEM_SNAKEEYES_DESCRIPTION", $"Using a shrine increases critical chance by <style=cIsDamage>{balance.SnakeCrit.Value:0.##}%</style> and critical damage by <style=cIsDamage>0%</style> <style=cStack>(+{balance.SnakeCritDamagePerStack.Value * 100f:0.##}% per item stack)</style>, up to <style=cIsDamage>{Math.Max(1, Math.Min(6, balance.SnakeStageActivations.Value))} times</style>. Resets each stage."); ReflectionUtil.AddLanguage("CLASSICITEMSRETURNS_ITEM_THALLIUM_DESCRIPTION", $"On hit, <style=cIsDamage>{balance.ThalliumChance.Value:0.##}% chance</style> to poison using <style=cIsDamage>{balance.ThalliumDamage.Value * 100f:0.#}% damage scaling</style> and slow movement by <style=cIsDamage>{balance.ThalliumSlow.Value * 100f:0.#}%</style> for <style=cIsDamage>{balance.ThalliumDuration.Value:0.##} seconds</style>."); ReflectionUtil.AddLanguage("CLASSICITEMSRETURNS_ITEM_TOUGHTIMES_DESCRIPTION", $"Grants <style=cIsHealing>+{balance.ToughTimesArmor.Value:0.#} armor</style> <style=cStack>(+{balance.ToughTimesArmor.Value:0.#} per stack)</style>."); } } private static void Set(string type, string field, object value) { ReflectionUtil.SetInstanceField(type, field, value, "com.RiskySleeps.ClassicItemsReturns"); } private static void Patch(Harmony harmony, string typeName, string methodName, string transpilerName) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(ReflectionUtil.RequireType(typeName, "com.RiskySleeps.ClassicItemsReturns"), methodName, (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeName, methodName); MethodInfo methodInfo2 = AccessTools.Method(typeof(ClassicItemsPatch), transpilerName, (Type[])null, (Type[])null); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null); } private static IEnumerable<CodeInstruction> CloverTranspiler(IEnumerable<CodeInstruction> instructions) { foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ldc_R4 && instruction.operand is float left) { if (Nearly(left, 4.5f)) { instruction.operand = Plugin.Balance.CloverBaseChance.Value; } else { if (!Nearly(left, 1.5f)) { yield return instruction; continue; } instruction.operand = Plugin.Balance.CloverStackChance.Value; } cloverConstantReplacements++; } yield return instruction; } } private static IEnumerable<CodeInstruction> EnergyCellTranspiler(IEnumerable<CodeInstruction> instructions) { foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ldc_R4 && instruction.operand is float left) { if (Nearly(left, 0.2f)) { instruction.operand = Plugin.Balance.EnergyCellHealthy.Value; } else { if (!Nearly(left, 0.4f)) { yield return instruction; continue; } instruction.operand = Plugin.Balance.EnergyCellLowHealthExtra.Value; } energyCellConstantReplacements++; } yield return instruction; } } private static IEnumerable<CodeInstruction> FireShieldTranspiler(IEnumerable<CodeInstruction> source) { //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(source); bool flag = false; for (int i = 0; i < list.Count; i++) { CodeInstruction val = list[i]; if (val.opcode == OpCodes.Ldc_R4 && val.operand is float left && Nearly(left, 0.049f)) { val.operand = Math.Max(0f, Plugin.Balance.FireShieldThreshold.Value - 0.001f); fireShieldThresholdReplacements++; } if (CodeInstructionExtensions.LoadsField(val, AccessTools.Field(ReflectionUtil.RequireType("ClassicItemsReturns.Items.Common.FireShield", "com.RiskySleeps.ClassicItemsReturns"), "igniteDamageCoefficient"), false)) { flag = true; } else if (flag && val.opcode == OpCodes.Conv_R4) { list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(ClassicItemsPatch), "FireShieldStackMultiplier", (Type[])null, (Type[])null))); flag = false; fireShieldStackInjections++; i++; } } return list; } private static float FireShieldStackMultiplier(float stacks) { return 1f + Plugin.Balance.FireShieldBurnPerStack.Value * Math.Max(0f, stacks - 1f); } private static IEnumerable<CodeInstruction> RustyJetpackTranspiler(IEnumerable<CodeInstruction> instructions) { foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ldc_R4 && instruction.operand is float left && Nearly(left, 0.25f)) { instruction.operand = Plugin.Balance.RustyJump.Value; rustyJetpackConstantReplacements++; } yield return instruction; } } private static IEnumerable<CodeInstruction> SnakeEyesCapTranspiler(IEnumerable<CodeInstruction> instructions) { int cap = Math.Max(1, Math.Min(6, Plugin.Balance.SnakeStageActivations.Value)); foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ldc_I4_6) { instruction.opcode = OpCodes.Ldc_I4; instruction.operand = cap; snakeEyesCapReplacements++; } yield return instruction; } } private static bool Nearly(float left, float right) { return Math.Abs(left - right) < 0.0001f; } private static void RequireCount(string patchName, int actual, int expected) { if (actual != expected) { throw new InvalidOperationException($"{patchName} patch expected {expected} replacement(s) but made {actual}."); } } } internal static class CloudburstPatch { private const string Glass = "Cloudburst.Items.Gray.GlassHarvester"; private const string Bismuth = "Cloudburst.Items.Green.BismuthEarrings"; private const string Fabinhoru = "Cloudburst.Items.Green.FabinhoruDagger"; private const string Japes = "Cloudburst.Items.Green.JapesCloak"; private const string Keycard = "Cloudburst.Items.Green.EnigmaticKeycard"; private static int glassReplacements; private static int japesArmorReplacements; private static int japesHealingReplacements; private static int japesDurationReplacements; private static int keycardDamageInjections; internal static void Install(Harmony harmony) { if (!(ReflectionUtil.FindType("Cloudburst.Items.Green.BismuthEarrings", "CloudBurstTeam.Cloudburst") == null)) { ApplyFieldsAndLanguage(); glassReplacements = 0; PatchTranspiler(harmony, "Cloudburst.Items.Gray.GlassHarvester", "RecalculateStatsAPI_GetStatCoefficients", "GlassTranspiler"); RequireCount("Glass Harvester", glassReplacements, 2); PatchPostfix(harmony, "Cloudburst.Items.Green.FabinhoruDagger", "RecalculateStatsAPI_GetStatCoefficients", "FabinhoruPostfix"); japesArmorReplacements = 0; PatchTranspiler(harmony, "Cloudburst.Items.Green.JapesCloak", "RecalculateStatsAPI_GetStatCoefficients", "JapesArmorTranspiler"); RequireCount("Jape's Cloak armor", japesArmorReplacements, 1); japesHealingReplacements = 0; PatchTranspiler(harmony, "Cloudburst.Items.Green.JapesCloak", "HealthComponent_Heal", "JapesHealingTranspiler"); RequireCount("Jape's Cloak healing", japesHealingReplacements, 1); japesDurationReplacements = 0; PatchTranspiler(harmony, "Cloudburst.Items.Green.JapesCloak", "GenericPickupController_AttemptGrant", "JapesDurationTranspiler"); RequireCount("Jape's Cloak duration", japesDurationReplacements, 1); keycardDamageInjections = 0; PatchTranspiler(harmony, "Cloudburst.Items.Green.EnigmaticKeycard", "GlobalEventManager_OnHitEnemy", "KeycardDamageTranspiler"); RequireCount("Enigmatic Keycard damage", keycardDamageInjections, 1); } } internal static void ApplyFieldsAndLanguage() { BalanceConfig balance = Plugin.Balance; if (balance != null && !(ReflectionUtil.FindType("Cloudburst.Items.Green.BismuthEarrings", "CloudBurstTeam.Cloudburst") == null)) { float num = Math.Max(0f, balance.CloudburstScale.Value); ReflectionUtil.SetStaticField("Cloudburst.Items.Green.BismuthEarrings", "BaseBarrier", 15f * num, "CloudBurstTeam.Cloudburst"); ReflectionUtil.SetStaticField("Cloudburst.Items.Green.BismuthEarrings", "StackingBarrier", 10f * num, "CloudBurstTeam.Cloudburst"); ReflectionUtil.SetStaticField("Cloudburst.Items.Green.EnigmaticKeycard", "Chance", 4f * num, "CloudBurstTeam.Cloudburst"); ReflectionUtil.AddLanguage("ITEM_GLASSHARVESTER_DESC", $"Gain <style=cIsDamage>5% critical strike chance</style>. <style=cIsDamage>Critical Strikes</style> deal an additional <style=cIsDamage>{40f * num:0.#}% damage</style> <style=cStack>(+{30f * num:0.#}% per stack)</style>. Taking damage below <style=cIsHealth>25% health</style> breaks this item and grants experience."); ReflectionUtil.AddLanguage("ITEM_BARRIERONCRIT_DESCRIPTION", $"Gain <style=cIsDamage>5% bleed chance</style>. Applying bleed grants <style=cIsHealing>{15f * num:0.#} barrier</style> <style=cStack>(+{10f * num:0.#} per stack)</style>."); ReflectionUtil.AddLanguage("ITEM_PICKUPBUFF_DESCRIPTION", $"Picking up an item grants a stacking buff for <style=cIsUtility>{10f * balance.JapesDurationScale.Value:0.#} seconds</style>. Each buff retains Jape's original stacking rules with its armor and healing values reduced by {100f * (1f - num):0.#}%."); ReflectionUtil.AddLanguage("ITEM_ENIGMATICKEYCARD_DESCRIPTION", $"{4f * num:0.#}% chance on hit to spawn a seeking orb. Its original complete damage calculation is multiplied by {num:0.##}."); } } private static void PatchTranspiler(Harmony harmony, string typeName, string methodName, string patchName) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(ReflectionUtil.RequireType(typeName, "CloudBurstTeam.Cloudburst"), methodName, (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeName, methodName); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CloudburstPatch), patchName, (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null); } private static void PatchPostfix(Harmony harmony, string typeName, string methodName, string patchName) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(ReflectionUtil.RequireType(typeName, "CloudBurstTeam.Cloudburst"), methodName, (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeName, methodName); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CloudburstPatch), patchName, (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static IEnumerable<CodeInstruction> GlassTranspiler(IEnumerable<CodeInstruction> instructions) { float scale = Math.Max(0f, Plugin.Balance.CloudburstScale.Value); foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ldc_R4 && instruction.operand is float left) { if (Nearly(left, 0.3f)) { instruction.operand = 0.3f * scale; } else { if (!Nearly(left, 0.1f)) { yield return instruction; continue; } instruction.operand = 0.1f * scale; } glassReplacements++; } yield return instruction; } } private static void FabinhoruPostfix(object sender, object args) { object staticField = ReflectionUtil.GetStaticField("Cloudburst.Items.Green.FabinhoruDagger", "fabinhorusBuff", "CloudBurstTeam.Cloudburst"); int buffCount = ReflectionUtil.GetBuffCount(sender, staticField); if (buffCount > 0) { float num = Math.Max(0f, Plugin.Balance.CloudburstScale.Value); float num2 = (float)buffCount * 15f + 15f; ReflectionUtil.AddFloatMember(args, "armorAdd", num2 * (1f - num)); } } private static IEnumerable<CodeInstruction> JapesArmorTranspiler(IEnumerable<CodeInstruction> instructions) { return ReplaceFloat(instructions, 5f, 5f * Math.Max(0f, Plugin.Balance.CloudburstScale.Value), delegate { japesArmorReplacements++; }); } private static IEnumerable<CodeInstruction> JapesHealingTranspiler(IEnumerable<CodeInstruction> instructions) { float num = Math.Max(0f, Plugin.Balance.CloudburstScale.Value); return ReplaceFloat(instructions, 1.3f, 1f + 0.3f * num, delegate { japesHealingReplacements++; }); } private static IEnumerable<CodeInstruction> JapesDurationTranspiler(IEnumerable<CodeInstruction> instructions) { return ReplaceFloat(instructions, 10f, 10f * Math.Max(0f, Plugin.Balance.JapesDurationScale.Value), delegate { japesDurationReplacements++; }); } private static IEnumerable<CodeInstruction> KeycardDamageTranspiler(IEnumerable<CodeInstruction> source) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(source); FieldInfo fieldInfo = AccessTools.Field(ReflectionUtil.RequireType("Cloudburst.Items.Green.EnigmaticKeycard", "CloudBurstTeam.Cloudburst"), "BaseDamage"); bool flag = false; for (int i = 0; i < list.Count; i++) { if (CodeInstructionExtensions.LoadsField(list[i], fieldInfo, false)) { flag = true; } else if (flag && list[i].opcode == OpCodes.Add) { list.Insert(i + 1, new CodeInstruction(OpCodes.Ldc_R4, (object)Math.Max(0f, Plugin.Balance.CloudburstScale.Value))); list.Insert(i + 2, new CodeInstruction(OpCodes.Mul, (object)null)); keycardDamageInjections++; break; } } return list; } private static IEnumerable<CodeInstruction> ReplaceFloat(IEnumerable<CodeInstruction> instructions, float oldValue, float newValue, Action replaced) { foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ldc_R4 && instruction.operand is float left && Nearly(left, oldValue)) { instruction.operand = newValue; replaced(); } yield return instruction; } } private static bool Nearly(float left, float right) { return Math.Abs(left - right) < 0.0001f; } private static void RequireCount(string patchName, int actual, int expected) { if (actual != expected) { throw new InvalidOperationException($"{patchName} patch expected {expected} replacement(s) but made {actual}."); } } } internal static class LotusGuard { private const string LotusType = "vanillaVoid.Items.CrystalLotus"; private static bool logged; internal static void Install(Harmony harmony) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown Type type = ReflectionUtil.FindType("vanillaVoid.Items.CrystalLotus", "com.Zenithrium.vanillaVoid"); if (!(type == null)) { MethodInfo methodInfo = AccessTools.Method(type, "LotusTeamBuff", (Type[])null, (Type[])null) ?? throw new MissingMethodException("vanillaVoid.Items.CrystalLotus", "LotusTeamBuff"); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(LotusGuard), "Finalizer", (Type[])null, (Type[])null)), (HarmonyMethod)null); } } private static Exception Finalizer(Exception __exception) { if (__exception is NullReferenceException) { if (!logged) { logged = true; Plugin.Instance.Log.LogWarning((object)"Crystalline Lotus skipped one invalid/destroyed visual target. Valid ward behavior remains active."); } return null; } return __exception; } } [HarmonyPatch] internal static class OppressiveAuraPhysicsPatch { private const string BehaviourTypeName = "NemesisRisingTides.Contents.Oppressive+AffixOppressive+NemesisAffixOppressiveBehaviour"; private static MethodBase TargetMethod() { Type type = ReflectionUtil.FindType("NemesisRisingTides.Contents.Oppressive+AffixOppressive+NemesisAffixOppressiveBehaviour", "prodzpod.NemesisRisingTides"); if (!(type == null)) { return AccessTools.Method(type, "FixedUpdate", (Type[])null, (Type[])null); } return null; } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { MethodInfo overlapCapsule = AccessTools.Method(typeof(Physics), "OverlapCapsule", new Type[3] { typeof(Vector3), typeof(Vector3), typeof(float) }, (Type[])null); MethodInfo overlapBubble = AccessTools.Method(typeof(OppressiveAuraPhysicsPatch), "OverlapBubble", (Type[])null, (Type[])null); bool replaced = false; foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Call && object.Equals(instruction.operand, overlapCapsule)) { instruction.operand = overlapBubble; replaced = true; } yield return instruction; } if (!replaced) { throw new InvalidOperationException("Nemesis Rising Tides Oppressive OverlapCapsule call was not found."); } } private static Collider[] OverlapBubble(Vector3 center, Vector3 ignoredEndpoint, float radius) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return Physics.OverlapSphere(center, radius); } } [HarmonyPatch] internal static class OppressiveAuraVisualPatch { private const string BehaviourTypeName = "NemesisRisingTides.Contents.Oppressive+AffixOppressive+NemesisAffixOppressiveBehaviour"; private static Mesh bubbleMesh; private static MethodBase TargetMethod() { Type type = ReflectionUtil.FindType("NemesisRisingTides.Contents.Oppressive+AffixOppressive+NemesisAffixOppressiveBehaviour", "prodzpod.NemesisRisingTides"); if (!(type == null)) { return AccessTools.Method(type, "Start", (Type[])null, (Type[])null); } return null; } private static void Postfix(object __instance) { if (__instance == null) { return; } object? obj = AccessTools.Field(__instance.GetType(), "aura")?.GetValue(__instance); Component val = (Component)((obj is Component) ? obj : null); if (!((Object)(object)val == (Object)null)) { EnsureBubbleMesh(); if (!((Object)(object)bubbleMesh == (Object)null)) { SetBubbleMesh(val.transform.Find("SphereOuter")); SetBubbleMesh(val.transform.Find("SphereInner")); } } } private static void EnsureBubbleMesh() { if (!((Object)(object)bubbleMesh != (Object)null)) { GameObject obj = GameObject.CreatePrimitive((PrimitiveType)0); MeshFilter component = obj.GetComponent<MeshFilter>(); bubbleMesh = (((Object)(object)component == (Object)null) ? null : component.sharedMesh); Object.Destroy((Object)(object)obj); } } private static void SetBubbleMesh(Transform visual) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)visual == (Object)null)) { MeshFilter component = ((Component)visual).GetComponent<MeshFilter>(); if ((Object)(object)component != (Object)null) { component.sharedMesh = bubbleMesh; } visual.localPosition = Vector3.zero; visual.localRotation = Quaternion.identity; visual.localScale = Vector3.one * 2f; } } } internal static class PerformancePatches { private const string EffectComponentType = "RoR2.EffectComponent"; private const string CorpseType = "RoR2.Corpse"; private const string DamageNumberManagerType = "RoR2.DamageNumberManager"; private static readonly BindingFlags AnyInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static int? originalCorpseLimit; internal static void Install(Harmony harmony) { PatchPostfix(harmony, "RoR2.EffectComponent", "Start", "EffectComponentStartPostfix"); PatchPostfix(harmony, "RoR2.DamageNumberManager", "Awake", "DamageNumberManagerAwakePostfix"); Plugin.Balance.PerformanceEnabled.SettingChanged += SettingsChanged; Plugin.Balance.MaximumCorpses.SettingChanged += SettingsChanged; ApplyGlobalSettings(); } internal static void RegisterRiskOfOptions() { Type type = ReflectionUtil.RequireType("RiskOfOptions.ModSettingsManager", "com.rune580.riskofoptions"); Type optionType = ReflectionUtil.RequireType("RiskOfOptions.Options.CheckBoxOption", "com.rune580.riskofoptions"); Type optionType2 = ReflectionUtil.RequireType("RiskOfOptions.Options.IntSliderOption", "com.rune580.riskofoptions"); MethodInfo methodInfo = type.GetMethod("SetModDescription", BindingFlags.Static | BindingFlags.Public, null, new Type[3] { typeof(string), typeof(string), typeof(string) }, null) ?? throw new MissingMethodException(type.FullName, "SetModDescription"); MethodInfo methodInfo2 = null; MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo3 in methods) { ParameterInfo[] parameters = methodInfo3.GetParameters(); if (methodInfo3.Name == "AddOption" && parameters.Length == 3 && parameters[1].ParameterType == typeof(string) && parameters[2].ParameterType == typeof(string)) { methodInfo2 = methodInfo3; break; } } if (methodInfo2 == null) { throw new MissingMethodException(type.FullName, "AddOption(option, modGuid, modName)"); } methodInfo.Invoke(null, new object[3] { "Pack compatibility, balance, stability, and optional client performance controls.", "com.lucie.thegodruntheorycompat", "The GodRun Theory Compatibility" }); AddOption(methodInfo2, optionType, (ConfigEntryBase)(object)Plugin.Balance.PerformanceEnabled); AddOption(methodInfo2, optionType, (ConfigEntryBase)(object)Plugin.Balance.EffectRendererOptimization); AddOption(methodInfo2, optionType2, (ConfigEntryBase)(object)Plugin.Balance.MaximumParticles); AddOption(methodInfo2, optionType2, (ConfigEntryBase)(object)Plugin.Balance.MaximumCorpses); AddOption(methodInfo2, optionType2, (ConfigEntryBase)(object)Plugin.Balance.MaximumDamageNumbers); AddOption(methodInfo2, optionType, (ConfigEntryBase)(object)Plugin.Balance.DisableEffectLightShadows); } private static void AddOption(MethodInfo addOption, Type optionType, ConfigEntryBase entry) { object obj = (optionType.GetConstructor(new Type[1] { ((object)entry).GetType() }) ?? throw new MissingMethodException(optionType.FullName, ".ctor(" + ((object)entry).GetType().Name + ")")).Invoke(new object[1] { entry }); addOption.Invoke(null, new object[3] { obj, "com.lucie.thegodruntheorycompat", "The GodRun Theory Compatibility" }); } private static void SettingsChanged(object sender, EventArgs eventArgs) { ApplyGlobalSettings(); } private static void ApplyGlobalSettings() { FieldInfo fieldInfo = ReflectionUtil.RequireType("RoR2.Corpse").GetField("maxCorpses", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingFieldException("RoR2.Corpse", "maxCorpses"); if (!originalCorpseLimit.HasValue) { originalCorpseLimit = (int)fieldInfo.GetValue(null); } int num = (Plugin.Balance.PerformanceEnabled.Value ? Math.Max(0, Plugin.Balance.MaximumCorpses.Value) : originalCorpseLimit.Value); fieldInfo.SetValue(null, num); } private static void EffectComponentStartPostfix(object __instance) { if (Plugin.Balance.PerformanceEnabled.Value) { Component val = (Component)((__instance is Component) ? __instance : null); if (val != null) { OptimizeEffect(val.gameObject); } } } private static void DamageNumberManagerAwakePostfix(object __instance) { if (Plugin.Balance.PerformanceEnabled.Value && __instance != null) { FieldInfo field = __instance.GetType().GetField("maxDamageNums", AnyInstance); if (!(field == null)) { int val = (int)field.GetValue(__instance); int val2 = Math.Max(0, Plugin.Balance.MaximumDamageNumbers.Value); field.SetValue(__instance, Math.Min(val, val2)); } } } private static void OptimizeEffect(GameObject effect) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)effect == (Object)null) { return; } if (Plugin.Balance.EffectRendererOptimization.Value) { int num = Math.Max(1, Plugin.Balance.MaximumParticles.Value); ParticleSystem[] componentsInChildren = effect.GetComponentsInChildren<ParticleSystem>(true); for (int i = 0; i < componentsInChildren.Length; i++) { MainModule main = componentsInChildren[i].main; if (((MainModule)(ref main)).maxParticles > num) { ((MainModule)(ref main)).maxParticles = num; } } Renderer[] componentsInChildren2 = effect.GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren2) { if (val is ParticleSystemRenderer || val is TrailRenderer || val is LineRenderer) { val.shadowCastingMode = (ShadowCastingMode)0; val.receiveShadows = false; val.motionVectorGenerationMode = (MotionVectorGenerationMode)2; } } } if (Plugin.Balance.DisableEffectLightShadows.Value) { Light[] componentsInChildren3 = effect.GetComponentsInChildren<Light>(true); for (int i = 0; i < componentsInChildren3.Length; i++) { componentsInChildren3[i].shadows = (LightShadows)0; } } } private static void PatchPostfix(Harmony harmony, string typeName, string methodName, string patchName) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(ReflectionUtil.RequireType(typeName), methodName, (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeName, methodName); MethodInfo methodInfo2 = AccessTools.Method(typeof(PerformancePatches), patchName, (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(PerformancePatches).FullName, patchName); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } [BepInPlugin("com.lucie.thegodruntheorycompat", "The GodRun Theory Compatibility", "2.0.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.lucie.thegodruntheorycompat"; public const string PluginName = "The GodRun Theory Compatibility"; public const string PluginVersion = "2.0.2"; internal const string ClassicItemsReturnsGuid = "com.RiskySleeps.ClassicItemsReturns"; internal const string CloudburstGuid = "CloudBurstTeam.Cloudburst"; internal const string VanillaVoidGuid = "com.Zenithrium.vanillaVoid"; internal const string NemesisRisingTidesGuid = "prodzpod.NemesisRisingTides"; internal const string RiskOfOptionsGuid = "com.rune580.riskofoptions"; private Harmony harmony; internal static Plugin Instance { get; private set; } internal static BalanceConfig Balance { get; private set; } internal ManualLogSource Log => ((BaseUnityPlugin)this).Logger; private void Awake() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown Instance = this; Balance = new BalanceConfig(((BaseUnityPlugin)this).Config); harmony = new Harmony("com.lucie.thegodruntheorycompat"); InstallOptional("Oppressive spherical aura", delegate { PatchClass(typeof(OppressiveAuraPhysicsPatch)); PatchClass(typeof(OppressiveAuraVisualPatch)); }); InstallOptional("ClassicItemsReturns balance", delegate { ClassicItemsPatch.Install(harmony); }); InstallOptional("Cloudburst balance", delegate { CloudburstPatch.Install(harmony); }); InstallOptional("Crystalline Lotus null guard", delegate { LotusGuard.Install(harmony); }); InstallOptional("base-game runtime stability guards", delegate { RuntimeStabilityPatches.Install(harmony); }); InstallOptional("client performance controls", delegate { PerformancePatches.Install(harmony); }); InstallOptional("Risk of Options integration", PerformancePatches.RegisterRiskOfOptions); ((BaseUnityPlugin)this).Logger.LogInfo((object)"The GodRun Theory Compatibility 2.0.2 initialized. Numeric patches are event-driven and perform no per-frame inventory scans."); } private void Start() { ClassicItemsPatch.ApplyFieldsAndLanguage(); CloudburstPatch.ApplyFieldsAndLanguage(); } private void PatchClass(Type patchType) { harmony.CreateClassProcessor(patchType).Patch(); } private void InstallOptional(string name, Action install) { try { install(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Applied: " + name)); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Skipped " + name + ": " + ex.GetType().Name + ": " + ex.Message)); } } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } } internal sealed class BalanceConfig { internal readonly ConfigEntry<float> CloverBaseChance; internal readonly ConfigEntry<float> CloverStackChance; internal readonly ConfigEntry<int> ArmsRaceMissiles; internal readonly ConfigEntry<int> ArmsRaceMissilesPerStack; internal readonly ConfigEntry<float> ArmsRaceDamage; internal readonly ConfigEntry<float> ArmsRaceCooldown; internal readonly ConfigEntry<float> EnergyCellHealthy; internal readonly ConfigEntry<float> EnergyCellLowHealthExtra; internal readonly ConfigEntry<float> FireShieldThreshold; internal readonly ConfigEntry<float> FireShieldExplosion; internal readonly ConfigEntry<float> FireShieldBurnBase; internal readonly ConfigEntry<float> FireShieldBurnPerStack; internal readonly ConfigEntry<float> JewelBarrier; internal readonly ConfigEntry<float> JewelBarrierPerStack; internal readonly ConfigEntry<int> JewelGold; internal readonly ConfigEntry<float> MedallionChance; internal readonly ConfigEntry<float> MedallionDuration; internal readonly ConfigEntry<float> MedallionDurationPerStack; internal readonly ConfigEntry<float> RustyJump; internal readonly ConfigEntry<float> ShopperGold; internal readonly ConfigEntry<float> SnakeCrit; internal readonly ConfigEntry<float> SnakeCritDamagePerStack; internal readonly ConfigEntry<int> SnakeStageActivations; internal readonly ConfigEntry<float> ThalliumChance; internal readonly ConfigEntry<float> ThalliumDamage; internal readonly ConfigEntry<float> ThalliumSlow; internal readonly ConfigEntry<float> ThalliumDuration; internal readonly ConfigEntry<float> ThalliumDurationPerStack; internal readonly ConfigEntry<float> ToughTimesArmor; internal readonly ConfigEntry<float> CloudburstScale; internal readonly ConfigEntry<float> JapesDurationScale; internal readonly ConfigEntry<bool> EquipmentSlotGuard; internal readonly ConfigEntry<bool> DroneVendorGuard; internal readonly ConfigEntry<bool> DelusionChestGuard; internal readonly ConfigEntry<bool> PerformanceEnabled; internal readonly ConfigEntry<bool> EffectRendererOptimization; internal readonly ConfigEntry<int> MaximumParticles; internal readonly ConfigEntry<int> MaximumCorpses; internal readonly ConfigEntry<int> MaximumDamageNumbers; internal readonly ConfigEntry<bool> DisableEffectLightShadows; internal BalanceConfig(ConfigFile config) { //IL_0489: Unknown result type (might be due to invalid IL or missing references) //IL_0493: Expected O, but got Unknown //IL_04b8: Unknown result type (might be due to invalid IL or missing references) //IL_04c2: Expected O, but got Unknown //IL_04ea: Unknown result type (might be due to invalid IL or missing references) //IL_04f4: Expected O, but got Unknown CloverBaseChance = config.Bind<float>("ClassicItemsReturns - 56 Leaf Clover", "Base elite drop chance percent", 0.5f, "Chance with one stack. Requires a restart."); CloverStackChance = config.Bind<float>("ClassicItemsReturns - 56 Leaf Clover", "Additional chance percent per stack", 0.1f, "Additional percentage points for each extra stack. Requires a restart."); ArmsRaceMissiles = config.Bind<int>("ClassicItemsReturns - Arms Race", "Base missiles", 2, "Missiles in the first-stack barrage."); ArmsRaceMissilesPerStack = config.Bind<int>("ClassicItemsReturns - Arms Race", "Missiles per extra stack", 1, "Additional missiles per stack."); ArmsRaceDamage = config.Bind<float>("ClassicItemsReturns - Arms Race", "Damage coefficient", 0.6f, "1.0 means 100% base damage."); ArmsRaceCooldown = config.Bind<float>("ClassicItemsReturns - Arms Race", "Recharge seconds", 15f, "Time required to reload a barrage."); EnergyCellHealthy = config.Bind<float>("ClassicItemsReturns - Energy Cell", "Healthy attack speed per stack", 0.08f, "0.08 means 8%. Requires a restart."); EnergyCellLowHealthExtra = config.Bind<float>("ClassicItemsReturns - Energy Cell", "Low health extra attack speed per stack", 0.16f, "Added progressively as health falls, producing 24% total at low health. Requires a restart."); FireShieldThreshold = config.Bind<float>("ClassicItemsReturns - Fire Shield", "Trigger health fraction", 0.08f, "Fraction of maximum combined health lost in one hit. Requires a restart."); FireShieldExplosion = config.Bind<float>("ClassicItemsReturns - Fire Shield", "Explosion damage coefficient", 1.2f, "1.2 means 120% base damage."); FireShieldBurnBase = config.Bind<float>("ClassicItemsReturns - Fire Shield", "Burn base damage coefficient", 1f, "1.0 means 100% base damage."); FireShieldBurnPerStack = config.Bind<float>("ClassicItemsReturns - Fire Shield", "Burn coefficient per extra stack", 0.75f, "0.75 means 75% additional base damage. Requires a restart."); JewelBarrier = config.Bind<float>("ClassicItemsReturns - Locked Jewel", "Barrier fraction", 0.1f, "0.10 means 10% maximum health."); JewelBarrierPerStack = config.Bind<float>("ClassicItemsReturns - Locked Jewel", "Barrier fraction per extra stack", 0.05f, "0.05 means 5% maximum health."); JewelGold = config.Bind<int>("ClassicItemsReturns - Locked Jewel", "Base gold reward", 4, "Value before difficulty/time scaling."); MedallionChance = config.Bind<float>("ClassicItemsReturns - Royal Medallion", "Orb chance percent", 5f, "Chance on a valid boss hit."); MedallionDuration = config.Bind<float>("ClassicItemsReturns - Royal Medallion", "Buff duration", 6f, "Seconds with one item."); MedallionDurationPerStack = config.Bind<float>("ClassicItemsReturns - Royal Medallion", "Duration per extra stack", 3f, "Additional seconds per stack."); RustyJump = config.Bind<float>("ClassicItemsReturns - Rusty Jetpack", "Jump power per stack", 0.12f, "0.12 means 12%. Requires a restart."); ShopperGold = config.Bind<float>("ClassicItemsReturns - Smart Shopper", "Extra enemy gold per stack", 0.1f, "0.10 means 10%."); SnakeCrit = config.Bind<float>("ClassicItemsReturns - Snake Eyes", "Critical chance per activation", 3f, "Percentage points per shrine activation."); SnakeCritDamagePerStack = config.Bind<float>("ClassicItemsReturns - Snake Eyes", "Critical damage per item stack and activation", 0.025f, "0.025 means 2.5%."); SnakeStageActivations = config.Bind<int>("ClassicItemsReturns - Snake Eyes", "Maximum activations per stage", 4, "Clamped between one and six. Requires a restart."); ThalliumChance = config.Bind<float>("ClassicItemsReturns - Thallium", "Proc chance percent", 5f, "Scales with proc coefficient."); ThalliumDamage = config.Bind<float>("ClassicItemsReturns - Thallium", "Victim damage coefficient", 0.6f, "0.6 means 60% of the greater attacker/victim damage value per tick calculation."); ThalliumSlow = config.Bind<float>("ClassicItemsReturns - Thallium", "Movement reduction", 0.4f, "0.4 means 40%."); ThalliumDuration = config.Bind<float>("ClassicItemsReturns - Thallium", "Base duration", 2f, "Seconds."); ThalliumDurationPerStack = config.Bind<float>("ClassicItemsReturns - Thallium", "Duration per extra stack", 0.75f, "Stored upstream but unused by CIR 3.6.26; retained for forward compatibility."); ToughTimesArmor = config.Bind<float>("ClassicItemsReturns - Tough Times", "Armor per stack", 8f, "Permanent armor per item stack."); CloudburstScale = config.Bind<float>("Cloudburst", "Finished item numerical scale", 0.5f, "Scales Glass Harvester crit damage, Bismuth barrier, Fabinhoru armor reduction, Jape healing/armor, and Keycard chance/damage. Requires a restart."); JapesDurationScale = config.Bind<float>("Cloudburst", "Japes Cloak duration scale", 0.6f, "0.6 changes ten seconds to six seconds. Requires a restart."); EquipmentSlotGuard = config.Bind<bool>("Stability", "Guard uninitialized equipment slots", true, "Prevents null equipment-set arrays from throwing every fixed update."); DroneVendorGuard = config.Bind<bool>("Stability", "Guard malformed drone vendor terminals", true, "Skips missing terminals or interactors while preserving valid drone dispatches."); DelusionChestGuard = config.Bind<bool>("Stability", "Guard malformed Delusion chests", true, "Disables only a malformed DelusionChestController when its Start method throws a null reference."); PerformanceEnabled = config.Bind<bool>("Performance", "Enable performance module", true, "Master switch for local visual performance changes. Does not alter combat or networking."); EffectRendererOptimization = config.Bind<bool>("Performance", "Optimize spawned effect renderers", true, "Caps particles and disables shadows and motion vectors on spawned particle, trail, and line effects."); MaximumParticles = config.Bind<int>("Performance", "Maximum particles per effect system", 400, new ConfigDescription("Upper limit for each newly spawned effect particle system.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(64, 2000), Array.Empty<object>())); MaximumCorpses = config.Bind<int>("Performance", "Maximum retained corpses", 10, new ConfigDescription("RoR2 normally retains up to 25 corpses. Lower values reduce late-stage renderer and physics load.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 25), Array.Empty<object>())); MaximumDamageNumbers = config.Bind<int>("Performance", "Maximum simultaneous damage numbers", 40, new ConfigDescription("Caps the existing client-side damage-number pool. Set to 0 to hide damage numbers.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 200), Array.Empty<object>())); DisableEffectLightShadows = config.Bind<bool>("Performance", "Disable shadows on spawned effect lights", true, "Keeps effect lighting but removes its dynamic shadow cost."); } } internal static class ReflectionUtil { private const BindingFlags AnyStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private const BindingFlags AnyInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly Dictionary<string, Type> TypeCache = new Dictionary<string, Type>(StringComparer.Ordinal); internal static Type FindType(string name, string pluginGuid = null) { string key = (pluginGuid ?? string.Empty) + "|" + name; lock (TypeCache) { if (TypeCache.TryGetValue(key, out var value)) { return value; } } Type type = null; if (!string.IsNullOrEmpty(pluginGuid) && Chainloader.PluginInfos.TryGetValue(pluginGuid, out var value2) && (Object)(object)value2.Instance != (Object)null) { type = ((object)value2.Instance).GetType().Assembly.GetType(name, throwOnError: false, ignoreCase: false); } if (type == null) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { type = assembly.GetType(name, throwOnError: false, ignoreCase: false); } catch { } if (type != null) { break; } } } if (type != null) { lock (TypeCache) { TypeCache[key] = type; } } return type; } internal static Type RequireType(string name, string pluginGuid = null) { return FindType(name, pluginGuid) ?? throw new TypeLoadException(name); } internal static object GetSingleton(string typeName, string pluginGuid = null) { Type type = RequireType(typeName, pluginGuid); while (type != null) { PropertyInfo property = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { object value = property.GetValue(null, null); if (value != null) { return value; } } FieldInfo fieldInfo = type.GetField("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo != null) { object value2 = fieldInfo.GetValue(null); if (value2 != null) { return value2; } } type = type.BaseType; } throw new MissingMemberException(typeName, "Instance"); } internal static void SetInstanceField(string typeName, string fieldName, object value, string pluginGuid = null) { object singleton = GetSingleton(typeName, pluginGuid); FieldInfo fieldInfo = singleton.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingFieldException(typeName, fieldName); fieldInfo.SetValue(singleton, ConvertValue(value, fieldInfo.FieldType)); } internal static void SetStaticField(string typeName, string fieldName, object value, string pluginGuid = null) { FieldInfo fieldInfo = RequireType(typeName, pluginGuid).GetField(fieldName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingFieldException(typeName, fieldName); fieldInfo.SetValue(null, ConvertValue(value, fieldInfo.FieldType)); } internal static object GetStaticField(string typeName, string fieldName, string pluginGuid = null) { return (RequireType(typeName, pluginGuid).GetField(fieldName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingFieldException(typeName, fieldName)).GetValue(null); } internal static int GetBuffCount(object body, object buffDef) { if (body == null || buffDef == null) { return 0; } MethodInfo[] methods = body.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (methodInfo.Name == "GetBuffCount" && parameters.Length == 1 && parameters[0].ParameterType.IsInstanceOfType(buffDef)) { return (int)methodInfo.Invoke(body, new object[1] { buffDef }); } } return 0; } internal static void AddFloatMember(object target, string name, float delta) { if (target == null) { return; } FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(target, Convert.ToSingle(field.GetValue(target), CultureInfo.InvariantCulture) + delta); return; } PropertyInfo property = target.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead && property.CanWrite) { property.SetValue(target, Convert.ToSingle(property.GetValue(target, null), CultureInfo.InvariantCulture) + delta, null); } } internal static void AddLanguage(string token, string text) { Type type = FindType("R2API.LanguageAPI", "com.bepis.r2api.language"); if (type == null) { throw new TypeLoadException("R2API.LanguageAPI"); } (AccessTools.Method(type, "Add", new Type[2] { typeof(string), typeof(string) }, (Type[])null) ?? throw new MissingMethodException("R2API.LanguageAPI", "Add(string, string)")).Invoke(null, new object[2] { token, text }); } private static object ConvertValue(object value, Type destination) { if (value == null || destination.IsInstanceOfType(value)) { return value; } return Convert.ChangeType(value, destination, CultureInfo.InvariantCulture); } } internal static class RuntimeStabilityPatches { [HarmonyPatch] private static class EquipmentSlotStartGuard { private static readonly Type SlotType = ReflectionUtil.RequireType("RoR2.EquipmentSlot"); private static readonly FieldInfo ActiveSet = AccessTools.Field(SlotType, "<activeEquipmentSet>k__BackingField"); private static MethodBase TargetMethod() { return AccessTools.Method(SlotType, "Start", (Type[])null, (Type[])null); } private static Exception Finalizer(object __instance, Exception __exception) { if (__exception is NullReferenceException && ActiveSet.GetValue(__instance) == null) { ActiveSet.SetValue(__instance, new byte[8]); LogOnce(ref equipmentLogged, "Recovered an EquipmentSlot that started before its run/components were fully available."); return null; } return __exception; } } [HarmonyPatch] private static class EquipmentSlotInventoryGuard { private static readonly Type SlotType = ReflectionUtil.RequireType("RoR2.EquipmentSlot"); private static readonly FieldInfo ActiveSet = AccessTools.Field(SlotType, "<activeEquipmentSet>k__BackingField"); private static readonly PropertyInfo CharacterBody = AccessTools.Property(SlotType, "characterBody"); private static readonly Type BodyType = ReflectionUtil.RequireType("RoR2.CharacterBody"); private static readonly PropertyInfo Inventory = AccessTools.Property(BodyType, "inventory"); private static readonly Type InventoryType = ReflectionUtil.RequireType("RoR2.Inventory"); private static readonly PropertyInfo InventoryActiveSet = AccessTools.Property(InventoryType, "activeEquipmentSet"); private static MethodBase TargetMethod() { return AccessTools.Method(SlotType, "UpdateInventory", (Type[])null, (Type[])null); } private static bool Prefix(object __instance) { object value = CharacterBody.GetValue(__instance, null); if (Missing(value)) { LogOnce(ref equipmentLogged, "Skipped an EquipmentSlot inventory update with no CharacterBody."); return false; } object value2 = Inventory.GetValue(value, null); if (Missing(value2)) { LogOnce(ref equipmentLogged, "Skipped an EquipmentSlot inventory update before its Inventory was available."); return false; } byte[] array = InventoryActiveSet.GetValue(value2, null) as byte[]; byte[] array2 = ActiveSet.GetValue(__instance) as byte[]; int num = Math.Max(8, (array != null) ? array.Length : 0); if (array2 == null || array2.Length < num) { ActiveSet.SetValue(__instance, new byte[num]); LogOnce(ref equipmentLogged, "Initialized a missing EquipmentSlot active-equipment array before UpdateInventory."); } return true; } } [HarmonyPatch] private static class DroneVendorDispatchGuard { private static readonly Type ControllerType = ReflectionUtil.RequireType("RoR2.DroneVendorMultiShopController"); private static readonly FieldInfo Terminals = AccessTools.Field(ControllerType, "_terminals"); private static readonly FieldInfo Purchase = AccessTools.Field(ControllerType, "_purchaseInteraction"); private static readonly FieldInfo Interactor = AccessTools.Field(ControllerType, "_interactor"); private static readonly Type TerminalType = ReflectionUtil.RequireType("RoR2.DroneVendorTerminalBehavior"); private static readonly FieldInfo TerminalPurchase = AccessTools.Field(TerminalType, "purchaseInteraction"); private static readonly MethodInfo Dispatch = AccessTools.Method(TerminalType, "DispatchDrone", (Type[])null, (Type[])null); private static MethodBase TargetMethod() { return AccessTools.Method(ControllerType, "DispatchDrone", (Type[])null, (Type[])null); } private static bool Prefix(object __instance) { Array array = Terminals.GetValue(__instance) as Array; object value = Purchase.GetValue(__instance); object value2 = Interactor.GetValue(__instance); if (array == null || Missing(value) || Missing(value2)) { LogOnce(ref vendorLogged, "Skipped malformed Drone Vendor dispatch data instead of throwing a null reference."); return false; } foreach (object item in array) { if (!Missing(item) && TerminalPurchase.GetValue(item) == value) { Dispatch.Invoke(item, new object[1] { value2 }); } } return false; } } [HarmonyPatch] private static class DelusionChestStartGuard { private static readonly Type ControllerType = ReflectionUtil.RequireType("RoR2.DelusionChestController"); private static MethodBase TargetMethod() { return AccessTools.Method(ControllerType, "Start", (Type[])null, (Type[])null); } private static Exception Finalizer(object __instance, Exception __exception) { if (__exception is NullReferenceException) { Behaviour val = (Behaviour)((__instance is Behaviour) ? __instance : null); if ((Object)(object)val != (Object)null) { val.enabled = false; } LogOnce(ref delusionLogged, "Disabled one malformed DelusionChestController after its required component was missing."); return null; } return __exception; } } private static readonly object LogLock = new object(); private static bool equipmentLogged; private static bool vendorLogged; private static bool delusionLogged; internal static void Install(Harmony harmony) { BalanceConfig balance = Plugin.Balance; if (balance.EquipmentSlotGuard.Value) { harmony.CreateClassProcessor(typeof(EquipmentSlotStartGuard)).Patch(); harmony.CreateClassProcessor(typeof(EquipmentSlotInventoryGuard)).Patch(); } if (balance.DroneVendorGuard.Value) { harmony.CreateClassProcessor(typeof(DroneVendorDispatchGuard)).Patch(); } if (balance.DelusionChestGuard.Value) { harmony.CreateClassProcessor(typeof(DelusionChestStartGuard)).Patch(); } } private static bool Missing(object value) { if (value == null) { return true; } Object val = (Object)((value is Object) ? value : null); if (val != null) { return !Object.op_Implicit(val); } return false; } private static void LogOnce(ref bool flag, string message) { lock (LogLock) { if (!flag) { flag = true; Plugin.Instance.Log.LogWarning((object)message); } } } } }