using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CrestsEvolve.Core.Combat; using CrestsEvolve.Core.Events; using CrestsEvolve.Core.Input; using CrestsEvolve.Core.State; using CrestsEvolve.Core.Time; using CrestsEvolve.Core.UI; using CrestsEvolve.Features; using CrestsEvolve.Features.Plasma; using CrestsEvolve.Features.Runa; using CrestsEvolve.Features.StatusIcons; using CrestsEvolve.GameInterop; using CrestsEvolve.GameInterop.Patches; using CrestsEvolve.LegendsNeverDie.Bootstrap; using GlobalSettings; using HarmonyLib; using HutongGames.PlayMaker; using HutongGames.PlayMaker.Actions; using Microsoft.CodeAnalysis; using Silksong.FsmUtil; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("CrestsEvolve.Core.Tests")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("CrestsEvolve.LegendsNeverDie")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+757e75a2d4534028308600b7117b47c56eb48037")] [assembly: AssemblyProduct("CrestsEvolve.LegendsNeverDie")] [assembly: AssemblyTitle("CrestsEvolve.LegendsNeverDie")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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; } } internal static class IsExternalInit { } } namespace CrestsEvolve.GameInterop { public static class BlueHealthTintBridge { public const string PoisonVariableName = "Is Poison"; private static readonly Color RedColor = new Color(0.78f, 0.16f, 0.16f, 1f); private static readonly Color GreenColor = new Color(0.28f, 0.78f, 0.36f, 1f); public static Func? IsBeastTintActive; public static Func? IsWitchTintActive; public static Func? IsCursedTintActive; private static ManualLogSource? _log; private static bool _warned; public static void Configure(ManualLogSource log) { _log = log; _warned = false; } public static void MarkActivePostfix(BlueHealth __instance) { ApplyTint(__instance); } public static void CheckPoisonPostfix(BlueHealth __instance) { ApplyTint(__instance); } public static void SetPoisonTintOverridePostfix(SetPoisonTintOverride __instance) { try { object obj; if (__instance == null) { obj = null; } else { Fsm fsm = ((FsmStateAction)__instance).Fsm; obj = ((fsm != null) ? fsm.GameObject : null); } GameObject val = (GameObject)obj; if (!((Object)(object)val == (Object)null)) { BlueHealth component = val.GetComponent(); if (!((Object)(object)component == (Object)null)) { ApplyTint(component); } } } catch (Exception ex) { WarnOnce(ex); } } public static void RefreshAll() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) try { BlueHealth[] array = Resources.FindObjectsOfTypeAll(); foreach (BlueHealth val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)null)) { Scene scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { ApplyTint(val); } } } } catch (Exception ex) { WarnOnce(ex); } } private static void ApplyTint(BlueHealth mask) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) try { BlueHealthTintKind blueHealthTintKind = BlueHealthTintResolver.Resolve(IsBeastTintActive?.Invoke() ?? false, IsWitchTintActive?.Invoke() ?? false, IsCursedTintActive?.Invoke() ?? false, IsPoison(mask)); bool flag = ApplyToSprite(mask, blueHealthTintKind switch { BlueHealthTintKind.Red => RedColor, BlueHealthTintKind.Green => GreenColor, _ => null, }); if (flag && blueHealthTintKind != BlueHealthTintKind.None) { ManualLogSource? log = _log; if (log != null) { log.LogDebug((object)$"[BlueHealthTint][probe] tint applied: kind={blueHealthTintKind} go={((Object)mask).name}"); } } else if (!flag && blueHealthTintKind != BlueHealthTintKind.None) { ManualLogSource? log2 = _log; if (log2 != null) { log2.LogDebug((object)$"[BlueHealthTint][probe] tint skipped (sprite not found): kind={blueHealthTintKind} go={((Object)mask).name}"); } } } catch (Exception ex) { WarnOnce(ex); } } private static bool IsPoison(BlueHealth mask) { PlayMakerFSM[] components = ((Component)mask).GetComponents(); foreach (PlayMakerFSM val in components) { if (!((Object)(object)val == (Object)null) && val.FsmVariables != null) { FsmBool val2 = val.FsmVariables.FindFsmBool("Is Poison"); if (val2 != null && val2.Value) { return true; } } } return false; } private static bool ApplyToSprite(BlueHealth mask, Color? tint) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) object obj = FindTk2dSprite(mask); if (obj != null) { return SetTk2dColor(obj, tint); } SpriteRenderer componentInChildren = ((Component)mask).GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.color = (Color)(((??)tint) ?? Color.white); return true; } return false; } private static object? FindTk2dSprite(BlueHealth mask) { Component[] componentsInChildren = ((Component)mask).GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { string name = ((object)val).GetType().Name; if (name == "tk2dSpriteAnimator") { PropertyInfo property = ((object)val).GetType().GetProperty("Sprite"); if (property != null) { object value = property.GetValue(val); if (value != null) { return value; } } } else if (name == "tk2dSprite") { return val; } } return null; } private static bool SetTk2dColor(object sprite, Color? tint) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) Type type = sprite.GetType(); PropertyInfo property = type.GetProperty("color", BindingFlags.Instance | BindingFlags.Public); if (property == null) { return false; } property.SetValue(sprite, (object)(Color)(((??)tint) ?? Color.white)); (tint.HasValue ? type.GetMethod("EnableKeyword", BindingFlags.Instance | BindingFlags.Public) : type.GetMethod("DisableKeyword", BindingFlags.Instance | BindingFlags.Public))?.Invoke(sprite, new object[1] { "RECOLOUR" }); return true; } private static void WarnOnce(Exception ex) { if (!_warned) { _warned = true; ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[BlueHealthTint] " + ex.GetType().Name + ": " + ex.Message)); } } } } public enum HealSourceKind { Unknown, General, Bind, ChargedAttack } public sealed class HealClassificationBridge { private readonly ManualLogSource _log; private double _bindWindowEnd; private double _chargedWindowEnd; public HealClassificationBridge(ManualLogSource log) { _log = log ?? throw new ArgumentNullException("log"); } public HealSourceKind Classify(string sourceTag) { HeroController instance = HeroController.instance; if ((Object)(object)instance != (Object)null && instance.cState != null && (instance.cState.focusing || instance.cState.isBinding)) { return HealSourceKind.Bind; } double unscaledTimeAsDouble = Time.unscaledTimeAsDouble; if (_bindWindowEnd > unscaledTimeAsDouble) { return HealSourceKind.Bind; } if (_chargedWindowEnd > unscaledTimeAsDouble) { return HealSourceKind.ChargedAttack; } if (Contains(sourceTag, "bind")) { return HealSourceKind.Bind; } if (Contains(sourceTag, "charge")) { return HealSourceKind.ChargedAttack; } return HealSourceKind.Unknown; } public void OpenBindHealWindow(double seconds) { if (!(seconds <= 0.0)) { _bindWindowEnd = Math.Max(_bindWindowEnd, Time.unscaledTimeAsDouble + seconds); _log.LogDebug((object)$"[HealClassification] bind heal classification window opened ({seconds}s)."); } } public void OpenChargedHealWindow(double seconds) { if (!(seconds <= 0.0)) { _chargedWindowEnd = Math.Max(_chargedWindowEnd, Time.unscaledTimeAsDouble + seconds); _log.LogDebug((object)$"[HealClassification] charged heal classification window opened ({seconds}s)."); } } public void NotifyBindCompleted() { _log.LogInfo((object)"[HealClassification][probe] BindCompleted fired; opening bind heal window."); OpenBindHealWindow(1.0); } private static bool Contains(string? value, string keyword) { if (value != null) { return value.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0; } return false; } } public static class PlasmaBindGatePatch { public static Func? IsGreenPlasmaActive; private static ManualLogSource? _log; private static bool _warned; public static void Configure(ManualLogSource? log) { _log = log; _warned = false; } public static void Prefix(BoolTest __instance) { if (IsGreenPlasmaActive == null || !IsGreenPlasmaActive()) { return; } try { if (((__instance != null) ? ((FsmStateAction)__instance).Fsm : null) != null && string.Equals(((FsmStateAction)__instance).Fsm.Name, "Spell Control", StringComparison.Ordinal) && ((FsmStateAction)__instance).State != null && string.Equals(((FsmStateAction)__instance).State.Name, "Lifeblood State?", StringComparison.Ordinal) && __instance.boolVariable != null && string.Equals(((NamedVariable)__instance.boolVariable).Name, "Chooser", StringComparison.Ordinal)) { __instance.boolVariable.Value = false; ManualLogSource? log = _log; if (log != null) { log.LogDebug((object)"[Plasma] green plasma bind gate unblocked."); } } } catch (Exception ex) { WarnOnce(ex); } } private static void WarnOnce(Exception ex) { if (!_warned) { _warned = true; ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[PlasmaBind] unblock disabled after error: " + ex.GetType().Name + ": " + ex.Message)); } } } } public static class PlasmaRegenSuppressor { public static Func? IsSuppressing; private static ManualLogSource? _log; private static bool _warned; public static void Configure(ManualLogSource? log) { _log = log; _warned = false; } public static bool Prefix(CallMethodProper __instance) { if (IsSuppressing == null || !IsSuppressing()) { return true; } try { if (((__instance != null) ? ((FsmStateAction)__instance).Fsm : null) == null) { return true; } if (!string.Equals(((FsmStateAction)__instance).Fsm.Name, "Hive Health Regen", StringComparison.Ordinal)) { return true; } if ((Object)(object)((FsmStateAction)__instance).Fsm.GameObject == (Object)null || !string.Equals(((Object)((FsmStateAction)__instance).Fsm.GameObject).name, "Health", StringComparison.Ordinal)) { return true; } if (__instance.methodName == null || !string.Equals(__instance.methodName.Value, "AddHealth", StringComparison.Ordinal)) { return true; } ManualLogSource? log = _log; if (log != null) { log.LogDebug((object)"[Plasma] suppressed vanilla Hive Health Regen AddHealth."); } return false; } catch (Exception ex) { WarnOnce(ex); return true; } } private static void WarnOnce(Exception ex) { if (!_warned) { _warned = true; ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[PlasmaRegen] suppression disabled after error: " + ex.GetType().Name + ": " + ex.Message)); } } } } public static class PlasmaStateBridge { public static Action? OnPlasmaEntered; public static Action? OnPlasmaExited; private static ManualLogSource? _log; private static bool _knownPlasma; public static void Configure(ManualLogSource? log) { _log = log; } public static void HitMaxBlueHealthPostfix(HeroController __instance) { if (!((Object)(object)__instance == (Object)null)) { SetPlasma(__instance.IsInLifebloodState); } } public static void ResetLifebloodStatePostfix(HeroController __instance) { if (!((Object)(object)__instance == (Object)null)) { SetPlasma(__instance.IsInLifebloodState); } } public static void SyncFromHero() { HeroController instance = HeroController.instance; SetPlasma((Object)(object)instance != (Object)null && instance.IsInLifebloodState); } public static void ResetTracking() { _knownPlasma = false; } private static void SetPlasma(bool active) { if (active != _knownPlasma) { _knownPlasma = active; ManualLogSource? log = _log; if (log != null) { log.LogDebug((object)("[PlasmaState] " + (active ? "entered" : "exited"))); } if (active) { OnPlasmaEntered?.Invoke(); } else { OnPlasmaExited?.Invoke(); } } } } public static class PlasmaTintBridge { private static readonly Color RedColor = new Color(0.78f, 0.16f, 0.16f, 1f); private static readonly Color GreenColor = new Color(0.28f, 0.78f, 0.36f, 1f); public static Func? IsRedPlasmaActive; public static Func? IsGreenPlasmaActive; private static ManualLogSource? _log; private static bool _warned; private static bool _tinted; public static void Configure(ManualLogSource? log) { _log = log; _warned = false; _tinted = false; } public static void RefreshAll() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) try { Color? val = ResolveColor(); if (val.HasValue) { ApplyTint(val.Value); _tinted = true; } else if (_tinted) { RestoreTint(); _tinted = false; } } catch (Exception ex) { WarnOnce(ex); } } public static void RestoreAll() { try { if (_tinted) { RestoreTint(); _tinted = false; } } catch (Exception ex) { WarnOnce(ex); } } public static void HitMaxBlueHealthBurstPostfix(HeroController __instance) { RefreshAll(); } public static void SpawnObjectFromGlobalPoolPostfix(SpawnObjectFromGlobalPool __instance) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) Color? val = ResolveColor(); if (!val.HasValue) { return; } try { if (((__instance != null) ? ((FsmStateAction)__instance).Fsm : null) == null || !string.Equals(((FsmStateAction)__instance).Fsm.Name, "health_display", StringComparison.Ordinal)) { return; } FsmGameObject gameObject = __instance.gameObject; GameObject val2 = ((gameObject != null) ? gameObject.Value : null); if ((Object)(object)val2 == (Object)null || !string.Equals(((Object)val2).name, "HP Up Particles HiveB", StringComparison.Ordinal)) { return; } FsmGameObject storeObject = __instance.storeObject; GameObject val3 = ((storeObject != null) ? storeObject.Value : null); if (!((Object)(object)val3 == (Object)null)) { ApplyTintToHierarchy(val3, val.Value); ManualLogSource? log = _log; if (log != null) { log.LogDebug((object)"[PlasmaTint][probe] heal particles tinted."); } } } catch (Exception ex) { WarnOnce(ex); } } private static Color? ResolveColor() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) bool num = IsRedPlasmaActive?.Invoke() ?? false; bool flag = IsGreenPlasmaActive?.Invoke() ?? false; if (num) { return RedColor; } if (flag) { return GreenColor; } return null; } private static void ApplyTint(Color color) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) foreach (GameObject item in FindRegularMasks()) { ApplyTintToHierarchy(item, color); } ApplyTintToHierarchy(FindRecoveryBlob(), color); } private static void RestoreTint() { foreach (GameObject item in FindRegularMasks()) { ApplyTintToHierarchy(item, null); } ApplyTintToHierarchy(FindRecoveryBlob(), null); } private static IEnumerable FindRegularMasks() { PlayMakerFSM[] array = Resources.FindObjectsOfTypeAll(); foreach (PlayMakerFSM val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)null) && string.Equals(val.FsmName, "health_display", StringComparison.Ordinal) && !((Object)(object)((Component)val).gameObject.GetComponent() != (Object)null)) { Scene scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { yield return ((Component)val).gameObject; } } } } private static GameObject? FindRecoveryBlob() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) PlayMakerFSM[] array = Resources.FindObjectsOfTypeAll(); foreach (PlayMakerFSM val in array) { if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null || !string.Equals(val.FsmName, "Hive Health Regen", StringComparison.Ordinal)) { continue; } Scene scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { FsmVariables fsmVariables = val.FsmVariables; FsmGameObject val2 = ((fsmVariables != null) ? fsmVariables.FindFsmGameObject("Recovery Blob") : null); if ((Object)(object)((val2 != null) ? val2.Value : null) != (Object)null) { return val2.Value; } } } return null; } private static void ApplyTintToHierarchy(GameObject? root, Color? color) { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)root == (Object)null) { return; } Component[] componentsInChildren = root.GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } try { string name = ((object)val).GetType().Name; if (name == "tk2dSpriteAnimator") { PropertyInfo property = ((object)val).GetType().GetProperty("Sprite"); if (property != null) { object value = property.GetValue(val); if (value != null) { SetTk2dColor(value, color); } } } else if (name == "tk2dSprite") { SetTk2dColor(val, color); } else { SpriteRenderer val2 = (SpriteRenderer)(object)((val is SpriteRenderer) ? val : null); if (val2 != null) { val2.color = (Color)(((??)color) ?? Color.white); } } } catch (Exception ex) { WarnOnce(ex); } } } private static void SetTk2dColor(object sprite, Color? color) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) Type type = sprite.GetType(); PropertyInfo property = type.GetProperty("color", BindingFlags.Instance | BindingFlags.Public); if (!(property == null)) { property.SetValue(sprite, (object)(Color)(((??)color) ?? Color.white)); (color.HasValue ? type.GetMethod("EnableKeyword", BindingFlags.Instance | BindingFlags.Public) : type.GetMethod("DisableKeyword", BindingFlags.Instance | BindingFlags.Public))?.Invoke(sprite, new object[1] { "RECOLOUR" }); } } private static void WarnOnce(Exception ex) { if (!_warned) { _warned = true; ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[PlasmaTint] tint disabled after error: " + ex.GetType().Name + ": " + ex.Message)); } } } } public static class ReaperFireBladeBridge { public static Func Layers { get; set; } = () => 0; public static Func DamageMultiplier { get; set; } = () => 1.0; public static bool IsActive() { return Layers() > 0; } } public sealed class ShardGeoBridge { private readonly ManualLogSource _log; private bool _warned; public ShardGeoBridge(ManualLogSource log) { _log = log ?? throw new ArgumentNullException("log"); } public int AddShardsReturningAdded(int amount) { if (amount <= 0) { return 0; } try { HeroController instance = HeroController.instance; if (instance?.playerData == null) { return 0; } int shellShards = instance.playerData.ShellShards; instance.playerData.AddShards(amount); return Math.Max(0, instance.playerData.ShellShards - shellShards); } catch (Exception ex) { WarnOnce(ex); return 0; } } public void AddGeo(int amount) { if (amount <= 0) { return; } try { HeroController instance = HeroController.instance; if (instance != null) { instance.AddGeo(amount); } } catch (Exception ex) { WarnOnce(ex); } } private void WarnOnce(Exception ex) { if (!_warned) { _warned = true; _log.LogWarning((object)("[ShardGeoBridge] " + ex.GetType().Name + ": " + ex.Message)); } } } public sealed class SpiritFlameSpawner { private readonly ManualLogSource _log; private readonly FieldInfo? _wispPrefabField; private bool _warned; public SpiritFlameSpawner(ManualLogSource log) { _log = log; _wispPrefabField = AccessTools.Field(typeof(HeroWispLantern), "wispPrefab"); } public void Spawn() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) try { HeroController instance = HeroController.instance; GameObject val = ResolveWispPrefab(); if ((Object)(object)instance == (Object)null || (Object)(object)val == (Object)null) { WarnOnce("hero or spirit flame prefab is missing"); return; } Object.Destroy((Object)(object)Object.Instantiate(val, ((Component)instance).transform.position, ((Component)instance).transform.rotation), 1f); _log.LogDebug((object)"[SpiritFlame] blue wisp flame spawned at player."); } catch (Exception ex) { WarnOnce(ex.Message); } } private GameObject? ResolveWispPrefab() { try { HeroController instance = HeroController.instance; if ((Object)(object)instance != (Object)null && _wispPrefabField != null) { HeroWispLantern componentInChildren = ((Component)instance).GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { object? value = _wispPrefabField.GetValue(componentInChildren); GameObject val = (GameObject)((value is GameObject) ? value : null); if (val != null) { _log.LogInfo((object)"[SpiritFlame][probe] using HeroWispLantern.wispPrefab (E1)."); return val; } } } } catch (Exception ex) { WarnOnce(ex.Message); } GameObject lifebloodHealEffect = Effects.LifebloodHealEffect; if ((Object)(object)lifebloodHealEffect != (Object)null) { _log.LogInfo((object)"[SpiritFlame][probe] fallback Effects.LifebloodHealEffect."); } return lifebloodHealEffect; } private void WarnOnce(string message) { if (!_warned) { _warned = true; _log.LogWarning((object)("[SpiritFlame] " + message)); } } } } namespace CrestsEvolve.GameInterop.Patches { public static class ArchitectToolRepairPatch { private static ManualLogSource? _log; private static bool _warned; public static void Configure(ManualLogSource log) { _log = log; _warned = false; } public static void Postfix() { //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) if (!ArchitectRuneBridge.IsToolRepairActive()) { return; } try { PlayerData instance = PlayerData.instance; if (instance == null || string.IsNullOrEmpty(instance.CurrentCrestID)) { return; } List allCrests = ToolItemManager.GetAllCrests(); if (allCrests == null) { return; } foreach (ToolCrest item in allCrests) { if ((Object)(object)item == (Object)null) { continue; } string name = item.name; if (string.IsNullOrEmpty(name) || string.Equals(name, instance.CurrentCrestID, StringComparison.OrdinalIgnoreCase)) { continue; } List equippedToolsForCrest = ToolItemManager.GetEquippedToolsForCrest(name); if (equippedToolsForCrest == null) { continue; } foreach (ToolItem item2 in equippedToolsForCrest) { if ((Object)(object)item2 == (Object)null) { continue; } Data toolData = instance.GetToolData(item2.name); int toolStorageAmount = ToolItemManager.GetToolStorageAmount(item2); ToolItemStatesLiquid val = (ToolItemStatesLiquid)(object)((item2 is ToolItemStatesLiquid) ? item2 : null); if (val != null) { val.RefillRefills(false); ManualLogSource? log = _log; if (log != null) { log.LogDebug((object)("[ArchitectToolRepair] liquid refills reset for " + item2.name + " (" + name + ").")); } } if (toolData.AmountLeft < toolStorageAmount) { toolData.AmountLeft = toolStorageAmount; instance.SetToolData(item2.name, toolData); } ManualLogSource? log2 = _log; if (log2 != null) { log2.LogDebug((object)$"[ArchitectToolRepair] repaired {item2.name} ({name}) to {toolStorageAmount}."); } } } } catch (Exception ex) { WarnOnce(ex); } } private static void WarnOnce(Exception ex) { if (!_warned) { _warned = true; ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[ArchitectToolRepair] " + ex.GetType().Name + ": " + ex.Message)); } } } } public static class ArchitectToolTransformPatch { public static void Prefix(ToolItemType promptToolType, CanChangeEquipsTypes changeType, ref bool __result) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)changeType == 2 && ArchitectRuneBridge.IsToolTransformActive()) { __result = true; } } } public static class AttackStartedSignalPatch { public static Action? AttackStarted; public static void Postfix() { AttackStarted?.Invoke(); } } public static class BeastDownspikeInvulnPatch { public const int InvulnerabilitySteps = 15; public static void Postfix(Downspike __instance) { if (BeastRuneBridge.IsEnhancementActive()) { HeroController instance = HeroController.instance; if (!((Object)(object)instance == (Object)null) && instance.cState != null) { instance.cState.downspikeInvulnerabilitySteps = Math.Max(instance.cState.downspikeInvulnerabilitySteps, 15); } } } } public static class BeastPredatorChargeSlashInvulnPatch { public static void Postfix(NailAttackBase __instance) { if (BeastRuneBridge.IsPredatorCharged()) { Transform transform = ((Component)__instance).transform; if (!((Object)(object)transform == (Object)null) && ((Object)transform).name.IndexOf("Charge Slash", StringComparison.OrdinalIgnoreCase) >= 0) { BeastRuneBridge.AcquireChargeSlashInvulnerability?.Invoke(); } } } } public static class BeastPredatorDamagePatch { public static double DamageMultiplier { get; set; } = 1.2; public static void Prefix(ref HitInstance hitInstance) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 if (BeastRuneBridge.IsPredatorCharged() && hitInstance.IsHeroDamage && (int)hitInstance.AttackType == 16) { hitInstance.DamageDealt = (int)Math.Round((double)hitInstance.DamageDealt * DamageMultiplier); } } } public static class BeastRageBindFsmPatch { private sealed class BindFsmMarker : MonoBehaviour { } private sealed class SkipWarriorKickupAction : FsmStateAction { public override void OnEnter() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) try { HeroController instance = HeroController.instance; if ((Object)(object)instance != (Object)null && IsActive() && instance.WarriorState.IsInRageMode) { ((FsmStateAction)this).Fsm.Event("FINISHED"); } } catch { } ((FsmStateAction)this).Finish(); } } private sealed class SuppressDazzleAction : FsmStateAction { public override void OnEnter() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) try { HeroController instance = HeroController.instance; if ((Object)(object)instance != (Object)null && IsActive() && instance.WarriorState.IsInRageMode) { FsmBool val = ((FsmStateAction)this).Fsm.Variables.FindFsmBool("Is Dazzle Equipped"); if (val != null) { val.Value = false; } } } catch { } ((FsmStateAction)this).Finish(); } } private static ManualLogSource? _log; private static bool _warned; public static bool Enabled { get; set; } public static Func IsActive { get; set; } = () => true; public static void Configure(ManualLogSource log) { _log = log; _warned = false; } public static void Postfix(PlayMakerFSM __instance) { if (!Enabled || (Object)(object)__instance == (Object)null || __instance.Fsm == null || __instance.FsmName != "Bind" || (Object)(object)((Component)__instance).gameObject == (Object)null || !((Object)((Component)__instance).gameObject).name.StartsWith("Hero_Hornet", StringComparison.Ordinal) || (Object)(object)((Component)__instance).GetComponent() != (Object)null) { return; } try { FsmState state = FsmUtil.GetState(__instance, "Warrior Kickup"); FsmState state2 = FsmUtil.GetState(__instance, "Dazzle?"); if (state == null || state2 == null) { WarnOnce(new InvalidOperationException($"Bind FSM states missing: Warrior Kickup={state != null}, Dazzle?={state2 != null}")); return; } FsmUtil.InsertAction(state, 0, (FsmStateAction)(object)new SkipWarriorKickupAction()); FsmUtil.InsertAction(state2, 0, (FsmStateAction)(object)new SuppressDazzleAction()); ((Component)__instance).gameObject.AddComponent(); ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)"[BeastRageBind] BEA-005 hooks installed: skip Warrior Kickup + suppress Dazzle? while in Rage mode."); } } catch (Exception ex) { WarnOnce(ex); } } private static void WarnOnce(Exception ex) { if (!_warned) { _warned = true; ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[BeastRageBind] BEA-005 FSM hook disabled: " + ex.GetType().Name + ": " + ex.Message)); } } } } public static class BeastRageDamageProtectPatch { private static readonly FieldInfo _warriorStateField = AccessTools.Field(typeof(HeroController), "warriorState"); private static ManualLogSource? _log; private static bool _warned; private static float _savedEffect; private static float _savedHeal; private static bool _saved; public static Func IsActive { get; set; } = () => false; public static void Configure(ManualLogSource log) { _log = log; } public static void Prefix(HeroController __instance) { //IL_002c: 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_0032: 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_0047: Unknown result type (might be due to invalid IL or missing references) _saved = false; if (!IsActive() || _warriorStateField == null) { return; } try { WarriorCrestStateInfo val = (WarriorCrestStateInfo)_warriorStateField.GetValue(__instance); if (val.IsInRageMode) { _savedEffect = val.RageEffectTimeLeft; _savedHeal = val.RageHealTimeLeft; _saved = true; } } catch (Exception ex) { WarnOnce(ex); } } public static void Postfix(HeroController __instance) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) if (!_saved || _warriorStateField == null) { return; } try { WarriorCrestStateInfo val = (WarriorCrestStateInfo)_warriorStateField.GetValue(__instance); val.RageEffectTimeLeft = _savedEffect; val.RageHealTimeLeft = _savedHeal; _warriorStateField.SetValue(__instance, val); } catch (Exception ex) { WarnOnce(ex); } finally { _saved = false; } } private static void WarnOnce(Exception ex) { if (!_warned) { _warned = true; ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[BeastRageProtect] " + ex.GetType().Name + ": " + ex.Message)); } } } } public static class BindCostPatch { public static bool Prefix(ref float __result) { if (CloaklessRuneBridge.IsFreeBindCharged()) { __result = 0f; return false; } if (CursedRuneBridge.IsBindEnabled()) { __result = 9f; return false; } return true; } } public static class CursedBindInvulnerabilityPatch { private sealed class CursedBindFsmMarker : MonoBehaviour { } private sealed class AcquireBindInvulnerabilityAction : FsmStateAction { public override void OnEnter() { try { if (!CursedRuneBridge.IsBindInvulnerabilityActive()) { ((FsmStateAction)this).Finish(); return; } if (CursedRuneBridge.BindInvulnerabilitySeconds() <= 0.0) { ((FsmStateAction)this).Finish(); return; } _lease?.Dispose(); _lease = CursedRuneBridge.AcquireBindInvulnerability(); } catch (Exception ex) { WarnOnce(ex); } ((FsmStateAction)this).Finish(); } } private static readonly string[] BindStates = new string[4] { "Binding", "Cursed Bind Start", "Cursed Bind Mid", "Cursed Bind Wait" }; private static ManualLogSource? _log; private static bool _warned; private static IDisposable? _lease; public static void Configure(ManualLogSource log) { _log = log; _warned = false; } public static void OnFsmStart(PlayMakerFSM __instance) { if ((Object)(object)__instance == (Object)null || __instance.Fsm == null || __instance.FsmName != "Spell Control" || (Object)(object)((Component)__instance).gameObject == (Object)null || !((Object)((Component)__instance).gameObject).name.StartsWith("Hero_Hornet", StringComparison.Ordinal) || (Object)(object)((Component)__instance).GetComponent() != (Object)null) { return; } try { int num = 0; string[] bindStates = BindStates; foreach (string text in bindStates) { FsmState state = FsmUtil.GetState(__instance, text); if (state != null) { FsmUtil.InsertAction(state, 0, (FsmStateAction)(object)new AcquireBindInvulnerabilityAction()); num++; } } if (num == 0) { WarnOnce(new InvalidOperationException("Spell Control bind states not found; bind invulnerability disabled.")); return; } ((Component)__instance).gameObject.AddComponent(); ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)$"[CursedBind] bind-process invulnerability hooks installed ({num} states)."); } } catch (Exception ex) { WarnOnce(ex); } } public static void Reset() { try { _lease?.Dispose(); } catch (Exception ex) { WarnOnce(ex); } _lease = null; } private static void WarnOnce(Exception ex) { if (!_warned) { _warned = true; ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[CursedBind] disabled: " + ex.GetType().Name + ": " + ex.Message)); } } } } public static class DashSignalPatch { public static Action? DashStarted { get; set; } public static void Postfix() { DashStarted?.Invoke(); } } public static class GlacialSlowPatch { private sealed class EnemySlowDriver : MonoBehaviour { private sealed record WalkerEntry(Walker Walker, float Left, float Right); private sealed record WalkerV2Entry(WalkerV2 Walker, float Walk, float Run); private readonly List _walkers = new List(); private readonly List _walkersV2 = new List(); private Rigidbody2D[] _bodies = Array.Empty(); private float _factor = 0.5f; private float _remaining = 10f; public void Initialize(float factor, float duration) { _factor = Mathf.Clamp(factor, 0.05f, 1f); _remaining = Mathf.Max(0.1f, duration); Capture(); } public void Refresh(float factor, float duration) { _factor = Mathf.Clamp(factor, 0.05f, 1f); _remaining = Mathf.Max(0.1f, duration); } private void Capture() { Walker[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); foreach (Walker val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { _walkers.Add(new WalkerEntry(val, val.walkSpeedL, val.walkSpeedR)); val.walkSpeedL *= _factor; val.walkSpeedR *= _factor; } } WalkerV2[] componentsInChildren2 = ((Component)this).GetComponentsInChildren(true); foreach (WalkerV2 val2 in componentsInChildren2) { if (!((Object)(object)val2 == (Object)null)) { _walkersV2.Add(new WalkerV2Entry(val2, val2.WalkSpeed, val2.RunSpeed)); val2.WalkSpeed *= _factor; val2.RunSpeed *= _factor; } } _bodies = ((Component)this).GetComponentsInChildren(true); } private void Update() { _remaining -= Time.deltaTime; if (_remaining <= 0f) { Object.Destroy((Object)(object)this); } } private void FixedUpdate() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (_factor >= 1f) { return; } Rigidbody2D[] bodies = _bodies; foreach (Rigidbody2D val in bodies) { if (!((Object)(object)val == (Object)null)) { val.linearVelocity *= _factor; val.angularVelocity *= _factor; } } } private void OnDestroy() { foreach (WalkerEntry walker in _walkers) { if ((Object)(object)walker.Walker != (Object)null) { walker.Walker.walkSpeedL = walker.Left; walker.Walker.walkSpeedR = walker.Right; } } foreach (WalkerV2Entry item in _walkersV2) { if ((Object)(object)item.Walker != (Object)null) { item.Walker.WalkSpeed = item.Walk; item.Walker.RunSpeed = item.Run; } } } } public static double SlowDurationSeconds { get; set; } = 10.0; public static double SlowFactor { get; set; } = 0.5; public static void Prefix(HealthManager __instance, HitInstance hitInstance) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance == (Object)null) && !((Object)(object)((Component)__instance).gameObject == (Object)null) && hitInstance.IsHeroDamage && CloaklessRuneBridge.TryConsumeGlacialAugment()) { ApplySlow(((Component)__instance).gameObject); } } private static void ApplySlow(GameObject enemy) { if (!((Object)(object)enemy == (Object)null)) { float factor = (float)SlowFactor; float duration = (float)SlowDurationSeconds; EnemySlowDriver component = enemy.GetComponent(); if ((Object)(object)component != (Object)null) { component.Refresh(factor, duration); } else { enemy.AddComponent().Initialize(factor, duration); } } } } public static class HailOfBladesCritPatch { public static void Prefix(ref HitInstance hitInstance) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (ReaperRuneBridge.IsHailOfBladesFullLayers() && hitInstance.IsHeroDamage && !hitInstance.CriticalHit && (int)hitInstance.AttackType == 0) { hitInstance.CriticalHit = true; } } } public static class HunterConquerorDamagePatch { public static Func? Multiplier; public static Func? IsEnhanced; public static void Prefix(ref HitInstance hitInstance) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) double multiplier = Multiplier?.Invoke() ?? 1.0; bool enhanced = IsEnhanced?.Invoke() ?? false; hitInstance.DamageDealt = ApplyMultiplier(hitInstance.DamageDealt, multiplier, enhanced, hitInstance.IsHeroDamage, hitInstance.AttackType); } public static int ApplyMultiplier(int damage, double multiplier, bool enhanced, bool isHeroDamage, AttackTypes attackType) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 if (multiplier <= 1.0) { return damage; } if (!enhanced || !isHeroDamage) { return damage; } if ((int)attackType == 2) { return damage; } return (int)Math.Round((double)damage * multiplier); } } public static class HunterVanillaComboPreserver { public static Func? LayerCount { get; set; } public static bool Prefix() { return (LayerCount?.Invoke() ?? 0) <= 0; } } public static class NailAttackRangePatch { public static bool Enabled { get; set; } = true; public static float RangeMultiplier { get; set; } = 2f; public static void Postfix(NailAttackBase __instance) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) if (Enabled && !(RangeMultiplier <= 0f) && WandererRuneBridge.IsActive()) { WandererEnhancementState current = WandererEnhancementState.Current; if (current != null && current.IsEnhanced) { Vector3 localScale = ((Component)__instance).transform.localScale; ((Component)__instance).transform.localScale = new Vector3(localScale.x * RangeMultiplier, localScale.y, localScale.z); } } } } public static class ParrySignalPatch { public static Action? Parried; public static void Postfix() { Parried?.Invoke(); } } public static class ReaperChargeSlashDamagePatch { public static double DamageMultiplier { get; set; } = 1.5; public static void Prefix(ref HitInstance hitInstance) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 if (!(DamageMultiplier <= 1.0) && ReaperRuneBridge.IsChargeSlashEnhanced() && hitInstance.IsHeroDamage && (int)hitInstance.AttackType == 16) { hitInstance.DamageDealt = (int)Math.Round((double)hitInstance.DamageDealt * DamageMultiplier); } } } public static class ReaperChargeSlashScalePatch { private sealed class ChargeSlashScaleMarker : MonoBehaviour { public Vector3 Original; } public static Func IsChargeSlashEnhanced { get; set; } = () => false; public static float ScaleMultiplier { get; set; } = 1.5f; public static void Postfix(NailAttackBase __instance) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) Transform transform = ((Component)__instance).transform; if (!((Object)(object)transform == (Object)null) && ((Object)transform).name.IndexOf("Charge Slash", StringComparison.OrdinalIgnoreCase) >= 0) { ChargeSlashScaleMarker chargeSlashScaleMarker = ((Component)transform).GetComponent(); if ((Object)(object)chargeSlashScaleMarker == (Object)null) { chargeSlashScaleMarker = ((Component)transform).gameObject.AddComponent(); chargeSlashScaleMarker.Original = transform.localScale; } transform.localScale = (Vector3)(IsChargeSlashEnhanced() ? new Vector3(chargeSlashScaleMarker.Original.x * ScaleMultiplier, chargeSlashScaleMarker.Original.y * ScaleMultiplier, chargeSlashScaleMarker.Original.z) : chargeSlashScaleMarker.Original); } } } public static class ReaperFireBladeDamagePatch { public static void Prefix(ref HitInstance hitInstance) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Invalid comparison between Unknown and I4 //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Invalid comparison between Unknown and I4 //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 if (!ReaperFireBladeBridge.IsActive()) { return; } double num = ReaperFireBladeBridge.DamageMultiplier(); if (!(num <= 1.0) && hitInstance.IsHeroDamage) { AttackTypes attackType = hitInstance.AttackType; if ((int)attackType <= 1 || (int)attackType == 6 || (int)attackType == 16) { hitInstance.DamageDealt = (int)Math.Round((double)hitInstance.DamageDealt * num); } } } } public static class ReaperSilkBundleAutoCollect { private sealed class SilkOrbMarker : MonoBehaviour { public PlayMakerFSM? Fsm; private void OnDestroy() { lock (_markers) { _markers.Remove(this); } } } private const string FsmName = "Control"; private const string GetSilkState = "Get Silk"; private const string CollectableState = "Collectable"; private const string CollectState = "Collect"; private const string DisappearEvent = "DISAPPEAR"; private static readonly HashSet _markers = new HashSet(); private static bool _failed; private static bool _warned; public static bool Enabled { get; set; } public static void Postfix(GameObject obj, GameObject prefab) { if (!Enabled || _failed || (Object)(object)obj == (Object)null || (Object)(object)prefab == (Object)null || ((Object)prefab).name != "Reaper Silk Bundle" || (Object)(object)obj.GetComponent() != (Object)null) { return; } PlayMakerFSM val = ((IEnumerable)obj.GetComponents()).FirstOrDefault((Func)((PlayMakerFSM f) => (Object)(object)f != (Object)null && f.Fsm != null && f.Fsm.Name == "Control")); if ((Object)(object)val == (Object)null) { Fail("Control FSM not found on Reaper Silk Bundle"); return; } try { Fsm fsm = val.Fsm; FsmState state = fsm.GetState("Get Silk"); FsmState state2 = fsm.GetState("Collectable"); FsmState state3 = fsm.GetState("Collect"); if (state == null || state2 == null || state3 == null) { Fail("one of states Get Silk/Collectable/Collect missing"); return; } DisableCameraShake(state); RetargetDisappear(state2, state3); SilkOrbMarker silkOrbMarker = obj.AddComponent(); silkOrbMarker.Fsm = val; lock (_markers) { _markers.Add(silkOrbMarker); } } catch (Exception ex) { Fail(ex.Message); } } public static void NotifyBindCompleted() { if (!Enabled || _failed) { return; } SilkOrbMarker[] array; lock (_markers) { array = _markers.ToArray(); } SilkOrbMarker[] array2 = array; foreach (SilkOrbMarker silkOrbMarker in array2) { if (!((Object)(object)silkOrbMarker == (Object)null) && !((Object)(object)silkOrbMarker.Fsm == (Object)null) && !((Object)(object)((Component)silkOrbMarker).gameObject == (Object)null)) { try { silkOrbMarker.Fsm.SetState("Collect"); } catch (Exception ex) { WarnOnce(ex); } } } } private static void DisableCameraShake(FsmState getSilk) { FsmStateAction val = ((IEnumerable)getSilk.Actions).FirstOrDefault((Func)((FsmStateAction a) => a != null && ((object)a).GetType().Name.IndexOf("Shake", StringComparison.OrdinalIgnoreCase) >= 0)); if (val != null) { val.Enabled = false; } else if (getSilk.Actions.Length > 2) { getSilk.Actions[2].Enabled = false; } } private static void RetargetDisappear(FsmState collectable, FsmState collect) { FsmTransition val = ((IEnumerable)collectable.Transitions).FirstOrDefault((Func)((FsmTransition t) => t != null && t.EventName == "DISAPPEAR")); if (val != null) { val.ToFsmState = collect; } } private static void Fail(string reason) { _failed = true; Enabled = false; WarnOnce(new InvalidOperationException(reason)); } private static void WarnOnce(Exception ex) { if (!_warned) { _warned = true; Debug.LogWarning((object)("[ReaperSilkBundle] " + ex.GetType().Name + ": " + ex.Message)); } } } public static class ReaperSilkDropProbabilityPatch { private static FieldInfo? _dropsField; private static FieldInfo? _probabilityField; private static bool _checked; private static bool _available; private static bool _warned; private static float[]? _saved; public static bool Enabled { get; set; } public static void Prefix(HeroController __instance) { if (!Enabled || !ReaperRuneBridge.IsDropRateWindowActive() || !EnsureAvailable()) { return; } try { if (!(_dropsField.GetValue(__instance) is Array { Length: >=3 } array)) { return; } _saved = new float[3]; for (int i = 0; i < 3; i++) { object value = array.GetValue(i); if (value == null) { _saved = null; break; } _saved[i] = Convert.ToSingle(_probabilityField.GetValue(value)); _probabilityField.SetValue(value, i switch { 1 => 0.5f, 0 => 0.15f, _ => 0.35f, }); } } catch (Exception ex) { _saved = null; WarnOnce(ex); } } public static void Restore() { if (_saved == null) { return; } try { HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null || !(_dropsField.GetValue(instance) is Array { Length: >=3 } array)) { return; } for (int i = 0; i < 3; i++) { object value = array.GetValue(i); if (value != null) { _probabilityField.SetValue(value, _saved[i]); } } } catch (Exception ex) { WarnOnce(ex); } finally { _saved = null; } } private static bool EnsureAvailable() { if (_checked) { return _available; } _checked = true; try { _dropsField = AccessTools.Field(typeof(HeroController), "reaperBundleDrops"); if (_dropsField == null || _dropsField.FieldType == null || !_dropsField.FieldType.IsArray) { WarnOnce(new InvalidOperationException("reaperBundleDrops field not found or not an array")); return false; } _probabilityField = AccessTools.Field(_dropsField.FieldType.GetElementType(), "Probability"); if (_probabilityField == null) { WarnOnce(new InvalidOperationException("Probability field not found on bundle drop element")); return false; } _available = true; } catch (Exception ex) { WarnOnce(ex); } return _available; } private static void WarnOnce(Exception ex) { if (!_warned) { _warned = true; Debug.LogWarning((object)("[ReaperSilkDrop] " + ex.GetType().Name + ": " + ex.Message)); } } } public static class ReaperSubBladeAttackSpeedPatch { private static bool _probeLogged; public static double Bonus { get; set; } = 0.3; public static void Postfix(ref float __result) { if (ReaperRuneBridge.IsSubBladeCharged()) { float num = __result; __result = (float)((double)__result * AttackCycleSpeed.TimeFactor(1, Bonus)); if (!_probeLogged) { _probeLogged = true; Debug.LogWarning((object)($"[HailOfBlades][probe] attack-speed getter scaled {num} -> {__result} " + $"(bonus={Bonus}, charged={ReaperRuneBridge.SubBladeChargedAttacksRemaining()}).")); } } } } public static class UnsealedSpellbookFreeBindPatch { private static bool _probeLogged; public static void Prefix(HeroController __instance, ref int amount, SilkTakeSource source) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) if (amount < 9 || (int)source != 0) { return; } HeroController val = (HeroController)(((Object)(object)__instance != (Object)null) ? ((object)__instance) : ((object)HeroController.instance)); if (!((Object)(object)val == (Object)null) && val.cState != null && (val.cState.isBinding || val.cState.focusing) && CloaklessRuneBridge.TryConsumeFreeBind()) { amount = 0; if (!_probeLogged) { _probeLogged = true; Debug.LogWarning((object)"[UnsealedSpellbook][probe] free bind consumed; silk cost set to 0."); } } } } public static class WandererAttackSpeedPatch { public static double PerLayer { get; set; } = 0.06; public static void Postfix(ref float __result) { if (WandererRuneBridge.IsActive()) { int num = WandererRuneBridge.LayerCount(); if (num > 0) { __result = (float)((double)__result * AttackCycleSpeed.TimeFactor(num, PerLayer)); } } } } public static class WandererCritChancePatch { public static double CritBonus { get; set; } = 0.02; public static void Postfix(ref float __result) { if (!(CritBonus <= 0.0) && WandererRuneBridge.IsActive() && WandererRuneBridge.IsFullLayers()) { __result += (float)CritBonus; } } } public static class WandererLuckyPatch { public static void Postfix(HeroController __instance, ref bool __result) { if (!__result && WandererRuneBridge.IsActive() && WandererRuneBridge.LayerCount() > 0 && !__instance.cState.isMaggoted && ((ToolBase)Gameplay.WandererCrest).IsEquipped) { __result = true; } } } public static class WandererParryCritPatch { public static void Postfix(HeroController __instance) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: 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_0026: Unknown result type (might be due to invalid IL or missing references) if (WandererRuneBridge.IsActive()) { WandererCrestStateInfo wandererState = __instance.WandererState; if (!wandererState.QueuedNextHitCritical) { wandererState.QueuedNextHitCritical = true; __instance.WandererState = wandererState; } } } } public static class WitchAftershockDamagePatch { public const double DamageMultiplier = 1.5; public static void Prefix(ref HitInstance hitInstance) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Invalid comparison between Unknown and I4 if (WitchRuneBridge.AftershockLayers() <= 0 || !hitInstance.IsHeroDamage) { return; } if ((int)hitInstance.AttackType == 6) { hitInstance.DamageDealt = (int)Math.Round((double)hitInstance.DamageDealt * 1.5); } else if ((int)hitInstance.AttackType == 0 || (int)hitInstance.AttackType == 1) { string text = (((Object)(object)hitInstance.Source != (Object)null) ? ((Object)hitInstance.Source).name : string.Empty); if (text.IndexOf("Down", StringComparison.OrdinalIgnoreCase) >= 0 && (text.IndexOf("Slash", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Spike", StringComparison.OrdinalIgnoreCase) >= 0)) { hitInstance.DamageDealt = (int)Math.Round((double)hitInstance.DamageDealt * 1.5); } } } } public static class WitchDashInvulnPatch { public static void Postfix() { if (WitchRuneBridge.AftershockLayers() > 0) { WitchRuneBridge.AcquireDashInvulnerability?.Invoke(); } } } public static class WitchGraspAttackSpeedPatch { public static void Postfix(ref float __result) { if (WitchRuneBridge.IsGraspCharged()) { __result = (float)((double)__result * AttackCycleSpeed.TimeFactor(1, 0.3)); } } } public static class WitchGraspDamagePatch { public const double DamageMultiplier = 1.3; public static void Prefix(ref HitInstance hitInstance) { //IL_0017: 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_0025: Invalid comparison between Unknown and I4 if (WitchRuneBridge.IsGraspCharged() && hitInstance.IsHeroDamage && ((int)hitInstance.AttackType == 0 || (int)hitInstance.AttackType == 1)) { hitInstance.DamageDealt = (int)Math.Round((double)hitInstance.DamageDealt * 1.3); } } } public static class WitchGraspDownslashDamagePatch { public const double DamageMultiplier = 1.5; public static void Prefix(ref HitInstance hitInstance) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 if (WitchRuneBridge.GraspLayers() > 0 && hitInstance.IsHeroDamage && ((int)hitInstance.AttackType == 0 || (int)hitInstance.AttackType == 1)) { string text = (((Object)(object)hitInstance.Source != (Object)null) ? ((Object)hitInstance.Source).name : string.Empty); if (text.IndexOf("Down", StringComparison.OrdinalIgnoreCase) >= 0 && (text.IndexOf("Slash", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Spike", StringComparison.OrdinalIgnoreCase) >= 0)) { hitInstance.DamageDealt = (int)Math.Round((double)hitInstance.DamageDealt * 1.5); } } } } public static class WitchGraspRangePatch { public const float RangeMultiplier = 1.3f; public static void Postfix(NailAttackBase __instance) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_002b: 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_0037: Unknown result type (might be due to invalid IL or missing references) if (WitchRuneBridge.IsGraspCharged()) { Vector3 localScale = ((Component)__instance).transform.localScale; ((Component)__instance).transform.localScale = new Vector3(localScale.x * 1.3f, localScale.y, localScale.z); } } } public static class WitchTakeSilkCostPatch { public const int ReducedCost = 5; private static bool _probeLogged; public static void Prefix(ref int amount, SilkTakeSource source) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (WitchRuneBridge.IsBindChainActive() && WitchRuneBridge.IsGraspActive() && amount >= 9) { if (!_probeLogged) { _probeLogged = true; Debug.LogWarning((object)$"[WitchBind][probe] TakeSilk reduced 9 -> {5} while bind chain active (source={source})."); } amount = 5; } } } } namespace CrestsEvolve.Features { public sealed class ArchitectCrest : ICrestRuneSet, IDisposable { private readonly bool _enabled; public string Key => "architect"; public CrestGate Gate { get; } public IReadOnlyList Runes { get; } public IReadOnlyList RuneKeys { get; } public FirstStrikeRune FirstStrike { get; } public ElectrocuteRune Electrocute { get; } public bool IsToolRepairActive => Gate.IsActive; public bool IsToolTransformActive => Gate.IsActive; public ArchitectCrest(bool enabled, FirstStrikeRune firstStrike, ElectrocuteRune electrocute) { _enabled = enabled; Gate = new CrestGate("architect", RuneCrestIds.Architect, enabled); FirstStrike = firstStrike ?? throw new ArgumentNullException("firstStrike"); Electrocute = electrocute ?? throw new ArgumentNullException("electrocute"); Runes = new IRuneEffect[2] { FirstStrike, Electrocute }; RuneKeys = new string[2] { FirstStrike.Key, Electrocute.Key }; } public void Start(string currentCrestId) { if (_enabled) { FirstStrike.Start(currentCrestId); Electrocute.Start(currentCrestId); } } public void Tick(float deltaSeconds) { if (_enabled) { FirstStrike.Tick(deltaSeconds); Electrocute.Tick(deltaSeconds); } } public void Dispose() { FirstStrike.Dispose(); Electrocute.Dispose(); } } public sealed class BeastCrest : ICrestRuneSet, IDisposable { private readonly bool _enabled; public string Key => "beast"; public CrestGate Gate { get; } public IReadOnlyList Runes { get; } public IReadOnlyList RuneKeys { get; } public DarkHarvestRune DarkHarvest { get; } public PredatorRune Predator { get; } public bool IsRedBloodTintActive => Gate.IsActive; public bool IsRageProtectionActive => Gate.IsActive; public bool IsBindNoPrepActive => Gate.IsActive; public BeastCrest(bool enabled, DarkHarvestRune darkHarvest, PredatorRune predator) { _enabled = enabled; Gate = new CrestGate("beast", RuneCrestIds.Beast, enabled); DarkHarvest = darkHarvest ?? throw new ArgumentNullException("darkHarvest"); Predator = predator ?? throw new ArgumentNullException("predator"); Runes = new IRuneEffect[2] { DarkHarvest, Predator }; RuneKeys = new string[2] { DarkHarvest.Key, Predator.Key }; } public void Start(string currentCrestId) { if (_enabled) { DarkHarvest.Start(currentCrestId); Predator.Start(currentCrestId); } } public void Tick(float deltaSeconds) { if (_enabled) { DarkHarvest.Tick(deltaSeconds); Predator.Tick(deltaSeconds); } } public void Dispose() { DarkHarvest.Dispose(); Predator.Dispose(); } } public enum BlueHealthTintKind { None, Red, Green } public static class BlueHealthTintResolver { public static BlueHealthTintKind Resolve(bool beastTintActive, bool witchTintActive, bool cursedTintActive, bool isPoison) { if (isPoison) { return BlueHealthTintKind.None; } if (beastTintActive) { return BlueHealthTintKind.Red; } if (witchTintActive || cursedTintActive) { return BlueHealthTintKind.Green; } return BlueHealthTintKind.None; } } public sealed class CloaklessCrest : ICrestRuneSet, IDisposable { private readonly bool _enabled; private readonly IRuneEffect? _extraRune; public string Key => "cloakless"; public CrestGate Gate { get; } public IReadOnlyList Runes { get; } public IReadOnlyList RuneKeys { get; } public GlacialAugmentRune GlacialAugment { get; } public UnsealedSpellbookRune UnsealedSpellbook { get; } public IRuneEffect? RandomPassive => _extraRune; public string? RandomPassiveKey => _extraRune?.Key; public CloaklessCrest(bool enabled, GlacialAugmentRune glacialAugment, UnsealedSpellbookRune unsealedSpellbook, bool randomPassiveEnabled, IReadOnlyList? randomPool, Random? random = null, string? pinnedPassiveKey = null) { _enabled = enabled; Gate = new CrestGate("cloakless", RuneCrestIds.Cloakless, enabled); GlacialAugment = glacialAugment ?? throw new ArgumentNullException("glacialAugment"); UnsealedSpellbook = unsealedSpellbook ?? throw new ArgumentNullException("unsealedSpellbook"); List list = new List { GlacialAugment, UnsealedSpellbook }; List list2 = new List { GlacialAugment.Key, UnsealedSpellbook.Key }; if (randomPassiveEnabled && randomPool != null && randomPool.Count > 0) { List list3 = new List(); foreach (IRuneEffect item in randomPool) { if (item != null && !string.Equals(item.Key, GlacialAugment.Key, StringComparison.Ordinal) && !string.Equals(item.Key, UnsealedSpellbook.Key, StringComparison.Ordinal)) { list3.Add(item); } } if (list3.Count > 0) { _extraRune = (string.IsNullOrWhiteSpace(pinnedPassiveKey) ? null : list3.FirstOrDefault((IRuneEffect r) => string.Equals(r.Key, pinnedPassiveKey, StringComparison.OrdinalIgnoreCase))) ?? list3[(random ?? new Random()).Next(list3.Count)]; list.Add(_extraRune); list2.Add(_extraRune.Key); } } Runes = list.ToArray(); RuneKeys = list2.ToArray(); } public void Start(string currentCrestId) { if (_enabled) { GlacialAugment.Start(currentCrestId); UnsealedSpellbook.Start(currentCrestId); } } public void Tick(float deltaSeconds) { if (_enabled) { GlacialAugment.Tick(deltaSeconds); UnsealedSpellbook.Tick(deltaSeconds); } } public void Dispose() { GlacialAugment.Dispose(); UnsealedSpellbook.Dispose(); } } public sealed class CursedCrest : ICrestRuneSet, IDisposable { private readonly bool _enabled; public string Key => "cursed"; public CrestGate Gate { get; } public IReadOnlyList Runes { get; } public IReadOnlyList RuneKeys { get; } public AftershockRune Aftershock { get; } public GuardianRune Guardian { get; } public bool IsBindInvulnerabilityActive => Gate.IsActive; public bool IsBindEnabled => Gate.IsActive; public bool IsGreenBloodTintActive => Gate.IsActive; public CursedCrest(bool enabled, AftershockRune aftershock, GuardianRune guardian) { _enabled = enabled; Gate = new CrestGate("cursed", RuneCrestIds.Cursed, enabled); Aftershock = aftershock ?? throw new ArgumentNullException("aftershock"); Guardian = guardian ?? throw new ArgumentNullException("guardian"); Runes = new IRuneEffect[2] { Aftershock, Guardian }; RuneKeys = new string[2] { Aftershock.Key, Guardian.Key }; } public void Start(string currentCrestId) { if (_enabled) { Aftershock.Start(currentCrestId); Guardian.Start(currentCrestId); } } public void Tick(float deltaSeconds) { if (_enabled) { Aftershock.Tick(deltaSeconds); Guardian.Tick(deltaSeconds); } } public void Dispose() { Aftershock.Dispose(); Guardian.Dispose(); } } public sealed class HunterCrest : ICrestRuneSet, IDisposable { private readonly bool _enabled; public string Key => "hunter"; public CrestGate Gate { get; } public IReadOnlyList Runes { get; } public IReadOnlyList RuneKeys { get; } public ConquerorRune Conqueror { get; } public int VanillaFocusProtectionLayers => Conqueror.LayerCount; public HunterCrest(bool enabled, ConquerorRune conqueror) { _enabled = enabled; Gate = new CrestGate("hunter", RuneCrestIds.Hunter, enabled); Conqueror = conqueror ?? throw new ArgumentNullException("conqueror"); Runes = new IRuneEffect[1] { Conqueror }; RuneKeys = new string[1] { Conqueror.Key }; } public void Start(string currentCrestId) { if (_enabled) { Conqueror.Start(currentCrestId); } } public void Tick(float deltaSeconds) { if (_enabled) { Conqueror.Tick(deltaSeconds); } } public void Dispose() { Conqueror.Dispose(); } } public sealed class ReaperCrest : ICrestRuneSet, IDisposable { private readonly bool _enabled; public string Key => "reaper"; public CrestGate Gate { get; } public IReadOnlyList Runes { get; } public IReadOnlyList RuneKeys { get; } public PressTheAttackRune PressTheAttack { get; } public HailOfBladesRune HailOfBlades { get; } public bool IsDropRateOverrideActive => PressTheAttack.IsDropRateOverrideActive; public bool IsAutoCollectEnabled => PressTheAttack.IsAutoCollectEnabled; public ReaperCrest(bool enabled, PressTheAttackRune pressTheAttack, HailOfBladesRune hailOfBlades) { _enabled = enabled; Gate = new CrestGate("reaper", RuneCrestIds.Reaper, enabled); PressTheAttack = pressTheAttack ?? throw new ArgumentNullException("pressTheAttack"); HailOfBlades = hailOfBlades ?? throw new ArgumentNullException("hailOfBlades"); Runes = new IRuneEffect[2] { PressTheAttack, HailOfBlades }; RuneKeys = new string[2] { PressTheAttack.Key, HailOfBlades.Key }; } public void Start(string currentCrestId) { if (_enabled) { PressTheAttack.Start(currentCrestId); HailOfBlades.Start(currentCrestId); } } public void Tick(float deltaSeconds) { if (_enabled) { PressTheAttack.Tick(deltaSeconds); HailOfBlades.Tick(deltaSeconds); } } public void Dispose() { PressTheAttack.Dispose(); HailOfBlades.Dispose(); } } public sealed class ShamanCrest : ICrestRuneSet, IDisposable { private readonly bool _enabled; public string Key => "shaman"; public CrestGate Gate { get; } public IReadOnlyList Runes { get; } public IReadOnlyList RuneKeys { get; } public ArcaneCometRune ArcaneComet { get; } public PhaseRushRune PhaseRush { get; } public ShamanCrest(bool enabled, ArcaneCometRune arcaneComet, PhaseRushRune phaseRush) { _enabled = enabled; Gate = new CrestGate("shaman", RuneCrestIds.Shaman, enabled); ArcaneComet = arcaneComet ?? throw new ArgumentNullException("arcaneComet"); PhaseRush = phaseRush ?? throw new ArgumentNullException("phaseRush"); Runes = new IRuneEffect[2] { ArcaneComet, PhaseRush }; RuneKeys = new string[2] { ArcaneComet.Key, PhaseRush.Key }; } public void Start(string currentCrestId) { if (_enabled) { ArcaneComet.Start(currentCrestId); PhaseRush.Start(currentCrestId); } } public void Tick(float deltaSeconds) { if (_enabled) { ArcaneComet.Tick(deltaSeconds); PhaseRush.Tick(deltaSeconds); } } public void Dispose() { ArcaneComet.Dispose(); PhaseRush.Dispose(); } } public sealed class WandererCrest : ICrestRuneSet, IDisposable { private readonly bool _enabled; public string Key => "wanderer"; public CrestGate Gate { get; } public IReadOnlyList Runes { get; } public IReadOnlyList RuneKeys { get; } public LethalTempoRune LethalTempo { get; } public double FullLayerCritBonus { get; } public bool IsFullLayers => LethalTempo.IsFullLayers; public WandererCrest(bool enabled, LethalTempoRune lethalTempo, double fullLayerCritBonus) { _enabled = enabled; Gate = new CrestGate("wanderer", RuneCrestIds.Wanderer, enabled); LethalTempo = lethalTempo ?? throw new ArgumentNullException("lethalTempo"); FullLayerCritBonus = Math.Max(0.0, fullLayerCritBonus); Runes = new IRuneEffect[1] { LethalTempo }; RuneKeys = new string[1] { LethalTempo.Key }; } public void Start(string currentCrestId) { if (_enabled) { LethalTempo.Start(currentCrestId); } } public void Tick(float deltaSeconds) { if (_enabled) { LethalTempo.Tick(deltaSeconds); } } public void Dispose() { LethalTempo.Dispose(); } } public sealed class WitchCrest : ICrestRuneSet, IDisposable { public sealed class WitchBindChain : IDisposable { public const double ChainDurationSeconds = 3.0; public const double RefundDelaySeconds = 0.8; public const int RefundSilk = 5; public const int ChainCostSilk = 5; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly IClock _clock; private readonly Func _classify; private readonly Action _addSilk; private bool _started; private bool _active; private bool _chainActive; private double _chainExpiresAt; private double? _refundAt; private bool _bindHealSinceBind; public CrestGate Gate { get; } public bool IsChainActive { get { if (_started && _active) { return _chainActive; } return false; } } public WitchBindChain(ManualLogSource log, GameEventHub events, IClock clock, Func classify, Action addSilk, CrestGate gate) { _log = log; _events = events; _clock = clock; _classify = classify; _addSilk = addSilk; Gate = gate ?? throw new ArgumentNullException("gate"); Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _events.HealResolved += OnHealResolved; _events.BindCompleted += OnBindCompleted; _events.BenchRested += OnLifecycleReset; _events.PlayerDied += OnLifecycleReset; _events.SceneChanged += OnSceneChanged; OnGateChanged(Gate.IsActive); } } public void Tick() { if (!_started) { return; } if (_chainActive && _clock.Now >= _chainExpiresAt) { _chainActive = false; _log.LogDebug((object)"[WitchBind] bind chain expired."); } if (_refundAt.HasValue && _clock.Now >= _refundAt.Value && !_bindHealSinceBind) { _refundAt = null; if (_active) { _addSilk(5); _log.LogDebug((object)$"[WitchBind] bind missed; refunded {5} silk."); } } } public void Dispose() { _started = false; _events.HealResolved -= OnHealResolved; _events.BindCompleted -= OnBindCompleted; _events.BenchRested -= OnLifecycleReset; _events.PlayerDied -= OnLifecycleReset; _events.SceneChanged -= OnSceneChanged; Gate.ActiveChanged -= OnGateChanged; Reset(); } private void OnHealResolved(HealEvent e) { if (_active && _classify(e.SourceTag) == HealSourceKind.Bind) { _bindHealSinceBind = true; _refundAt = null; _chainActive = true; _chainExpiresAt = _clock.Now + 3.0; _log.LogDebug((object)"[WitchBind] bind healed; chain active, next cost reduced to 5."); } } private void OnBindCompleted() { if (_active) { _bindHealSinceBind = false; _refundAt = _clock.Now + 0.8; } } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset(); } } private void OnSceneChanged(string scene) { OnLifecycleReset(); } private void OnLifecycleReset() { Reset(); } private void Reset() { _chainActive = false; _refundAt = null; _bindHealSinceBind = false; } } private readonly bool _enabled; public string Key => "witch"; public CrestGate Gate { get; } public IReadOnlyList Runes { get; } public IReadOnlyList RuneKeys { get; } public GraspOfTheUndyingRune Grasp { get; } public FleetFootworkRune Swift { get; } public WitchBindChain BindChain { get; } public bool IsGreenBloodTintActive => Gate.IsActive; public bool IsGraspActive => Grasp.Layers > 0; public WitchCrest(bool enabled, ManualLogSource log, GameEventHub events, IClock clock, Func classifyHeal, Action addSilk, GraspOfTheUndyingRune grasp, FleetFootworkRune fleetFootwork) { _enabled = enabled; Gate = new CrestGate("witch", RuneCrestIds.Witch, enabled); Grasp = grasp ?? throw new ArgumentNullException("grasp"); Swift = fleetFootwork ?? throw new ArgumentNullException("fleetFootwork"); Runes = new IRuneEffect[2] { Grasp, Swift }; RuneKeys = new string[2] { Grasp.Key, Swift.Key }; BindChain = new WitchBindChain(log, events, clock, classifyHeal, addSilk, Gate); } public void Start(string currentCrestId) { if (_enabled) { Grasp.Start(currentCrestId); Swift.Start(currentCrestId); BindChain.Start(currentCrestId); } } public void Tick(float deltaSeconds) { if (_enabled) { Grasp.Tick(deltaSeconds); Swift.Tick(deltaSeconds); BindChain.Tick(); } } public void Dispose() { Grasp.Dispose(); Swift.Dispose(); BindChain.Dispose(); } } } namespace CrestsEvolve.Features.StatusIcons { public enum CycleClearOn { None, OnAttack, OnSkillHit, OnAnyDamage } public sealed class CrestCycleDriver : IDisposable { private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconTimer _timer; private readonly string _crestId; private readonly CycleClearOn _clearOn; public CrestCycleDriver(ManualLogSource log, GameEventHub events, StatusIconTimer timer, string crestId, CycleClearOn clearOn) { _log = log; _events = events; _crestId = crestId; _clearOn = clearOn; _timer = timer; } public void Start() { _events.AttackResolved += OnAttack; } public void Dispose() { _events.AttackResolved -= OnAttack; } public void Tick(double deltaSeconds) { if (RuneEffects.RunesActive) { _timer.Tick(deltaSeconds); } } private void OnAttack(AttackEvent e) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Invalid comparison between Unknown and I4 if (RuneEffects.RunesActive && !e.IsAdditionalDamage && _clearOn switch { CycleClearOn.OnAttack => true, CycleClearOn.OnSkillHit => (int)e.Kind == 4, CycleClearOn.OnAnyDamage => true, _ => false, }) { _timer.Consume(); _log.LogDebug((object)("[StatusIcons] " + _crestId + " cycle cleared.")); } } } public sealed class CrestRuneIconDriver : IDisposable { private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly IReadOnlyDictionary> _targets; private readonly Func _currentCrestId; private readonly Func _isRuneToolEquipped; public CrestRuneIconDriver(GameEventHub events, StatusIconLedger ledger, IReadOnlyDictionary> targets, Func currentCrestId, Func isRuneToolEquipped) { _events = events; _ledger = ledger; _targets = targets; _currentCrestId = currentCrestId; _isRuneToolEquipped = isRuneToolEquipped ?? throw new ArgumentNullException("isRuneToolEquipped"); } public void Start(string currentCrestId) { _events.CrestChanged += OnCrestChanged; _events.SceneChanged += OnSceneChanged; _events.ToolEquipsChanged += OnToolEquipsChanged; Apply(currentCrestId); } public void Dispose() { _events.CrestChanged -= OnCrestChanged; _events.SceneChanged -= OnSceneChanged; _events.ToolEquipsChanged -= OnToolEquipsChanged; } private void OnCrestChanged(CrestChangedEvent e) { Apply(e.Current); } private void OnSceneChanged(string scene) { Apply(_currentCrestId()); } private void OnToolEquipsChanged() { Apply(_currentCrestId()); } private void Apply(string crestId) { if (!_isRuneToolEquipped()) { foreach (KeyValuePair> target in _targets) { foreach (var item in target.Value) { if (_ledger.IsRegistered(item.Item1)) { _ledger.SetVisible(item.Item1, item.Item2, false); } } } return; } foreach (KeyValuePair> target2 in _targets) { foreach (var item2 in target2.Value) { if (_ledger.IsRegistered(item2.Item1)) { bool flag = string.Equals(target2.Key, crestId, StringComparison.OrdinalIgnoreCase); _ledger.SetVisible(item2.Item1, item2.Item2, flag); } } } } } public sealed class FireBladeDriver : IDisposable { public const string IconKey = "reaper.fireblade"; public const string IconOwner = "reaper-fireblade"; private const int MaxLayers = 3; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly RuneGate _gate; private readonly ReaperFireBladeOptions _options; private readonly Func _spawnExplosion; private readonly Action? _applyFireVisual; private readonly AttackSkillWindow _window; private int _layers; private bool _started; private bool _active; public int Layers => _layers; public RuneGate Gate => _gate; public double CurrentAttackMultiplier { get; private set; } = 1.0; public FireBladeDriver(ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, ReaperFireBladeOptions options, RuneGate gate, Func spawnExplosion, Action? applyFireVisual = null) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown _log = log; _events = events; _ledger = ledger; _gate = gate ?? throw new ArgumentNullException("gate"); _options = options; _spawnExplosion = spawnExplosion; _applyFireVisual = applyFireVisual; _window = new AttackSkillWindow(clock, options.WindowEnd); _gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _events.AttackStarted += OnAttackStarted; _events.SkillUsed += OnSkillUsed; _events.AttackResolved += OnAttack; _events.Parried += OnParried; _events.BenchRested += OnLifecycleReset; _events.PlayerDied += OnLifecycleReset; _events.SceneChanged += OnSceneChanged; Reset("start"); OnGateChanged(_gate.IsActive); } } public void Tick(float deltaSeconds) { if (_started && !Active()) { Reset("inactive"); } } public void Dispose() { _started = false; _events.AttackStarted -= OnAttackStarted; _events.SkillUsed -= OnSkillUsed; _events.AttackResolved -= OnAttack; _events.Parried -= OnParried; _events.BenchRested -= OnLifecycleReset; _events.PlayerDied -= OnLifecycleReset; _events.SceneChanged -= OnSceneChanged; _gate.ActiveChanged -= OnGateChanged; Reset("driver disposed"); } private void OnAttackStarted() { if (!Active()) { return; } _window.OnAttack(); CurrentAttackMultiplier = ((_layers > 0) ? (1.0 + 2.0 * _options.FireBuffBonus) : 1.0); if (_layers <= 0) { return; } try { _applyFireVisual?.Invoke(); } catch (Exception ex) { _log.LogWarning((object)("[FireBlade] fire visual failed: " + ex.Message)); } } private void OnSkillUsed(SkillUseEvent e) { if (Active()) { if (_window.TryConsumeIn(_options.PreciseStart, _options.PreciseEnd)) { AddLayers(3); } else if (_window.TryConsumeIn(_options.WindowStart, _options.WindowEnd)) { AddLayers(1); } else { _window.Clear(); } } } private void OnAttack(AttackEvent e) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Invalid comparison between Unknown and I4 //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Invalid comparison between Unknown and I4 if (!Active() || e.IsAdditionalDamage) { return; } if ((int)e.Kind == 0) { if (_layers > 0) { _layers--; SetIcon(); _log.LogDebug((object)$"[FireBlade] normal attack consumed 1 layer ({_layers} left), no explosion."); } } else if (((int)e.Kind == 1 || (int)e.Kind == 3) && _layers >= 3) { SpawnExplosionForAttack(e.BaseDamage); } } private void OnParried() { if (Active() && _layers >= 3) { SpawnExplosion(_options.ParryExplosionDamage); } } private void SpawnExplosionForAttack(double baseDamage) { SpawnExplosion(baseDamage * _options.ExplosionDamageMultiplier); } private void SpawnExplosion(double damage) { bool flag = false; try { flag = _spawnExplosion(damage); } catch (Exception ex) { _log.LogWarning((object)("[FireBlade] explosion spawn threw: " + ex.Message)); } _log.LogDebug((object)$"[FireBlade] explosion consumed 3 layers: damage={damage}, spawned={flag}."); _layers = 0; SetIcon(); } private void AddLayers(int amount) { _layers = Math.Min(3, _layers + amount); SetIcon(); _log.LogDebug((object)$"[FireBlade] fire-blade window hit: +{amount} layer(s) ({_layers}/{3})."); } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnSceneChanged(string scene) { OnLifecycleReset(); } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _layers != 0 || CurrentAttackMultiplier != 1.0; _layers = 0; CurrentAttackMultiplier = 1.0; _window.Clear(); SetIcon(); if (num) { _log.LogDebug((object)("[FireBlade] reset (" + reason + ").")); } } private void SetIcon() { _ledger.Set("reaper.fireblade", "reaper-fireblade", _layers); } private bool Active() { if (_started && _active) { return _options.Enabled; } return false; } } public sealed record ReaperFireBladeOptions(bool Enabled, double WindowStart, double WindowEnd, double PreciseStart, double PreciseEnd, double FireBuffBonus, double ExplosionRadius, double ExplosionDamageMultiplier, double ParryExplosionDamage, bool FireVisualEnabled); public static class FireBladeBinder { public static ReaperFireBladeOptions Bind(ConfigFile config, ManualLogSource log) { bool value = config.Bind("Reaper.FireBlade", "Enabled", true, "Enable the Fire Blade passive (Flintslate second form; window stacking, damage, explosions).").Value; double value2 = config.Bind("Reaper.FireBlade", "WindowStart", 0.2, "Fire-blade press window start after a nail swing.").Value; double value3 = config.Bind("Reaper.FireBlade", "WindowEnd", 0.3, "Fire-blade press window end after a nail swing.").Value; double value4 = config.Bind("Reaper.FireBlade", "PreciseStart", 0.27, "Precise fire-blade window start; grants +3 layers.").Value; double value5 = config.Bind("Reaper.FireBlade", "PreciseEnd", 0.3, "Precise fire-blade window end; grants +3 layers.").Value; double value6 = config.Bind("Reaper.FireBlade", "FireBuffBonus", 0.25, "Vanilla fire-buff damage bonus assumed by the passive (damage x (1 + 2 x bonus), [待实测]).").Value; double value7 = config.Bind("Reaper.FireBlade", "ExplosionRadius", 3.0, "Fire-blade explosion radius in meters (REA-006/007, [待实测]).").Value; double value8 = config.Bind("Reaper.FireBlade", "ExplosionDamageMultiplier", 3.0, "Fire-blade explosion damage over the triggering attack's base damage (REA-006/007, [待实测]).").Value; double value9 = config.Bind("Reaper.FireBlade", "ParryExplosionDamage", 10.0, "Fire-blade explosion damage when triggered by parry (no base attack damage, [待实测]).").Value; ReaperFireBladeOptions reaperFireBladeOptions = new ReaperFireBladeOptions(FireVisualEnabled: config.Bind("Reaper.FireBlade", "FireVisualEnabled", true, "Apply vanilla fire-nail visual (HeroNailImbuement.SetElement) while layers > 0.").Value, Enabled: value, WindowStart: Math.Max(0.0, value2), WindowEnd: Math.Max(value2, value3), PreciseStart: Math.Max(value2, value4), PreciseEnd: Math.Max(value4, value5), FireBuffBonus: Math.Max(0.0, value6), ExplosionRadius: Math.Max(0.1, value7), ExplosionDamageMultiplier: Math.Max(0.0, value8), ParryExplosionDamage: Math.Max(0.0, value9)); log.LogDebug((object)$"[FireBlade] config bound: {reaperFireBladeOptions}"); return reaperFireBladeOptions; } } public sealed class WitchAftershockDriver : IDisposable { public const string IconKey = "witch.aftershock"; public const string IconOwner = "witch-aftershock"; private const int MaxLayers = 10; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly RuneGate _gate; private readonly Func _classify; private int _layers; private bool _started; private bool _active; public int Layers => _layers; public RuneGate Gate => _gate; public WitchAftershockDriver(ManualLogSource log, GameEventHub events, StatusIconLedger ledger, Func classify, RuneGate gate) { _log = log; _events = events; _ledger = ledger; _classify = classify; _gate = gate ?? throw new ArgumentNullException("gate"); _gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _events.HealResolved += OnHealResolved; _events.PlayerDamageAfter += OnPlayerDamage; _events.BenchRested += OnLifecycleReset; _events.PlayerDied += OnLifecycleReset; _events.SceneChanged += OnSceneChanged; Apply(); OnGateChanged(_gate.IsActive); } } public void Dispose() { _started = false; _events.HealResolved -= OnHealResolved; _events.PlayerDamageAfter -= OnPlayerDamage; _events.BenchRested -= OnLifecycleReset; _events.PlayerDied -= OnLifecycleReset; _events.SceneChanged -= OnSceneChanged; _gate.ActiveChanged -= OnGateChanged; _layers = 0; Apply(); } private void OnHealResolved(HealEvent e) { if (_started && _active && _classify(e.SourceTag) == HealSourceKind.Bind) { int num = ((e.Applied > 0) ? e.Applied : e.Requested); if (num > 0) { AddLayers(num); } } } private void OnPlayerDamage(PlayerDamageEvent e) { if (_started && _active && !e.Cancelled && _layers > 0) { ConsumeLayers(2); } } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnSceneChanged(string scene) { OnLifecycleReset(); } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void AddLayers(int amount) { _layers = Math.Min(10, _layers + amount); Apply(); _log.LogDebug((object)$"[WitchAftershock] +{amount} layer(s) ({_layers}/{10})."); } private void ConsumeLayers(int amount) { _layers = Math.Max(0, _layers - amount); Apply(); _log.LogDebug((object)$"[WitchAftershock] damaged: -{amount} layer(s) ({_layers} left)."); } private void Reset(string reason) { if (_layers != 0) { _layers = 0; Apply(); _log.LogDebug((object)("[WitchAftershock] reset (" + reason + ").")); } } private void Apply() { _ledger.Set("witch.aftershock", "witch-aftershock", _layers); _ledger.SetVisible("witch.aftershock", "witch-aftershock", _layers > 0); } } } namespace CrestsEvolve.Features.Runa { public sealed class AftershockRune : IRuneEffect, IDisposable { public const string IconKey = "cursed.aftershock"; public const string IconOwner = "cursed-aftershock"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly AftershockOptions _options; private readonly Action _addBlueMasks; private bool _started; private bool _active; private bool _charged; private double _elapsed; public string Key => "aftershock"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("cursed.aftershock", "cursed-aftershock") }; public bool IsCharged => _charged; public AftershockRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, AftershockOptions options, Action addBlueMasks) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _options = options ?? throw new ArgumentNullException("options"); _addBlueMasks = addBlueMasks ?? throw new ArgumentNullException("addBlueMasks"); Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _events.BindCompleted += OnBindCompleted; _events.PlayerDied += OnLifecycleReset; SetIcon(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { if (!_started) { return; } if (!_active) { Reset("gate closed"); } else if (!_charged) { _elapsed += deltaSeconds; if (_elapsed >= _options.CycleSeconds) { _charged = true; _log.LogDebug((object)"[Aftershock] charged; next bind restores masks."); } SetIcon(); } } public void Dispose() { _started = false; _events.BindCompleted -= OnBindCompleted; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; Reset("driver disposed"); } private void OnBindCompleted() { if (_started && _active && _charged) { try { _addBlueMasks(_options.BlueMasksOnBind); } catch (Exception ex) { _log.LogWarning((object)("[Aftershock] blue-mask grant failed: " + ex.Message)); } Reset("bind consumed"); _log.LogDebug((object)$"[Aftershock] bind consumed charge; +{_options.BlueMasksOnBind} masks."); } } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _charged || _elapsed != 0.0; _charged = false; _elapsed = 0.0; SetIcon(); if (num) { _log.LogDebug((object)("[Aftershock] reset (" + reason + ").")); } } private void SetIcon() { int num = (int)Math.Round(_options.CycleSeconds); _ledger.SetMax("cursed.aftershock", "cursed-aftershock", num); _ledger.SetVisible("cursed.aftershock", "cursed-aftershock", _active); _ledger.Set("cursed.aftershock", "cursed-aftershock", _charged ? num : Math.Min(num, (int)Math.Floor(_elapsed))); } } public sealed record AftershockOptions(bool Enabled, double CycleSeconds, int BlueMasksOnBind); public static class AftershockBinder { public static AftershockOptions Bind(ConfigFile config, ManualLogSource log) { AftershockOptions aftershockOptions = new AftershockOptions(config.Bind("Cursed.Aftershock", "Enabled", true, "Enable the Cursed Aftershock rune (10s charge; bind restores 3 blue masks).").Value, Math.Max(0.1, config.Bind("Cursed.Aftershock", "CycleSeconds", 10.0, "Seconds until the rune is fully charged.").Value), Math.Max(1, config.Bind("Cursed.Aftershock", "BlueMasksOnBind", 3, "Blue masks restored when binding at full charge.").Value)); log.LogDebug((object)$"[Aftershock] config bound: {aftershockOptions}"); return aftershockOptions; } } public sealed class ArcaneCometRune : IRuneEffect, IDisposable { public const string IconKey = "shaman.cycle"; public const string IconOwner = "shaman"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly ArcaneCometOptions _options; private readonly Action _spawnSpiritFlame; private bool _started; private bool _active; private bool _charged; private double _elapsed; public string Key => "arcane-comet"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("shaman.cycle", "shaman") }; public bool IsCharged => _charged; public ArcaneCometRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, ArcaneCometOptions options, Action spawnSpiritFlame) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _options = options ?? throw new ArgumentNullException("options"); _spawnSpiritFlame = spawnSpiritFlame ?? throw new ArgumentNullException("spawnSpiritFlame"); Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _events.EnemyDamageResolved += OnEnemyDamage; _events.PlayerDied += OnLifecycleReset; SetIcon(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { if (!_started) { return; } if (!_active) { Reset("gate closed"); } else if (!_charged) { _elapsed += deltaSeconds; if (_elapsed >= _options.CycleSeconds) { _charged = true; _log.LogDebug((object)"[ArcaneComet] charged; next skill hit summons a spirit flame."); } SetIcon(); } } public void Dispose() { _started = false; _events.EnemyDamageResolved -= OnEnemyDamage; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; Reset("driver disposed"); } private void OnEnemyDamage(AttackEvent e) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Invalid comparison between Unknown and I4 if (_started && _active && _charged && !e.IsAdditionalDamage && e.IsHeroDamage && (int)e.Kind == 4) { try { _spawnSpiritFlame(); } catch (Exception ex) { _log.LogWarning((object)("[ArcaneComet] spirit flame spawn failed: " + ex.Message)); } Reset("skill hit consumed"); } } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _charged || _elapsed != 0.0; _charged = false; _elapsed = 0.0; SetIcon(); if (num) { _log.LogDebug((object)("[ArcaneComet] reset (" + reason + ").")); } } private void SetIcon() { int num = (int)Math.Round(_options.CycleSeconds); _ledger.SetMax("shaman.cycle", "shaman", num); _ledger.SetVisible("shaman.cycle", "shaman", _active); _ledger.Set("shaman.cycle", "shaman", _charged ? num : Math.Min(num, (int)Math.Floor(_elapsed))); } } public sealed record ArcaneCometOptions(bool Enabled, double CycleSeconds); public static class ArcaneCometBinder { public static ArcaneCometOptions Bind(ConfigFile config, ManualLogSource log) { ArcaneCometOptions arcaneCometOptions = new ArcaneCometOptions(config.Bind("Shaman.ArcaneComet", "Enabled", true, "Enable the Shaman Arcane-Comet rune (10s cycle, skill hit summons blue flame).").Value, Math.Max(0.1, config.Bind("Shaman.ArcaneComet", "CycleSeconds", 10.0, "Seconds until the next skill hit summons a spirit flame.").Value)); log.LogDebug((object)$"[ArcaneComet] config bound: {arcaneCometOptions}"); return arcaneCometOptions; } } public sealed class ConquerorRune : IRuneEffect, IDisposable { public const string IconKey = "hunter.conqueror"; public const string IconOwner = "hunter"; private readonly bool _enabled; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly DecayingStackCounter _combo; private readonly Action _addSilk; private readonly int _bindSilkBonus; private readonly int _maxLayers; private bool _active; public string Key => "conqueror"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("hunter.conqueror", "hunter") }; public int LayerCount => _combo.Count; public ConquerorRune(bool enabled, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, Action addSilk, int bindSilkBonus = 4, int maxLayers = 12) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown _enabled = enabled; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _addSilk = addSilk ?? throw new ArgumentNullException("addSilk"); _bindSilkBonus = Math.Max(0, bindSilkBonus); _maxLayers = Math.Max(1, maxLayers); _combo = new DecayingStackCounter(clock, _maxLayers, 5.0, 5.0); _combo.Changed += delegate(int count) { if (_active && _ledger.IsRegistered("hunter.conqueror")) { _ledger.Set("hunter.conqueror", "hunter", count); } }; Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (_enabled) { _events.AttackResolved += OnAttack; _events.BindCompleted += OnBindCompleted; _events.PlayerDied += OnLifecycleReset; OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { _combo.Tick(); } public void Dispose() { _events.AttackResolved -= OnAttack; _events.BindCompleted -= OnBindCompleted; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; _combo.Clear(); } private void OnAttack(AttackEvent e) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (_active && !e.IsAdditionalDamage && (int)e.Kind == 0) { _combo.Add(1, Array.Empty()); } } private void OnBindCompleted() { if (_active && _combo.Count >= _maxLayers && _bindSilkBonus > 0) { _addSilk(_bindSilkBonus); } } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset(); } } private void OnLifecycleReset() { Reset(); } private void Reset() { _combo.Clear(); if (_ledger.IsRegistered("hunter.conqueror")) { _ledger.Set("hunter.conqueror", "hunter", 0); } } } public sealed class CrestGate { private readonly IReadOnlyList _crestIds; private string _currentCrestId = ""; private bool _toolGateActive; private bool _enabled; private bool? _lastActive; public string Key { get; } public IReadOnlyList CrestIds => _crestIds; public bool Enabled => _enabled; public bool IsActive { get { if (_enabled && _toolGateActive) { return Matches(_currentCrestId); } return false; } } public event Action? ActiveChanged; public CrestGate(string key, IEnumerable crestIds, bool enabled = true) { if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (crestIds == null) { throw new ArgumentNullException("crestIds"); } Key = key; _crestIds = crestIds.Select((string x) => x).ToArray(); if (_crestIds.Count == 0) { throw new ArgumentException("At least one crest id is required.", "crestIds"); } _enabled = enabled; } public bool Matches(string crestId) { return _crestIds.Any((string id) => string.Equals(id, crestId, StringComparison.OrdinalIgnoreCase)); } public void Update(string currentCrestId, bool toolGateActive) { _currentCrestId = currentCrestId ?? ""; _toolGateActive = toolGateActive; Refresh(); } public void SetEnabled(bool enabled) { if (_enabled != enabled) { _enabled = enabled; Refresh(); } } private void Refresh() { bool isActive = IsActive; if (_lastActive != isActive) { _lastActive = isActive; this.ActiveChanged?.Invoke(isActive); } } } public sealed class DarkHarvestRune : IRuneEffect, IDisposable { public const string IconKey = "beast.enhancement"; public const string IconOwner = "beast"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly DarkHarvestOptions _options; private readonly Action _addBlueMasks; private bool _started; private bool _active; private double _remaining; private int _displayMax; public string Key => "dark-harvest"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("beast.enhancement", "beast") }; public bool IsActive { get { if (_started && _active) { return _remaining > 0.0; } return false; } } public double RemainingSeconds => Math.Max(0.0, _remaining); public DarkHarvestRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, DarkHarvestOptions options, Action addBlueMasks) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _options = options ?? throw new ArgumentNullException("options"); _addBlueMasks = addBlueMasks ?? throw new ArgumentNullException("addBlueMasks"); Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _events.EnemyKilled += OnEnemyKilled; _events.BindCompleted += OnBindCompleted; _events.HealResolved += OnHealResolved; _events.PlayerDied += OnLifecycleReset; Hide(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { if (!_started) { return; } if (!_active) { Reset("gate closed"); } else if (!(_remaining <= 0.0)) { _remaining = Math.Max(0.0, _remaining - (double)deltaSeconds); if (_remaining <= 0.0) { Reset("enhancement ended"); } else { UpdateIcon(); } } } public void Dispose() { _started = false; _events.EnemyKilled -= OnEnemyKilled; _events.BindCompleted -= OnBindCompleted; _events.HealResolved -= OnHealResolved; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; Hide(); } private void OnEnemyKilled(EnemyKilledEvent e) { if (_active) { AddDuration(_options.DurationPerTrigger, "kill"); } } private void OnBindCompleted() { if (_active) { AddDuration(_options.DurationPerTrigger, "bind"); } } private void OnHealResolved(HealEvent e) { if (!_active || !_options.ConvertOverflowToBlueMasks) { return; } int num = ((e.Applied > 0) ? e.Applied : e.Requested); if (num > 0) { int num2 = Math.Max(0, num - Math.Max(0, e.MissingWhiteHealth)); if (num2 > 0) { _addBlueMasks(num2); _log.LogDebug((object)$"[DarkHarvest] overflow heal converted: {num2} blue mask(s)."); } } } private void AddDuration(double seconds, string source) { if (!(seconds <= 0.0)) { _remaining = Math.Min(_options.MaxDurationSeconds, _remaining + seconds); UpdateIcon(); _log.LogDebug((object)$"[DarkHarvest] +{seconds}s via {source} (remaining {_remaining:F1})."); } } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _remaining > 0.0; _remaining = 0.0; _displayMax = 0; Hide(); if (num) { _log.LogDebug((object)("[DarkHarvest] reset (" + reason + ").")); } } private void UpdateIcon() { int num = (int)Math.Ceiling(_remaining); if (num > _displayMax) { _displayMax = num; } int num2 = Math.Max(1, Math.Min((int)Math.Round(_options.MaxDurationSeconds), _displayMax)); _ledger.SetMax("beast.enhancement", "beast", num2); _ledger.SetVisible("beast.enhancement", "beast", true); _ledger.Set("beast.enhancement", "beast", Math.Min(num2, num)); } private void Hide() { _ledger.SetVisible("beast.enhancement", "beast", false); _ledger.Set("beast.enhancement", "beast", 0); } } public sealed record DarkHarvestOptions(bool Enabled, double DurationPerTrigger, double MaxDurationSeconds, bool ConvertOverflowToBlueMasks); public static class DarkHarvestBinder { public static DarkHarvestOptions Bind(ConfigFile config, ManualLogSource log) { DarkHarvestOptions darkHarvestOptions = new DarkHarvestOptions(config.Bind("Beast.DarkHarvest", "Enabled", true, "Enable the Beast Dark-Harvest rune (kill/bind extends enhancement; overflow heal to blue masks).").Value, Math.Max(0.1, config.Bind("Beast.DarkHarvest", "DurationPerTrigger", 5.0, "Enhancement seconds added per kill or bind.").Value), Math.Max(0.1, config.Bind("Beast.DarkHarvest", "MaxDurationSeconds", 24.0, "Maximum enhancement duration.").Value), config.Bind("Beast.DarkHarvest", "ConvertOverflowToBlueMasks", true, "Convert overflow healing at full health into blue masks while active.").Value); log.LogDebug((object)$"[DarkHarvest] config bound: {darkHarvestOptions}"); return darkHarvestOptions; } } public sealed class ElectrocuteRune : IRuneEffect, IDisposable { public const string IconKey = "architect.charge"; public const string IconOwner = "architect-electrocute"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly IClock _clock; private readonly ElectrocuteOptions _options; private readonly Func _spawnNativeExplosion; private bool _started; private bool _active; private int _layers; private double _baseReadyAt; private bool _baseReady; public string Key => "electrocute"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("architect.charge", "architect-electrocute") }; public int Layers => _layers; public ElectrocuteRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, ElectrocuteOptions options, RuneGate gate, Func spawnNativeExplosion) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; _clock = clock; _options = options ?? throw new ArgumentNullException("options"); Gate = gate ?? throw new ArgumentNullException("gate"); _spawnNativeExplosion = spawnNativeExplosion ?? throw new ArgumentNullException("spawnNativeExplosion"); Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _ledger.SetMax("architect.charge", "architect-electrocute", _options.MaxLayers); _events.EnemyDamageResolved += OnDamage; _events.PlayerDied += OnLifecycleReset; RestartBase(); SetIcon(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { if (!_started) { return; } if (!_active) { Clear("gate closed"); } else if (_layers <= 0 && !_baseReady && !(_clock.Now < _baseReadyAt)) { _baseReady = true; if (_layers < _options.MaxLayers) { _layers++; SetIcon(); _log.LogDebug((object)$"[Electrocute] base charge ready ({_layers}/{_options.MaxLayers})."); } } } public void Dispose() { _started = false; _events.EnemyDamageResolved -= OnDamage; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; Clear("driver disposed"); } private void OnDamage(AttackEvent e) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (_started && _active && e.IsHeroDamage && !e.IsAdditionalDamage && (int)e.Kind == 0) { if (_layers >= _options.MaxLayers) { TriggerExplosion(e.PositionX, e.PositionY, e.PositionZ); } else if (_layers != 0) { _layers++; SetIcon(); _log.LogDebug((object)$"[Electrocute] damage +1 layer ({_layers}/{_options.MaxLayers})."); } } } private void TriggerExplosion(float x, float y, float z) { bool flag = false; try { flag = _spawnNativeExplosion(x, y, z); } catch (Exception ex) { _log.LogWarning((object)("[Electrocute] explosion spawn threw: " + ex.Message)); } _log.LogDebug((object)$"[Electrocute] triggered: position=({x:F2},{y:F2},{z:F2}), spawned={flag}; clearing layers."); Clear("electrocute triggered"); } private void OnGateChanged(bool active) { _active = active; if (!active) { Clear("gate closed"); } } private void OnLifecycleReset() { Clear("lifecycle reset"); } private void Clear(string reason) { _layers = 0; RestartBase(); SetIcon(); _log.LogDebug((object)("[Electrocute] cleared (" + reason + ").")); } private void RestartBase() { _baseReady = false; _baseReadyAt = _clock.Now + _options.ChargeDelaySeconds; } private void SetIcon() { _ledger.Set("architect.charge", "architect-electrocute", _layers); } } public sealed record ElectrocuteOptions(bool Enabled, double ChargeDelaySeconds, int MaxLayers); public static class ElectrocuteBinder { public static ElectrocuteOptions Bind(ConfigFile config, ManualLogSource log) { bool value = config.Bind("Architect.Electrocute", "Enabled", true, "Enable the Architect Electrocute rune (ARC-051).").Value; double value2 = config.Bind("Architect.Electrocute", "ChargeDelaySeconds", 15.0, "Seconds from 0 layers to first +1 layer (timer only runs at 0 layers).").Value; ElectrocuteOptions electrocuteOptions = new ElectrocuteOptions(MaxLayers: Math.Max(1, config.Bind("Architect.Electrocute", "MaxLayers", 3, "Layer cap.").Value), Enabled: value, ChargeDelaySeconds: Math.Max(0.1, value2)); log.LogDebug((object)$"[Electrocute] config bound: {electrocuteOptions}"); return electrocuteOptions; } } public sealed class FirstStrikeRune : IRuneEffect, IDisposable { public const string IconKey = "architect.cycle"; public const string IconOwner = "architect"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly FirstStrikeOptions _options; private readonly Func _grantShardsWithOverflow; private readonly Action _addGeo; private bool _started; private bool _active; private bool _charged; private double _elapsed; public string Key => "first-strike"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("architect.cycle", "architect") }; public bool IsCharged => _charged; public FirstStrikeRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, FirstStrikeOptions options, Func grantShardsWithOverflow, Action addGeo) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _options = options ?? throw new ArgumentNullException("options"); _grantShardsWithOverflow = grantShardsWithOverflow ?? throw new ArgumentNullException("grantShardsWithOverflow"); _addGeo = addGeo ?? throw new ArgumentNullException("addGeo"); Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _events.AttackResolved += OnAttack; _events.PlayerDied += OnLifecycleReset; SetIcon(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { if (!_started) { return; } if (!_active) { Reset("gate closed"); } else if (!_charged) { _elapsed += deltaSeconds; if (_elapsed >= _options.CycleSeconds) { _charged = true; _log.LogDebug((object)"[FirstStrike] charged; next attack grants shards."); } SetIcon(); } } public void Dispose() { _started = false; _events.AttackResolved -= OnAttack; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; Reset("driver disposed"); } private void OnAttack(AttackEvent e) { if (!_started || !_active || !_charged || e.IsAdditionalDamage) { return; } int num = Math.Max(0, (int)e.BaseDamage); int num2; try { num2 = _grantShardsWithOverflow(num); } catch (Exception ex) { _log.LogWarning((object)("[FirstStrike] shard grant failed: " + ex.Message)); num2 = 0; } int num3 = Math.Max(0, num - num2); if (num3 > 0) { try { _addGeo(num3); } catch (Exception ex2) { _log.LogWarning((object)("[FirstStrike] overflow to beads failed: " + ex2.Message)); } } Reset("attack consumed"); _log.LogDebug((object)$"[FirstStrike] attack consumed: shards={num2}, overflowToBeads={num3}."); } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _charged || _elapsed != 0.0; _charged = false; _elapsed = 0.0; SetIcon(); if (num) { _log.LogDebug((object)("[FirstStrike] reset (" + reason + ").")); } } private void SetIcon() { int num = (int)Math.Round(_options.CycleSeconds); _ledger.SetMax("architect.cycle", "architect", num); _ledger.SetVisible("architect.cycle", "architect", _active); _ledger.Set("architect.cycle", "architect", _charged ? num : Math.Min(num, (int)Math.Floor(_elapsed))); } } public sealed record FirstStrikeOptions(bool Enabled, double CycleSeconds); public static class FirstStrikeBinder { public static FirstStrikeOptions Bind(ConfigFile config, ManualLogSource log) { FirstStrikeOptions firstStrikeOptions = new FirstStrikeOptions(config.Bind("Architect.FirstStrike", "Enabled", true, "Enable the Architect First-Strike rune (10s cycle, shards on hit, overflow to beads).").Value, Math.Max(0.1, config.Bind("Architect.FirstStrike", "CycleSeconds", 10.0, "Seconds until the next attack grants shards.").Value)); log.LogDebug((object)$"[FirstStrike] config bound: {firstStrikeOptions}"); return firstStrikeOptions; } } public sealed class FleetFootworkRune : IRuneEffect, IDisposable { public const string IconKey = "witch.swift"; public const string IconOwner = "witch-swift"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly FleetFootworkOptions _options; private readonly Action _acquireDashInvulnerability; private readonly Action _addBlueMasks; private readonly Action _grantFleaBrew; private bool _started; private bool _active; private int _layers; public string Key => "fleet-footwork"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("witch.swift", "witch-swift") }; public int Layers => _layers; public FleetFootworkRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, FleetFootworkOptions options, Action acquireDashInvulnerability, Action addBlueMasks, Action grantFleaBrew) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _options = options ?? throw new ArgumentNullException("options"); _acquireDashInvulnerability = acquireDashInvulnerability ?? throw new ArgumentNullException("acquireDashInvulnerability"); _addBlueMasks = addBlueMasks ?? throw new ArgumentNullException("addBlueMasks"); _grantFleaBrew = grantFleaBrew ?? throw new ArgumentNullException("grantFleaBrew"); Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _ledger.SetMax("witch.swift", "witch-swift", _options.MaxLayers); _events.DashStarted += OnDashStarted; _events.AttackResolved += OnAttack; _events.PlayerDied += OnLifecycleReset; SetIcon(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { } public void Dispose() { _started = false; _events.DashStarted -= OnDashStarted; _events.AttackResolved -= OnAttack; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; Reset("driver disposed"); } private void OnDashStarted() { if (!_started || !_active) { return; } _layers = Math.Min(_options.MaxLayers, _layers + 1); if (_layers > 0 && _layers % 2 == 0) { try { _acquireDashInvulnerability(); } catch (Exception ex) { _log.LogWarning((object)("[FleetFootwork] dash invulnerability failed: " + ex.Message)); } } SetIcon(); } private void OnAttack(AttackEvent e) { if (_started && _active && !e.IsAdditionalDamage && _layers >= _options.MaxLayers) { try { _addBlueMasks(_options.BlueMaskOnTrigger); _grantFleaBrew(); } catch (Exception ex) { _log.LogWarning((object)("[FleetFootwork] full-charge trigger failed: " + ex.Message)); } _layers = 0; SetIcon(); _log.LogDebug((object)"[FleetFootwork] full charge consumed; +1 blue mask + Flea Brew; layers cleared."); } } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _layers != 0; _layers = 0; SetIcon(); if (num) { _log.LogDebug((object)("[FleetFootwork] reset (" + reason + ").")); } } private void SetIcon() { _ledger.Set("witch.swift", "witch-swift", _layers); } } public sealed record FleetFootworkOptions(bool Enabled, int MaxLayers, int BlueMaskOnTrigger, double BrewDurationSeconds); public static class FleetFootworkBinder { public static FleetFootworkOptions Bind(ConfigFile config, ManualLogSource log) { FleetFootworkOptions fleetFootworkOptions = new FleetFootworkOptions(config.Bind("Witch.FleetFootwork", "Enabled", true, "Enable the Witch Fleet-Footwork rune (dash stacks, even-layer invuln, full-charge reward).").Value, Math.Max(1, config.Bind("Witch.FleetFootwork", "MaxLayers", 6, "Layer cap (6/6).").Value), Math.Max(1, config.Bind("Witch.FleetFootwork", "BlueMaskOnTrigger", 1, "Blue masks gained on the full-charge attack.").Value), Math.Max(0.1, config.Bind("Witch.FleetFootwork", "BrewDurationSeconds", 3.0, "Flea Brew duration on the full-charge attack.").Value)); log.LogDebug((object)$"[FleetFootwork] config bound: {fleetFootworkOptions}"); return fleetFootworkOptions; } } public sealed class GlacialAugmentRune : IRuneEffect, IDisposable { public const string IconKey = "cloakless.glacial"; public const string IconOwner = "cloakless-glacial"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly GlacialAugmentOptions _options; private bool _started; private bool _active; private bool _charged; private double _elapsed; public string Key => "glacial-augment"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("cloakless.glacial", "cloakless-glacial") }; public bool IsCharged => _charged; public GlacialAugmentRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, GlacialAugmentOptions options) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _options = options ?? throw new ArgumentNullException("options"); Gate.ActiveChanged += OnGateChanged; } public bool TryConsume() { if (!_started || !_active || !_charged) { return false; } Reset("attack consumed"); _log.LogDebug((object)"[GlacialAugment] charged attack consumed; slow applied."); return true; } public void Start(string currentCrestId) { if (!_started) { _started = true; _events.PlayerDied += OnLifecycleReset; SetIcon(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { if (!_started) { return; } if (!_active) { Reset("gate closed"); } else if (!_charged) { _elapsed += deltaSeconds; if (_elapsed >= _options.CycleSeconds) { _charged = true; _log.LogDebug((object)"[GlacialAugment] charged; next attack slows enemies."); } SetIcon(); } } public void Dispose() { _started = false; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; Reset("driver disposed"); } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _charged || _elapsed != 0.0; _charged = false; _elapsed = 0.0; SetIcon(); if (num) { _log.LogDebug((object)("[GlacialAugment] reset (" + reason + ").")); } } private void SetIcon() { int num = (int)Math.Round(_options.CycleSeconds); _ledger.SetMax("cloakless.glacial", "cloakless-glacial", num); _ledger.SetVisible("cloakless.glacial", "cloakless-glacial", _active); _ledger.Set("cloakless.glacial", "cloakless-glacial", _charged ? num : Math.Min(num, (int)Math.Floor(_elapsed))); } } public sealed record GlacialAugmentOptions(bool Enabled, double CycleSeconds, double SlowDurationSeconds, double SlowFactor); public static class GlacialAugmentBinder { public static GlacialAugmentOptions Bind(ConfigFile config, ManualLogSource log) { GlacialAugmentOptions glacialAugmentOptions = new GlacialAugmentOptions(config.Bind("Cloakless.GlacialAugment", "Enabled", true, "Enable the Cloakless Glacial-Augment rune (25s charge; next attack slows enemies 10s).").Value, Math.Max(0.1, config.Bind("Cloakless.GlacialAugment", "CycleSeconds", 25.0, "Seconds until the next attack is charged.").Value), Math.Max(0.1, config.Bind("Cloakless.GlacialAugment", "SlowDurationSeconds", 10.0, "Enemy slow duration in seconds after the charged attack.").Value), Math.Clamp(config.Bind("Cloakless.GlacialAugment", "SlowFactor", 0.5, "Enemy movement speed multiplier while slowed (0.5 = half speed; 1 = no slow).").Value, 0.05, 1.0)); log.LogDebug((object)$"[GlacialAugment] config bound: {glacialAugmentOptions}"); return glacialAugmentOptions; } } public sealed class GraspOfTheUndyingRune : IRuneEffect, IDisposable { public const string IconKey = "witch.cycle"; public const string IconOwner = "witch"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly GraspOfTheUndyingOptions _options; private readonly Action _addBlueMasks; private bool _started; private bool _active; private int _layers; private double _decayAccumulator; public string Key => "grasp-of-the-undying"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("witch.cycle", "witch") }; public int Layers => _layers; public GraspOfTheUndyingRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, GraspOfTheUndyingOptions options, Action addBlueMasks) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _options = options ?? throw new ArgumentNullException("options"); _addBlueMasks = addBlueMasks ?? throw new ArgumentNullException("addBlueMasks"); Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _ledger.SetMax("witch.cycle", "witch", _options.MaxLayers); _events.EnemyDamageResolved += OnEnemyDamageResolved; _events.HealResolved += OnHealResolved; _events.PlayerDied += OnLifecycleReset; SetIcon(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { if (!_started) { return; } if (!_active) { Reset("gate closed"); } else if (_layers > 0) { _decayAccumulator += deltaSeconds; while (_decayAccumulator >= _options.DecayIntervalSeconds && _layers > 0) { _decayAccumulator -= _options.DecayIntervalSeconds; _layers--; } SetIcon(); } } public void Dispose() { _started = false; _events.EnemyDamageResolved -= OnEnemyDamageResolved; _events.HealResolved -= OnHealResolved; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; Reset("driver disposed"); } private void OnEnemyDamageResolved(AttackEvent e) { if (_started && _active && e.IsHeroDamage && !e.IsAdditionalDamage && _layers < _options.MaxLayers) { _layers = Math.Min(_options.MaxLayers, _layers + _options.LayersPerDamage); SetIcon(); _log.LogDebug((object)$"[GraspOfTheUndying] damage +{_options.LayersPerDamage} layer(s) ({_layers}/{_options.MaxLayers})."); } } private void OnHealResolved(HealEvent e) { if (_started && _active && _layers > 0) { int num = ((e.Applied > 0) ? e.Applied : e.Requested); if (num > 0) { _addBlueMasks(num); _log.LogDebug((object)$"[GraspOfTheUndying] heal +{num} blue mask(s) (layers={_layers})."); } } } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _layers != 0 || _decayAccumulator != 0.0; _layers = 0; _decayAccumulator = 0.0; SetIcon(); if (num) { _log.LogDebug((object)("[GraspOfTheUndying] reset (" + reason + ").")); } } private void SetIcon() { _ledger.Set("witch.cycle", "witch", _layers); } } public sealed record GraspOfTheUndyingOptions(bool Enabled, int MaxLayers, int LayersPerDamage, double DecayIntervalSeconds, int BlueMasksPerHeal); public static class GraspOfTheUndyingBinder { public static GraspOfTheUndyingOptions Bind(ConfigFile config, ManualLogSource log) { GraspOfTheUndyingOptions graspOfTheUndyingOptions = new GraspOfTheUndyingOptions(config.Bind("Witch.Grasp", "Enabled", true, "Enable the Witch Grasp-of-the-Undying rune (damage stacks, heal to blue masks).").Value, Math.Max(1, config.Bind("Witch.Grasp", "MaxLayers", 4, "Layer cap (4/4).").Value), Math.Max(1, config.Bind("Witch.Grasp", "LayersPerDamage", 4, "Layers gained per damage segment.").Value), Math.Max(0.1, config.Bind("Witch.Grasp", "DecayIntervalSeconds", 1.0, "Seconds per layer decay.").Value), Math.Max(1, config.Bind("Witch.Grasp", "BlueMasksPerHeal", 1, "Blue masks per heal point while layers > 0.").Value)); log.LogDebug((object)$"[GraspOfTheUndying] config bound: {graspOfTheUndyingOptions}"); return graspOfTheUndyingOptions; } } public sealed class GuardianRune : IRuneEffect, IDisposable { public const string IconKey = "cursed.guardian"; public const string IconOwner = "cursed-guardian"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly GuardianOptions _options; private readonly Action _addBlueMasks; private bool _started; private bool _active; private int _layers; public string Key => "guardian"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("cursed.guardian", "cursed-guardian") }; public int Layers => _layers; public GuardianRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, GuardianOptions options, Action addBlueMasks) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _options = options ?? throw new ArgumentNullException("options"); _addBlueMasks = addBlueMasks ?? throw new ArgumentNullException("addBlueMasks"); Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _ledger.SetMax("cursed.guardian", "cursed-guardian", _options.MaxLayers); _events.PlayerDamageAfter += OnPlayerDamaged; _events.PlayerDied += OnLifecycleReset; SetIcon(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { if (_started && !_active && _layers != 0) { Reset("gate closed"); } } public void Dispose() { _started = false; _events.PlayerDamageAfter -= OnPlayerDamaged; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; Reset("driver disposed"); } private void OnPlayerDamaged(PlayerDamageEvent e) { if (!_started || !_active || e.Cancelled) { return; } _layers = Math.Min(_options.MaxLayers, _layers + 1); if (_layers >= _options.MaxLayers) { try { _addBlueMasks(_options.BlueMasksOnTrigger); } catch (Exception ex) { _log.LogWarning((object)("[Guardian] blue-mask grant failed: " + ex.Message)); } _layers = 0; _log.LogDebug((object)$"[Guardian] full charge triggered on hit; +{_options.BlueMasksOnTrigger} masks; layers cleared."); } SetIcon(); } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _layers != 0; _layers = 0; SetIcon(); if (num) { _log.LogDebug((object)("[Guardian] reset (" + reason + ").")); } } private void SetIcon() { _ledger.SetVisible("cursed.guardian", "cursed-guardian", _active); _ledger.Set("cursed.guardian", "cursed-guardian", _layers); } } public sealed record GuardianOptions(bool Enabled, int MaxLayers, int BlueMasksOnTrigger); public static class GuardianBinder { public static GuardianOptions Bind(ConfigFile config, ManualLogSource log) { GuardianOptions guardianOptions = new GuardianOptions(config.Bind("Cursed.Guardian", "Enabled", true, "Enable the Cursed Guardian rune (hit stacks; at 4 stacks being hit restores 3 blue masks).").Value, Math.Max(1, config.Bind("Cursed.Guardian", "MaxLayers", 4, "Layer cap (4/4).").Value), Math.Max(1, config.Bind("Cursed.Guardian", "BlueMasksOnTrigger", 3, "Blue masks restored when the full stack is hit.").Value)); log.LogDebug((object)$"[Guardian] config bound: {guardianOptions}"); return guardianOptions; } } public sealed class HailOfBladesRune : IRuneEffect, IDisposable { public const string IconKey = "reaper.subblade"; public const string IconOwner = "reaper-subblade"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly HailOfBladesOptions _options; private bool _started; private bool _active; private int _layers; private double _chargeAccumulator; private bool _charged; public string Key => "hail-of-blades"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("reaper.subblade", "reaper-subblade") }; public int Layers => _layers; public bool IsFullLayers => _layers >= _options.MaxLayers; public int ChargedAttacksRemaining { get { if (!_charged) { return 0; } return _layers; } } public HailOfBladesRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, HailOfBladesOptions options) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _options = options ?? throw new ArgumentNullException("options"); Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _ledger.SetMax("reaper.subblade", "reaper-subblade", _options.MaxLayers); _events.AttackResolved += OnAttack; _events.PlayerDied += OnLifecycleReset; SetIcon(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { if (!_started) { return; } if (!_active) { Reset("gate closed"); } else if (!_charged && _layers < _options.MaxLayers) { _chargeAccumulator += deltaSeconds; while (_chargeAccumulator >= _options.ChargeIntervalSeconds && _layers < _options.MaxLayers) { _chargeAccumulator -= _options.ChargeIntervalSeconds; _layers++; } if (_layers >= _options.MaxLayers && !_charged) { _charged = true; _log.LogDebug((object)($"[HailOfBlades] charged ({_layers}/{_options.MaxLayers}); " + $"attack speed +{(int)(_options.AttackSpeedBonus * 100.0)}% active.")); } SetIcon(); } } public void Dispose() { _started = false; _events.AttackResolved -= OnAttack; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; Reset("driver disposed"); } private void OnAttack(AttackEvent e) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (_active && !e.IsAdditionalDamage && (int)e.Kind == 0 && _charged && _layers > 0) { _layers = Math.Max(0, _layers - _options.ConsumePerNormalHit); if (_layers == 0) { _chargeAccumulator = 0.0; _charged = false; } SetIcon(); _log.LogDebug((object)$"[HailOfBlades] normal hit consumed {_options.ConsumePerNormalHit} layer(s) ({_layers}/{_options.MaxLayers})."); } } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _layers != 0 || _chargeAccumulator != 0.0; _layers = 0; _chargeAccumulator = 0.0; _charged = false; SetIcon(); if (num) { _log.LogDebug((object)("[HailOfBlades] reset (" + reason + ").")); } } private void SetIcon() { _ledger.Set("reaper.subblade", "reaper-subblade", _layers); } } public sealed record HailOfBladesOptions(bool Enabled, int MaxLayers, double ChargeIntervalSeconds, double AttackSpeedBonus, int ConsumePerNormalHit); public static class HailOfBladesBinder { public static HailOfBladesOptions Bind(ConfigFile config, ManualLogSource log) { bool value = config.Bind("Reaper.SubBlade", "Enabled", true, "Enable the Reaper Hail-of-Blades rune (9/9 charge, attack speed, crit).").Value; int value2 = config.Bind("Reaper.SubBlade", "MaxLayers", 9, "Layer cap (9/9).").Value; double value3 = config.Bind("Reaper.SubBlade", "ChargeIntervalSeconds", 1.0, "Seconds per layer while charging (layers == 0).").Value; double value4 = config.Bind("Reaper.SubBlade", "AttackSpeedBonus", 0.3, "Attack-speed bonus while layers > 0 (timings divided by 1 + bonus).").Value; HailOfBladesOptions hailOfBladesOptions = new HailOfBladesOptions(ConsumePerNormalHit: Math.Max(1, config.Bind("Reaper.SubBlade", "ConsumePerNormalHit", 3, "Layers consumed per normal-attack damage segment.").Value), Enabled: value, MaxLayers: Math.Max(1, value2), ChargeIntervalSeconds: Math.Max(0.1, value3), AttackSpeedBonus: Math.Max(0.0, value4)); log.LogDebug((object)$"[HailOfBlades] config bound: {hailOfBladesOptions}"); return hailOfBladesOptions; } } public interface ICrestRuneSet : IDisposable { string Key { get; } CrestGate Gate { get; } IReadOnlyList Runes { get; } IReadOnlyList RuneKeys { get; } void Start(string currentCrestId); void Tick(float deltaSeconds); } public interface IRuneEffect : IDisposable { string Key { get; } RuneGate Gate { get; } IReadOnlyList<(string Key, string Owner)> Icons { get; } void Start(string currentCrestId); void Tick(float deltaSeconds); } public sealed class LethalTempoRune : IRuneEffect, IDisposable { public const string IconKey = "wanderer.combo"; public const string IconOwner = "wanderer"; private readonly bool _enabled; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly DecayingStackCounter _combo; private readonly WandererEnhancementState _enhancement; private readonly LethalTempoOptions _options; private readonly bool _resetOnDamage; private bool _active; public string Key => "lethal-tempo"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("wanderer.combo", "wanderer") }; public int LayerCount => _combo.Count; public bool IsFullLayers => _combo.Count >= _options.MaxLayers; public LethalTempoRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, WandererEnhancementState enhancement, LethalTempoOptions options, RuneGate gate) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown LethalTempoRune lethalTempoRune = this; _enabled = enabled; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _enhancement = enhancement; _options = options ?? throw new ArgumentNullException("options"); _resetOnDamage = options.ResetOnDamage; _combo = new DecayingStackCounter(clock, options.MaxLayers, options.DecayFirstDelaySeconds, options.DecayIntervalSeconds); _combo.Changed += delegate(int count) { if (lethalTempoRune._active && lethalTempoRune._ledger.IsRegistered("wanderer.combo")) { lethalTempoRune._ledger.Set("wanderer.combo", "wanderer", count); } lethalTempoRune._enhancement.Set(lethalTempoRune.IsActiveForEffects() && count >= options.MaxLayers); }; Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (_enabled) { _events.EnemyDamageResolved += OnEnemyDamageResolved; _events.PlayerDamageAfter += OnDamaged; _events.PlayerDied += OnLifecycleReset; OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { _combo.Tick(); } public void Dispose() { _events.EnemyDamageResolved -= OnEnemyDamageResolved; _events.PlayerDamageAfter -= OnDamaged; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; _combo.Clear(); _enhancement.Set(false); } private bool IsActiveForEffects() { return _active; } private void OnEnemyDamageResolved(AttackEvent e) { if (_active && e.IsHeroDamage && (!IsSpell(e) || IsCrossStitch(e))) { _combo.Add(1, Array.Empty()); } } private static bool IsSpell(AttackEvent e) { return string.Equals(e.SourceTag, "enemy.damage.Spell", StringComparison.OrdinalIgnoreCase); } private static bool IsCrossStitch(AttackEvent e) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)e.Kind == 4) { return IsSpell(e); } return false; } private void OnGateChanged(bool active) { _active = active; if (!active) { ResetCombo(); } } private void OnDamaged(PlayerDamageEvent e) { if (_active && _resetOnDamage && !e.Cancelled) { ResetCombo(); } } private void OnLifecycleReset() { ResetCombo(); } private void ResetCombo() { _combo.Clear(); if (_ledger.IsRegistered("wanderer.combo")) { _ledger.Set("wanderer.combo", "wanderer", 0); } _enhancement.Set(false); } } public sealed record LethalTempoOptions(bool Enabled, int MaxLayers, double AttackSpeedPerLayer, double RangeMultiplierAtMax, double DecayFirstDelaySeconds, double DecayIntervalSeconds, bool ResetOnHeal, bool ResetOnDamage, double FullLayerCritBonus = 0.02); public static class LethalTempoBinder { public static LethalTempoOptions Bind(ConfigFile config, ManualLogSource log) { LethalTempoOptions lethalTempoOptions = new LethalTempoOptions(config.Bind("Wanderer", "Enabled", true, "Enable the Wanderer rune (Lethal Tempo stacking layers, attack speed, range, crit).").Value, config.Bind("Wanderer", "MaxLayers", 6, "Maximum rune layers.").Value, config.Bind("Wanderer", "AttackSpeedPerLayer", 0.06, "Attack cycle speed increase per layer (time divided by 1 + perLayer * layers).").Value, config.Bind("Wanderer", "RangeMultiplierAtMax", 1.25, "Nail attack X-scale multiplier while at max layers (range +25% default).").Value, config.Bind("Wanderer", "DecayFirstDelaySeconds", 6.0, "Seconds without dealing damage before the first layer decays.").Value, config.Bind("Wanderer", "DecayIntervalSeconds", 6.0, "Seconds between subsequent layer decays.").Value, config.Bind("Wanderer", "ResetOnHeal", false, "Reset layers when the player heals (kept for config compatibility).").Value, config.Bind("Wanderer", "ResetOnDamage", false, "Reset layers when the player takes damage (final WAN-001 keeps this false).").Value, config.Bind("Wanderer", "FullLayerCritBonus", 0.02, "Bonus crit chance while at max layers (crest feature, +2%).").Value); log.LogDebug((object)$"[LethalTempo] config bound: enabled={lethalTempoOptions.Enabled}, max={lethalTempoOptions.MaxLayers}, speedPerLayer={lethalTempoOptions.AttackSpeedPerLayer}, range={lethalTempoOptions.RangeMultiplierAtMax}, critBonus={lethalTempoOptions.FullLayerCritBonus}"); return lethalTempoOptions; } } public sealed class PhaseRushRune : IRuneEffect, IDisposable { public const string IconKey = "shaman.phase"; public const string IconOwner = "shaman-phase"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly PhaseRushOptions _options; private readonly Action _grantBrew; private bool _started; private bool _active; private double _remaining; public string Key => "phase-rush"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("shaman.phase", "shaman-phase") }; public bool IsActive { get { if (_started && _active) { return _remaining > 0.0; } return false; } } public PhaseRushRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, PhaseRushOptions options, Action grantBrew) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _options = options ?? throw new ArgumentNullException("options"); _grantBrew = grantBrew ?? throw new ArgumentNullException("grantBrew"); Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _events.SpellKilled += OnSpellKilled; _events.PlayerDied += OnLifecycleReset; Hide(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { if (!_started) { return; } if (!_active) { Reset("gate closed"); } else if (!(_remaining <= 0.0)) { _remaining = Math.Max(0.0, _remaining - (double)deltaSeconds); if (_remaining <= 0.0) { Reset("brew expired"); } else { SetIcon(); } } } public void Dispose() { _started = false; _events.SpellKilled -= OnSpellKilled; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; Hide(); } private void OnSpellKilled() { if (_started && _active) { try { _grantBrew(); } catch (Exception ex) { _log.LogWarning((object)("[PhaseRush] brew grant failed: " + ex.Message)); } _remaining = _options.DurationSeconds; SetIcon(); _log.LogDebug((object)$"[PhaseRush] spell kill; Flea Brew {_options.DurationSeconds}s."); } } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _remaining > 0.0; _remaining = 0.0; Hide(); if (num) { _log.LogDebug((object)("[PhaseRush] reset (" + reason + ").")); } } private void SetIcon() { int num = (int)Math.Round(_options.DurationSeconds); _ledger.SetMax("shaman.phase", "shaman-phase", num); _ledger.SetVisible("shaman.phase", "shaman-phase", true); _ledger.Set("shaman.phase", "shaman-phase", Math.Min(num, (int)Math.Ceiling(_remaining))); } private void Hide() { _ledger.SetVisible("shaman.phase", "shaman-phase", false); _ledger.Set("shaman.phase", "shaman-phase", 0); } } public sealed record PhaseRushOptions(bool Enabled, double DurationSeconds); public static class PhaseRushBinder { public static PhaseRushOptions Bind(ConfigFile config, ManualLogSource log) { PhaseRushOptions phaseRushOptions = new PhaseRushOptions(config.Bind("Shaman.PhaseRush", "Enabled", true, "Enable the Shaman Phase-Rush rune (spell kill grants Flea Brew 8s).").Value, Math.Max(0.1, config.Bind("Shaman.PhaseRush", "DurationSeconds", 8.0, "Flea Brew duration on a spell kill.").Value)); log.LogDebug((object)$"[PhaseRush] config bound: {phaseRushOptions}"); return phaseRushOptions; } } public sealed class PredatorRune : IRuneEffect, IDisposable { public const string IconKey = "beast.predator"; public const string IconOwner = "beast-predator"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly PredatorOptions _options; private readonly Action _addBlueMasks; private bool _started; private bool _active; private bool _charged; private double _elapsed; public string Key => "predator"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("beast.predator", "beast-predator") }; public bool IsCharged => _charged; public PredatorRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, PredatorOptions options, Action addBlueMasks) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _options = options ?? throw new ArgumentNullException("options"); _addBlueMasks = addBlueMasks ?? throw new ArgumentNullException("addBlueMasks"); Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _events.AttackResolved += OnAttack; _events.PlayerDied += OnLifecycleReset; SetIcon(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { if (!_started) { return; } if (!_active) { Reset("gate closed"); } else if (!_charged) { _elapsed += deltaSeconds; if (_elapsed >= _options.CycleSeconds) { _charged = true; _log.LogDebug((object)"[Predator] charged; next charge attack is enhanced."); } SetIcon(); } } public void Dispose() { _started = false; _events.AttackResolved -= OnAttack; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; Reset("driver disposed"); } private void OnAttack(AttackEvent e) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 if (_started && _active && _charged && !e.IsAdditionalDamage && (int)e.Kind == 3) { try { _addBlueMasks(_options.BlueMaskOnChargedHit); } catch (Exception ex) { _log.LogWarning((object)("[Predator] blue mask grant failed: " + ex.Message)); } Reset("charged hit consumed"); } } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _charged || _elapsed != 0.0; _charged = false; _elapsed = 0.0; SetIcon(); if (num) { _log.LogDebug((object)("[Predator] reset (" + reason + ").")); } } private void SetIcon() { _ledger.SetMax("beast.predator", "beast-predator", (int)Math.Round(_options.CycleSeconds)); _ledger.SetVisible("beast.predator", "beast-predator", _active); _ledger.Set("beast.predator", "beast-predator", _charged ? ((int)Math.Round(_options.CycleSeconds)) : Math.Min((int)Math.Round(_options.CycleSeconds), (int)Math.Floor(_elapsed))); } } public sealed record PredatorOptions(bool Enabled, double CycleSeconds, double DamageMultiplier, int BlueMaskOnChargedHit); public static class PredatorBinder { public static PredatorOptions Bind(ConfigFile config, ManualLogSource log) { PredatorOptions predatorOptions = new PredatorOptions(config.Bind("Beast.Predator", "Enabled", true, "Enable the Beast Predator rune (10s charge, enhanced charge attack).").Value, Math.Max(0.1, config.Bind("Beast.Predator", "CycleSeconds", 10.0, "Seconds to charge one enhanced charge attack.").Value), Math.Max(1.0, config.Bind("Beast.Predator", "DamageMultiplier", 1.2, "Charged attack damage multiplier.").Value), Math.Max(1, config.Bind("Beast.Predator", "BlueMaskOnChargedHit", 1, "Blue masks gained on the charged hit.").Value)); log.LogDebug((object)$"[Predator] config bound: {predatorOptions}"); return predatorOptions; } } public sealed class PressTheAttackRune : IRuneEffect, IDisposable { public const string IconKey = "reaper.strong"; public const string IconOwner = "reaper-strong"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly IClock _clock; private readonly PressTheAttackOptions _options; private bool _started; private bool _active; private double _remaining; private int _displayMax; public string Key => "press-the-attack"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("reaper.strong", "reaper-strong") }; public bool IsActive { get { if (_started && _active) { return _remaining > 0.0; } return false; } } public double RemainingSeconds => Math.Max(0.0, _remaining); public bool IsDropRateOverrideActive { get { if (IsActive) { return _remaining > _options.InitialDurationSeconds; } return false; } } public bool IsAutoCollectEnabled { get { if (IsActive) { return _options.AutoCollectEnabled; } return false; } } public PressTheAttackRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, PressTheAttackOptions options) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; _clock = clock; Gate = gate ?? throw new ArgumentNullException("gate"); _options = options ?? throw new ArgumentNullException("options"); Gate.ActiveChanged += OnGateChanged; } public void Start(string currentCrestId) { if (!_started) { _started = true; _events.BindCompleted += OnBindCompleted; _events.PlayerDamageAfter += OnPlayerDamaged; _events.BenchRested += OnLifecycleReset; _events.PlayerDied += OnLifecycleReset; _events.SceneChanged += OnSceneChanged; Hide(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { if (!_started) { return; } if (!_active) { Reset("gate closed"); } else if (!(_remaining <= 0.0)) { _remaining = Math.Max(0.0, _remaining - (double)deltaSeconds); if (_remaining <= 0.0) { Reset("window ended"); } else { UpdateIcon(); } } } public void Dispose() { _started = false; _events.BindCompleted -= OnBindCompleted; _events.PlayerDamageAfter -= OnPlayerDamaged; _events.BenchRested -= OnLifecycleReset; _events.PlayerDied -= OnLifecycleReset; _events.SceneChanged -= OnSceneChanged; Gate.ActiveChanged -= OnGateChanged; Hide(); } private void OnBindCompleted() { if (_active) { if (_remaining <= 0.0) { _remaining = _options.InitialDurationSeconds; _log.LogDebug((object)$"[PressTheAttack] window opened ({_options.InitialDurationSeconds}s)."); } else { _remaining = Math.Min(_options.MaxDurationSeconds, _remaining + _options.StackDurationSeconds); _log.LogDebug((object)$"[PressTheAttack] window stacked to {_remaining:F1}s (max {_options.MaxDurationSeconds})."); } UpdateIcon(); } } private void OnPlayerDamaged(PlayerDamageEvent e) { } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnSceneChanged(string scene) { Reset("scene changed"); } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _remaining > 0.0; _remaining = 0.0; _displayMax = 0; Hide(); if (num) { _log.LogDebug((object)("[PressTheAttack] reset (" + reason + ").")); } } private void UpdateIcon() { int num = (int)Math.Ceiling(_remaining); if (num > _displayMax) { _displayMax = num; } int num2 = Math.Max(1, Math.Min((int)Math.Round(_options.MaxDurationSeconds), _displayMax)); _ledger.SetMax("reaper.strong", "reaper-strong", num2); _ledger.SetVisible("reaper.strong", "reaper-strong", true); _ledger.Set("reaper.strong", "reaper-strong", Math.Min(num2, num)); } private void Hide() { _ledger.SetVisible("reaper.strong", "reaper-strong", false); _ledger.Set("reaper.strong", "reaper-strong", 0); } } public sealed record PressTheAttackOptions(bool Enabled, double InitialDurationSeconds, double StackDurationSeconds, double MaxDurationSeconds, double SilkDropP1, double SilkDropP2, double SilkDropP3, bool AutoCollectEnabled, double ChargeSlashScaleMultiplier, double ChargeSlashDamageMultiplier); public static class PressTheAttackBinder { public static PressTheAttackOptions Bind(ConfigFile config, ManualLogSource log) { bool value = config.Bind("Reaper.StrongAttack", "Enabled", true, "Enable the Reaper Press-the-Attack rune (bind window, charge-slash buff, drop rates).").Value; double value2 = config.Bind("Reaper.StrongAttack", "InitialDurationSeconds", 10.0, "Window seconds opened by a bind completion.").Value; double value3 = config.Bind("Reaper.StrongAttack", "StackDurationSeconds", 10.0, "Seconds added by each further bind while the window is active.").Value; double value4 = config.Bind("Reaper.StrongAttack", "MaxDurationSeconds", 24.0, "Maximum window duration.").Value; string value5 = config.Bind("Reaper.StrongAttack", "SilkDropProbabilities", "0.15,0.50,0.35", "Silk bundle drop probabilities (1/2/3 orbs) while remaining > InitialDurationSeconds.").Value; bool value6 = config.Bind("Reaper.StrongAttack", "AutoCollectEnabled", true, "Auto-collect Reaper silk bundles on natural expiry or bind completion while the window is active.").Value; double value7 = config.Bind("Reaper.StrongAttack", "ChargeSlashScaleMultiplier", 1.5, "Charge-slash hitbox scale multiplier (+50%) while the window is active.").Value; double value8 = config.Bind("Reaper.StrongAttack", "ChargeSlashDamageMultiplier", 1.5, "Charge-slash damage multiplier (+50%) while the window is active.").Value; (double P1, double P2, double P3) tuple = ParseDropProbabilities(value5, log); PressTheAttackOptions pressTheAttackOptions = new PressTheAttackOptions(SilkDropP1: tuple.P1, SilkDropP2: tuple.P2, SilkDropP3: tuple.P3, Enabled: value, InitialDurationSeconds: Math.Max(0.1, value2), StackDurationSeconds: Math.Max(0.1, value3), MaxDurationSeconds: Math.Max(value2, value4), AutoCollectEnabled: value6, ChargeSlashScaleMultiplier: Math.Max(1.0, value7), ChargeSlashDamageMultiplier: Math.Max(1.0, value8)); log.LogDebug((object)$"[PressTheAttack] config bound: {pressTheAttackOptions}"); return pressTheAttackOptions; } private static (double P1, double P2, double P3) ParseDropProbabilities(string text, ManualLogSource log) { string[] array = text.Split(','); for (int i = 0; i < array.Length; i++) { array[i] = array[i].Trim(); } if (array.Length == 3 && double.TryParse(array[0], out var result) && double.TryParse(array[1], out var result2) && double.TryParse(array[2], out var result3)) { return (P1: result, P2: result2, P3: result3); } log.LogWarning((object)("[PressTheAttack] invalid SilkDropProbabilities '" + text + "'; using 0.15,0.50,0.35.")); return (P1: 0.15, P2: 0.5, P3: 0.35); } } public static class RuneCrestIds { public static readonly IReadOnlyList Hunter = new string[3] { "hunter", "hunter_v2", "hunter_v3" }; public static readonly IReadOnlyList Wanderer = new string[1] { "wanderer" }; public static readonly IReadOnlyList Reaper = new string[1] { "reaper" }; public static readonly IReadOnlyList Beast = new string[2] { "warrior", "beast" }; public static readonly IReadOnlyList Witch = new string[1] { "witch" }; public static readonly IReadOnlyList Architect = new string[1] { "toolmaster" }; public static readonly IReadOnlyList Shaman = new string[1] { "spell" }; public static readonly IReadOnlyList Cursed = new string[1] { "cursed" }; public static readonly IReadOnlyList Cloakless = new string[1] { "cloakless" }; } public sealed class RuneEffectOptions { public bool Enabled { get; set; } = true; public string RuneToolName { get; set; } = "Ionia"; public bool Hunter { get; set; } = true; public int HunterBindSilkBonus { get; set; } = 4; public bool Wanderer { get; set; } = true; public bool Reaper { get; set; } = true; public bool Beast { get; set; } = true; public bool Witch { get; set; } = true; public bool Architect { get; set; } = true; public bool Shaman { get; set; } = true; public bool Cursed { get; set; } = true; public bool Cloakless { get; set; } = true; public bool CloaklessRandomPassive { get; set; } = true; public string CloaklessRandomPassiveKey { get; set; } = ""; } public static class RuneEffectBinder { public static RuneEffectOptions Bind(ConfigFile config, ManualLogSource log) { RuneEffectOptions runeEffectOptions = new RuneEffectOptions { Enabled = config.Bind("Runes", "Enabled", true, "Master switch for all rune effects; disable to return to vanilla.").Value, RuneToolName = config.Bind("Runes", "RuneToolName", "Ionia", "White tool that gates all crest rune effects.").Value, Hunter = config.Bind("Runes.Hunter", "Enabled", true, "Hunter rune (Conqueror combo decay + HUD icon).").Value, HunterBindSilkBonus = config.Bind("Runes.Hunter", "BindSilkBonus", 4, "满 12 层后任意缚丝完成增加的丝线数(outline 新数值:+4)。").Value, Wanderer = config.Bind("Runes.Wanderer", "Enabled", true, "Wanderer rune (Lethal Tempo combo decay + HUD icon).").Value, Reaper = config.Bind("Runes.Reaper", "Enabled", true, "Reaper runes (Press the Attack + Hail of Blades).").Value, Beast = config.Bind("Runes.Beast", "Enabled", true, "Beast runes (Dark Harvest + Predator).").Value, Witch = config.Bind("Runes.Witch", "Enabled", true, "Witch runes (Grasp of the Undying + Fleet Footwork).").Value, Architect = config.Bind("Runes.Architect", "Enabled", true, "Architect runes (First Strike + Electrocute).").Value, Shaman = config.Bind("Runes.Shaman", "Enabled", true, "Shaman runes (Arcane Comet + Phase Rush).").Value, Cursed = config.Bind("Runes.Cursed", "Enabled", true, "Cursed-crest runes (Aftershock + Guardian).").Value, Cloakless = config.Bind("Runes.Cloakless", "Enabled", true, "Cloakless-crest runes (Glacial Augment + Unsealed Spellbook).").Value, CloaklessRandomPassive = config.Bind("Runes.Cloakless", "RandomPassiveEnabled", true, "Cloakless extra random passive: pick one shared rune from the 12 implemented runes per session.").Value, CloaklessRandomPassiveKey = config.Bind("Runes.Cloakless", "RandomPassiveKey", "", "Optional: pin the Cloakless extra passive to a specific rune key (e.g. conqueror); empty = random per session.").Value }; log.LogDebug((object)$"[Runes] config bound: enabled={runeEffectOptions.Enabled}"); return runeEffectOptions; } } public sealed class RuneEffects : IDisposable { private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly Func _isRuneToolEquipped; private readonly List _crests = new List(); private readonly List _runes = new List(); private string _currentCrestId = ""; public static bool RunesActive { get; private set; } public IReadOnlyList Crests => _crests; public IReadOnlyList Runes => _runes; public static void SetRunesActive(bool value) { RunesActive = value; } public RuneEffects(bool enabled, ManualLogSource log, GameEventHub events, Func isRuneToolEquipped, IEnumerable crests) { _enabled = enabled; _log = log; _events = events; _isRuneToolEquipped = isRuneToolEquipped ?? throw new ArgumentNullException("isRuneToolEquipped"); foreach (ICrestRuneSet crest in crests) { _crests.Add(crest ?? throw new ArgumentNullException("crests")); foreach (IRuneEffect rune in crest.Runes) { if (!_runes.Contains(rune)) { _runes.Add(rune); } } } } public void Start(string currentCrestId) { if (!_enabled) { return; } _events.ToolEquipsChanged += OnToolEquipsChanged; _events.CrestChanged += OnCrestChanged; _events.SceneChanged += OnSceneChanged; _currentCrestId = currentCrestId ?? ""; RefreshGates(); foreach (ICrestRuneSet crest in _crests) { crest.Start(_currentCrestId); } } public void Tick(float deltaSeconds) { if (!_enabled || !RunesActive) { return; } foreach (ICrestRuneSet crest in _crests) { crest.Tick(deltaSeconds); } } public void Dispose() { _events.ToolEquipsChanged -= OnToolEquipsChanged; _events.CrestChanged -= OnCrestChanged; _events.SceneChanged -= OnSceneChanged; RunesActive = false; foreach (ICrestRuneSet crest in _crests) { crest.Dispose(); } _crests.Clear(); _runes.Clear(); } public bool IsCrestActive(string crestKey) { return _crests.FirstOrDefault((ICrestRuneSet c) => string.Equals(c.Key, crestKey, StringComparison.Ordinal))?.Gate.IsActive ?? false; } private void OnToolEquipsChanged() { RefreshGates(); } private void OnCrestChanged(CrestChangedEvent e) { _currentCrestId = e.Current; RefreshGates(); } private void OnSceneChanged(string scene) { RefreshGates(); } private void RefreshGates() { RunesActive = _isRuneToolEquipped(); HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (ICrestRuneSet crest in _crests) { crest.Gate.Update(_currentCrestId, RunesActive); if (!crest.Gate.IsActive) { continue; } foreach (string runeKey in crest.RuneKeys) { hashSet.Add(runeKey); } } foreach (IRuneEffect rune in _runes) { rune.Gate.Update(RunesActive, hashSet.Contains(rune.Key)); } _log.LogDebug((object)string.Format("[Runes] gates refreshed: active={0}, crest={1}, assigned=[{2}]", RunesActive, _currentCrestId, string.Join(",", hashSet))); } public IReadOnlyDictionary> BuildCrestIconTargets() { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (ICrestRuneSet crest in _crests) { foreach (string crestId in crest.Gate.CrestIds) { if (!dictionary.TryGetValue(crestId, out var value)) { value = (dictionary[crestId] = new List<(string, string)>()); } foreach (IRuneEffect rune in crest.Runes) { foreach (var icon in rune.Icons) { if (!value.Contains(icon)) { value.Add(icon); } } } } } Dictionary> dictionary2 = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair> item in dictionary) { dictionary2[item.Key] = item.Value.ToArray(); } return dictionary2; } } public sealed class RuneGate { private bool _toolGateActive; private bool _assignedByCurrentCrest; private bool _enabled; private bool? _lastActive; public string RuneKey { get; } public bool Enabled => _enabled; public bool IsActive { get { if (_enabled && _toolGateActive) { return _assignedByCurrentCrest; } return false; } } public event Action? ActiveChanged; public RuneGate(string runeKey, bool enabled = true) { if (string.IsNullOrWhiteSpace(runeKey)) { throw new ArgumentNullException("runeKey"); } RuneKey = runeKey; _enabled = enabled; } public void Update(bool toolGateActive, bool assignedByCurrentCrest) { _toolGateActive = toolGateActive; _assignedByCurrentCrest = assignedByCurrentCrest; Refresh(); } public void SetEnabled(bool enabled) { if (_enabled != enabled) { _enabled = enabled; Refresh(); } } private void Refresh() { bool isActive = IsActive; if (_lastActive != isActive) { _lastActive = isActive; this.ActiveChanged?.Invoke(isActive); } } } public sealed class UnsealedSpellbookRune : IRuneEffect, IDisposable { public const string IconKey = "cloakless.spellbook"; public const string IconOwner = "cloakless-spellbook"; private readonly bool _enabled; private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly StatusIconLedger _ledger; private readonly UnsealedSpellbookOptions _options; private bool _started; private bool _active; private bool _charged; private double _elapsed; public string Key => "unsealed-spellbook"; public RuneGate Gate { get; } public IReadOnlyList<(string Key, string Owner)> Icons => new(string, string)[1] { ("cloakless.spellbook", "cloakless-spellbook") }; public bool IsCharged => _charged; public UnsealedSpellbookRune(bool enabled, ManualLogSource log, GameEventHub events, StatusIconLedger ledger, IClock clock, RuneGate gate, UnsealedSpellbookOptions options) { _enabled = enabled; _log = log; _events = events; _ledger = ledger; Gate = gate ?? throw new ArgumentNullException("gate"); _options = options ?? throw new ArgumentNullException("options"); Gate.ActiveChanged += OnGateChanged; } public bool TryConsumeFreeBind() { if (!_started || !_active || !_charged) { return false; } Reset("free bind consumed"); _log.LogDebug((object)"[UnsealedSpellbook] free bind consumed."); return true; } public void Start(string currentCrestId) { if (!_started) { _started = true; _ledger.SetMax("cloakless.spellbook", "cloakless-spellbook", 1); _events.PlayerDied += OnLifecycleReset; SetIcon(); OnGateChanged(Gate.IsActive); } } public void Tick(float deltaSeconds) { if (!_started) { return; } if (!_active) { Reset("gate closed"); } else if (!_charged) { _elapsed += deltaSeconds; if (_elapsed >= _options.CycleSeconds) { _charged = true; _log.LogDebug((object)"[UnsealedSpellbook] charged; next bind is free."); } SetIcon(); } } public void Dispose() { _started = false; _events.PlayerDied -= OnLifecycleReset; Gate.ActiveChanged -= OnGateChanged; Reset("driver disposed"); } private void OnGateChanged(bool active) { _active = active; if (!active) { Reset("gate closed"); } } private void OnLifecycleReset() { Reset("lifecycle reset"); } private void Reset(string reason) { bool num = _charged || _elapsed != 0.0; _charged = false; _elapsed = 0.0; SetIcon(); if (num) { _log.LogDebug((object)("[UnsealedSpellbook] reset (" + reason + ").")); } } private void SetIcon() { _ledger.SetVisible("cloakless.spellbook", "cloakless-spellbook", _active && _charged); _ledger.Set("cloakless.spellbook", "cloakless-spellbook", _charged ? 1 : 0); } } public sealed record UnsealedSpellbookOptions(bool Enabled, double CycleSeconds); public static class UnsealedSpellbookBinder { public static UnsealedSpellbookOptions Bind(ConfigFile config, ManualLogSource log) { UnsealedSpellbookOptions unsealedSpellbookOptions = new UnsealedSpellbookOptions(config.Bind("Cloakless.UnsealedSpellbook", "Enabled", true, "Enable the Cloakless Unsealed-Spellbook rune (25s charge; next bind is free).").Value, Math.Max(0.1, config.Bind("Cloakless.UnsealedSpellbook", "CycleSeconds", 25.0, "Seconds until the next bind is free.").Value)); log.LogDebug((object)$"[UnsealedSpellbook] config bound: {unsealedSpellbookOptions}"); return unsealedSpellbookOptions; } } } namespace CrestsEvolve.Features.Plasma { public enum PlasmaKind { None, Red, Green } public static class PlasmaTintPolicy { public static PlasmaKind Resolve(bool redActive, bool greenActive) { if (redActive) { return PlasmaKind.Red; } if (greenActive) { return PlasmaKind.Green; } return PlasmaKind.None; } } public sealed record PlasmaOptions(bool Enabled, bool TintEnabled, bool SuppressVanillaRegen, bool BindUnblockEnabled, int BeastAttacksPerHeal, bool WitchFullHealOnBind); public static class PlasmaBinder { public static PlasmaOptions Bind(ConfigFile config, ManualLogSource log) { PlasmaOptions plasmaOptions = new PlasmaOptions(config.Bind("Plasma", "Enabled", true, "Master switch for red/green plasma tint and heal rules.").Value, config.Bind("Plasma", "TintEnabled", true, "Tint Overblue Hive masks / recovery blob / heal particles red (beast) or green (witch).").Value, config.Bind("Plasma", "SuppressVanillaRegen", true, "Disable vanilla 6s auto-regen while red/green plasma is active.").Value, config.Bind("Plasma", "BindUnblockEnabled", true, "Let Bind complete during green plasma (vanilla blocks it inside Overblue).").Value, Math.Max(1, config.Bind("Plasma.Beast", "AttacksPerHeal", 2, "Melee hits required to heal 1 mask in red plasma.").Value), config.Bind("Plasma.Witch", "FullHealOnBind", true, "Heal to full white masks when a Bind heal resolves during green plasma.").Value); log.LogDebug((object)$"[Plasma] config bound: {plasmaOptions}"); return plasmaOptions; } } public sealed class PlasmaRules : IDisposable { private readonly ManualLogSource _log; private readonly GameEventHub _events; private readonly IGameApi _game; private readonly Func _classifyHeal; private readonly Func _isRedGate; private readonly Func _isGreenGate; private readonly PlasmaOptions _options; private bool _started; private bool _inPlasma; private int _attackCount; private bool _toppingUp; public bool IsInPlasma { get { if (_started) { return _inPlasma; } return false; } } public bool IsRedActive { get { if (_started && _inPlasma) { return _isRedGate(); } return false; } } public bool IsGreenActive { get { if (_started && _inPlasma) { return _isGreenGate(); } return false; } } public PlasmaKind Kind => PlasmaTintPolicy.Resolve(IsRedActive, IsGreenActive); public int AttackCount => _attackCount; public PlasmaRules(ManualLogSource log, GameEventHub events, IGameApi game, Func classifyHeal, Func isRedGate, Func isGreenGate, PlasmaOptions options) { _log = log ?? throw new ArgumentNullException("log"); _events = events ?? throw new ArgumentNullException("events"); _game = game ?? throw new ArgumentNullException("game"); _classifyHeal = classifyHeal ?? throw new ArgumentNullException("classifyHeal"); _isRedGate = isRedGate ?? throw new ArgumentNullException("isRedGate"); _isGreenGate = isGreenGate ?? throw new ArgumentNullException("isGreenGate"); _options = options ?? throw new ArgumentNullException("options"); } public void Start() { if (!_started) { _started = true; _events.AttackResolved += OnAttackResolved; _events.HealResolved += OnHealResolved; _events.CrestChanged += OnCrestChanged; _events.SceneChanged += OnSceneChanged; _events.BenchRested += OnBenchRested; _events.PlayerDied += OnPlayerDied; } } public void Dispose() { _started = false; _events.AttackResolved -= OnAttackResolved; _events.HealResolved -= OnHealResolved; _events.CrestChanged -= OnCrestChanged; _events.SceneChanged -= OnSceneChanged; _events.BenchRested -= OnBenchRested; _events.PlayerDied -= OnPlayerDied; _inPlasma = false; _attackCount = 0; _toppingUp = false; } public void OnPlasmaEntered() { if (_started && !_inPlasma) { _inPlasma = true; _attackCount = 0; _toppingUp = false; _log.LogDebug((object)$"[Plasma] entered (kind={Kind})."); } } public void OnPlasmaExited() { if (_started && _inPlasma) { _inPlasma = false; _attackCount = 0; _toppingUp = false; _log.LogDebug((object)"[Plasma] exited."); } } private void OnAttackResolved(AttackEvent e) { if (!_started || !IsRedActive) { return; } _attackCount++; if (_attackCount >= _options.BeastAttacksPerHeal) { _attackCount = 0; if (_game.CurrentHealth < _game.MaxHealth && _game.TryHeal(1, "beast.plasma-attack-heal")) { _log.LogDebug((object)"[Plasma] beast attack heal (+1 mask)."); } } } private void OnHealResolved(HealEvent e) { if (!_started || !IsGreenActive || !_options.WitchFullHealOnBind || _toppingUp || _classifyHeal(e.SourceTag) != HealSourceKind.Bind) { return; } int num = _game.MaxHealth - _game.CurrentHealth; if (num <= 0) { return; } _toppingUp = true; try { if (_game.TryHeal(num, "witch.plasma-bind-full-heal")) { _log.LogDebug((object)$"[Plasma] witch bind full-heal (+{num} masks)."); } } finally { _toppingUp = false; } } private void OnCrestChanged(CrestChangedEvent e) { ResetCounters(); } private void OnSceneChanged(string scene) { ResetCounters(); } private void OnBenchRested() { OnLifecycleReset(); } private void OnPlayerDied() { OnLifecycleReset(); } private void ResetCounters() { _attackCount = 0; _toppingUp = false; } private void OnLifecycleReset() { _inPlasma = false; ResetCounters(); } } } namespace CrestsEvolve.LegendsNeverDie { [BepInPlugin("local.crestsevolve.legendsneverdie", "Legends Never Die", "0.9.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "local.crestsevolve.legendsneverdie"; public const string PluginName = "Legends Never Die"; public const string PluginVersion = "0.9.1"; private Harmony? _harmony; private RunesBootstrap? _bootstrap; private void Awake() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown _harmony = new Harmony("local.crestsevolve.legendsneverdie"); _bootstrap = new RunesBootstrap(((BaseUnityPlugin)this).Logger, ((BaseUnityPlugin)this).Config, _harmony); _bootstrap.Start(); } private void OnDestroy() { _bootstrap?.Dispose(); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } } namespace CrestsEvolve.LegendsNeverDie.Bootstrap { public sealed class RunesBootstrap : IDisposable { private sealed class RuneStateService : IRuneStateService { private readonly RuneEffects _runeEffects; public bool RunesActive => RuneEffects.RunesActive; public RuneStateService(RuneEffects runeEffects) { _runeEffects = runeEffects; } public bool IsActive(string runeKey) { return _runeEffects.IsCrestActive(runeKey); } } private sealed class RunesIconDriver : IStatusIconDriver, IDisposable { private readonly CrestRuneIconDriver _crestRune; private readonly RuneEffects _runeEffects; public RunesIconDriver(CrestRuneIconDriver crestRune, RuneEffects runeEffects) { _crestRune = crestRune; _runeEffects = runeEffects; } public void Start(string currentCrestId) { _crestRune.Start(currentCrestId); _runeEffects.Start(currentCrestId); } public void Dispose() { _crestRune.Dispose(); _runeEffects.Dispose(); } } private readonly ManualLogSource _log; private readonly ConfigFile _config; private readonly Harmony _harmony; private RuneEffects? _runeEffects; private HealClassificationBridge? _healClassification; private WandererEnhancementState? _wandererEnhancement; private HunterCrest? _hunterCrest; private WandererCrest? _wandererCrest; private ReaperCrest? _reaperCrest; private BeastCrest? _beastCrest; private WitchCrest? _witchCrest; private ArchitectCrest? _architectCrest; private ShamanCrest? _shamanCrest; private CursedCrest? _cursedCrest; private CloaklessCrest? _cloaklessCrest; private PlasmaRules? _plasmaRules; private bool _runeGateProbed; private bool _lastRuneGate; private bool _catalogProbed; private bool _disposed; public RunesBootstrap(ManualLogSource log, ConfigFile config, Harmony harmony) { _log = log; _config = config; _harmony = harmony; } public void Start() { //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Expected O, but got Unknown //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Expected O, but got Unknown //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Expected O, but got Unknown FoundationServices services = FoundationServices.Current; if (services == null) { _log.LogWarning((object)"[LegendsNeverDie] foundation not loaded; runes disabled."); return; } RuneEffectOptions runeOptions = RuneEffectBinder.Bind(_config, _log); LethalTempoOptions lethalTempoOptions = LethalTempoBinder.Bind(_config, _log); PressTheAttackOptions pressTheAttackOptions = PressTheAttackBinder.Bind(_config, _log); HailOfBladesOptions hailOfBladesOptions = HailOfBladesBinder.Bind(_config, _log); ElectrocuteOptions options = ElectrocuteBinder.Bind(_config, _log); DarkHarvestOptions options2 = DarkHarvestBinder.Bind(_config, _log); PredatorOptions predatorOptions = PredatorBinder.Bind(_config, _log); GraspOfTheUndyingOptions options3 = GraspOfTheUndyingBinder.Bind(_config, _log); FleetFootworkOptions fleetFootworkOptions = FleetFootworkBinder.Bind(_config, _log); FirstStrikeOptions options4 = FirstStrikeBinder.Bind(_config, _log); ArcaneCometOptions options5 = ArcaneCometBinder.Bind(_config, _log); PhaseRushOptions phaseRushOptions = PhaseRushBinder.Bind(_config, _log); AftershockOptions options6 = AftershockBinder.Bind(_config, _log); GuardianOptions options7 = GuardianBinder.Bind(_config, _log); GlacialAugmentOptions glacialAugmentOptions = GlacialAugmentBinder.Bind(_config, _log); UnsealedSpellbookOptions options8 = UnsealedSpellbookBinder.Bind(_config, _log); double cursedBindInvulnSeconds = Math.Max(0.1, _config.Bind("Cursed", "BindInvulnerabilitySeconds", 2.0, "Cursed bind-process invulnerability duration in seconds.").Value); FireBladeBinder.Bind(_config, _log); Func isRuneToolEquipped = delegate { try { if (!_catalogProbed) { _catalogProbed = true; ToolItem toolByName = ToolItemManager.GetToolByName(runeOptions.RuneToolName); _log.LogInfo((object)("[Runes][probe] GetToolByName('" + runeOptions.RuneToolName + "') = " + (((Object)(object)toolByName != (Object)null) ? toolByName.name : ""))); } bool flag = WhiteToolSystem.IsEquippedAnywhere(runeOptions.RuneToolName); if (!_runeGateProbed || flag != _lastRuneGate) { _runeGateProbed = true; _lastRuneGate = flag; _log.LogInfo((object)$"[Runes][probe] rune tool '{runeOptions.RuneToolName}' equipped={flag} crest={services.Game.CurrentCrestId}"); } return flag; } catch (Exception ex) { _log.LogWarning((object)("[Runes] rune tool gate query failed: " + ex.Message)); return false; } }; RegisterIcons(services); _wandererEnhancement = new WandererEnhancementState(true); SpiritFlameSpawner spiritFlameSpawner = new SpiritFlameSpawner(_log); FleaBrewApplicator val = new FleaBrewApplicator(_log, (IClock)(object)services.Clock, fleetFootworkOptions.BrewDurationSeconds); FleaBrewApplicator val2 = new FleaBrewApplicator(_log, (IClock)(object)services.Clock, phaseRushOptions.DurationSeconds); ShardGeoBridge shardGeoBridge = new ShardGeoBridge(_log); Action addBlueMasks = delegate(int amount) { if (amount <= 0) { return; } try { GameManager instance = GameManager.instance; if (!((Object)(object)instance == (Object)null)) { for (int i = 0; i < amount; i++) { instance.AddBlueHealthQueued(); } } } catch (Exception ex) { _log.LogWarning((object)("[Runes] add blue masks failed: " + ex.Message)); } }; ConquerorRune conquerorRune = new ConquerorRune(runeOptions.Hunter, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("conqueror"), delegate(int amount) { services.Game.AddSilk(amount, "conqueror.bind-silk"); }, runeOptions.HunterBindSilkBonus); PressTheAttackRune pressTheAttackRune = new PressTheAttackRune(runeOptions.Reaper, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("press-the-attack"), pressTheAttackOptions); HailOfBladesRune hailOfBladesRune = new HailOfBladesRune(runeOptions.Reaper, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("hail-of-blades"), hailOfBladesOptions); LethalTempoRune lethalTempoRune = new LethalTempoRune(runeOptions.Wanderer && lethalTempoOptions.Enabled, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, _wandererEnhancement, lethalTempoOptions, new RuneGate("lethal-tempo")); DarkHarvestRune darkHarvestRune = new DarkHarvestRune(runeOptions.Beast, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("dark-harvest"), options2, addBlueMasks); PredatorRune predatorRune = new PredatorRune(runeOptions.Beast, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("predator"), predatorOptions, addBlueMasks); GraspOfTheUndyingRune graspOfTheUndyingRune = new GraspOfTheUndyingRune(runeOptions.Witch, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("grasp-of-the-undying"), options3, addBlueMasks); FleetFootworkRune fleetFootworkRune = new FleetFootworkRune(runeOptions.Witch, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("fleet-footwork"), fleetFootworkOptions, (Action)delegate { services.Game.AcquireInvulnerability("witch.swift-dash", 0.35); }, addBlueMasks, (Action)val.Apply); FirstStrikeRune firstStrikeRune = new FirstStrikeRune(runeOptions.Architect, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("first-strike"), options4, shardGeoBridge.AddShardsReturningAdded, shardGeoBridge.AddGeo); ElectrocuteRune electrocuteRune = new ElectrocuteRune(runeOptions.Architect, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, options, new RuneGate("electrocute"), (float x, float y, float z) => services.Game.SpawnNativeExplosionAt(new Vector3(x, y, z))); ArcaneCometRune arcaneCometRune = new ArcaneCometRune(runeOptions.Shaman, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("arcane-comet"), options5, spiritFlameSpawner.Spawn); PhaseRushRune phaseRushRune = new PhaseRushRune(runeOptions.Shaman, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("phase-rush"), phaseRushOptions, (Action)val2.Apply); AftershockRune aftershock = new AftershockRune(runeOptions.Cursed, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("aftershock"), options6, addBlueMasks); GuardianRune guardian = new GuardianRune(runeOptions.Cursed, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("guardian"), options7, addBlueMasks); GlacialAugmentRune glacialAugment = new GlacialAugmentRune(runeOptions.Cloakless, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("glacial-augment"), glacialAugmentOptions); UnsealedSpellbookRune unsealedSpellbook = new UnsealedSpellbookRune(runeOptions.Cloakless, _log, services.Events, services.StatusLedger, (IClock)(object)services.Clock, new RuneGate("unsealed-spellbook"), options8); _healClassification = new HealClassificationBridge(_log); services.Events.BindCompleted += _healClassification.NotifyBindCompleted; _hunterCrest = new HunterCrest(runeOptions.Hunter, conquerorRune); _wandererCrest = new WandererCrest(runeOptions.Wanderer && lethalTempoOptions.Enabled, lethalTempoRune, lethalTempoOptions.FullLayerCritBonus); _reaperCrest = new ReaperCrest(runeOptions.Reaper, pressTheAttackRune, hailOfBladesRune); _beastCrest = new BeastCrest(runeOptions.Beast, darkHarvestRune, predatorRune); _witchCrest = new WitchCrest(runeOptions.Witch, _log, services.Events, (IClock)(object)services.Clock, _healClassification.Classify, delegate(int amount) { services.Game.AddSilk(amount, "witch.bind-refund"); }, graspOfTheUndyingRune, fleetFootworkRune); _architectCrest = new ArchitectCrest(runeOptions.Architect, firstStrikeRune, electrocuteRune); _shamanCrest = new ShamanCrest(runeOptions.Shaman, arcaneCometRune, phaseRushRune); _cursedCrest = new CursedCrest(runeOptions.Cursed, aftershock, guardian); _cloaklessCrest = new CloaklessCrest(runeOptions.Cloakless, glacialAugment, unsealedSpellbook, runeOptions.CloaklessRandomPassive, new IRuneEffect[12] { conquerorRune, pressTheAttackRune, hailOfBladesRune, lethalTempoRune, darkHarvestRune, predatorRune, graspOfTheUndyingRune, fleetFootworkRune, firstStrikeRune, electrocuteRune, arcaneCometRune, phaseRushRune }, null, string.IsNullOrWhiteSpace(runeOptions.CloaklessRandomPassiveKey) ? null : runeOptions.CloaklessRandomPassiveKey); _log.LogInfo((object)("[Cloakless] random passive: " + (_cloaklessCrest.RandomPassiveKey ?? ""))); _runeEffects = new RuneEffects(runeOptions.Enabled, _log, services.Events, isRuneToolEquipped, new ICrestRuneSet[9] { _hunterCrest, _wandererCrest, _reaperCrest, _beastCrest, _witchCrest, _architectCrest, _shamanCrest, _cursedCrest, _cloaklessCrest }); WandererRuneBridge.IsActive = () => _wandererCrest?.LethalTempo.Gate.IsActive ?? false; WandererRuneBridge.LayerCount = () => _wandererCrest?.LethalTempo.LayerCount ?? 0; WandererRuneBridge.IsFullLayers = () => _wandererCrest?.IsFullLayers ?? false; ReaperRuneBridge.IsDropRateOverrideActive = () => _reaperCrest?.IsDropRateOverrideActive ?? false; ReaperRuneBridge.IsAutoCollectEnabled = () => _reaperCrest?.IsAutoCollectEnabled ?? false; ReaperRuneBridge.SubBladeChargedAttacksRemaining = () => _reaperCrest?.HailOfBlades.ChargedAttacksRemaining ?? 0; ReaperRuneBridge.IsHailOfBladesFullLayers = () => _reaperCrest?.HailOfBlades.IsFullLayers ?? false; ReaperRuneBridge.IsChargeSlashEnhanced = () => _reaperCrest?.PressTheAttack.IsActive ?? false; BeastRuneBridge.IsEnhancementActive = () => _beastCrest?.DarkHarvest.IsActive ?? false; BeastRuneBridge.IsPredatorCharged = () => _beastCrest?.Predator.IsCharged ?? false; BeastRuneBridge.AcquireChargeSlashInvulnerability = delegate { services.Game.AcquireInvulnerability("beast.predator-charge-slash", 0.35); }; BeastRuneBridge.IsDamageProtectActive = () => _beastCrest?.IsRageProtectionActive ?? false; WitchRuneBridge.IsBindChainActive = () => _witchCrest?.BindChain.IsChainActive ?? false; WitchRuneBridge.IsGraspActive = () => _witchCrest?.IsGraspActive ?? false; ArchitectRuneBridge.IsToolRepairActive = () => _architectCrest?.IsToolRepairActive ?? false; ArchitectRuneBridge.IsToolTransformActive = () => _architectCrest?.IsToolTransformActive ?? false; CursedRuneBridge.IsBindEnabled = () => _cursedCrest?.IsBindEnabled ?? false; CursedRuneBridge.IsBindInvulnerabilityActive = () => _cursedCrest?.IsBindInvulnerabilityActive ?? false; CursedRuneBridge.BindInvulnerabilitySeconds = () => cursedBindInvulnSeconds; CursedRuneBridge.AcquireBindInvulnerability = () => services.Game.AcquireInvulnerability("cursed.bind", cursedBindInvulnSeconds); CloaklessRuneBridge.IsFreeBindCharged = () => _cloaklessCrest?.UnsealedSpellbook.IsCharged ?? false; CloaklessRuneBridge.TryConsumeFreeBind = () => _cloaklessCrest?.UnsealedSpellbook.TryConsumeFreeBind() ?? false; CloaklessRuneBridge.IsGlacialAugmentCharged = () => _cloaklessCrest?.GlacialAugment.IsCharged ?? false; CloaklessRuneBridge.TryConsumeGlacialAugment = () => _cloaklessCrest?.GlacialAugment.TryConsume() ?? false; services.Runes = (IRuneStateService)(object)new RuneStateService(_runeEffects); CrestRuneIconDriver crestRune = new CrestRuneIconDriver(services.Events, services.StatusLedger, _runeEffects.BuildCrestIconTargets(), () => services.Game.CurrentCrestId, isRuneToolEquipped); services.StatusIcons.RegisterDriver((IStatusIconDriver)(object)new RunesIconDriver(crestRune, _runeEffects)); services.StatusIcons.AddTick((Action)delegate(float delta) { _runeEffects.Tick(delta); }); InstallCrestFeaturePatches(services, pressTheAttackOptions); InstallImplementedRunePatches(services, lethalTempoOptions, pressTheAttackOptions, hailOfBladesOptions, predatorOptions, phaseRushOptions); InstallCursedCloaklessPatches(services, glacialAugmentOptions); services.StatusIcons.Start(services.Game.CurrentCrestId); InstallBlueHealthTint(services); InstallPlasmaFeature(services); _log.LogInfo((object)"[LegendsNeverDie] runes wired (12 implemented; cursed/cloakless pending)."); } private void InstallPlasmaFeature(FoundationServices services) { //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Expected O, but got Unknown //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Expected O, but got Unknown //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Expected O, but got Unknown //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Expected O, but got Unknown //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Expected O, but got Unknown //IL_032f: Unknown result type (might be due to invalid IL or missing references) //IL_033c: Expected O, but got Unknown PlasmaOptions plasmaOptions = PlasmaBinder.Bind(_config, _log); _plasmaRules = new PlasmaRules(_log, services.Events, services.Game, (_healClassification != null) ? new Func(_healClassification.Classify) : ((Func)((string _) => HealSourceKind.Unknown)), () => _beastCrest?.IsRedBloodTintActive ?? false, () => _witchCrest?.IsGreenBloodTintActive ?? false, plasmaOptions); _plasmaRules.Start(); PlasmaStateBridge.Configure(_log); PlasmaStateBridge.OnPlasmaEntered = delegate { _plasmaRules?.OnPlasmaEntered(); PlasmaTintBridge.RefreshAll(); }; PlasmaStateBridge.OnPlasmaExited = delegate { _plasmaRules?.OnPlasmaExited(); PlasmaTintBridge.RestoreAll(); }; PlasmaRegenSuppressor.Configure(_log); PlasmaRegenSuppressor.IsSuppressing = () => plasmaOptions.SuppressVanillaRegen && _plasmaRules != null && _plasmaRules.Kind != PlasmaKind.None; PlasmaBindGatePatch.Configure(_log); PlasmaBindGatePatch.IsGreenPlasmaActive = () => plasmaOptions.BindUnblockEnabled && (_plasmaRules?.IsGreenActive ?? false); PlasmaTintBridge.Configure(_log); PlasmaTintBridge.IsRedPlasmaActive = () => plasmaOptions.TintEnabled && (_plasmaRules?.IsRedActive ?? false); PlasmaTintBridge.IsGreenPlasmaActive = () => plasmaOptions.TintEnabled && (_plasmaRules?.IsGreenActive ?? false); MethodInfo methodInfo = AccessTools.Method(typeof(HeroController), "HitMaxBlueHealth", (Type[])null, (Type[])null); if (methodInfo != null) { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(PlasmaStateBridge), "HitMaxBlueHealthPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[Plasma] HeroController.HitMaxBlueHealth not found; plasma state tracking disabled."); } MethodInfo methodInfo2 = AccessTools.Method(typeof(HeroController), "HitMaxBlueHealthBurst", (Type[])null, (Type[])null); if (methodInfo2 != null) { _harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(PlasmaTintBridge), "HitMaxBlueHealthBurstPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[Plasma] HeroController.HitMaxBlueHealthBurst not found; Hive-mask re-tint disabled."); } MethodInfo methodInfo3 = AccessTools.Method(typeof(HeroController), "ResetLifebloodState", (Type[])null, (Type[])null); if (methodInfo3 != null) { _harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(PlasmaStateBridge), "ResetLifebloodStatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[Plasma] HeroController.ResetLifebloodState not found; plasma exit tracking disabled."); } MethodInfo methodInfo4 = AccessTools.Method(typeof(CallMethodProper), "OnEnter", (Type[])null, (Type[])null); if (methodInfo4 != null) { _harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(PlasmaRegenSuppressor), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[Plasma] CallMethodProper.OnEnter not found; vanilla regen suppression disabled."); } MethodInfo methodInfo5 = AccessTools.Method(typeof(BoolTest), "OnEnter", (Type[])null, (Type[])null); if (methodInfo5 != null) { _harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(typeof(PlasmaBindGatePatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[Plasma] BoolTest.OnEnter not found; green plasma bind unblock disabled."); } MethodInfo methodInfo6 = AccessTools.Method(typeof(SpawnObjectFromGlobalPool), "OnEnter", (Type[])null, (Type[])null); if (methodInfo6 != null) { _harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, new HarmonyMethod(typeof(PlasmaTintBridge), "SpawnObjectFromGlobalPoolPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[Plasma] SpawnObjectFromGlobalPool.OnEnter not found; heal particle tint disabled."); } services.Events.HealResolved += OnPlasmaHealResolved; services.Events.PlayerDamageAfter += OnPlasmaPlayerDamage; PlasmaStateBridge.SyncFromHero(); PlasmaTintBridge.RefreshAll(); _log.LogInfo((object)"[Plasma] red/green Overblue tint + heal rules wired."); } private void InstallBlueHealthTint(FoundationServices services) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Expected O, but got Unknown //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Expected O, but got Unknown BlueHealthTintBridge.Configure(_log); BlueHealthTintBridge.IsBeastTintActive = () => _beastCrest?.IsRedBloodTintActive ?? false; BlueHealthTintBridge.IsWitchTintActive = () => _witchCrest?.IsGreenBloodTintActive ?? false; BlueHealthTintBridge.IsCursedTintActive = () => _cursedCrest?.IsGreenBloodTintActive ?? false; MethodInfo methodInfo = AccessTools.Method(typeof(BlueHealth), "MarkActive", (Type[])null, (Type[])null); if (methodInfo != null) { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(BlueHealthTintBridge), "MarkActivePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[BlueHealthTint] BlueHealth.MarkActive not found; new-mask tinting disabled."); } MethodInfo methodInfo2 = AccessTools.Method(typeof(BlueHealth), "CheckPoison", (Type[])null, (Type[])null); if (methodInfo2 != null) { _harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(BlueHealthTintBridge), "CheckPoisonPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[BlueHealthTint] BlueHealth.CheckPoison not found; poison recoloring disabled."); } MethodInfo methodInfo3 = AccessTools.Method(typeof(SetPoisonTintOverride), "OnEnter", (Type[])null, (Type[])null); if (methodInfo3 != null) { _harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(BlueHealthTintBridge), "SetPoisonTintOverridePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[BlueHealthTint] SetPoisonTintOverride.OnEnter not found; FSM re-tint disabled."); } services.Events.CrestChanged += OnTintCrestChanged; services.Events.SceneChanged += OnTintSceneChanged; } private void RegisterIcons(FoundationServices services) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Expected O, but got Unknown Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (IconDefault item in StatusIconDefaults.All) { if (!(item.Owner == "rage") && !(item.Owner == "rage-plus")) { StatusIconEntryOptions val = StatusIconBinder.BindIcon(_config, "UI.StatusIcons." + item.Key, item.Key, new StatusIconEntryOptions(true, item.Max, item.ColorHex, item.Texture ?? "placeholder", item.IconColorHex)); dictionary[item.Key] = val; services.StatusIcons.RegisterIcon(new StatusIconDefinition(item.Key, item.Owner, val.Max)); } } services.StatusOptions.RegisterIconOptions((IEnumerable>)dictionary); } private void InstallCrestFeaturePatches(FoundationServices services, PressTheAttackOptions pressTheAttackOptions) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Expected O, but got Unknown //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Expected O, but got Unknown //IL_01f0: Expected O, but got Unknown //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Expected O, but got Unknown //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02f6: Expected O, but got Unknown //IL_02f6: Expected O, but got Unknown //IL_0354: Unknown result type (might be due to invalid IL or missing references) //IL_0361: Expected O, but got Unknown //IL_03bb: Unknown result type (might be due to invalid IL or missing references) //IL_03c9: Expected O, but got Unknown //IL_0410: Unknown result type (might be due to invalid IL or missing references) //IL_041d: Expected O, but got Unknown //IL_0477: Unknown result type (might be due to invalid IL or missing references) //IL_0485: Expected O, but got Unknown HunterVanillaComboPreserver.LayerCount = () => _hunterCrest?.Conqueror.LayerCount ?? 0; _harmony.Patch((MethodBase)AccessTools.Method(typeof(HeroController), "ResetHunterUpgCrestState", (Type[])null, (Type[])null), new HarmonyMethod(typeof(HunterVanillaComboPreserver), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo = AccessTools.Method(typeof(HeroController), "NailParry", (Type[])null, (Type[])null); if (methodInfo != null) { ParrySignalPatch.Parried = services.Events.PublishParried; _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(ParrySignalPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.Method(typeof(HeroController), "HeroDashPressed", (Type[])null, (Type[])null); if (methodInfo2 != null) { DashSignalPatch.DashStarted = services.Events.PublishDashStarted; _harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(DashSignalPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[FleetFootwork] HeroController.HeroDashPressed not found; dash stacking disabled."); } MethodInfo methodInfo3 = AccessTools.Method(typeof(NailAttackBase), "OnSlashStarting", (Type[])null, (Type[])null); if (methodInfo3 != null) { AttackStartedSignalPatch.AttackStarted = services.Events.PublishAttackStarted; _harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(AttackStartedSignalPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } FieldInfo fieldInfo = services.Resolver.ResolveField("player.reaper.bundle.probabilities"); ReaperSilkDropProbabilityPatch.Enabled = pressTheAttackOptions.Enabled && fieldInfo != null; MethodBase methodBase = services.Resolver.Resolve("player.reaper.payout"); if (methodBase != null) { _harmony.Patch(methodBase, new HarmonyMethod(typeof(ReaperSilkDropProbabilityPatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(ReaperSilkDropProbabilityPatch), "Restore", (Type[])null), (HarmonyMethod)null); } ReaperSilkBundleAutoCollect.Enabled = pressTheAttackOptions.Enabled; MethodInfo methodInfo4 = AccessTools.Method(typeof(ObjectPool), "AddSpawnedObject", (Type[])null, (Type[])null); if (methodInfo4 != null) { _harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(ReaperSilkBundleAutoCollect), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } services.Events.BindCompleted += ReaperSilkBundleAutoCollect.NotifyBindCompleted; BeastRageDamageProtectPatch.Configure(_log); BeastRageDamageProtectPatch.IsActive = () => BeastRuneBridge.IsDamageProtectActive(); MethodInfo methodInfo5 = AccessTools.Method(typeof(HeroController), "TakeDamage", (Type[])null, (Type[])null); if (methodInfo5 != null) { _harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(typeof(BeastRageDamageProtectPatch), "Prefix", (Type[])null), new HarmonyMethod(typeof(BeastRageDamageProtectPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } BeastRageBindFsmPatch.Configure(_log); BeastRageBindFsmPatch.Enabled = true; BeastRageBindFsmPatch.IsActive = () => _beastCrest?.IsBindNoPrepActive ?? false; MethodInfo methodInfo6 = AccessTools.Method(typeof(PlayMakerFSM), "Start", (Type[])null, (Type[])null); if (methodInfo6 != null) { _harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, new HarmonyMethod(typeof(BeastRageBindFsmPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo7 = AccessTools.Method(typeof(HeroController), "TakeSilk", new Type[2] { typeof(int), typeof(SilkTakeSource) }, (Type[])null); if (methodInfo7 != null) { _harmony.Patch((MethodBase)methodInfo7, new HarmonyMethod(typeof(WitchTakeSilkCostPatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } ArchitectToolRepairPatch.Configure(_log); MethodInfo methodInfo8 = AccessTools.Method(typeof(ToolItemManager), "TryReplenishTools", (Type[])null, (Type[])null); if (methodInfo8 != null) { _harmony.Patch((MethodBase)methodInfo8, (HarmonyMethod)null, new HarmonyMethod(typeof(ArchitectToolRepairPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo9 = AccessTools.Method(typeof(InventoryItemToolManager), "CanChangeEquips", new Type[2] { typeof(ToolItemType), typeof(CanChangeEquipsTypes) }, (Type[])null); if (methodInfo9 != null) { _harmony.Patch((MethodBase)methodInfo9, new HarmonyMethod(typeof(ArchitectToolTransformPatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[ArchitectToolTransform] InventoryItemToolManager.CanChangeEquips not found; backpack transform disabled."); } } private void InstallImplementedRunePatches(FoundationServices services, LethalTempoOptions lethalTempoOptions, PressTheAttackOptions pressTheAttackOptions, HailOfBladesOptions hailOfBladesOptions, PredatorOptions predatorOptions, PhaseRushOptions phaseRushOptions) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Expected O, but got Unknown //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Expected O, but got Unknown //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Expected O, but got Unknown //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Expected O, but got Unknown //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Expected O, but got Unknown //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Expected O, but got Unknown //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02de: Expected O, but got Unknown //IL_0329: Unknown result type (might be due to invalid IL or missing references) //IL_0336: Expected O, but got Unknown //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_0383: Expected O, but got Unknown NailAttackRangePatch.Enabled = true; NailAttackRangePatch.RangeMultiplier = (float)lethalTempoOptions.RangeMultiplierAtMax; _harmony.Patch((MethodBase)AccessTools.Method(typeof(NailAttackBase), "OnSlashStarting", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(NailAttackRangePatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); WandererAttackSpeedPatch.PerLayer = lethalTempoOptions.AttackSpeedPerLayer; ReaperSubBladeAttackSpeedPatch.Bonus = hailOfBladesOptions.AttackSpeedBonus; MethodInfo[] array = new MethodInfo[6] { AccessTools.PropertyGetter(typeof(HeroControllerConfig), "AttackDuration"), AccessTools.PropertyGetter(typeof(HeroControllerConfig), "AttackRecoveryTime"), AccessTools.PropertyGetter(typeof(HeroControllerConfig), "AttackCooldownTime"), AccessTools.PropertyGetter(typeof(HeroControllerConfig), "QuickAttackCooldownTime"), AccessTools.PropertyGetter(typeof(HeroController), "CurrentNailChargeTime"), AccessTools.PropertyGetter(typeof(HeroController), "CurrentNailChargeBeginTime") }; foreach (MethodInfo methodInfo in array) { if (!(methodInfo == null)) { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(WandererAttackSpeedPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(ReaperSubBladeAttackSpeedPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } WandererCritChancePatch.CritBonus = _wandererCrest?.FullLayerCritBonus ?? 0.0; MethodInfo methodInfo2 = AccessTools.PropertyGetter(typeof(Gameplay), "WandererCritChance"); if (methodInfo2 != null) { _harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(WandererCritChancePatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[WandererCritChance] Gameplay.WandererCritChance getter not found; full-layer crit bonus disabled."); } ReaperChargeSlashScalePatch.ScaleMultiplier = (float)pressTheAttackOptions.ChargeSlashScaleMultiplier; _harmony.Patch((MethodBase)AccessTools.Method(typeof(NailAttackBase), "OnSlashStarting", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(ReaperChargeSlashScalePatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ReaperChargeSlashDamagePatch.DamageMultiplier = pressTheAttackOptions.ChargeSlashDamageMultiplier; MethodInfo methodInfo3 = AccessTools.Method(typeof(HealthManager), "Hit", new Type[1] { typeof(HitInstance) }, (Type[])null); if (methodInfo3 != null) { _harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(ReaperChargeSlashDamagePatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(HailOfBladesCritPatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); BeastPredatorDamagePatch.DamageMultiplier = predatorOptions.DamageMultiplier; _harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(BeastPredatorDamagePatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[Runes] HealthManager.Hit not found; charge-slash damage / hail crit / predator damage disabled."); } MethodInfo methodInfo4 = AccessTools.Method(typeof(Downspike), "StartSlash", (Type[])null, (Type[])null); if (methodInfo4 != null) { _harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(BeastDownspikeInvulnPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[DarkHarvest] Downspike.StartSlash not found; down-slash invuln disabled."); } _harmony.Patch((MethodBase)AccessTools.Method(typeof(NailAttackBase), "OnSlashStarting", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(BeastPredatorChargeSlashInvulnPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private void InstallCursedCloaklessPatches(FoundationServices services, GlacialAugmentOptions glacialAugmentOptions) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Expected O, but got Unknown //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Expected O, but got Unknown //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.PropertyGetter(typeof(SilkSpool), "BindCost"); if (methodInfo != null) { _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(BindCostPatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[Cursed/Cloakless] SilkSpool.BindCost getter not found; bind cost overrides disabled."); } MethodInfo methodInfo2 = AccessTools.Method(typeof(HeroController), "TakeSilk", new Type[2] { typeof(int), typeof(SilkTakeSource) }, (Type[])null); if (methodInfo2 != null) { _harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(UnsealedSpellbookFreeBindPatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[UnsealedSpellbook] HeroController.TakeSilk(int, SilkTakeSource) not found; free bind disabled."); } CursedBindInvulnerabilityPatch.Configure(_log); MethodInfo methodInfo3 = AccessTools.Method(typeof(PlayMakerFSM), "Start", (Type[])null, (Type[])null); if (methodInfo3 != null) { _harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(CursedBindInvulnerabilityPatch), "OnFsmStart", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[CursedBind] PlayMakerFSM.Start not found; bind invulnerability disabled."); } GlacialSlowPatch.SlowDurationSeconds = glacialAugmentOptions.SlowDurationSeconds; GlacialSlowPatch.SlowFactor = glacialAugmentOptions.SlowFactor; MethodInfo methodInfo4 = AccessTools.Method(typeof(HealthManager), "Hit", new Type[1] { typeof(HitInstance) }, (Type[])null); if (methodInfo4 != null) { _harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(GlacialSlowPatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[GlacialAugment] HealthManager.Hit not found; enemy slow disabled."); } } public void Dispose() { if (_disposed) { return; } _disposed = true; FoundationServices current = FoundationServices.Current; if (_runeEffects != null) { if (current != null) { current.Events.BindCompleted -= ReaperSilkBundleAutoCollect.NotifyBindCompleted; } _runeEffects.Dispose(); } if (current != null) { if (_healClassification != null) { current.Events.BindCompleted -= _healClassification.NotifyBindCompleted; } current.Events.CrestChanged -= OnTintCrestChanged; current.Events.SceneChanged -= OnTintSceneChanged; current.Events.HealResolved -= OnPlasmaHealResolved; current.Events.PlayerDamageAfter -= OnPlasmaPlayerDamage; } _plasmaRules?.Dispose(); _plasmaRules = null; BlueHealthTintBridge.IsBeastTintActive = null; BlueHealthTintBridge.IsWitchTintActive = null; BlueHealthTintBridge.IsCursedTintActive = null; PlasmaStateBridge.Configure(null); PlasmaStateBridge.OnPlasmaEntered = null; PlasmaStateBridge.OnPlasmaExited = null; PlasmaStateBridge.ResetTracking(); PlasmaRegenSuppressor.Configure(null); PlasmaRegenSuppressor.IsSuppressing = null; PlasmaBindGatePatch.Configure(null); PlasmaBindGatePatch.IsGreenPlasmaActive = null; PlasmaTintBridge.Configure(null); PlasmaTintBridge.IsRedPlasmaActive = null; PlasmaTintBridge.IsGreenPlasmaActive = null; WandererRuneBridge.IsActive = () => false; WandererRuneBridge.LayerCount = () => 0; WandererRuneBridge.IsFullLayers = () => false; ReaperRuneBridge.IsDropRateOverrideActive = () => false; ReaperRuneBridge.IsAutoCollectEnabled = () => false; ReaperRuneBridge.SubBladeChargedAttacksRemaining = () => 0; ReaperRuneBridge.IsHailOfBladesFullLayers = () => false; ReaperRuneBridge.IsChargeSlashEnhanced = () => false; BeastRuneBridge.IsEnhancementActive = () => false; BeastRuneBridge.IsPredatorCharged = () => false; BeastRuneBridge.AcquireChargeSlashInvulnerability = null; WitchRuneBridge.IsGraspActive = () => false; ArchitectRuneBridge.IsToolTransformActive = () => false; CursedRuneBridge.IsBindEnabled = () => false; CursedRuneBridge.IsBindInvulnerabilityActive = () => false; CursedRuneBridge.BindInvulnerabilitySeconds = () => 2.0; CloaklessRuneBridge.IsFreeBindCharged = () => false; CloaklessRuneBridge.TryConsumeFreeBind = () => false; CloaklessRuneBridge.IsGlacialAugmentCharged = () => false; CloaklessRuneBridge.TryConsumeGlacialAugment = () => false; GlacialSlowPatch.SlowDurationSeconds = 10.0; GlacialSlowPatch.SlowFactor = 0.5; CursedBindInvulnerabilityPatch.Reset(); HunterVanillaComboPreserver.LayerCount = null; NailAttackRangePatch.Enabled = false; WandererCritChancePatch.CritBonus = 0.0; BeastRageBindFsmPatch.IsActive = () => true; ReaperChargeSlashScalePatch.IsChargeSlashEnhanced = () => false; ReaperChargeSlashScalePatch.ScaleMultiplier = 1.5f; ReaperChargeSlashDamagePatch.DamageMultiplier = 1.0; BeastPredatorDamagePatch.DamageMultiplier = 1.2; ReaperSilkDropProbabilityPatch.Enabled = false; ReaperSilkBundleAutoCollect.Enabled = false; WandererEnhancementState? wandererEnhancement = _wandererEnhancement; if (wandererEnhancement != null) { wandererEnhancement.Dispose(); } _wandererEnhancement = null; BeastRuneBridge.IsDamageProtectActive = () => false; WitchRuneBridge.IsBindChainActive = () => false; ArchitectRuneBridge.IsToolRepairActive = () => false; _harmony.UnpatchSelf(); } private void OnTintCrestChanged(CrestChangedEvent e) { BlueHealthTintBridge.RefreshAll(); PlasmaStateBridge.SyncFromHero(); PlasmaTintBridge.RefreshAll(); } private void OnTintSceneChanged(string scene) { BlueHealthTintBridge.RefreshAll(); PlasmaStateBridge.SyncFromHero(); PlasmaTintBridge.RefreshAll(); } private void OnPlasmaHealResolved(HealEvent e) { PlasmaTintBridge.RefreshAll(); } private void OnPlasmaPlayerDamage(PlayerDamageEvent e) { PlasmaTintBridge.RefreshAll(); } } }