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 AcridFeedingFrenzy v0.1.2
plugins/AcridFeedingFrenzy.dll
Decompiled 10 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using EntityStates; using EntityStates.Croco; using HarmonyLib; using Microsoft.CodeAnalysis; using R2API; using RoR2; using RoR2.Skills; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace AcridFeedingFrenzy { internal sealed class HealingBudget { private struct Entry { public double Time; public float Fraction; } private readonly Queue<Entry> spent = new Queue<Entry>(); private double lastTime = double.NegativeInfinity; internal float Claim(int stacks, double now, float perStack, float cap) { if (double.IsNaN(now) || double.IsInfinity(now) || stacks <= 0 || float.IsNaN(perStack) || float.IsInfinity(perStack) || perStack <= 0f || float.IsNaN(cap) || float.IsInfinity(cap) || cap <= 0f) { return 0f; } if (now < lastTime) { spent.Clear(); } lastTime = now; while (spent.Count > 0 && now - spent.Peek().Time >= 1.0) { spent.Dequeue(); } float num = 0f; foreach (Entry item in spent) { num += item.Fraction; } float num2 = Math.Min((float)stacks * perStack, Math.Max(0f, cap - num)); if (num2 > 0f) { spent.Enqueue(new Entry { Time = now, Fraction = num2 }); } return num2; } } [BepInPlugin("local.acrid.feedingfrenzy", "Acrid Feeding Frenzy", "0.1.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "local.acrid.feedingfrenzy"; public const string Version = "0.1.2"; private static Plugin instance; private Harmony patches; private static ModdedDamageType finisher; private static readonly ConditionalWeakTable<CharacterBody, HealingBudget> budgets = new ConditionalWeakTable<CharacterBody, HealingBudget>(); private ConfigEntry<float> healPerStack; private ConfigEntry<float> healCap; private ConfigEntry<float> breathCooldown; private readonly Dictionary<SkillDef, float> originalCooldowns = new Dictionary<SkillDef, float>(); private bool cooldownApplied; private void Awake() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Expected O, but got Unknown //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Expected O, but got Unknown instance = this; healPerStack = ((BaseUnityPlugin)this).Config.Bind<float>("Feeding Frenzy", "HealingPerStack", 0.005f, new ConfigDescription("Bonus fraction of maximum health healed per existing blight stack on a successful third primary hit. Uses normal healing modifiers.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.1f), Array.Empty<object>())); healCap = ((BaseUnityPlugin)this).Config.Bind<float>("Feeding Frenzy", "HealingCapPerSecond", 0.05f, new ConfigDescription("Maximum bonus fraction of maximum health requested in any rolling second, shared across all targets. Cap is before healing modifiers; normal Regenerative healing is separate.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); breathCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("Cloying Breath", "CooldownSeconds", 10f, new ConfigDescription("Base recharge time for Cloying Breath only. Restart after changing configuration.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), Array.Empty<object>())); patches = new Harmony("local.acrid.feedingfrenzy"); try { finisher = DamageAPI.ReserveDamageType(); MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(Slash), "AuthorityModifyOverlapAttack", (Type[])null, (Type[])null); if (methodInfo == null || AccessTools.Field(typeof(Slash), "step") == null) { throw new MissingMethodException("Acrid third-swipe hook changed."); } patches.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "TagFinisher", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); patches.Patch((MethodBase)AccessTools.Method(typeof(Language), "GetLocalizedStringByToken", new Type[1] { typeof(string) }, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "Describe", (Type[])null) { priority = 0 }, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); GlobalEventManager.onServerDamageDealt += OnDamage; RoR2Application.onLoad = (Action)Delegate.Combine(RoR2Application.onLoad, new Action(ApplyCooldown)); List<string> list = new List<string>(NetworkModCompatibilityHelper.networkModList); string item = "local.acrid.feedingfrenzy;0.1.2"; if (!list.Contains(item)) { list.Add(item); } NetworkModCompatibilityHelper.networkModList = list; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Feeding Frenzy loaded. Blight third swipes add Blight and capped bonus healing; Cloying Breath cooldown will be set after content loads."); } catch (Exception ex) { GlobalEventManager.onServerDamageDealt -= OnDamage; RoR2Application.onLoad = (Action)Delegate.Remove(RoR2Application.onLoad, new Action(ApplyCooldown)); patches.UnpatchSelf(); ((BaseUnityPlugin)this).Logger.LogError((object)("Feeding Frenzy disabled: " + ex)); ((Behaviour)this).enabled = false; } } private static bool UsesBlight(CharacterBody body) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 if (!Object.op_Implicit((Object)(object)body)) { return false; } CrocoDamageTypeController component = ((Component)body).GetComponent<CrocoDamageTypeController>(); if (Object.op_Implicit((Object)(object)component)) { return (component.GetDamageType().damageType & 0x100000) > 0; } return false; } private static void TagFinisher(OverlapAttack __0, int ___step) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (___step == 2 && __0 != null && Object.op_Implicit((Object)(object)__0.attacker) && UsesBlight(__0.attacker.GetComponent<CharacterBody>())) { ref DamageType damageType = ref __0.damageType.damageType; damageType = (DamageType)((uint)damageType | 0x100000u); DamageAPI.AddModdedDamageType(__0, finisher); } } private static void OnDamage(DamageReport report) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Invalid comparison between Unknown and I4 //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Invalid comparison between Unknown and I4 //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) if (!NetworkServer.active || (Object)(object)instance == (Object)null || !((Behaviour)instance).enabled || report == null) { return; } DamageInfo damageInfo = report.damageInfo; CharacterBody attackerBody = report.attackerBody; if (damageInfo != null && !damageInfo.rejected && !(report.damageDealt <= 0f) && Object.op_Implicit((Object)(object)attackerBody) && attackerBody.isPlayerControlled && Object.op_Implicit((Object)(object)attackerBody.healthComponent) && attackerBody.healthComponent.alive && Object.op_Implicit((Object)(object)report.victimBody) && report.attackerTeamIndex != report.victimTeamIndex && (int)damageInfo.dotIndex == -1 && (int)damageInfo.damageType.damageSource == 1 && ((ProcChainMask)(ref damageInfo.procChainMask)).Equals(default(ProcChainMask)) && DamageAPI.HasModdedDamageType(damageInfo, finisher) && UsesBlight(attackerBody)) { int buffCount = report.victimBody.GetBuffCount(Buffs.Blight); float num = budgets.GetValue(attackerBody, (CharacterBody key) => new HealingBudget()).Claim(buffCount, Time.fixedTime, instance.healPerStack.Value, instance.healCap.Value); if (num > 0f) { attackerBody.healthComponent.Heal(attackerBody.healthComponent.fullHealth * num, default(ProcChainMask), true); } } } private void ApplyCooldown() { foreach (SkillDef allSkillDef in SkillCatalog.allSkillDefs) { if (Object.op_Implicit((Object)(object)allSkillDef) && !(allSkillDef.skillNameToken != "CROCO_SPECIAL_ALT_NAME") && !(((SerializableEntityStateType)(ref allSkillDef.activationState)).stateType != typeof(Breath))) { if (!originalCooldowns.ContainsKey(allSkillDef)) { originalCooldowns.Add(allSkillDef, allSkillDef.baseRechargeInterval); } allSkillDef.baseRechargeInterval = breathCooldown.Value; cooldownApplied = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Cloying Breath base cooldown: " + originalCooldowns[allSkillDef] + "s -> " + allSkillDef.baseRechargeInterval + "s.")); } } if (!cooldownApplied) { ((BaseUnityPlugin)this).Logger.LogError((object)"Cloying Breath skill was not found; its cooldown was not changed."); } } private static void Describe(string __0, ref string __result) { if (!((Object)(object)instance == (Object)null) && ((Behaviour)instance).enabled && !string.IsNullOrEmpty(__result)) { if (__0 == "CROCO_PRIMARY_DESCRIPTION" && !__result.Contains("Feeding Frenzy")) { __result = __result + "\n\n<style=cIsHealing>Feeding Frenzy:</style> With Blight equipped, the third swipe applies <style=cIsDamage>1 Blight stack</style>. Successful hits heal <style=cIsHealing>" + (instance.healPerStack.Value * 100f).ToString("0.##") + "% maximum health</style> per existing Blight stack, capped at <style=cIsHealing>" + (instance.healCap.Value * 100f).ToString("0.##") + "% per second</style> before healing modifiers. Keeps existing stacks and normal Regenerative healing."; } if (__0 == "CROCO_SPECIAL_ALT_DESCRIPTION" && instance.cooldownApplied && !__result.Contains("Base cooldown:")) { __result = __result + "\n\nBase cooldown: <style=cIsUtility>" + instance.breathCooldown.Value.ToString("0.##") + " seconds</style>."; } } } private void OnDestroy() { GlobalEventManager.onServerDamageDealt -= OnDamage; RoR2Application.onLoad = (Action)Delegate.Remove(RoR2Application.onLoad, new Action(ApplyCooldown)); Harmony obj = patches; if (obj != null) { obj.UnpatchSelf(); } foreach (KeyValuePair<SkillDef, float> originalCooldown in originalCooldowns) { if (Object.op_Implicit((Object)(object)originalCooldown.Key)) { originalCooldown.Key.baseRechargeInterval = originalCooldown.Value; } } if ((Object)(object)instance == (Object)(object)this) { instance = null; } } } }