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 RexEnhancements v1.0.0
RexEnhancements.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using EntityStates.Treebot; using EntityStates.Treebot.Weapon; using HarmonyLib; using RoR2; using RoR2.ContentManagement; using RoR2.Projectile; using RoR2.Skills; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.Networking; [assembly: IgnoresAccessChecksTo("RoR2")] [assembly: IgnoresAccessChecksTo("HGCSharpUtils")] [assembly: IgnoresAccessChecksTo("HGUnityUtils")] [assembly: IgnoresAccessChecksTo("com.unity.multiplayer-hlapi.Runtime")] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [module: UnverifiableCode] namespace RexEnhancements { [BepInPlugin("com.Sagittariod.RexEnhancements", "Rex Enhancements", "1.1.0")] public class RexEnhancementsPlugin : BaseUnityPlugin { private class RexContentPackProvider : IContentPackProvider { private readonly ContentPack contentPack = new ContentPack(); public string identifier => "com.Sagittariod.RexEnhancements"; public IEnumerator LoadStaticContentAsync(LoadStaticContentAsyncArgs args) { try { PropertyInfo propertyInfo = AccessTools.Property(typeof(ContentPack), "identifier"); if (propertyInfo != null) { propertyInfo.SetValue(contentPack, identifier, null); } } catch (Exception ex) { Log.LogWarning((object)("Could not set content pack identifier: " + ex.Message)); } contentPack.buffDefs.Add((BuffDef[])(object)new BuffDef[1] { curseCleanserBuff }); args.ReportProgress(1f); yield break; } public IEnumerator GenerateContentPackAsync(GetContentPackAsyncArgs args) { ContentPack.Copy(contentPack, args.output); args.ReportProgress(1f); yield break; } public IEnumerator FinalizeAsync(FinalizeAsyncArgs args) { args.ReportProgress(1f); yield break; } } public class MortarHitTracker : MonoBehaviour { public int hits; } public class ConvalescenceController : MonoBehaviour { public CharacterBody body; public BuffDef buffDef; public float remainingTime; private void Awake() { body = ((Component)this).GetComponent<CharacterBody>(); } public void AddStack() { if (Object.op_Implicit((Object)(object)body) && Object.op_Implicit((Object)(object)buffDef)) { int buffCount = body.GetBuffCount(buffDef); remainingTime += 9f * Mathf.Pow(0.815f, (float)buffCount); UpdateBuffStacks(); } } private void FixedUpdate() { if (NetworkServer.active && !(remainingTime <= 0f)) { remainingTime -= Time.fixedDeltaTime; if (remainingTime <= 0f) { remainingTime = 0f; ClearBuffs(); Object.Destroy((Object)(object)this); } else { UpdateBuffStacks(); } } } private void UpdateBuffStacks() { if (Object.op_Implicit((Object)(object)body) && Object.op_Implicit((Object)(object)buffDef)) { int stacksForTime = GetStacksForTime(remainingTime); int i; for (i = body.GetBuffCount(buffDef); i < stacksForTime; i++) { body.AddBuff(buffDef); } while (i > stacksForTime) { body.RemoveBuff(buffDef); i--; } } } private void ClearBuffs() { if (Object.op_Implicit((Object)(object)body) && Object.op_Implicit((Object)(object)buffDef)) { for (int num = body.GetBuffCount(buffDef); num > 0; num--) { body.RemoveBuff(buffDef); } } } public static int GetStacksForTime(float t) { if (t <= 0f) { return 0; } int num = 0; float num2 = 0f; float num3 = 10f; while (t > num2) { num++; num2 += num3; num3 *= 0.815f; if (num > 100) { break; } } return num; } } internal static ManualLogSource Log; public static ConfigEntry<float> maxBonusDamage; public static ConfigEntry<float> maxBonusArmor; public static ConfigEntry<float> brambleVolleyDamage; public static ConfigEntry<float> disperseCooldown; public static ConfigEntry<bool> harvestEndlagReduction; public static ConfigEntry<float> injectProcCoefficient; public static ConfigEntry<float> injectDamage; public static ConfigEntry<bool> enableNewPassive; public static ConfigEntry<bool> enableConvalescenceBuff; public static BuffDef curseCleanserBuff; private const string TreebotBodyPath = "RoR2/Base/Treebot/TreebotBody.prefab"; private const string KeywordToken = "KEYWORD_CONVALESCENCE"; private const string TreebotNameToken = "TREEBOT_BODY_NAME"; internal static readonly Dictionary<string, string> LanguageOverrides = new Dictionary<string, string>(); private static readonly HashSet<string> ConvalescenceSkillTokens = new HashSet<string> { "TREEBOT_SECONDARY_DESCRIPTION", "TREEBOT_SECONDARY_ALT1_DESCRIPTION", "TREEBOT_SPECIAL_DESCRIPTION", "TREEBOT_SPECIAL_ALT1_DESCRIPTION" }; private static Action<CharacterBody, float> setDamage; private static Action<CharacterBody, float> setArmor; private static Action<CharacterBody, float> setMaxHealth; private static Action<CharacterBody, float> setCursePenalty; private static BodyIndex treebotBodyIndex = (BodyIndex)(-1); private Harmony harmony; private void Awake() { //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Expected O, but got Unknown //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; enableNewPassive = ((BaseUnityPlugin)this).Config.Bind<bool>("Passive", "Enable New Passive", true, "If true, grants a new passive which increases armor and damage based on missing health"); maxBonusDamage = ((BaseUnityPlugin)this).Config.Bind<float>("Passive", "Max Bonus Damage", 0.3f, "Maximum percentage damage increase granted when at minimum health"); maxBonusArmor = ((BaseUnityPlugin)this).Config.Bind<float>("Passive", "Max Bonus Armor", 50f, "Maximum armor increase granted when at minimum health"); harvestEndlagReduction = ((BaseUnityPlugin)this).Config.Bind<bool>("Existing Skills", "Enable Faster Harvest", true, "If true, halves the endlag of DIRECTIVE: HARVEST"); injectProcCoefficient = ((BaseUnityPlugin)this).Config.Bind<float>("Existing Skills", "DIRECTIVE - Inject Proc Coefficient", 0.8f, "Proc coefficient for DIRECTIVE: INJECT"); brambleVolleyDamage = ((BaseUnityPlugin)this).Config.Bind<float>("Skills - Utility", "Bramble Volley Damage", 10f, "Damage coefficient for Bramble Volley"); disperseCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("Skills - Utility", "Directive Disperse Cooldown", 4.5f, "Base cooldown in seconds for Directive: DISPERSE"); injectDamage = ((BaseUnityPlugin)this).Config.Bind<float>("Existing Skills", "DIRECTIVE - Inject Damage", 0.9f, "Damage coefficient for DIRECTIVE: INJECT"); enableConvalescenceBuff = ((BaseUnityPlugin)this).Config.Bind<bool>("Existing Skills", "Enable Convalescence Buff", true, "If true, specific abilities grant the Convalescence buff which temporarily reduce Curse"); setDamage = MakeSetter("damage"); setArmor = MakeSetter("armor"); setMaxHealth = MakeSetter("maxHealth"); setCursePenalty = MakeSetter("cursePenalty"); SetupBuff(); ContentManager.collectContentPackProviders += (CollectContentPackProvidersDelegate)delegate(AddContentPackProviderDelegate add) { add.Invoke((IContentPackProvider)(object)new RexContentPackProvider()); }; BuildLanguage(); harmony = new Harmony("com.Sagittariod.RexEnhancements"); Patch(AccessTools.Method(typeof(Language), "GetLocalizedStringByToken", new Type[1] { typeof(string) }, (Type[])null), "LanguagePrefix"); Patch(AccessTools.Method(typeof(CharacterBody), "RecalculateStats", (Type[])null, (Type[])null), null, "RecalculateStatsPostfix"); Patch(AccessTools.Method(typeof(TreebotFireFruitSeed), "OnEnter", (Type[])null, (Type[])null), "FireFruitSeedPrefix"); Patch(AccessTools.Method(typeof(FireSyringe), "OnEnter", (Type[])null, (Type[])null), "FireSyringePrefix"); Patch(AccessTools.Method(typeof(FirePlantSonicBoom), "OnEnter", (Type[])null, (Type[])null), "FirePlantSonicBoomPrefix"); Patch(AccessTools.Method(typeof(GlobalEventManager), "OnHitEnemy", new Type[2] { typeof(DamageInfo), typeof(GameObject) }, (Type[])null), null, "OnHitEnemyPostfix"); RoR2Application.onLoad = (Action)Delegate.Combine(RoR2Application.onLoad, new Action(OnGameLoaded)); } private void OnDestroy() { if (harmony != null) { harmony.UnpatchSelf(); } RoR2Application.onLoad = (Action)Delegate.Remove(RoR2Application.onLoad, new Action(OnGameLoaded)); } private void Patch(MethodInfo target, string prefix = null, string postfix = null) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (target == null) { Log.LogError((object)("Patch target not found for " + (prefix ?? postfix))); return; } try { harmony.Patch((MethodBase)target, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(RexEnhancementsPlugin), prefix, (Type[])null), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(RexEnhancementsPlugin), postfix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Log.LogError((object)string.Concat("Failed to patch ", target.DeclaringType, ".", target.Name, ": ", ex)); } } private static Action<CharacterBody, float> MakeSetter(string property) { MethodInfo setter = AccessTools.PropertySetter(typeof(CharacterBody), property); if (setter == null) { Log.LogError((object)("CharacterBody." + property + " setter not found.")); return delegate { }; } try { return (Action<CharacterBody, float>)Delegate.CreateDelegate(typeof(Action<CharacterBody, float>), setter); } catch { return delegate(CharacterBody b, float v) { setter.Invoke(b, new object[1] { v }); }; } } private void SetupBuff() { curseCleanserBuff = ScriptableObject.CreateInstance<BuffDef>(); ((Object)curseCleanserBuff).name = "Convalescence"; curseCleanserBuff.canStack = true; curseCleanserBuff.isDebuff = false; curseCleanserBuff.isHidden = false; curseCleanserBuff.iconSprite = LoadSpriteFromManifest("Convalescence.png"); } private Sprite LoadSpriteFromManifest(string fileName) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) try { using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("RexEnhancements." + fileName); if (stream == null) { return null; } byte[] array = new byte[stream.Length]; stream.Read(array, 0, array.Length); Texture2D val = new Texture2D(2, 2); Type type = Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule"); MethodInfo methodInfo = ((!(type != null)) ? null : type.GetMethod("LoadImage", new Type[2] { typeof(Texture2D), typeof(byte[]) })); if (methodInfo == null) { Log.LogWarning((object)"ImageConversion.LoadImage not found; buff icon unavailable."); return null; } methodInfo.Invoke(null, new object[2] { val, array }); return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f)); } catch (Exception ex) { Log.LogError((object)ex); return null; } } private void OnGameLoaded() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) treebotBodyIndex = BodyCatalog.FindBodyIndex("TreebotBody"); try { SkillDef val = Addressables.LoadAssetAsync<SkillDef>((object)"RoR2/Base/Treebot/TreebotBodySonicBoom.asset").WaitForCompletion(); if (Object.op_Implicit((Object)(object)val)) { val.baseRechargeInterval = disperseCooldown.Value; } else { Log.LogWarning((object)"DIRECTIVE: DISPERSE skill not found."); } } catch (Exception ex) { Log.LogError((object)ex); } try { ModifyPrefabs(); } catch (Exception ex2) { Log.LogError((object)ex2); } try { GameObject val2 = Addressables.LoadAssetAsync<GameObject>((object)"RoR2/Base/Treebot/TreebotBody.prefab").WaitForCompletion(); SkillLocator val3 = ((!Object.op_Implicit((Object)(object)val2)) ? null : val2.GetComponent<SkillLocator>()); if (!Object.op_Implicit((Object)(object)val3)) { Log.LogWarning((object)"REX SkillLocator not found."); return; } if (enableNewPassive.Value) { val3.passiveSkill.keywordToken = string.Empty; } if (enableConvalescenceBuff.Value) { AddKeyword(val3.secondary); AddKeyword(val3.special); } } catch (Exception ex3) { Log.LogError((object)ex3); } } private static void AddKeyword(GenericSkill slot) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)slot)) { return; } SkillFamily skillFamily = slot.skillFamily; if (!Object.op_Implicit((Object)(object)skillFamily) || skillFamily.variants == null) { return; } Variant[] variants = skillFamily.variants; for (int i = 0; i < variants.Length; i++) { Variant val = variants[i]; SkillDef skillDef = val.skillDef; if (Object.op_Implicit((Object)(object)skillDef) && ConvalescenceSkillTokens.Contains(skillDef.skillDescriptionToken)) { string[] array = skillDef.keywordTokens ?? new string[0]; if (!array.Contains("KEYWORD_CONVALESCENCE")) { skillDef.keywordTokens = array.Concat(new string[1] { "KEYWORD_CONVALESCENCE" }).ToArray(); } } } } private static void ModifyPrefabs() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) string[] array = new string[2] { "RoR2/Base/Treebot/SyringeProjectile.prefab", "RoR2/Base/Treebot/SyringeProjectileHealing.prefab" }; foreach (string text in array) { GameObject val = Addressables.LoadAssetAsync<GameObject>((object)text).WaitForCompletion(); ProjectileController val2 = ((!Object.op_Implicit((Object)(object)val)) ? null : val.GetComponent<ProjectileController>()); if (Object.op_Implicit((Object)(object)val2)) { val2.procCoefficient = injectProcCoefficient.Value; } else { Log.LogWarning((object)("Projectile not found: " + text)); } } } private static void BuildLanguage() { LanguageOverrides["TREEBOT_PRIMARY_DESCRIPTION"] = $"Fire 3 syringes for <style=cIsDamage>3x{injectDamage.Value * 100f}% damage</style>. The last syringe <style=cIsDamage>Weakens</style> and <style=cIsHealing>heals for 60% of damage dealt</style>."; LanguageOverrides["TREEBOT_UTILITY_ALT1_DESCRIPTION"] = $"<style=cIsHealth>20% HP</style>. Fire a <style=cIsUtility>Sonic Boom</style> that damages enemies for <style=cIsDamage>{brambleVolleyDamage.Value * 100f}% damage</style>. <style=cIsHealing>Heals for every target hit</style>."; if (enableNewPassive.Value) { LanguageOverrides["TREEBOT_PASSIVE_NAME"] = "Adaptive Plating"; LanguageOverrides["TREEBOT_PASSIVE_DESCRIPTION"] = string.Format("Gain up to <style=cIsUtility>{1} armor</style> and deal up to <style=cIsDamage>+{0}% damage</style> based on your <style=cIsHealth>missing health</style>.", maxBonusDamage.Value * 100f, maxBonusArmor.Value); } else { LanguageOverrides["TREEBOT_PASSIVE_NAME"] = "Natural Toxins"; LanguageOverrides["TREEBOT_PASSIVE_DESCRIPTION"] = "Certain attacks <style=cIsDamage>Weaken</style> enemies hit, reducing their movement speed, armor, and damage."; } LanguageOverrides["TREEBOT_SPECIAL_DESCRIPTION"] = "<style=cIsHealth>25% HP</style>. Fire a flower that <style=cIsDamage>roots</style> for <style=cIsDamage>200% damage</style>. <style=cIsHealing>Heals for every target hit</style>." + ((!enableConvalescenceBuff.Value) ? string.Empty : " <style=cIsUtility>Direct hits grant Convalescence.</style>"); LanguageOverrides["TREEBOT_SPECIAL_ALT1_DESCRIPTION"] = "Fire a bolt that deals <style=cIsDamage>330% damage</style> and <style=cIsDamage>injects</style> an enemy. On death, drop multiple <style=cIsHealing>healing fruits</style> that heal for <style=cIsHealing>25% HP</style>." + ((!enableConvalescenceBuff.Value) ? string.Empty : " <style=cIsUtility>Direct hits grant Convalescence.</style>"); LanguageOverrides["TREEBOT_SECONDARY_ALT1_DESCRIPTION"] = "Launch a series of seed bullets into the sky, raining down for <style=cIsDamage>450% damage per second</style>." + ((!enableConvalescenceBuff.Value) ? string.Empty : " <style=cIsUtility>Every 4 hits grants Convalescence.</style>"); LanguageOverrides["TREEBOT_SECONDARY_DESCRIPTION"] = "<style=cIsHealth>15% HP</style>. Launch a mortar into the sky for <style=cIsDamage>450% damage</style>." + ((!enableConvalescenceBuff.Value) ? string.Empty : " <style=cIsUtility>Each hit grants Convalescence.</style>"); if (enableConvalescenceBuff.Value) { LanguageOverrides["KEYWORD_CONVALESCENCE"] = "<style=cKeywordName>Convalescence</style><style=cSub>Reduces current <style=cIsHealth>Curse</style> by 15% per stack. Higher stacks have shorter durations.</style>"; } } private static bool LanguagePrefix(string token, ref string __result) { if (token != null && LanguageOverrides.TryGetValue(token, out var value)) { __result = value; return false; } return true; } private static bool IsRex(CharacterBody body) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)body)) { return false; } if ((int)treebotBodyIndex != -1) { return body.bodyIndex == treebotBodyIndex; } return body.baseNameToken == "TREEBOT_BODY_NAME"; } private static void RecalculateStatsPostfix(CharacterBody __instance) { //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Invalid comparison between Unknown and I4 try { if (!IsRex(__instance)) { return; } HealthComponent healthComponent = __instance.healthComponent; if (!Object.op_Implicit((Object)(object)healthComponent)) { return; } if (enableNewPassive.Value && healthComponent.fullHealth > 0f) { float num = Mathf.Clamp01(healthComponent.health / healthComponent.fullHealth); setDamage(__instance, __instance.damage * (1f + maxBonusDamage.Value * (1f - num))); setArmor(__instance, __instance.armor + maxBonusArmor.Value * (1f - num)); } if (enableConvalescenceBuff.Value && Object.op_Implicit((Object)(object)curseCleanserBuff) && (int)curseCleanserBuff.buffIndex != -1) { int buffCount = __instance.GetBuffCount(curseCleanserBuff); if (buffCount > 0 && !(__instance.cursePenalty <= 1f)) { float cursePenalty = __instance.cursePenalty; float num2 = __instance.maxHealth * cursePenalty; float num3 = num2 - __instance.maxHealth; float num4 = num3 * Mathf.Pow(0.85f, (float)buffCount); float num5 = num2 - num4; setMaxHealth(__instance, num5); setCursePenalty(__instance, num2 / num5); } } } catch (Exception ex) { Log.LogError((object)ex); } } private static void FireFruitSeedPrefix(TreebotFireFruitSeed __instance) { if (harvestEndlagReduction.Value) { __instance.baseDuration = 0.5f; } } private static void FireSyringePrefix() { FireSyringe.damageCoefficient = injectDamage.Value; } private static void FirePlantSonicBoomPrefix() { FirePlantSonicBoom.damageCoefficient = brambleVolleyDamage.Value; } private static void OnHitEnemyPostfix(DamageInfo damageInfo) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Invalid comparison between Unknown and I4 try { if (!enableConvalescenceBuff.Value || !NetworkServer.active || damageInfo == null || !Object.op_Implicit((Object)(object)curseCleanserBuff) || (int)curseCleanserBuff.buffIndex == -1 || !Object.op_Implicit((Object)(object)damageInfo.attacker) || !Object.op_Implicit((Object)(object)damageInfo.inflictor)) { return; } CharacterBody component = damageInfo.attacker.GetComponent<CharacterBody>(); if (!IsRex(component)) { return; } string name = ((Object)damageInfo.inflictor).name; bool flag = false; if (name.Contains("TreebotFruitSeed") || name.Contains("TreebotMortar2") || name.Contains("TreebotFlower")) { flag = true; } else if (name.Contains("TreebotMortar")) { MortarHitTracker mortarHitTracker = ((Component)component).GetComponent<MortarHitTracker>(); if (!Object.op_Implicit((Object)(object)mortarHitTracker)) { mortarHitTracker = ((Component)component).gameObject.AddComponent<MortarHitTracker>(); } mortarHitTracker.hits++; if (mortarHitTracker.hits >= 4) { flag = true; mortarHitTracker.hits = 0; } } if (flag) { ConvalescenceController convalescenceController = ((Component)component).GetComponent<ConvalescenceController>(); if (!Object.op_Implicit((Object)(object)convalescenceController)) { convalescenceController = ((Component)component).gameObject.AddComponent<ConvalescenceController>(); convalescenceController.buffDef = curseCleanserBuff; } convalescenceController.AddStack(); } } catch (Exception ex) { Log.LogError((object)ex); } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public string AssemblyName { get; } public IgnoresAccessChecksToAttribute(string assemblyName) { AssemblyName = assemblyName; } } }