Decompiled source of AcridFeedingFrenzy v0.1.2

plugins/AcridFeedingFrenzy.dll

Decompiled 10 hours ago
using 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;
			}
		}
	}
}