using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; 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.CrestSwitching; using CrestsEvolve.Core.Events; using CrestsEvolve.Core.State; using CrestsEvolve.Core.UI; using CrestsEvolve.Features; using CrestsEvolve.Features.CrestSwitching; using CrestsEvolve.Features.StatusIcons; using CrestsEvolve.GameInterop; using CrestsEvolve.GameInterop.CrestSwitching; using CrestsEvolve.GameInterop.Input; using CrestsEvolve.InsectMayCry.Features.AttackModules; using CrestsEvolve.InsectMayCry.Features.CrossStitch; using CrestsEvolve.InsectMayCry.Features.SecondToolSet; using CrestsEvolve.InsectMayCry.Features.Vergil; using CrestsEvolve.InsectMayCry.Features.WindBlade; using CrestsEvolve.InsectMayCry.GameInterop; using CrestsEvolve.Patches; using GlobalSettings; using HarmonyLib; using HutongGames.PlayMaker; using HutongGames.PlayMaker.Actions; using InControl; using Microsoft.CodeAnalysis; using Needleforge; using Needleforge.Data; using Silksong.FsmUtil; using TeamCherry.Localization; using TeamCherry.SharedUtils; using UnityEngine; using UnityEngine.Events; [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.InsectMayCry")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+757e75a2d4534028308600b7117b47c56eb48037")] [assembly: AssemblyProduct("CrestsEvolve.InsectMayCry")] [assembly: AssemblyTitle("CrestsEvolve.InsectMayCry")] [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.Patches { [HarmonyPatch(typeof(HeroController), "Awake")] internal static class CrestQuickSwitchHeroAwakePatch { [HarmonyPostfix] private static void Postfix(HeroController __instance) { CrestQuickSwitchIntegration.Instance?.AttachHero(__instance); } } [HarmonyPatch(typeof(HeroController), "Update")] internal static class CrestQuickSwitchHeroUpdatePatch { [HarmonyPostfix] private static void Postfix(HeroController __instance) { CrestQuickSwitchIntegration.Instance?.OnHeroUpdate(__instance); } } [HarmonyPatch(typeof(ToolItemManager), "TryReplenishTools")] internal static class CrestQuickSwitchBenchReplenishPatch { [HarmonyPrefix] private static bool Prefix(ref bool doReplenish, ReplenishMethod method) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) return CrestQuickSwitchIntegration.Instance?.TryReplenishAllCrestsAtBench(ref doReplenish, method) ?? true; } } } namespace CrestsEvolve.GameInterop.CrestSwitching { public sealed class CrestNameProvider { private readonly ConfigEntry _queenCrestId; public string? Toolmaster => SafeCrestName(() => Gameplay.ToolmasterCrest); public string Queen => _queenCrestId.Value; public string? Spell => SafeCrestName(() => Gameplay.SpellCrest); public string? Hunter => GetBestHunterName(); public string? Reaper => SafeCrestName(() => Gameplay.ReaperCrest); public string? Warrior => SafeCrestName(() => Gameplay.WarriorCrest); public string? Wanderer => SafeCrestName(() => Gameplay.WandererCrest); public string? Witch => SafeCrestName(() => Gameplay.WitchCrest); public CrestNameProvider(ConfigEntry queenCrestId) { _queenCrestId = queenCrestId; } public string[] AllCrestNames() { return new string[8] { Toolmaster, Queen, Spell, Hunter, Reaper, Warrior, Wanderer, Witch }.Where((string x) => !string.IsNullOrWhiteSpace(x)).Distinct(StringComparer.Ordinal).ToArray(); } public string? ResolveDirectionTarget(string current, int directionMask, bool isLong) { return directionMask switch { 1 => ToggleLogic(current, isLong, Toolmaster, Queen), 2 => ToggleLogic(current, isLong, Spell, Hunter), 4 => ToggleLogic(current, isLong, Reaper, Warrior), 8 => ToggleLogic(current, isLong, Wanderer, Witch), _ => null, }; } private static string? ToggleLogic(string current, bool isLong, string? primary, string? secondary) { if (string.IsNullOrWhiteSpace(primary) || string.IsNullOrWhiteSpace(secondary)) { return null; } if (!isLong) { if (!(current == primary)) { return primary; } return secondary; } if (!(current == secondary)) { return secondary; } return primary; } private static string? SafeCrestName(Func getter) { try { ToolCrest obj = getter(); return (obj != null) ? obj.name : null; } catch { return null; } } private string? GetBestHunterName() { try { if ((Object)(object)Gameplay.HunterCrest3 != (Object)null && Gameplay.HunterCrest3.IsUnlocked) { return Gameplay.HunterCrest3.name; } } catch { } try { if ((Object)(object)Gameplay.HunterCrest2 != (Object)null && Gameplay.HunterCrest2.IsUnlocked) { return Gameplay.HunterCrest2.name; } } catch { } return SafeCrestName(() => Gameplay.HunterCrest); } } public sealed class CrestSwitchService { private readonly ManualLogSource _log; private readonly HeroReflectionCache _heroReflection; private readonly ConfigEntry _invulnerabilitySeconds; private readonly ConfigEntry _replenishAllCrestsAtBench; private readonly ConfigEntry _queenCrestId; private readonly ConfigEntry _postSwapRecoveryFrames; public bool IsApplyingSwap { get; private set; } public bool IsReplenishLooping { get; private set; } public bool PostSwapWasFromDash { get; private set; } public int PostSwapRecoveryUntilFrame { get; private set; } = -1; public CrestSwitchService(ManualLogSource log, HeroReflectionCache heroReflection, ConfigEntry invulnerabilitySeconds, ConfigEntry replenishAllCrestsAtBench, ConfigEntry queenCrestId, ConfigEntry postSwapRecoveryFrames) { _log = log; _heroReflection = heroReflection; _invulnerabilitySeconds = invulnerabilitySeconds; _replenishAllCrestsAtBench = replenishAllCrestsAtBench; _queenCrestId = queenCrestId; _postSwapRecoveryFrames = postSwapRecoveryFrames; } public bool IsSafeToSwapNow(HeroController hero) { if ((Object)(object)hero == (Object)null || hero.playerData == null) { return false; } if (_heroReflection.IsDashLikeActive(hero)) { return false; } if (_heroReflection.IsAttackOrChargeActive(hero)) { return false; } if (!_heroReflection.HasControl(hero)) { return false; } return true; } public bool TryApply(string targetName, string reason, bool fromDash, HeroController hero) { if (IsApplyingSwap || hero?.playerData == null) { return false; } IsApplyingSwap = true; try { ToolCrest crestByName = ToolItemManager.GetCrestByName(targetName); if ((Object)(object)crestByName == (Object)null) { _log.LogWarning((object)("Crest quick switch target not found: " + targetName)); return false; } int silk = hero.playerData.silk; if (fromDash) { _heroReflection.NormalizeDashExit(hero, true); } ToolEquipsSnapshot.Log(_log, "switch-before:" + reason + ":" + crestByName.name); _heroReflection.ApplyInvincibility(hero, Math.Max(0f, _invulnerabilitySeconds.Value)); hero.ResetAllCrestState(); ToolItemManager.SetEquippedCrest(crestByName.name); ToolItemManager.SendEquippedChangedEvent(true); ToolEquipsSnapshot.Log(_log, "switch-after:" + reason + ":" + crestByName.name); hero.playerData.silk = silk; if (fromDash) { PostSwapWasFromDash = true; PostSwapRecoveryUntilFrame = Time.frameCount + Math.Max(1, _postSwapRecoveryFrames.Value); _heroReflection.NormalizeDashExit(hero, true); } _log.LogInfo((object)("Crest switched -> " + crestByName.name + " [" + reason + "]")); return true; } catch (Exception arg) { _log.LogError((object)$"Crest switch failed: target={targetName}, reason={reason}, error={arg}"); return false; } finally { IsApplyingSwap = false; } } public void EndPostSwapRecovery() { PostSwapWasFromDash = false; PostSwapRecoveryUntilFrame = -1; } public unsafe bool TryReplenishAllCrestsAtBench(ref bool doReplenish, ReplenishMethod method, HeroController hero, CrestNameProvider names) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) if (!_replenishAllCrestsAtBench.Value) { return true; } if (hero?.playerData == null) { return true; } if (((object)(*(ReplenishMethod*)(&method))/*cast due to .constrained prefix*/).ToString().IndexOf("Bench", StringComparison.OrdinalIgnoreCase) < 0) { return true; } if (IsReplenishLooping) { return true; } string currentCrestID = hero.playerData.CurrentCrestID; string[] array = names.AllCrestNames(); try { IsReplenishLooping = true; string[] array2 = array; for (int i = 0; i < array2.Length; i++) { ToolCrest crestByName = ToolItemManager.GetCrestByName(array2[i]); if (!((Object)(object)crestByName == (Object)null) && crestByName.IsUnlocked && !crestByName.IsHidden) { hero.ResetAllCrestState(); ToolItemManager.SetEquippedCrest(crestByName.name); ToolItemManager.TryReplenishTools(true, method); } } } catch (Exception arg) { _log.LogError((object)$"Replenish-all-crests-at-bench failed: {arg}"); } finally { try { hero.ResetAllCrestState(); ToolItemManager.SetEquippedCrest(currentCrestID); ToolItemManager.SendEquippedChangedEvent(true); } catch (Exception arg2) { _log.LogError((object)$"Failed to restore crest after bench replenishment: {arg2}"); } IsReplenishLooping = false; } return true; } } } namespace CrestsEvolve.Features.CrestSwitching { public sealed class CrestQuickSwitchIntegration : IDisposable { private const int UpMask = 1; private const int DownMask = 2; private const int LeftMask = 4; private const int RightMask = 8; private static CrestQuickSwitchIntegration? _instance; private readonly ManualLogSource _log; private readonly Harmony _harmony; private readonly NativeInputAdapter _input; private readonly HeroReflectionCache _heroReflection; private readonly DashSafetyPolicy _safety; private readonly CrestSwapQueue _queue = new CrestSwapQueue(); private readonly CrestNameProvider _names; private readonly CrestSwitchService _switchService; private readonly ConfigEntry _enabled; private readonly ConfigEntry _longPressThreshold; private readonly ConfigEntry _specialPressThreshold; private readonly ConfigEntry _cooldownSeconds; private readonly ConfigEntry _dashSettleFrames; private readonly ConfigEntry _mainActionCandidates; private readonly ConfigEntry _verboseLogging; private HeroController? _hero; private bool _disposed; private float _nextSwapTime; private float _mainKeyDownTime = -1f; private int _lastChosenDirMask; private bool _swappedThisPress; private bool _specialTriggeredThisPress; private int _lastTriggeredDirMask; private bool _lastTriggeredLong; private bool _previousMainHeld; private int _previousDirectionMask; private long _nextRequestId = 1L; internal static CrestQuickSwitchIntegration? Instance => _instance; private CrestQuickSwitchIntegration(ManualLogSource log, ConfigFile config, Harmony harmony) { //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Expected O, but got Unknown //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Expected O, but got Unknown _log = log; _harmony = harmony; _enabled = config.Bind("CrestQuickSwitch", "Enabled", true, "Enable directional crest quick switching."); _longPressThreshold = config.Bind("CrestQuickSwitch", "LongPressThresholdSeconds", 0.2f, "Holding the main action for at least this duration selects the secondary crest."); _specialPressThreshold = config.Bind("CrestQuickSwitch", "SpecialPressThresholdSeconds", 3f, "Holding the main action without a direction toggles Cursed/Cloakless crest."); _cooldownSeconds = config.Bind("CrestQuickSwitch", "CooldownSeconds", 0.3f, "Minimum time between two crest switches."); ConfigEntry invulnerabilitySeconds = config.Bind("CrestQuickSwitch", "SwapInvulnerabilitySeconds", 0.1f, "Short protection applied when a queued switch is committed."); _dashSettleFrames = config.Bind("CrestQuickSwitch", "DashSettleFrames", 3, "Frames to wait after the final dash frame before changing crest. Increase to 4-5 if another movement mod still leaves dash velocity behind."); ConfigEntry postSwapRecoveryFrames = config.Bind("CrestQuickSwitch", "PostSwapRecoveryFrames", 2, "Frames after a dash-origin switch during which stale dash flags and horizontal velocity are cleared."); ConfigEntry queenCrestId = config.Bind("CrestQuickSwitch", "QueenCrestId", "Vergil", "Internal ID/name used by the custom Silk Mother/Queen crest."); _mainActionCandidates = config.Bind("CrestQuickSwitch", "MainActionCandidates", "taunt,ringTaunt,quickMap,dreamNail,inventory,cast", "Comma-separated game inputActions member names. The first existing action is used as the quick-switch modifier."); ConfigEntry replenishAllCrestsAtBench = config.Bind("CrestQuickSwitch", "ReplenishAllCrestsAtBench", false, "Legacy behavior from the standalone mod. Disabled by default because the overhaul should eventually replenish virtual tool inventories without temporarily changing crests."); _verboseLogging = config.Bind("CrestQuickSwitch", "VerboseLogging", false, "Write input binding, queue, dash recovery, and switch diagnostics."); _heroReflection = new HeroReflectionCache(log); _input = new NativeInputAdapter(log); _safety = new DashSafetyPolicy(() => Math.Max(1, _dashSettleFrames.Value)); _names = new CrestNameProvider(queenCrestId); _switchService = new CrestSwitchService(log, _heroReflection, invulnerabilitySeconds, replenishAllCrestsAtBench, queenCrestId, postSwapRecoveryFrames); } public static CrestQuickSwitchIntegration Install(ManualLogSource log, ConfigFile config, Harmony harmony) { if (_instance != null) { return _instance; } CrestQuickSwitchIntegration? crestQuickSwitchIntegration = (_instance = new CrestQuickSwitchIntegration(log, config, harmony)); harmony.CreateClassProcessor(typeof(CrestQuickSwitchHeroAwakePatch)).Patch(); harmony.CreateClassProcessor(typeof(CrestQuickSwitchHeroUpdatePatch)).Patch(); harmony.CreateClassProcessor(typeof(CrestQuickSwitchBenchReplenishPatch)).Patch(); crestQuickSwitchIntegration.AttachHero(HeroController.instance); log.LogInfo((object)"Crest quick switch integrated into CrestsEvolve."); return crestQuickSwitchIntegration; } public void Dispose() { if (!_disposed) { _disposed = true; _input.Reset(); _queue.Cancel(); _safety.Reset(); _hero = null; _instance = null; } } internal void AttachHero(HeroController? hero) { if (!_disposed && !((Object)(object)hero == (Object)null) && _hero != hero) { _hero = hero; _heroReflection.Initialize(hero); _input.Bind(hero, (IReadOnlyList)ParseCandidates(_mainActionCandidates.Value)); ResetPressState(); _previousMainHeld = false; _previousDirectionMask = 0; Trace("Attached HeroController and refreshed native input bindings."); } } internal void OnHeroUpdate(HeroController hero) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) if (_disposed || !_enabled.Value) { return; } AttachHero(hero); if (!((Object)(object)_hero == (Object)null) && _hero.playerData != null) { _input.RefreshIfActionSetChanged(_hero, (IReadOnlyList)ParseCandidates(_mainActionCandidates.Value)); TrackUnsafeFrames(); RunPostSwapRecovery(); TryConsumeQueuedSwap(); if (!_switchService.IsReplenishLooping && !_switchService.IsApplyingSwap && _input.IsReady) { InputFrame frame = _input.ReadFrame(); ProcessInputFrame(frame); } } } internal bool TryReplenishAllCrestsAtBench(ref bool doReplenish, ReplenishMethod method) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_hero == (Object)null)) { return _switchService.TryReplenishAllCrestsAtBench(ref doReplenish, method, _hero, _names); } return true; } private void ProcessInputFrame(InputFrame frame) { bool num = ((InputFrame)(ref frame)).MainPressed || (((InputFrame)(ref frame)).MainHeld && !_previousMainHeld); bool flag = ((InputFrame)(ref frame)).MainReleased || (!((InputFrame)(ref frame)).MainHeld && _previousMainHeld); int downMask = ((InputFrame)(ref frame)).DirectionMask & ~_previousDirectionMask; _previousMainHeld = ((InputFrame)(ref frame)).MainHeld; _previousDirectionMask = ((InputFrame)(ref frame)).DirectionMask; if (num) { _mainKeyDownTime = Time.unscaledTime; ResetPressState(); } if (flag || (_mainKeyDownTime >= 0f && !((InputFrame)(ref frame)).MainHeld)) { _mainKeyDownTime = -1f; ResetPressState(); } else { if (!((InputFrame)(ref frame)).MainHeld) { return; } if (_mainKeyDownTime < 0f) { _mainKeyDownTime = Time.unscaledTime; } int num2 = ChooseDirection(((InputFrame)(ref frame)).DirectionMask, downMask); float num3 = Time.unscaledTime - _mainKeyDownTime; if (num2 == 0 && !_specialTriggeredThisPress && num3 >= Math.Max(0.1f, _specialPressThreshold.Value)) { HandleSpecialSwap(); _specialTriggeredThisPress = true; _nextSwapTime = Time.unscaledTime + Math.Max(0f, _cooldownSeconds.Value); } else if (num2 != 0 && !(Time.unscaledTime < _nextSwapTime)) { bool flag2 = num3 >= Math.Max(0.01f, _longPressThreshold.Value); bool num4 = num2 != _lastTriggeredDirMask; bool flag3 = !num4 && flag2 != _lastTriggeredLong; if (num4 || (!_swappedThisPress && flag3)) { PerformDirectionalSwap(num2, flag2); } } } } private void ResetPressState() { _lastChosenDirMask = 0; _lastTriggeredDirMask = 0; _lastTriggeredLong = false; _swappedThisPress = false; _specialTriggeredThisPress = false; } private void HandleSpecialSwap() { if (_hero?.playerData != null) { string currentCrestID = _hero.playerData.CurrentCrestID; string text = SafeCrestName(() => Gameplay.CursedCrest); string text2 = SafeCrestName(() => Gameplay.CloaklessCrest) ?? "Cloakless Crest"; if (!string.IsNullOrWhiteSpace(text)) { RequestSwap((currentCrestID == text) ? text2 : text, "SPECIAL_3S"); } } } private void PerformDirectionalSwap(int directionMask, bool isLong) { if (_hero?.playerData != null) { string currentCrestID = _hero.playerData.CurrentCrestID; string text = _names.ResolveDirectionTarget(currentCrestID, directionMask, isLong); if (!string.IsNullOrWhiteSpace(text) && !(text == currentCrestID)) { RequestSwap(text, isLong ? "LONG" : "SHORT"); _lastTriggeredDirMask = directionMask; _lastTriggeredLong = isLong; _swappedThisPress = true; _nextSwapTime = Time.unscaledTime + Math.Max(0f, _cooldownSeconds.Value); } } } private void RequestSwap(string targetName, string reason) { if (_hero?.playerData != null && !string.IsNullOrWhiteSpace(targetName)) { bool flag = _heroReflection.IsDashLikeActive(_hero); bool num = _switchService.IsSafeToSwapNow(_hero); bool flag2 = _safety.HasDashSettled(Time.frameCount); if (num && flag2) { _switchService.TryApply(targetName, reason, fromDash: false, _hero); return; } SwapOrigin origin = ((flag || Time.frameCount - _safety.LastDashFrame <= Math.Max(1, _dashSettleFrames.Value)) ? SwapOrigin.Dash : (_heroReflection.IsAttackOrChargeActive(_hero) ? SwapOrigin.Attack : SwapOrigin.Unknown)); _queue.Enqueue(new SwapRequest(_nextRequestId++, _hero.playerData.CurrentCrestID, targetName, origin, Time.frameCount, Time.unscaledTime, _safety.EarliestFrame(Time.frameCount), 3600.0, PreserveSilk: true, PreserveMarkedHealth: false, reason)); Trace("Queued crest switch -> " + targetName + "; reason=" + reason + "; " + $"dash={_queue.IsDashOrigin}; earliestFrame={_safety.EarliestFrame(Time.frameCount)}"); } } private void TryConsumeQueuedSwap() { if (_hero?.playerData != null) { SwapRequest swapRequest = _queue.TryDequeue(Time.frameCount, Time.unscaledTime, (SwapRequest _) => _switchService.IsSafeToSwapNow(_hero) && _safety.HasDashSettled(Time.frameCount)); if ((object)swapRequest != null && !string.IsNullOrWhiteSpace(swapRequest.ToCrest) && !(swapRequest.ToCrest == _hero.playerData.CurrentCrestID)) { SwapOrigin origin = swapRequest.Origin; bool flag = (uint)(origin - 5) <= 2u; bool flag2 = flag; string reason = (swapRequest.Reason ?? "QUEUED") + (flag2 ? "_DASH_SAFE" : "_SAFE"); _switchService.TryApply(swapRequest.ToCrest, reason, flag2, _hero); } } } private void TrackUnsafeFrames() { if (!((Object)(object)_hero == (Object)null)) { bool flag = _heroReflection.IsDashLikeActive(_hero); bool unsafeNow = flag || _heroReflection.IsAttackOrChargeActive(_hero) || !_heroReflection.HasControl(_hero); _safety.RecordFrame(Time.frameCount, flag, unsafeNow); } } private void RunPostSwapRecovery() { if (_switchService.PostSwapWasFromDash && !((Object)(object)_hero == (Object)null)) { if (Time.frameCount > _switchService.PostSwapRecoveryUntilFrame) { _switchService.EndPostSwapRecovery(); return; } _heroReflection.NormalizeDashExit(_hero, true); Trace($"Dash post-swap recovery frame {Time.frameCount}/{_switchService.PostSwapRecoveryUntilFrame}"); } } private static string? SafeCrestName(Func getter) { try { ToolCrest obj = getter(); return (obj != null) ? obj.name : null; } catch { return null; } } private int ChooseDirection(int heldMask, int downMask) { if (BitCount(downMask) == 1) { _lastChosenDirMask = downMask; return downMask; } if (BitCount(heldMask) == 1) { _lastChosenDirMask = heldMask; return heldMask; } if (heldMask != 0 && _lastChosenDirMask != 0 && (heldMask & _lastChosenDirMask) != 0) { return _lastChosenDirMask; } return 0; } private static int BitCount(int value) { int num = 0; while (value != 0) { value &= value - 1; num++; } return num; } private static string[] ParseCandidates(string value) { return (from x in (value ?? string.Empty).Split(new char[3] { ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries) select x.Trim() into x where x.Length > 0 select x).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); } private void Trace(string message) { if (_verboseLogging.Value) { _log.LogInfo((object)("[CrestQuickSwitch] " + message)); } } } } namespace CrestsEvolve.InsectMayCry { [BepInPlugin("local.crestsevolve.insectmaycry", "Insect May Cry", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "local.crestsevolve.insectmaycry"; public const string PluginName = "Insect May Cry"; public const string PluginVersion = "0.1.0"; private Harmony? _harmony; private CrestQuickSwitchIntegration? _quickSwitch; private AttackModuleCatalog? _attackModules; private VergilCrestFeature? _vergil; private SecondToolSetFeature? _secondToolSet; private void Awake() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown if (FoundationServices.Current == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[InsectMayCry] foundation not loaded; quick switch disabled."); return; } _harmony = new Harmony("local.crestsevolve.insectmaycry"); _quickSwitch = CrestQuickSwitchIntegration.Install(((BaseUnityPlugin)this).Logger, ((BaseUnityPlugin)this).Config, _harmony); AttackModuleOptions options = new AttackModuleOptions(((BaseUnityPlugin)this).Config); _attackModules = new AttackModuleCatalog(((BaseUnityPlugin)this).Logger, _harmony, options); _vergil = VergilCrestFeature.Install(((BaseUnityPlugin)this).Logger, _harmony, options); _secondToolSet = new SecondToolSetFeature(((BaseUnityPlugin)this).Logger, _harmony, FoundationServices.Current); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[InsectMayCry] loaded: crest quick switch + 11 attack modules + Dante/Vergil/Nero crest family."); } private void OnDestroy() { _secondToolSet?.Dispose(); _vergil?.Dispose(); _attackModules?.Dispose(); _quickSwitch?.Dispose(); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } } namespace CrestsEvolve.InsectMayCry.GameInterop { public static class SecondToolSetGameBridge { public static string? CurrentCrestId() { try { return PlayerData.instance?.CurrentCrestID; } catch (Exception ex) { Debug.LogWarning((object)("[SecondToolSet] current crest query failed: " + ex.Message)); return null; } } public static string? GetEquippedToolName(string crestId, int slotIndex) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_0040: 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) try { PlayerData instance = PlayerData.instance; if (instance == null || instance.ToolEquips == null) { return null; } Data data = ((SerializableNamedList)(object)instance.ToolEquips).GetData(crestId); if (data.Slots == null || slotIndex < 0 || slotIndex >= data.Slots.Count) { return null; } string equippedTool = data.Slots[slotIndex].EquippedTool; return string.IsNullOrWhiteSpace(equippedTool) ? null : equippedTool; } catch (Exception ex) { Debug.LogWarning((object)("[SecondToolSet] equipped tool query failed: " + ex.Message)); return null; } } public static SecondToolSnapshot? GetToolSnapshot(string toolName) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_00ae: Unknown result type (might be due to invalid IL or missing references) try { ToolItem toolByName = ToolItemManager.GetToolByName(toolName); if ((Object)(object)toolByName == (Object)null) { return null; } bool flag = (int)toolByName.Type == 3; PlayerData instance = PlayerData.instance; int num = ((flag && instance != null) ? instance.SilkSkillCost : 0); int num2 = ((flag && instance != null) ? instance.silk : 0); bool flag2 = !flag || num2 >= num; ToolItemSkill val = (ToolItemSkill)(object)((toolByName is ToolItemSkill) ? toolByName : null); Sprite val2 = ((val != null && flag2 && (Object)(object)val.HudGlowSprite != (Object)null) ? val.HudGlowSprite : toolByName.HudSpriteModified); if ((Object)(object)val2 == (Object)null) { val2 = toolByName.InventorySpriteModified; } int amountLeft = 0; int storage = 0; if (!flag) { amountLeft = ((instance != null) ? instance.GetToolData(toolName).AmountLeft : 0); storage = ToolItemManager.GetToolStorageAmount(toolByName); } return new SecondToolSnapshot(val2, amountLeft, storage, flag, flag2); } catch (Exception ex) { Debug.LogWarning((object)("[SecondToolSet] tool snapshot failed for '" + toolName + "': " + ex.Message)); return null; } } } public static class SecondToolSetPatches { private sealed class NeroShamanBindMarker : MonoBehaviour { } private sealed class NeroShamanBindGateAction : FsmStateAction { public override void OnEnter() { try { HeroController instance = HeroController.instance; if (instance?.playerData == null || !VergilCrestRules.IsNeroCrest(instance.playerData.CurrentCrestID)) { ((FsmStateAction)this).Finish(); return; } if (SecondToolSetRules.FindSpecialAction(SecondToolSetRegistry.TryGet(instance.playerData.CurrentCrestID), GetPressedBinding()) != SecondSpecialAction.ShamanBind) { ((FsmStateAction)this).Finish(); return; } Fsm fsm = ((FsmStateAction)this).Fsm; object obj; if (fsm == null) { obj = null; } else { FsmVariables variables = fsm.Variables; obj = ((variables != null) ? variables.GetFsmBool("Is Shaman Equipped") : null); } FsmBool val = (FsmBool)obj; if (val != null) { val.Value = true; } } catch (Exception ex) { WarnOnce(ex, "shaman bind gate action"); } ((FsmStateAction)this).Finish(); } } private sealed record ResolvedSlot(int Index, AttackToolBinding Binding) { [CompilerGenerated] public void Deconstruct(out int Index, out AttackToolBinding Binding) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected I4, but got Unknown Index = this.Index; Binding = (AttackToolBinding)(int)this.Binding; } } private static ManualLogSource? _log; private static bool _installed; private static bool _ok; private static bool _warned; private static FieldInfo? _acceptingInputField; private static FieldInfo? _queueStepsLimitField; private static FieldInfo? _customOverrideField; private static MethodInfo? _throwToolMethod; private static HeroController? _queueHero; private static bool _castQueueing; private static int _castQueueSteps; public static bool Install(Harmony harmony, ManualLogSource log) { //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Expected O, but got Unknown //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Expected O, but got Unknown //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Expected O, but got Unknown //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Expected O, but got Unknown //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Expected O, but got Unknown if (_installed) { return _ok; } _installed = true; _log = log; MethodInfo methodInfo = AccessTools.Method(typeof(ToolItemManager), "GetBoundAttackTool", new Type[3] { typeof(AttackToolBinding), typeof(ToolEquippedReadSource), typeof(AttackToolBinding).MakeByRefType() }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(HeroController), "CanBind", (Type[])null, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(HeroController), "LookForQueueInput", (Type[])null, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method(typeof(HeroController), "ResetInputQueues", (Type[])null, (Type[])null); MethodInfo methodInfo5 = AccessTools.Method(typeof(PlayMakerFSM), "Start", (Type[])null, (Type[])null); _acceptingInputField = AccessTools.Field(typeof(HeroController), "acceptingInput"); _queueStepsLimitField = AccessTools.Field(typeof(HeroController), "TOOLTHROW_QUEUE_STEPS"); _customOverrideField = AccessTools.Field(typeof(ToolItemManager), "customToolOverride"); _throwToolMethod = AccessTools.Method(typeof(HeroController), "ThrowTool", new Type[1] { typeof(bool) }, (Type[])null); if (methodInfo == null || methodInfo2 == null || methodInfo3 == null || _acceptingInputField == null || _queueStepsLimitField == null || _throwToolMethod == null) { _log.LogWarning((object)("[InsectMayCry][SecondToolSet] required members missing; second tool set disabled " + $"(GetBoundAttackTool={methodInfo != null}, CanBind={methodInfo2 != null}, " + $"LookForQueueInput={methodInfo3 != null}, acceptingInput={_acceptingInputField != null}, " + $"TOOLTHROW_QUEUE_STEPS={_queueStepsLimitField != null}, ThrowTool={_throwToolMethod != null}).")); return false; } _ok = true; harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(Declared("OnGetBoundAttackTool")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(Declared("OnCanBind")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(Declared("OnLookForQueueInput")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (methodInfo5 != null) { harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, new HarmonyMethod(Declared("OnFsmStart")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _log.LogWarning((object)"[InsectMayCry][SecondToolSet] PlayMakerFSM.Start not found; Shaman-bind special action will fall back to normal bind."); } if (methodInfo4 != null) { harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(Declared("OnResetInputQueues")), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } _log.LogInfo((object)"[InsectMayCry][SecondToolSet] input routing + bind suppression installed (dormant until a crest layout is registered)."); return true; } public static void Reset() { _queueHero = null; _castQueueing = false; _castQueueSteps = 0; } private static MethodInfo Declared(string name) { return AccessTools.DeclaredMethod(typeof(SecondToolSetPatches), name, (Type[])null, (Type[])null); } private static bool IsCastPressed() { try { InputHandler instance = ManagerSingleton.Instance; return (Object)(object)instance != (Object)null && instance.inputActions != null && instance.inputActions.Cast != null && ((OneAxisInputControl)instance.inputActions.Cast).IsPressed; } catch (Exception ex) { WarnOnce(ex, "cast input query"); return false; } } private static bool IsCustomToolOverrideActive() { if (_customOverrideField == null) { WarnOnce(new InvalidOperationException("customToolOverride field not resolved"), "custom override check"); return true; } try { return _customOverrideField.GetValue(ManagerSingleton.Instance) != null; } catch (Exception ex) { WarnOnce(ex, "custom override query"); return true; } } public static bool OnGetBoundAttackTool(AttackToolBinding binding, ToolEquippedReadSource readSource, ref AttackToolBinding usedBinding, ref ToolItem __result) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Invalid comparison between Unknown and I4 //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected I4, but got Unknown //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected I4, but got Unknown try { if ((int)readSource != 0 && (int)readSource != 1) { return true; } if ((Object)(object)ManagerSingleton.Instance == (Object)null) { return true; } ToolsActiveStates activeState = ToolItemManager.ActiveState; if ((int)activeState == 2) { return true; } if ((int)activeState == 1 && (int)readSource == 0) { return true; } if (CollectableItemManager.IsInHiddenMode()) { return true; } PlayerData instance = PlayerData.instance; if (instance == null || string.IsNullOrEmpty(instance.CurrentCrestID)) { return true; } SecondToolSetLayout secondToolSetLayout = SecondToolSetRegistry.TryGet(instance.CurrentCrestID); if (secondToolSetLayout == null || secondToolSetLayout.SecondSlots.Count == 0) { return true; } if (IsCustomToolOverrideActive()) { return true; } bool second = (int)readSource == 0 && IsCastPressed(); ResolvedSlot resolvedSlot = ResolveSlot(instance.CurrentCrestID, secondToolSetLayout, binding, second); if (resolvedSlot == null) { usedBinding = (AttackToolBinding)(int)binding; __result = null; return false; } usedBinding = (AttackToolBinding)(int)resolvedSlot.Binding; __result = GetEquippedTool(instance, resolvedSlot.Index); return false; } catch (Exception ex) { WarnOnce(ex, "bound tool resolve"); return true; } } public static bool OnCanBind(ref bool __result) { try { HeroController instance = HeroController.instance; if (instance?.playerData == null) { return true; } SecondToolSetLayout layout = SecondToolSetRegistry.TryGet(instance.playerData.CurrentCrestID); if (!SecondToolSetRules.ShouldSuppressBind(layout)) { return true; } SecondToolBinding pressedBinding = GetPressedBinding(); if (SecondToolSetRules.FindSpecialAction(layout, pressedBinding) == SecondSpecialAction.ShamanBind) { __result = true; return false; } __result = false; return false; } catch (Exception ex) { WarnOnce(ex, "bind suppression"); return true; } } public static void OnLookForQueueInput(HeroController __instance) { try { if ((Object)(object)__instance == (Object)null) { return; } if ((Object)(object)_queueHero != (Object)(object)__instance) { _queueHero = __instance; _castQueueing = false; _castQueueSteps = 0; } SecondToolSetLayout secondToolSetLayout = SecondToolSetRegistry.TryGet(__instance.playerData?.CurrentCrestID); if (secondToolSetLayout == null || secondToolSetLayout.SecondSlots.Count == 0) { _castQueueing = false; return; } if (_castQueueing) { _castQueueSteps++; } PlayerAction val = (ManagerSingleton.Instance?.inputActions)?.Cast; if (val == null) { return; } if (((OneAxisInputControl)val).WasPressed) { if (HasSpecialForPressed(secondToolSetLayout)) { _castQueueing = false; return; } if (ReadAcceptingInput(__instance) && __instance.CanThrowTool(false)) { if (__instance.GetWillThrowTool(true)) { InvokeThrowTool(__instance, isAutoThrow: false); } } else { _castQueueSteps = 0; _castQueueing = true; } } if (ReadAcceptingInput(__instance) && ((OneAxisInputControl)val).IsPressed && _castQueueing && _castQueueSteps <= ReadQueueStepsLimit(__instance) && __instance.CanThrowTool()) { InvokeThrowTool(__instance, isAutoThrow: false); } if (!((OneAxisInputControl)val).IsPressed) { _castQueueing = false; } } catch (Exception ex) { WarnOnce(ex, "cast tool queue"); } } public static void OnResetInputQueues() { _castQueueing = false; _castQueueSteps = 0; } public static void OnFsmStart(PlayMakerFSM __instance) { try { if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).gameObject == (Object)null) { return; } string fsmName = __instance.FsmName; if ((string.Equals(fsmName, "Bind", StringComparison.Ordinal) || string.Equals(fsmName, "Spell Control", StringComparison.Ordinal)) && !((Object)(object)((Component)__instance).GetComponent() != (Object)null)) { FsmState state = FsmUtil.GetState(__instance, "Bind Type"); if (state == null) { WarnOnce(new InvalidOperationException("Bind FSM state 'Bind Type' missing"), "shaman bind fsm gate"); return; } FsmUtil.InsertAction(state, 0, (FsmStateAction)(object)new NeroShamanBindGateAction()); ((Component)__instance).gameObject.AddComponent(); } } catch (Exception ex) { WarnOnce(ex, "shaman bind fsm gate install"); } } public static SecondToolBinding GetPressedBinding() { try { HeroActions val = ManagerSingleton.Instance?.inputActions; if (val != null && val.Up != null && ((OneAxisInputControl)val.Up).IsPressed) { return SecondToolBinding.Up; } if (val != null && val.Down != null && ((OneAxisInputControl)val.Down).IsPressed) { return SecondToolBinding.Down; } } catch (Exception ex) { WarnOnce(ex, "pressed binding"); } return SecondToolBinding.Neutral; } private static bool HasSpecialForPressed(SecondToolSetLayout layout) { return SecondToolSetRules.FindSpecialAction(layout, GetPressedBinding()).HasValue; } private static ResolvedSlot? ResolveSlot(string crestId, SecondToolSetLayout layout, AttackToolBinding binding, bool second) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) ToolCrest crestByName = ToolItemManager.GetCrestByName(crestId); if ((Object)(object)crestByName == (Object)null || crestByName.Slots == null) { return null; } PlayerData instance = PlayerData.instance; if (instance == null || instance.ToolEquips == null) { return null; } Data data = ((SerializableNamedList)(object)instance.ToolEquips).GetData(crestId); if (data.Slots == null) { return null; } int num = Math.Min(crestByName.Slots.Length, data.Slots.Count); if (second) { SecondToolBinding? secondToolBinding = MapBinding(binding); if (!secondToolBinding.HasValue) { return null; } SecondSlotSpec secondSlotSpec = SecondToolSetRules.FindSecondSlot(layout, secondToolBinding.Value); if (secondSlotSpec == null) { return null; } int num2 = secondSlotSpec.Index - 1; if (num2 >= num) { return null; } if (string.IsNullOrEmpty(data.Slots[num2].EquippedTool)) { return null; } ToolItem toolByName = ToolItemManager.GetToolByName(data.Slots[num2].EquippedTool); if ((Object)(object)toolByName == (Object)null || !ToolItemTypeExtensions.IsAttackType(toolByName.Type)) { return null; } return new ResolvedSlot(num2, binding); } for (int i = 0; i < num && i < 3; i++) { if (crestByName.Slots[i].AttackBinding == binding && ToolItemTypeExtensions.IsAttackType(crestByName.Slots[i].Type) && !string.IsNullOrEmpty(data.Slots[i].EquippedTool)) { ToolItem toolByName2 = ToolItemManager.GetToolByName(data.Slots[i].EquippedTool); if (!((Object)(object)toolByName2 == (Object)null) && ToolItemTypeExtensions.IsAttackType(toolByName2.Type)) { return new ResolvedSlot(i, binding); } } } return null; } private static SecondToolBinding? MapBinding(AttackToolBinding binding) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected I4, but got Unknown return (int)binding switch { 1 => SecondToolBinding.Up, 0 => SecondToolBinding.Neutral, 2 => SecondToolBinding.Down, _ => null, }; } private static ToolItem? GetEquippedTool(PlayerData playerData, int index) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (playerData == null || playerData.ToolEquips == null) { return null; } Data data = ((SerializableNamedList)(object)playerData.ToolEquips).GetData(playerData.CurrentCrestID); if (data.Slots == null || index < 0 || index >= data.Slots.Count) { return null; } string equippedTool = data.Slots[index].EquippedTool; if (!string.IsNullOrEmpty(equippedTool)) { return ToolItemManager.GetToolByName(equippedTool); } return null; } private static bool ReadAcceptingInput(HeroController hero) { if (_acceptingInputField == null) { return false; } try { object value = _acceptingInputField.GetValue(hero); return value is bool && (bool)value; } catch (Exception ex) { WarnOnce(ex, "acceptingInput"); return false; } } private static int ReadQueueStepsLimit(HeroController hero) { if (_queueStepsLimitField == null) { return 5; } try { return (_queueStepsLimitField.GetValue(hero) is int num) ? num : 5; } catch (Exception ex) { WarnOnce(ex, "TOOLTHROW_QUEUE_STEPS"); return 5; } } private static void InvokeThrowTool(HeroController hero, bool isAutoThrow) { if (!(_throwToolMethod == null)) { _throwToolMethod.Invoke(hero, new object[1] { isAutoThrow }); } } private static void WarnOnce(Exception ex, string context) { if (!_warned) { _warned = true; ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[InsectMayCry][SecondToolSet] " + context + " failed: " + ex.GetType().Name + ": " + ex.Message)); } } } } } namespace CrestsEvolve.InsectMayCry.Features.WindBlade { public sealed class HunterWindBladeFeature : IDisposable { private sealed class NailArtsFsmMarker : MonoBehaviour { } private sealed class SpawnWindBladeAction : FsmStateAction { public override void OnEnter() { try { _instance?.SpawnBarrage(); } catch (Exception ex) { HunterWindBladeFeature? instance = _instance; if (instance != null) { instance._log.LogWarning((object)("[WindBlade] action threw: " + ex.Message)); } } ((FsmStateAction)this).Finish(); } } private sealed class WindBladeTimeout : MonoBehaviour { private float _lifetime; private float _age; public void Init(float lifetime) { _lifetime = Mathf.Max(0.1f, lifetime); _age = 0f; } private void OnEnable() { _age = 0f; } private void Update() { _age += Time.deltaTime; if (_age >= _lifetime) { _age = 0f; ObjectPool.Recycle(((Component)this).gameObject); } } } private readonly ManualLogSource _log; private readonly Harmony _harmony; private readonly AttackModuleOptions _options; private static HunterWindBladeFeature? _instance; private bool _disposed; private GameObject? _projectilePrefab; private AssetBundle? _projectileBundle; private bool _prefabTried; private bool _warnedNoPrefab; private const string ProjectilePrefabName = "Song Knight Projectile"; private const string ProjectilePrefabPath = "Assets/Prefabs/Hornet Enemies/Song Knight Projectile.prefab"; private const string ProjectileBundleFileName = "localpoolprefabs_assets_areahangareasong.bundle"; private const string ProjectileBundleRelPath = "aa/StandaloneWindows64/localpoolprefabs_assets_areahangareasong.bundle"; public HunterWindBladeFeature(ManualLogSource log, Harmony harmony, AttackModuleOptions options) { _log = log ?? throw new ArgumentNullException("log"); _harmony = harmony ?? throw new ArgumentNullException("harmony"); _options = options ?? throw new ArgumentNullException("options"); } public static HunterWindBladeFeature Install(ManualLogSource log, Harmony harmony, AttackModuleOptions options) { if (_instance != null) { return _instance; } HunterWindBladeFeature hunterWindBladeFeature = new HunterWindBladeFeature(log, harmony, options); hunterWindBladeFeature.InstallPatch(); _instance = hunterWindBladeFeature; log.LogInfo((object)"[InsectMayCry][WindBlade] hunter charge-slash wind-blade feature installed."); return hunterWindBladeFeature; } private void InstallPatch() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(PlayMakerFSM), "Start", (Type[])null, (Type[])null); if (methodInfo == null) { _log.LogWarning((object)"[WindBlade] PlayMakerFSM.Start not found; feature disabled."); return; } _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(HunterWindBladeFeature), "OnFsmStart", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _log.LogInfo((object)("[WindBlade] patched " + GeneralExtensions.FullDescription((MethodBase)methodInfo) + ".")); } public static void OnFsmStart(PlayMakerFSM __instance) { HunterWindBladeFeature instance = _instance; if (instance == null || !instance._options.WindBladeEnabled || (Object)(object)__instance == (Object)null || __instance.Fsm == null || __instance.FsmName != "Nail Arts" || (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, "Do Slash"); if (state == null) { instance._log.LogWarning((object)"[WindBlade] Nail Arts FSM 'Do Slash' state not found."); return; } FsmUtil.InsertAction(state, state.Actions.Length, (FsmStateAction)(object)new SpawnWindBladeAction()); ((Component)__instance).gameObject.AddComponent(); instance._log.LogInfo((object)"[WindBlade] Nail Arts FSM hook installed (Do Slash -> spawn wind blades)."); } catch (Exception ex) { instance._log.LogWarning((object)("[WindBlade] FSM hook failed: " + ex.GetType().Name + ": " + ex.Message)); } } private void SpawnBarrage() { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null || instance.playerData == null) { return; } if (!VergilChargeVariantState.IsImcEquipped() || !VergilCrestRules.IsDanteCrest(instance.playerData.CurrentCrestID) || VergilChargeVariantState.Current != ChargeVariant.Up) { _log.LogDebug((object)"[WindBlade] skipped: not D up-variant charge."); return; } int num; float num2; if (instance.cState != null) { num = (instance.cState.facingRight ? 1 : 0); if (num != 0) { num2 = 0f; goto IL_006f; } } else { num = 0; } num2 = 180f; goto IL_006f; IL_006f: float baseAngle = num2; int windBladeCount = _options.WindBladeCount; Vector3 position = ((Component)instance).transform.position; Vector2 val = ((num != 0) ? Vector2.right : Vector2.left); position += Vector2.op_Implicit(val * _options.WindBladeSpawnForwardOffset); if (windBladeCount > 1 && _options.WindBladeBladeDelaySeconds > 0f) { ((MonoBehaviour)instance).StartCoroutine(SpawnBladeSequence(position, baseAngle, windBladeCount)); return; } Vector2 dir = default(Vector2); for (int i = 0; i < windBladeCount; i++) { float num3 = SpreadAngle(baseAngle, windBladeCount, i); ((Vector2)(ref dir))..ctor(Mathf.Cos(num3 * (MathF.PI / 180f)), Mathf.Sin(num3 * (MathF.PI / 180f))); SpawnBlade(position, dir); } _log.LogInfo((object)$"[WindBlade] hunter charge slash spawned {windBladeCount} wind blade(s)."); } private IEnumerator SpawnBladeSequence(Vector3 origin, float baseAngle, int count) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) Vector2 dir = default(Vector2); for (int i = 0; i < count; i++) { float num = SpreadAngle(baseAngle, count, i); ((Vector2)(ref dir))..ctor(Mathf.Cos(num * (MathF.PI / 180f)), Mathf.Sin(num * (MathF.PI / 180f))); SpawnBlade(origin, dir); if (i < count - 1) { yield return (object)new WaitForSeconds(_options.WindBladeBladeDelaySeconds); } } _log.LogInfo((object)$"[WindBlade] hunter charge slash spawned {count} wind blade(s) sequentially."); } private float SpreadAngle(float baseAngle, int count, int index) { if (count <= 1) { return baseAngle; } float windBladeSpreadDegrees = _options.WindBladeSpreadDegrees; if (windBladeSpreadDegrees <= 0f) { return baseAngle; } float num = baseAngle - windBladeSpreadDegrees / 2f; float num2 = ((count > 1) ? (windBladeSpreadDegrees / (float)(count - 1)) : 0f); return num + num2 * (float)index; } private void SpawnBlade(Vector3 origin, Vector2 dir) { //IL_0036: 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) try { EnsurePrefabLoaded(); if ((Object)(object)_projectilePrefab == (Object)null) { if (!_warnedNoPrefab) { _warnedNoPrefab = true; _log.LogWarning((object)"[WindBlade] Song Knight Projectile prefab unavailable; charge-slash wind blades skipped."); } } else { SpawnFromSongKnightPrefab(origin, dir); } } catch (Exception ex) { _log.LogWarning((object)("[WindBlade] blade spawn failed: " + ex.Message)); } } private void EnsurePrefabLoaded() { if (_prefabTried) { return; } _prefabTried = true; try { _projectileBundle = FindLoadedBundle("localpoolprefabs_assets_areahangareasong.bundle"); if ((Object)(object)_projectileBundle == (Object)null) { string text = Path.Combine(Application.streamingAssetsPath, "aa/StandaloneWindows64/localpoolprefabs_assets_areahangareasong.bundle"); if (File.Exists(text)) { _projectileBundle = AssetBundle.LoadFromFile(text); } else { _log.LogWarning((object)("[WindBlade] bundle not found at " + text)); } } if ((Object)(object)_projectileBundle == (Object)null) { _log.LogWarning((object)"[WindBlade] Song Knight Projectile bundle unavailable; wind blades disabled."); return; } _projectilePrefab = _projectileBundle.LoadAsset("Assets/Prefabs/Hornet Enemies/Song Knight Projectile.prefab") ?? _projectileBundle.LoadAsset("Song Knight Projectile"); if ((Object)(object)_projectilePrefab != (Object)null) { _log.LogInfo((object)("[WindBlade] loaded Song Knight Projectile prefab from " + ((Object)_projectileBundle).name + " (" + ((Object)_projectilePrefab).name + ").")); } else { _log.LogWarning((object)("[WindBlade] 'Assets/Prefabs/Hornet Enemies/Song Knight Projectile.prefab' not found in " + ((Object)_projectileBundle).name + "; wind blades disabled.")); } } catch (Exception ex) { _log.LogWarning((object)("[WindBlade] prefab lookup failed: " + ex.Message)); } } private AssetBundle? FindLoadedBundle(string fileName) { try { foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles()) { if ((Object)(object)allLoadedAssetBundle != (Object)null && string.Equals(((Object)allLoadedAssetBundle).name, fileName, StringComparison.OrdinalIgnoreCase)) { return allLoadedAssetBundle; } } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); foreach (AssetBundle allLoadedAssetBundle2 in AssetBundle.GetAllLoadedAssetBundles()) { if ((Object)(object)allLoadedAssetBundle2 != (Object)null && ((Object)allLoadedAssetBundle2).name.IndexOf(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) >= 0) { return allLoadedAssetBundle2; } } } catch (Exception ex) { _log.LogWarning((object)("[WindBlade] loaded-bundle search failed: " + ex.Message)); } return null; } private void SpawnFromSongKnightPrefab(Vector3 origin, Vector2 dir) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_projectilePrefab == (Object)null) { return; } GameObject val = ObjectPoolExtensions.Spawn(_projectilePrefab, origin, Quaternion.identity); if (!((Object)(object)val == (Object)null)) { ((Object)val).name = "HunterWindBlade"; tk2dSprite[] componentsInChildren = val.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { ((tk2dBaseSprite)componentsInChildren[i]).ForceBuild(); } Vector3 localScale = val.transform.localScale; localScale.x = Mathf.Abs(localScale.x) * ((dir.x >= 0f) ? (-1f) : 1f); val.transform.localScale = localScale; val.layer = 17; Transform val2 = val.transform.Find("Terrain Detector"); if ((Object)(object)val2 != (Object)null) { ((Component)val2).gameObject.SetActive(false); } DamageHero component = val.GetComponent(); if ((Object)(object)component != (Object)null) { ((Behaviour)component).enabled = false; } val.tag = "Nail Attack"; DamageEnemies val3 = val.GetComponent(); if ((Object)(object)val3 == (Object)null) { val3 = val.AddComponent(); } ConfigurePlayerDamager(val3); Rigidbody2D component2 = val.GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.linearVelocity = dir * _options.WindBladeSpeed; } WindBladeTimeout windBladeTimeout = val.GetComponent(); if ((Object)(object)windBladeTimeout == (Object)null) { windBladeTimeout = val.AddComponent(); } windBladeTimeout.Init(_options.WindBladeLifetime); } } private void ConfigurePlayerDamager(DamageEnemies damager) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown if ((Object)(object)damager == (Object)null) { return; } damager.useNailDamage = true; damager.nailDamageMultiplier = (float)_options.WindBladeDamage; damager.attackType = (AttackTypes)7; damager.stunDamage = 1f; damager.canWeakHit = false; damager.magnitudeMult = 1f; damager.direction = 0f; damager.moveDirection = false; damager.contactFSMEvent = ""; damager.damageFSMEvent = ""; damager.slashEffectOverrides = Array.Empty(); damager.corpseDirection = new OverrideFloat(); damager.corpseMagnitudeMult = new OverrideFloat(); damager.currencyMagnitudeMult = new OverrideFloat(); damager.DealtDamage = new UnityEvent(); damager.Tinked = new UnityEvent(); try { typeof(DamageEnemies).GetField("isHeroDamage", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(damager, true); } catch (Exception) { } } public void Dispose() { if (!_disposed) { _disposed = true; _harmony.UnpatchSelf(); if (_instance == this) { _instance = null; } } } } } namespace CrestsEvolve.InsectMayCry.Features.Vergil { public static class DanteCrossStitchRuneRules { public static float FirstCastMinSpawnRadius => 3.5f; public static float FirstCastMaxSpawnRadius => 8f; public static bool ShouldRequestRuneRage(bool enabled, string? crestId, ChargeVariant variant) { if (enabled && VergilCrestRules.IsDanteCrest(crestId)) { return variant == ChargeVariant.Default; } return false; } public static bool ShouldRedirect(bool enabled, string? crestId, bool requested, double now, double until) { if (enabled && VergilCrestRules.IsDanteCrest(crestId) && requested) { return now <= until; } return false; } public static int ClampCasts(int casts) { return Math.Clamp(casts, 1, 3); } public static double ClampInvulnerabilitySeconds(double seconds) { return Math.Max(0.0, seconds); } } public static class DvnSlotLayouts { public static SecondToolSetLayout Dante { get; } = new SecondToolSetLayout("Dante", new SecondSlotSpec[3] { Attack(4, SecondToolBinding.Up, -1.2f, 1.6f), Attack(5, SecondToolBinding.Neutral, -1.6f, -0.7f), Attack(6, SecondToolBinding.Down, -1.5f, -1.6f) }, new SecondSlotSpec[3] { Attack(1, SecondToolBinding.Up, 1.2f, 1.8f), Attack(2, SecondToolBinding.Neutral, 1.5f, -0.8f), Attack(3, SecondToolBinding.Down, 1.2f, -1.6f) }, null, new UtilitySlotSpec[3] { Utility(7, UtilitySlotKind.Yellow, -0.8f, 0.5f), Utility(8, UtilitySlotKind.Yellow, 0.8f, 0.5f), Utility(9, UtilitySlotKind.Blue, 0f, -2.1f) }); public static SecondToolSetLayout Vergil { get; } = new SecondToolSetLayout("Vergil", new SecondSlotSpec[3] { Attack(4, SecondToolBinding.Up, -1.8f, 0.2f, SecondSlotKind.Skill), Attack(5, SecondToolBinding.Neutral, -1.5f, -0.7f, SecondSlotKind.Skill), Attack(6, SecondToolBinding.Down, -0.8f, -1.5f, SecondSlotKind.Skill) }, new SecondSlotSpec[3] { Attack(1, SecondToolBinding.Up, 1.5f, 0.2f, SecondSlotKind.Skill), Attack(2, SecondToolBinding.Neutral, 1.1f, -0.7f, SecondSlotKind.Skill), Attack(3, SecondToolBinding.Down, 0.7f, -1.5f, SecondSlotKind.Skill) }, null, new UtilitySlotSpec[4] { Utility(7, UtilitySlotKind.Yellow, 1.6f, 1.1f), Utility(8, UtilitySlotKind.Blue, 0.5f, 1.6f), Utility(9, UtilitySlotKind.Blue, -1f, 1.6f), Utility(10, UtilitySlotKind.Yellow, -2f, 1.1f) }); public static SecondToolSetLayout Nero { get; } = new SecondToolSetLayout("Nero", new SecondSlotSpec[2] { Attack(4, SecondToolBinding.Up, -0.7f, 1.2f, SecondSlotKind.Skill), Attack(5, SecondToolBinding.Neutral, -1.6f, 0.4f, SecondSlotKind.Skill) }, new SecondSlotSpec[3] { Attack(1, SecondToolBinding.Up, 1.3f, 0.4f), Attack(2, SecondToolBinding.Neutral, 0f, 0f), Attack(3, SecondToolBinding.Down, -1.2f, -0.4f) }, new SecondSpecialBinding[1] { new SecondSpecialBinding(SecondToolBinding.Down, SecondSpecialAction.ShamanBind) }, new UtilitySlotSpec[4] { Utility(7, UtilitySlotKind.Yellow, 1.5f, -0.8f), Utility(8, UtilitySlotKind.Blue, 0.6f, -1.1f), Utility(9, UtilitySlotKind.Blue, -0.5f, -1.5f), Utility(10, UtilitySlotKind.Yellow, -1.5f, -1.2f) }); public static void RegisterAll(Action? warn = null) { SecondToolSetRegistry.Register(Dante, warn); SecondToolSetRegistry.Register(Vergil, warn); SecondToolSetRegistry.Register(Nero, warn); } private static SecondSlotSpec Attack(int index, SecondToolBinding binding, float x, float y, SecondSlotKind kind = SecondSlotKind.Red) { return new SecondSlotSpec(index, binding, kind, x, y, IsLocked: false); } private static UtilitySlotSpec Utility(int index, UtilitySlotKind kind, float x, float y) { return new UtilitySlotSpec(index, kind, x, y, IsLocked: false); } } public static class VergilChargeToolPatches { private static ManualLogSource? _log; private static Harmony? _harmony; private static bool _installed; private static bool _warned; public static void Install(Harmony harmony, ManualLogSource log) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Expected O, but got Unknown //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Expected O, but got Unknown //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Expected O, but got Unknown //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Expected O, but got Unknown //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Expected O, but got Unknown //IL_0304: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Expected O, but got Unknown //IL_036c: Unknown result type (might be due to invalid IL or missing references) //IL_037a: Expected O, but got Unknown //IL_03e2: Unknown result type (might be due to invalid IL or missing references) //IL_03ef: Expected O, but got Unknown if (!_installed) { _installed = true; _harmony = harmony; _log = log; MethodInfo methodInfo = AccessTools.Method(typeof(HeroController), "CanThrowTool", new Type[3] { typeof(ToolItem), typeof(AttackToolBinding), typeof(bool) }, (Type[])null); if (methodInfo == null) { Warn("HeroController.CanThrowTool not found; free charge throw may fail when silk/ammo is empty."); } else { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilChargeToolPatches), "OnCanThrowTool", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.PropertyGetter(typeof(PlayerData), "SilkSkillCost"); if (methodInfo2 == null) { Warn("PlayerData.SilkSkillCost getter not found; free skill charge may consume silk."); } else { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilChargeToolPatches), "OnSilkSkillCost", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo3 = AccessTools.Method(typeof(ToolItemManager), "GetAttackToolBinding", new Type[1] { typeof(ToolItem) }, (Type[])null); if (methodInfo3 == null) { Warn("ToolItemManager.GetAttackToolBinding(ToolItem) not found; free charge throw may fail when tool not equipped."); } else { harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilChargeToolPatches), "OnGetAttackToolBinding", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo4 = AccessTools.Method(typeof(HeroController), "DidUseAttackTool", new Type[1] { typeof(Data) }, (Type[])null); if (methodInfo4 == null) { Warn("HeroController.DidUseAttackTool not found; free charge may consume tool ammo."); } else { harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilChargeToolPatches), "OnDidUseAttackTool", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo5 = AccessTools.Method(typeof(ToolItemManager), "SetEquippedTools", new Type[2] { typeof(string), typeof(List) }, (Type[])null); if (methodInfo5 == null) { Warn("ToolItemManager.SetEquippedTools(string,List) not found; free charge slot guard disabled."); } else { harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilChargeToolPatches), "OnSetEquippedTools", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo6 = AccessTools.Method(typeof(ToolItemManager), "SetExtraEquippedTool", new Type[2] { typeof(string), typeof(string) }, (Type[])null); if (methodInfo6 == null) { Warn("ToolItemManager.SetExtraEquippedTool(string,string) not found; free charge slot guard (extra) disabled."); } else { harmony.Patch((MethodBase)methodInfo6, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilChargeToolPatches), "OnSetExtraEquippedTool", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo7 = AccessTools.Method(typeof(ToolItemManager), "ReplaceToolEquips", new Type[2] { typeof(string), typeof(string) }, (Type[])null); if (methodInfo7 == null) { Warn("ToolItemManager.ReplaceToolEquips(string,string) not found; free charge slot guard (replace) disabled."); } else { harmony.Patch((MethodBase)methodInfo7, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilChargeToolPatches), "OnReplaceToolEquips", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo8 = AccessTools.Method(typeof(HeroController), "TakeSilk", new Type[1] { typeof(int) }, (Type[])null); if (methodInfo8 == null) { Warn("HeroController.TakeSilk(int) not found; free silk-shot may consume 1 silk."); } else { harmony.Patch((MethodBase)methodInfo8, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilChargeToolPatches), "OnTakeSilk", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo9 = AccessTools.Method(typeof(DamageEnemies), "DoDamage", new Type[2] { typeof(GameObject), typeof(bool) }, (Type[])null); if (methodInfo9 == null) { Warn("DamageEnemies.DoDamage(GameObject,bool) not found; silk-shot explosion disabled."); } else { harmony.Patch((MethodBase)methodInfo9, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilChargeToolPatches), "OnDoDamage", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } log.LogInfo((object)"[InsectMayCry][Vergil] charge tool patches installed (free throw + silk-shot explosion)."); } } private static bool IsFreeWindowActive() { if (VergilChargeVariantState.FreeChargeToolName == null) { return false; } if (VergilChargeVariantState.FreeChargeToolUntil > 0.0 && Time.timeAsDouble > VergilChargeVariantState.FreeChargeToolUntil) { VergilChargeVariantState.FreeChargeToolName = null; VergilChargeVariantState.FreeChargeToolUntil = 0.0; return false; } return true; } public static bool OnCanThrowTool(ToolItem tool, ref bool __result) { if (!IsFreeWindowActive()) { return true; } string freeChargeToolName = VergilChargeVariantState.FreeChargeToolName; if ((Object)(object)tool == (Object)null || !string.Equals(tool.name, freeChargeToolName, StringComparison.Ordinal)) { return true; } __result = true; return false; } public static bool OnSilkSkillCost(ref int __result) { if (!IsFreeWindowActive()) { return true; } __result = 0; return false; } public static bool OnGetAttackToolBinding(ToolItem tool, ref AttackToolBinding? __result) { if (!IsFreeWindowActive()) { return true; } string freeChargeToolName = VergilChargeVariantState.FreeChargeToolName; if ((Object)(object)tool == (Object)null || !string.Equals(tool.name, freeChargeToolName, StringComparison.Ordinal)) { return true; } __result = (AttackToolBinding)0; return false; } public static bool OnDidUseAttackTool() { return !IsFreeWindowActive(); } public static void OnSetEquippedTools(string crestId, List equippedTools) { try { string freeToolName = GetFreeToolName(); if (freeToolName != null && equippedTools != null) { List collection = VergilFreeToolSlotGuardRules.SanitizeEquippedList(ReadCurrentSlots(crestId), equippedTools, freeToolName); equippedTools.Clear(); equippedTools.AddRange(collection); } } catch (Exception ex) { WarnOnce(ex); } } public static void OnSetExtraEquippedTool(string slotId, ref string toolName) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) try { string freeToolName = GetFreeToolName(); if (freeToolName != null && PlayerData.instance != null) { string equippedTool = ((SerializableNamedList)(object)PlayerData.instance.ExtraToolEquips).GetData(slotId).EquippedTool; toolName = VergilFreeToolSlotGuardRules.SanitizeSlotValue(equippedTool, toolName, freeToolName); } } catch (Exception ex) { WarnOnce(ex); } } public static bool OnReplaceToolEquips(string newToolName) { string freeToolName = GetFreeToolName(); if (freeToolName == null) { return true; } return !string.Equals(newToolName, freeToolName, StringComparison.Ordinal); } public static bool OnTakeSilk() { return !IsFreeWindowActive(); } public static void OnDoDamage(DamageEnemies __instance, GameObject target) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)__instance == (Object)null) { return; } string chargeExplosionToolName = VergilChargeVariantState.ChargeExplosionToolName; if (chargeExplosionToolName == null || Time.timeAsDouble > VergilChargeVariantState.ChargeExplosionUntil) { return; } ToolItem representingTool = __instance.RepresentingTool; if ((Object)(object)representingTool == (Object)null || !string.Equals(representingTool.name, chargeExplosionToolName, StringComparison.Ordinal)) { return; } Vector3 val = (((Object)(object)target != (Object)null) ? target.transform.position : ((Component)__instance).transform.position); FoundationServices current = FoundationServices.Current; if (current != null) { IGameApi game = current.Game; if (game != null) { game.SpawnNativeExplosionAt(val); } } VergilChargeVariantState.ChargeExplosionToolName = null; } catch (Exception ex) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[InsectMayCry][Vergil] silk-shot explosion failed: " + ex.GetType().Name + ": " + ex.Message)); } } } private static void Warn(string message) { if (!_warned) { _warned = true; ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[InsectMayCry][Vergil] " + message)); } } } private static string? GetFreeToolName() { if (!IsFreeWindowActive()) { return null; } return VergilChargeVariantState.FreeChargeToolName; } private static List? ReadCurrentSlots(string crestId) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (PlayerData.instance == null) { return null; } Data data = ((SerializableNamedList)(object)PlayerData.instance.ToolEquips).GetData(crestId); if (data.Slots == null) { return null; } List list = new List(data.Slots.Count); for (int i = 0; i < data.Slots.Count; i++) { list.Add(data.Slots[i].EquippedTool); } return list; } private static void WarnOnce(Exception ex) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)("[InsectMayCry][Vergil] free charge slot guard failed: " + ex.GetType().Name + ": " + ex.Message)); } } } public static class VergilChargeVariantState { public static ChargeVariant Current { get; set; } public static bool LungeActive { get; set; } public static bool ReleaseWasDashing { get; set; } public static string? FreeChargeToolName { get; set; } public static double FreeChargeToolUntil { get; set; } public static string? ChargeExplosionToolName { get; set; } public static double ChargeExplosionUntil { get; set; } public static bool CrossStitchRequested { get; set; } public static double CrossStitchUntil { get; set; } public static bool CrossStitchRequireRealParry { get; set; } public static bool RuneRageRequested { get; set; } public static double RuneRageUntil { get; set; } public static bool IsImcEquipped() { HeroController instance = HeroController.instance; if ((Object)(object)instance != (Object)null && instance.playerData != null) { return VergilCrestRules.IsImcCrest(instance.playerData.CurrentCrestID); } return false; } } public sealed class VergilCrestFeature : IDisposable { private sealed class CrossStitchTriggerMarker : MonoBehaviour { } private sealed class CrossStitchTriggerAction : FsmStateAction { private static bool _warnedNoParryWait; public override void OnEnter() { try { if (!VergilChargeVariantState.CrossStitchRequested) { return; } if (Time.timeAsDouble > VergilChargeVariantState.CrossStitchUntil) { VergilChargeVariantState.CrossStitchRequested = false; VergilChargeVariantState.CrossStitchRequireRealParry = false; return; } HeroController instance = HeroController.instance; if (DanteCrossStitchRuneRules.ShouldRequestRuneRage(_options?.DanteCrossStitchRuneEnabled ?? false, instance?.playerData?.CurrentCrestID, VergilChargeVariantState.Current)) { VergilChargeVariantState.RuneRageRequested = true; VergilChargeVariantState.RuneRageUntil = Time.timeAsDouble + (_options?.DanteCrossStitchRuneRequestTimeoutSeconds ?? 2.0); } if (VergilChargeVariantState.CrossStitchRequireRealParry) { Wait val = null; Fsm fsm = ((FsmStateAction)this).Fsm; FsmState val2 = ((fsm != null) ? fsm.GetState("Parry Stance") : null); if (((val2 != null) ? val2.Actions : null) != null) { FsmStateAction[] actions = val2.Actions; foreach (FsmStateAction obj in actions) { Wait val3 = (Wait)(object)((obj is Wait) ? obj : null); if (val3 != null) { val = val3; break; } } } if (val != null) { val.time = FsmFloat.op_Implicit(_options?.DanteChargeCrossStitchParryWindowSeconds ?? 0.25f); } else if (!_warnedNoParryWait) { _warnedNoParryWait = true; VergilCrestFeature? instance2 = _instance; if (instance2 != null) { instance2._log.LogWarning((object)"[InsectMayCry][Vergil] Parry Stance Wait not found; parry window override skipped."); } } ((FsmStateAction)this).Finish(); return; } VergilChargeVariantState.CrossStitchRequested = false; VergilChargeVariantState.CrossStitchRequireRealParry = false; Fsm fsm2 = ((FsmStateAction)this).Fsm; if (fsm2 != null) { fsm2.Event("PARRIED"); } } catch (Exception) { } ((FsmStateAction)this).Finish(); } } private sealed class CrossStitchEffectRedirectAction : FsmStateAction { private const string RuneRageTargetState = "Initial Silk Cost"; private const string SharpdartTargetState = "Silk Charge Begin"; private static bool _warned; public override void OnEnter() { try { Fsm fsm = ((FsmStateAction)this).Fsm; AttackModuleOptions options = _options; if (fsm == null || options == null) { ((FsmStateAction)this).Finish(); return; } HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null || instance.playerData == null) { ((FsmStateAction)this).Finish(); return; } string currentCrestID = instance.playerData.CurrentCrestID; if (DanteCrossStitchRuneRules.ShouldRedirect(options.DanteCrossStitchRuneEnabled, currentCrestID, VergilChargeVariantState.RuneRageRequested, Time.timeAsDouble, VergilChargeVariantState.RuneRageUntil)) { VergilChargeVariantState.RuneRageRequested = false; RedirectToRuneRage(fsm, instance, options); ((FsmStateAction)this).Finish(); return; } VergilChargeVariantState.RuneRageRequested = false; if (VergilCrestRules.IsDanteCrest(currentCrestID)) { RedirectToSharpdart(fsm, instance); ((FsmStateAction)this).Finish(); return; } } catch (Exception ex) { WarnOnce(ex.GetType().Name + ": " + ex.Message); } ((FsmStateAction)this).Finish(); } private static void RedirectToRuneRage(Fsm fsm, HeroController hero, AttackModuleOptions options) { if (fsm.GetState("Initial Silk Cost") == null) { WarnOnce("Silk Specials state 'Initial Silk Cost' not found; rune-rage redirect disabled."); return; } PlayMakerFSM val = ResolvePlayMakerFsm(fsm); if ((Object)(object)val == (Object)null) { WarnOnce("PlayMakerFSM component not found; rune-rage redirect disabled."); return; } if (hero.cState != null) { hero.cState.parrying = false; hero.cState.parryAttack = false; } SetFsmInt(fsm, "Casts Left", DanteCrossStitchRuneRules.ClampCasts(options.DanteCrossStitchRuneCasts)); SetFsmFloat(fsm, "Min Spawn Radius", options.DanteCrossStitchRuneMinSpawnRadius); SetFsmFloat(fsm, "Max Spawn Radius", options.DanteCrossStitchRuneMaxSpawnRadius); SetFsmFloat(fsm, "Silk Bomb Cooldown", options.DanteCrossStitchRuneSilkBombCooldownSeconds); SetBindCanBreak(fsm.GameObject, value: true); double num = DanteCrossStitchRuneRules.ClampInvulnerabilitySeconds(options.DanteCrossStitchRuneInvulnerabilitySeconds); if (num > 0.0) { FoundationServices current = FoundationServices.Current; if (current != null) { IGameApi game = current.Game; if (game != null) { game.AcquireInvulnerability("dante.cross-stitch-rune", num); } } } val.SetState("Initial Silk Cost"); } private static void RedirectToSharpdart(Fsm fsm, HeroController hero) { if (fsm.GetState("Silk Charge Begin") == null) { WarnOnce("Silk Specials state 'Silk Charge Begin' not found; sharpdart redirect disabled."); return; } PlayMakerFSM val = ResolvePlayMakerFsm(fsm); if ((Object)(object)val == (Object)null) { WarnOnce("PlayMakerFSM component not found; sharpdart redirect disabled."); return; } if (hero.cState != null) { hero.cState.parrying = false; hero.cState.parryAttack = false; } VergilChargeVariantState.FreeChargeToolName = "Parry"; VergilChargeVariantState.FreeChargeToolUntil = Time.timeAsDouble + (_options?.DanteCrossStitchSharpdartFreeWindowSeconds ?? 0.5); val.SetState("Silk Charge Begin"); } private static PlayMakerFSM? ResolvePlayMakerFsm(Fsm fsm) { if ((Object)(object)((fsm != null) ? fsm.GameObject : null) == (Object)null) { return null; } PlayMakerFSM[] components = fsm.GameObject.GetComponents(); foreach (PlayMakerFSM val in components) { if ((Object)(object)val != (Object)null && val.Fsm == fsm) { return val; } } return null; } private static void SetFsmInt(Fsm fsm, string variableName, int value) { object obj; if (fsm == null) { obj = null; } else { FsmVariables variables = fsm.Variables; obj = ((variables != null) ? variables.GetFsmInt(variableName) : null); } FsmInt val = (FsmInt)obj; if (val != null) { val.Value = value; } } private static void SetFsmFloat(Fsm fsm, string variableName, float value) { object obj; if (fsm == null) { obj = null; } else { FsmVariables variables = fsm.Variables; obj = ((variables != null) ? variables.GetFsmFloat(variableName) : null); } FsmFloat val = (FsmFloat)obj; if (val != null) { val.Value = value; } } private static void SetBindCanBreak(GameObject owner, bool value) { if ((Object)(object)owner == (Object)null) { return; } PlayMakerFSM[] components = owner.GetComponents(); foreach (PlayMakerFSM val in components) { if (!((Object)(object)val == (Object)null) && !(val.FsmName != "Bind")) { FsmVariables fsmVariables = val.FsmVariables; FsmBool val2 = ((fsmVariables != null) ? fsmVariables.GetFsmBool("Can Bind Break") : null); if (val2 != null) { val2.Value = value; } break; } } } private static void WarnOnce(string message) { if (!_warned) { _warned = true; VergilCrestFeature? instance = _instance; if (instance != null) { instance._log.LogWarning((object)("[InsectMayCry][Vergil] rune-rage redirect: " + message)); } } } } private sealed class CrestSpec { public string Id = ""; public CrestData? Crest; public Action? MovesetHandler; public GameObject? NormalSlash; public GameObject? AltSlash; public GameObject? DownSlash; public GameObject? AltDownSlash; public GameObject? DashStab; public GameObject? ChargeDash; public GameObject? ChargeWitch; public GameObject? ChargeHunter; public GameObject? ChargeShaman; public GameObject? ChargeWanderer; public GameObject? ChargeBeast; public GameObject? ChargeReaper; public GameObject? ChargeSpinSlash; public GameObject? ChargeNone; public GameObject? GetChargeInstance(ChargeVariant variant) { switch (Id) { case "Dante": switch (variant) { case ChargeVariant.Default: return ChargeNone; case ChargeVariant.Up: return ChargeHunter; case ChargeVariant.Down: return ChargeBeast; case ChargeVariant.Dash: return ChargeDash ?? ChargeWanderer; } break; case "Vergil": switch (variant) { case ChargeVariant.Default: return ChargeNone; case ChargeVariant.Up: return ChargeShaman; case ChargeVariant.Down: return ChargeWitch; case ChargeVariant.Dash: return ChargeDash ?? ChargeWanderer; } break; case "Nero": switch (variant) { case ChargeVariant.Default: return ChargeNone; case ChargeVariant.Up: return ChargeReaper; case ChargeVariant.Down: return ChargeNone; case ChargeVariant.Dash: return ChargeDash ?? ChargeWanderer; } break; } return null; } } private sealed class VergilNailArtsMarker : MonoBehaviour { private FsmTransition? _anticFinishTransition; private FsmState? _anticOriginalTarget; public void CaptureAnticFinish(FsmState? antic) { if (antic == null || antic.Transitions == null) { return; } FsmTransition[] transitions = antic.Transitions; foreach (FsmTransition val in transitions) { if (val != null && string.Equals(val.EventName, "FINISHED", StringComparison.Ordinal)) { _anticFinishTransition = val; _anticOriginalTarget = val.ToFsmState; break; } } } public void RouteAnticFinish(FsmState? target) { if (_anticFinishTransition != null && target != null) { _anticFinishTransition.ToFsmState = target; } } private void OnDestroy() { if (_anticFinishTransition != null && _anticOriginalTarget != null) { _anticFinishTransition.ToFsmState = _anticOriginalTarget; } } } private sealed class SprintAirDashMarker : MonoBehaviour { private readonly List<(FsmTransition Transition, FsmState OriginalTarget)> _attackTransitions = new List<(FsmTransition, FsmState)>(); public bool CaptureAttackTarget(FsmState state) { if (((state != null) ? state.Transitions : null) == null) { return false; } FsmTransition[] transitions = state.Transitions; foreach (FsmTransition val in transitions) { if (val != null && string.Equals(val.EventName, "ATTACK", StringComparison.Ordinal)) { _attackTransitions.Add((val, val.ToFsmState)); return true; } } return false; } public void ApplyFamilyTarget(FsmState? dashStabDir) { if (dashStabDir == null) { return; } foreach (var attackTransition in _attackTransitions) { attackTransition.Transition.ToFsmState = dashStabDir; } } public void Restore() { foreach (var (val, toFsmState) in _attackTransitions) { val.ToFsmState = toFsmState; } } private void OnDestroy() { Restore(); } } private sealed class AirDashGateAction : FsmStateAction { public override void OnEnter() { try { Fsm fsm = ((FsmStateAction)this).Fsm; SprintAirDashMarker sprintAirDashMarker = ((fsm != null && (Object)(object)fsm.GameObject != (Object)null) ? fsm.GameObject.GetComponent() : null); if ((Object)(object)sprintAirDashMarker == (Object)null || fsm == null) { ((FsmStateAction)this).Finish(); return; } string text = VergilCrestRules.ResolveAirDashTarget(HeroController.instance?.playerData?.CurrentCrestID, _options?.ShamanDashSlashEnabled ?? false, fsm.GetState("Shaman Antic") != null, fsm.GetState("Nero Start") != null); if (text.Length == 0) { sprintAirDashMarker.Restore(); } else { sprintAirDashMarker.ApplyFamilyTarget(fsm.GetState(text)); } } catch (Exception) { } ((FsmStateAction)this).Finish(); } } private sealed class VergilReleaseDashCaptureAction : FsmStateAction { private static bool _warned; public override void OnEnter() { try { if (_instance == null) { ((FsmStateAction)this).Finish(); return; } HeroController instance = HeroController.instance; VergilChargeVariantState.ReleaseWasDashing = (Object)(object)instance != (Object)null && instance.cState != null && instance.cState.dashing; } catch (Exception ex) { if (!_warned) { _warned = true; VergilCrestFeature? instance2 = _instance; if (instance2 != null) { instance2._log.LogWarning((object)("[InsectMayCry][Vergil] release-dash capture failed: " + ex.GetType().Name + ": " + ex.Message)); } } } ((FsmStateAction)this).Finish(); } } private sealed class VergilChargeVariantPickAction : FsmStateAction { public override void OnEnter() { try { VergilCrestFeature? instance = _instance; Fsm fsm = ((FsmStateAction)this).Fsm; VergilNailArtsMarker vergilNailArtsMarker = ((fsm != null && (Object)(object)fsm.GameObject != (Object)null) ? fsm.GameObject.GetComponent() : null); if (instance == null || fsm == null) { ((FsmStateAction)this).Finish(); return; } if (!VergilChargeVariantState.IsImcEquipped()) { VergilChargeVariantState.Current = ChargeVariant.None; vergilNailArtsMarker?.RouteAnticFinish(fsm.GetState("Lunge?")); ((FsmStateAction)this).Finish(); return; } string text = HeroController.instance?.playerData?.CurrentCrestID; HeroActions val = ManagerSingleton.Instance?.inputActions; ChargeVariant chargeVariant = (VergilChargeVariantState.Current = ((val == null) ? ChargeVariant.Default : VergilCrestRules.ResolveChargeVariant(VergilChargeVariantState.ReleaseWasDashing, ((OneAxisInputControl)val.Up).IsPressed, ((OneAxisInputControl)val.Down).IsPressed))); FsmVariables variables = fsm.Variables; FsmString val2 = ((variables != null) ? variables.GetFsmString("Recoil Method") : null); if (val2 != null) { val2.Value = ""; } GameObject val3 = FindChargeInstance(text, chargeVariant); HeroController instance2 = HeroController.instance; bool flag = chargeVariant == ChargeVariant.Down && VergilCrestRules.IsVergilCrest(text) && instance2?.cState != null && !instance2.cState.onGround; if (flag) { val3 = ((text != null && _specById.TryGetValue(text, out CrestSpec value)) ? value.ChargeNone : null); } if ((Object)(object)val3 != (Object)null && fsm.Variables != null) { FsmGameObject fsmGameObject = fsm.Variables.GetFsmGameObject("Current Charge Slash"); if (fsmGameObject != null) { fsmGameObject.Value = val3; } } string text2 = (((chargeVariant == ChargeVariant.Down && VergilCrestRules.IsVergilCrest(text) && !flag) || (chargeVariant == ChargeVariant.Dash && VergilCrestRules.IsNeroCrest(text))) ? "Lunge?" : "Slash Recoil?"); vergilNailArtsMarker?.RouteAnticFinish(fsm.GetState(text2)); } catch (Exception) { } ((FsmStateAction)this).Finish(); } } private sealed class ImcBeastChargeAction : FsmStateAction { public override void OnEnter() { try { HeroController instance = HeroController.instance; if (instance?.playerData == null || VergilChargeVariantState.Current != ChargeVariant.Down || !VergilCrestRules.IsDanteCrest(instance.playerData.CurrentCrestID)) { return; } ApplyBeastChargeVelocity(instance); } catch (Exception) { } ((FsmStateAction)this).Finish(); } } private sealed class VergilDefaultChargeAction : FsmStateAction { private static bool _warned; public override void OnEnter() { try { HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null || instance.playerData == null || !VergilChargeVariantState.IsImcEquipped()) { ((FsmStateAction)this).Finish(); return; } string currentCrestID = instance.playerData.CurrentCrestID; switch (VergilChargeVariantState.Current) { case ChargeVariant.Default: if (VergilCrestRules.IsDanteCrest(currentCrestID) || VergilCrestRules.IsVergilCrest(currentCrestID)) { PlayParryCrossStitch(instance); } else if (VergilCrestRules.IsNeroCrest(currentCrestID)) { ThrowNeroSilkShot(instance); } break; case ChargeVariant.Down: if (VergilCrestRules.IsVergilCrest(currentCrestID) && instance.cState != null && !instance.cState.onGround) { instance.ActivateQuickening(); } break; } } catch (Exception ex) { WarnOnce(HeroController.instance, ex.Message); } ((FsmStateAction)this).Finish(); } private static void PlayParryCrossStitch(HeroController hero) { ToolItem toolByName = ToolItemManager.GetToolByName("Parry"); if ((Object)(object)toolByName == (Object)null) { WarnOnce(hero, "tool 'Parry' not found; cross-stitch skipped."); return; } if (!toolByName.IsUnlocked) { toolByName.Unlock((Action)null, (PopupFlags)3); } ThrowChargeTool(hero, toolByName, isSkill: true); } private static void ThrowNeroSilkShot(HeroController hero) { string text = _options?.NeroChargeShotToolName ?? "WebShot Forge"; ToolItem toolByName = ToolItemManager.GetToolByName(text); if ((Object)(object)toolByName == (Object)null) { WarnOnce(hero, "silk-shot tool '" + text + "' not found; free silk-shot skipped."); } else { ThrowChargeTool(hero, toolByName, isSkill: false); } } private static void ThrowChargeTool(HeroController hero, ToolItem tool, bool isSkill) { VergilChargeVariantState.FreeChargeToolName = tool.name; if (isSkill) { VergilChargeVariantState.CrossStitchRequested = true; VergilChargeVariantState.CrossStitchRequireRealParry = ResolveCrossStitchRequireRealParry(hero); VergilChargeVariantState.CrossStitchUntil = Time.timeAsDouble + (_options?.DanteChargeCrossStitchRequestTimeoutSeconds ?? 2.0); } else if (_options == null || _options.NeroChargeShotExplosionOnHit) { VergilChargeVariantState.ChargeExplosionToolName = tool.name; VergilChargeVariantState.ChargeExplosionUntil = Time.timeAsDouble + (_options?.NeroChargeShotExplosionWindowSeconds ?? 5.0); } try { hero.SetToolCooldown((float)(_options?.ChargeToolCooldownSeconds ?? 0.0)); SetWillThrowTool(hero, tool); ThrowToolReflect(hero, isAutoThrow: false); } finally { VergilChargeVariantState.FreeChargeToolName = null; } } private static bool ResolveCrossStitchRequireRealParry(HeroController hero) { if (hero?.playerData == null) { return false; } string currentCrestID = hero.playerData.CurrentCrestID; CrossStitchEffectKind kind = CrossStitchEffectKind.Normal; if (VergilCrestRules.IsDanteCrest(currentCrestID)) { AttackModuleOptions? options = _options; kind = ((options != null && options.DanteCrossStitchRuneEnabled) ? CrossStitchEffectKind.RuneRage : CrossStitchEffectKind.Sharpdart); } return CrossStitchEffectCatalog.FindByKind(kind)?.RequireRealParry ?? false; } private static void WarnOnce(HeroController? hero, string message) { if (!_warned) { _warned = true; VergilCrestFeature? instance = _instance; if (instance != null) { instance._log.LogWarning((object)("[InsectMayCry][Vergil] default charge skill: " + message)); } } } } private sealed class BeastChargeDecelDriver : MonoBehaviour { public float Deceleration = 0.85f; public float MaxDurationSeconds = 0.6f; public float StopThreshold = 1f; private Rigidbody2D? _rb; private bool _armed; private float _elapsed; public void Arm() { _armed = true; _elapsed = 0f; } private void Awake() { _rb = ((Component)this).GetComponent(); } private void FixedUpdate() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (_armed && !((Object)(object)_rb == (Object)null)) { _elapsed += Time.fixedDeltaTime; Vector2 linearVelocity = _rb.linearVelocity; linearVelocity.x *= Deceleration; _rb.linearVelocity = linearVelocity; if (Mathf.Abs(linearVelocity.x) < StopThreshold || _elapsed >= MaxDurationSeconds) { _armed = false; } } } } private sealed class VergilChargeLungeBeginAction : FsmStateAction { public override void OnEnter() { try { HeroController instance = HeroController.instance; if (instance?.playerData != null && (VergilCrestRules.IsDanteCrest(instance.playerData.CurrentCrestID) || VergilCrestRules.IsVergilCrest(instance.playerData.CurrentCrestID)) && VergilChargeVariantState.IsImcEquipped() && VergilChargeVariantState.Current == ChargeVariant.Dash) { VergilChargeVariantState.LungeActive = true; } } catch (Exception) { } ((FsmStateAction)this).Finish(); } } private sealed class VergilChargeVariantClearAction : FsmStateAction { public override void OnEnter() { VergilChargeVariantState.LungeActive = false; VergilChargeVariantState.Current = ChargeVariant.None; VergilChargeVariantState.ReleaseWasDashing = false; ((FsmStateAction)this).Finish(); } } [CompilerGenerated] private static class <>O { public static FsmEdit <0>__DashSlashFsmEdit; public static FsmEdit <1>__DownSlashFsmEdit; } private const string NeedleforgeType = "Needleforge.NeedleforgePlugin, Needleforge"; private const string LocalizationSheet = "Mods.local.crestsevolve"; private const string SilhouetteResource = "VergilCrestSilhouette.png"; private const string GlowResource = "VergilCrestGlow.png"; private const string ParryToolName = "Parry"; private readonly ManualLogSource _log; private readonly Harmony _harmony; private static VergilCrestFeature? _instance; private static AttackModuleOptions? _options; private static readonly List _specs = new List(); private static readonly Dictionary _specById = new Dictionary(StringComparer.Ordinal); private bool _disposed; private static FieldInfo? _willThrowToolField; private static MethodInfo? _throwToolMethod; private static bool _throwToolWarned; public static VergilCrestFeature? Instance => _instance; public VergilCrestFeature(ManualLogSource log, Harmony harmony) { _log = log ?? throw new ArgumentNullException("log"); _harmony = harmony ?? throw new ArgumentNullException("harmony"); } public static VergilCrestFeature Install(ManualLogSource log, Harmony harmony, AttackModuleOptions? options) { if (_instance != null) { return _instance; } _options = options; VergilCrestFeature vergilCrestFeature = new VergilCrestFeature(log, harmony); try { vergilCrestFeature.InstallCore(); } catch (TypeLoadException ex) { log.LogWarning((object)("[InsectMayCry][Vergil] Needleforge unavailable: " + ex.Message)); } catch (Exception ex2) { log.LogWarning((object)("[InsectMayCry][Vergil] install failed: " + ex2.GetType().Name + ": " + ex2.Message)); } _instance = vergilCrestFeature; log.LogInfo((object)"[InsectMayCry][Vergil] crest family feature installed (Dante/Vergil/Nero)."); return vergilCrestFeature; } private void InstallCore() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown DvnSlotLayouts.RegisterAll(delegate(string message) { _log.LogWarning((object)message); }); MethodInfo methodInfo = AccessTools.Method(typeof(PlayMakerFSM), "Start", (Type[])null, (Type[])null); if (methodInfo != null) { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilCrestFeature), "OnFsmStart", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } VergilChargeToolPatches.Install(_harmony, _log); VergilCrestTextPatch.Install(_harmony, _log); if (Type.GetType("Needleforge.NeedleforgePlugin, Needleforge", throwOnError: false) == null) { _log.LogWarning((object)"[InsectMayCry][Vergil] Needleforge not found; Dante/Vergil/Nero not registered."); return; } RegisterCrest("Dante", "Dante", "DanteDesc", "001.png"); RegisterCrest("Vergil", "Vergil", "VergilDesc", "002.png"); RegisterCrest("Nero", "Nero", "NeroDesc", "003.png"); _log.LogInfo((object)($"[InsectMayCry][Vergil] registered {_specs.Count} crests: " + string.Join(", ", VergilCrestRules.FamilyCrestIds))); } private void RegisterCrest(string crestId, string nameKey, string descKey, string iconResource) { //IL_0019: 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) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) try { Sprite[] array = LoadSprites(iconResource); CrestData val = NeedleforgePlugin.AddCrest(crestId, new LocalisedString { Key = nameKey, Sheet = "Mods.local.crestsevolve" }, new LocalisedString { Key = descKey, Sheet = "Mods.local.crestsevolve" }, array[0], array[1], array[2]); CrestSpec spec = new CrestSpec { Id = crestId, Crest = val }; spec.MovesetHandler = delegate { OnMovesetInitialized(spec); }; val.Moveset.OnInitialized += spec.MovesetHandler; SetupSlots(val); val.HudFrame.Preset = (VanillaCrest)2; _specs.Add(spec); _specById[crestId] = spec; _log.LogInfo((object)("[InsectMayCry][Vergil] crest registered (Needleforge): " + crestId)); } catch (TypeLoadException ex) { _log.LogWarning((object)("[InsectMayCry][Vergil] Needleforge unavailable for " + crestId + ": " + ex.Message)); } catch (Exception ex2) { _log.LogWarning((object)("[InsectMayCry][Vergil] crest registration failed for " + crestId + ": " + ex2.GetType().Name + ": " + ex2.Message)); } } private void SetupSlots(CrestData crest) { //IL_0036: 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_0050: 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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) if (crest != null) { SecondToolSetLayout secondToolSetLayout = SecondToolSetRegistry.TryGet(crest.name); if (secondToolSetLayout != null) { ApplyRegisteredLayout(crest, secondToolSetLayout); return; } float num = 0f; crest.AddSkillSlot((AttackToolBinding)1, new Vector2(-0.9f, 1.85f + num), false); crest.AddSkillSlot((AttackToolBinding)2, new Vector2(0.93f, 1.93f + num), false); crest.AddRedSlot((AttackToolBinding)0, new Vector2(2.1f, 0.13f + num), false); crest.AddYellowSlot(new Vector2(-2.1f, 0.13f + num), false); crest.AddBlueSlot(new Vector2(-1.94f, -1.67f + num), false); crest.AddBlueSlot(new Vector2(1.94f, -1.67f + num), false); crest.AddBlueSlot(new Vector2(0f, -2.95f + num), false); crest.ApplyAutoSlotNavigation(true, 60f, (Vector2?)new Vector2(1.25f, 0.75f)); } } private void ApplyRegisteredLayout(CrestData crest, SecondToolSetLayout layout) { //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) if (layout.NativeSlots != null) { foreach (SecondSlotSpec nativeSlot in layout.NativeSlots) { AddAttackSlot(crest, nativeSlot, "native"); } } if (layout.SecondSlots != null) { foreach (SecondSlotSpec secondSlot in layout.SecondSlots) { AddAttackSlot(crest, secondSlot, "second"); } } if (layout.UtilitySlots != null) { Vector2 val = default(Vector2); foreach (UtilitySlotSpec utilitySlot in layout.UtilitySlots) { ((Vector2)(ref val))..ctor(utilitySlot.X, utilitySlot.Y); if (utilitySlot.Kind == UtilitySlotKind.Yellow) { crest.AddYellowSlot(val, utilitySlot.IsLocked); } else { crest.AddBlueSlot(val, utilitySlot.IsLocked); } } } crest.ApplyAutoSlotNavigation(true, 60f, (Vector2?)new Vector2(1.25f, 0.75f)); _log.LogInfo((object)("[InsectMayCry][Vergil] " + crest.name + " registered layout " + $"({layout.NativeSlots?.Count ?? 0} native / " + $"{layout.SecondSlots?.Count ?? 0} second / " + $"{layout.UtilitySlots?.Count ?? 0} utility slots).")); } private static void AddAttackSlot(CrestData crest, SecondSlotSpec spec, string group) { //IL_001c: 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_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) AttackToolBinding val = (AttackToolBinding)(spec.Binding switch { SecondToolBinding.Up => 1, SecondToolBinding.Neutral => 0, SecondToolBinding.Down => 2, _ => 0, }); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(spec.X, spec.Y); if (spec.Kind == SecondSlotKind.Red) { crest.AddRedSlot(val, val2, spec.IsLocked); } else { crest.AddSkillSlot(val, val2, spec.IsLocked); } } private void OnMovesetInitialized(CrestSpec spec) { //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Expected O, but got Unknown //IL_045d: Unknown result type (might be due to invalid IL or missing references) //IL_0515: Unknown result type (might be due to invalid IL or missing references) //IL_051f: Expected O, but got Unknown //IL_053c: Unknown result type (might be due to invalid IL or missing references) //IL_0541: Unknown result type (might be due to invalid IL or missing references) //IL_0477: Unknown result type (might be due to invalid IL or missing references) EnsureCrestWeaponCollectionsLoaded(); try { HeroController instance = HeroController.instance; CrestData? crest = spec.Crest; MovesetData val = ((crest != null) ? crest.Moveset : null); ConfigGroup val2 = ((val != null) ? val.ConfigGroup : null); if ((Object)(object)instance == (Object)null || val == null || val2 == null || (Object)(object)val2.ActiveRoot == (Object)null) { _log.LogWarning((object)("[InsectMayCry][Vergil] moveset not initialized (" + spec.Id + "; hero/config group missing).")); return; } Transform transform = val2.ActiveRoot.transform; HeroConfigNeedleforge heroConfig = val.HeroConfig; ConfigurePrimaryAttacks(spec, instance, transform, val2, heroConfig); Transform val3 = ((Component)instance).transform.Find("Attacks/Charge Slash Witch"); if ((Object)(object)val3 != (Object)null) { spec.ChargeWitch = Clone(((Component)val3).gameObject, transform, spec.Id + " Charge Witch"); val2.ChargeSlash = spec.ChargeWitch; if ((Object)(object)spec.ChargeWitch != (Object)null) { WitchChargeModule.AttachImcInstance(spec.ChargeWitch); } } ToolCrest crestByName = ToolItemManager.GetCrestByName("Witch"); HeroControllerConfig val4 = ((crestByName != null) ? crestByName.HeroConfig : null); object obj; if (!VergilCrestRules.IsNeroCrest(spec.Id)) { obj = val4; } else { ToolCrest crestByName2 = ToolItemManager.GetCrestByName("Toolmaster"); obj = ((crestByName2 != null) ? crestByName2.HeroConfig : null); } HeroControllerConfig val5 = (HeroControllerConfig)obj; if ((Object)(object)heroConfig != (Object)null && (Object)(object)val5 != (Object)null) { heroConfig.SetChargedSlashFields((int?)val5.ChargeSlashChain, (float?)val5.ChargeSlashLungeSpeed, (float?)val5.ChargeSlashLungeDeceleration, (bool?)val5.ChargeSlashRecoils, (bool?)null); } if (VergilCrestRules.IsVergilCrest(spec.Id)) { Transform val6 = ((Component)instance).transform.Find("Attacks/Witch/Dash Stab Parent"); if ((Object)(object)val6 != (Object)null) { spec.DashStab = Clone(((Component)val6).gameObject, transform, spec.Id + " Dash Stab Witch"); if ((Object)(object)spec.DashStab != (Object)null) { val2.DashStab = spec.DashStab; VergilGroundDashChargeModule.RegisterTarget(spec.Id, spec.DashStab.transform); } } if ((Object)(object)heroConfig != (Object)null && (Object)(object)val4 != (Object)null) { heroConfig.SetDashStabFields((float?)val4.DashStabTime, (float?)val4.DashStabSpeed, (float?)val4.DashStabBounceJumpSpeed, (bool?)val4.ForceShortDashStabBounce); FieldInfo fieldInfo = AccessTools.Field(typeof(HeroControllerConfig), "heroAnimOverrideLib"); if (fieldInfo != null) { object? value = fieldInfo.GetValue(val4); tk2dSpriteAnimation val7 = (tk2dSpriteAnimation)((value is tk2dSpriteAnimation) ? value : null); if (val7 != null) { ShamanDashSlashModule.InstallMergedHeroAnimLib((HeroControllerConfig)(object)heroConfig, val7); } } } } if (VergilCrestRules.IsNeroCrest(spec.Id)) { ToolCrest crestByName3 = ToolItemManager.GetCrestByName("Reaper"); HeroControllerConfig val8 = ((crestByName3 != null) ? crestByName3.HeroConfig : null); if ((Object)(object)heroConfig != (Object)null && (Object)(object)val8 != (Object)null) { object obj2 = <>O.<0>__DashSlashFsmEdit; if (obj2 == null) { FsmEdit val9 = ReaperDashSlashModule.DashSlashFsmEdit; <>O.<0>__DashSlashFsmEdit = val9; obj2 = (object)val9; } heroConfig.DashSlashFsmEdit = (FsmEdit)obj2; FieldInfo fieldInfo2 = AccessTools.Field(typeof(HeroControllerConfig), "heroAnimOverrideLib"); if (fieldInfo2 != null) { object? value2 = fieldInfo2.GetValue(val8); tk2dSpriteAnimation val10 = (tk2dSpriteAnimation)((value2 is tk2dSpriteAnimation) ? value2 : null); if (val10 != null) { fieldInfo2.SetValue(heroConfig, val10); } } } } Transform obj3 = ((Component)instance).transform.Find("Attacks/Charge Slash Basic"); spec.ChargeHunter = Clone((obj3 != null) ? ((Component)obj3).gameObject : null, transform, spec.Id + " Charge Hunter"); Transform obj4 = ((Component)instance).transform.Find("Attacks/Charge Slash Shaman"); spec.ChargeShaman = Clone((obj4 != null) ? ((Component)obj4).gameObject : null, transform, spec.Id + " Charge Shaman"); Transform obj5 = ((Component)instance).transform.Find("Attacks/Charge Slash Wanderer"); spec.ChargeWanderer = Clone((obj5 != null) ? ((Component)obj5).gameObject : null, transform, spec.Id + " Charge Wanderer"); Transform obj6 = ((Component)instance).transform.Find("Attacks/Charge Slash Warrior"); spec.ChargeBeast = Clone((obj6 != null) ? ((Component)obj6).gameObject : null, transform, spec.Id + " Charge Beast"); Transform obj7 = ((Component)instance).transform.Find("Attacks/Charge Slash Scythe"); spec.ChargeReaper = Clone((obj7 != null) ? ((Component)obj7).gameObject : null, transform, spec.Id + " Charge Reaper"); if ((Object)(object)spec.ChargeReaper != (Object)null) { Transform transform2 = spec.ChargeReaper.transform; transform2.localScale *= _options?.NeroChargeSlashReaperScaleMultiplier ?? 1.3f; } BeastDownslashModule.SetUpMoveset(spec.Id, transform, instance); ReaperDashSlashModule.SetUpMoveset(spec.Id, transform, instance); if (VergilCrestRules.IsVergilCrest(spec.Id)) { Transform obj8 = ((Component)instance).transform.Find("Attacks/Shaman/DashSlash"); spec.ChargeSpinSlash = Clone((obj8 != null) ? ((Component)obj8).gameObject : null, transform, spec.Id + " Charge Spin Slash"); if ((Object)(object)spec.ChargeSpinSlash != (Object)null) { ShamanDashSlashModule.RegisterTarget(spec.Id, spec.ChargeSpinSlash.transform); } } spec.ChargeNone = new GameObject(spec.Id + " Charge None"); spec.ChargeNone.transform.SetParent(transform, false); spec.ChargeNone.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); spec.ChargeNone.SetActive(false); if (VergilCrestRules.IsNeroCrest(spec.Id)) { Transform obj9 = ((Component)instance).transform.Find("Attacks/Charge Slash Toolmaster"); spec.ChargeDash = Clone((obj9 != null) ? ((Component)obj9).gameObject : null, transform, spec.Id + " Charge Architect"); } if ((Object)(object)spec.ChargeWanderer != (Object)null && (Object)(object)spec.ChargeDash == (Object)null) { WandererChargeAttackModule.AttachImcInstance(spec.ChargeWanderer); } if ((Object)(object)spec.ChargeShaman != (Object)null) { ShamanChargeSlashModule.RegisterTarget(spec.Id, spec.ChargeShaman.transform); } _log.LogInfo((object)("[InsectMayCry][Vergil] moveset assembled (" + spec.Id + ": primary slash/downslash per mapping / dash per mapping / witch charge + variants).")); } catch (Exception ex) { _log.LogWarning((object)("[InsectMayCry][Vergil] moveset setup failed (" + spec.Id + "): " + ex.GetType().Name + ": " + ex.Message)); } } private void ConfigurePrimaryAttacks(CrestSpec spec, HeroController hero, Transform root, ConfigGroup cg, HeroConfigNeedleforge? cfg) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Expected I4, but got Unknown //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Invalid comparison between Unknown and I4 //IL_034d: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Expected O, but got Unknown DownSlashTypes val = (DownSlashTypes)1; HeroControllerConfig val2 = null; string text; string text2; string text3; switch (spec.Id) { default: return; case "Dante": { text = "Warrior"; text2 = "Warrior"; text3 = "Warrior/DownSlash"; val = (DownSlashTypes)2; ToolCrest crestByName2 = ToolItemManager.GetCrestByName("Warrior"); val2 = ((crestByName2 != null) ? crestByName2.HeroConfig : null); break; } case "Vergil": text = "Default"; text2 = "Default"; text3 = "Shaman/DownSlash"; val = (DownSlashTypes)1; break; case "Nero": { text = "Scythe"; text2 = "Scythe"; text3 = "Wanderer/DownSlash"; val = (DownSlashTypes)1; ToolCrest crestByName = ToolItemManager.GetCrestByName("Reaper"); val2 = ((crestByName != null) ? crestByName.HeroConfig : null); break; } } Transform obj = ((Component)hero).transform.Find("Attacks/" + text + "/Slash"); spec.NormalSlash = Clone((obj != null) ? ((Component)obj).gameObject : null, root, spec.Id + " Slash"); if ((Object)(object)spec.NormalSlash != (Object)null) { cg.NormalSlashObject = spec.NormalSlash; } Transform obj2 = ((Component)hero).transform.Find("Attacks/" + text2 + "/AltSlash"); spec.AltSlash = Clone((obj2 != null) ? ((Component)obj2).gameObject : null, root, spec.Id + " AltSlash"); if ((Object)(object)spec.AltSlash != (Object)null) { cg.AlternateSlashObject = spec.AltSlash; } Transform obj3 = ((Component)hero).transform.Find("Attacks/" + text3); spec.DownSlash = Clone((obj3 != null) ? ((Component)obj3).gameObject : null, root, spec.Id + " DownSlash"); if ((Object)(object)spec.DownSlash != (Object)null) { cg.DownSlashObject = spec.DownSlash; } if (VergilCrestRules.IsNeroCrest(spec.Id)) { Transform obj4 = ((Component)hero).transform.Find("Attacks/Wanderer/DownSlashAlt"); spec.AltDownSlash = Clone((obj4 != null) ? ((Component)obj4).gameObject : null, root, spec.Id + " AltDownSlash"); if ((Object)(object)spec.AltDownSlash != (Object)null) { cg.AltDownSlashObject = spec.AltDownSlash; } } if ((Object)(object)cfg == (Object)null) { return; } AccessTools.Field(typeof(HeroControllerConfig), "downSlashType")?.SetValue(cfg, (int)val); if ((int)val == 2 && VergilCrestRules.IsDanteCrest(spec.Id)) { FieldInfo fieldInfo = AccessTools.Field(typeof(HeroControllerConfig), "downSlashEvent"); if (fieldInfo != null) { fieldInfo.SetValue(cfg, "DANTE DOWNSLASH"); } object obj5 = <>O.<1>__DownSlashFsmEdit; if (obj5 == null) { FsmEdit val3 = BeastDownslashModule.DownSlashFsmEdit; <>O.<1>__DownSlashFsmEdit = val3; obj5 = (object)val3; } cfg.DownSlashFsmEdit = (FsmEdit)obj5; FieldInfo fieldInfo2 = AccessTools.Field(typeof(HeroControllerConfig), "heroAnimOverrideLib"); if (fieldInfo2 != null && (Object)(object)val2 != (Object)null) { object? value = fieldInfo2.GetValue(val2); tk2dSpriteAnimation val4 = (tk2dSpriteAnimation)((value is tk2dSpriteAnimation) ? value : null); if (val4 != null) { fieldInfo2.SetValue(cfg, val4); } } } if (!((Object)(object)val2 == (Object)null)) { cfg.SetAttackFields((float?)val2.AttackDuration, (float?)val2.AttackRecoveryTime, (float?)val2.AttackCooldownTime, (float?)val2.QuickAttackSpeedMult, (float?)val2.QuickAttackCooldownTime); if ((int)val == 0) { float? num = val2.DownSpikeAnticTime; float? num2 = val2.DownSpikeTime; float? num3 = val2.DownspikeRecoveryTime; bool? flag = val2.DownspikeThrusts; float? num4 = val2.DownspikeSpeed; bool? flag2 = val2.DownspikeBurstEffect; cfg.SetDownspikeFields(num, num2, num3, flag, num4, (Vector2?)null, (Vector2?)null, flag2); } } } private static void EnsureCrestWeaponCollectionsLoaded() { try { string[] array = new string[4] { "herocollections_assets_crestbeast.bundle", "herocollections_assets_crestreaper.bundle", "herocollections_assets_crestshaman.bundle", "herocollections_assets_crestwitch.bundle" }; foreach (string text in array) { bool flag = false; string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles()) { if ((Object)(object)allLoadedAssetBundle != (Object)null && (string.Equals(((Object)allLoadedAssetBundle).name, text, StringComparison.OrdinalIgnoreCase) || ((Object)allLoadedAssetBundle).name.IndexOf(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) >= 0)) { flag = true; break; } } if (flag) { continue; } string text2 = Path.Combine(Application.streamingAssetsPath, "aa/StandaloneWindows64", text); if (!File.Exists(text2)) { VergilCrestFeature? instance = _instance; if (instance != null) { instance._log.LogWarning((object)("[InsectMayCry][Vergil] weapon collection not found at " + text2)); } continue; } AssetBundle val = AssetBundle.LoadFromFile(text2); if ((Object)(object)val != (Object)null) { VergilCrestFeature? instance2 = _instance; if (instance2 != null) { instance2._log.LogInfo((object)("[InsectMayCry][Vergil] weapon collection loaded: " + ((Object)val).name + ".")); } } } } catch (Exception ex) { VergilCrestFeature? instance3 = _instance; if (instance3 != null) { instance3._log.LogWarning((object)("[InsectMayCry][Vergil] weapon collection load failed: " + ex.GetType().Name + ": " + ex.Message)); } } } private static GameObject? Clone(GameObject? source, Transform parent, string name) { if ((Object)(object)source == (Object)null) { return null; } GameObject obj = Object.Instantiate(source, parent); ((Object)obj).name = name; return obj; } internal static GameObject? FindChargeInstance(string? crestId, ChargeVariant variant) { if (crestId == null || !_specById.TryGetValue(crestId, out CrestSpec value)) { return null; } return value.GetChargeInstance(variant); } public static void OnFsmStart(PlayMakerFSM __instance) { VergilCrestFeature instance = _instance; if (instance == null || (Object)(object)__instance == (Object)null || __instance.Fsm == null) { return; } if (__instance.FsmName == "Sprint") { InstallAirSprintDash(instance, __instance); } else if (__instance.FsmName == "Silk Specials") { InstallCrossStitchTrigger(instance, __instance); } else { if (__instance.FsmName != "Nail Arts" || (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 { VergilNailArtsMarker vergilNailArtsMarker = ((Component)__instance).gameObject.AddComponent(); FsmState state = FsmUtil.GetState(__instance, "Antic"); FsmState state2 = FsmUtil.GetState(__instance, "Can Nail Art?"); if (state2 != null) { FsmUtil.InsertAction(state2, 0, (FsmStateAction)(object)new VergilReleaseDashCaptureAction()); } if (state != null) { FsmUtil.InsertAction(state, 0, (FsmStateAction)(object)new VergilChargeVariantPickAction()); } FsmState state3 = FsmUtil.GetState(__instance, "Do Slash"); if (state3 != null) { FsmUtil.InsertAction(state3, 0, (FsmStateAction)(object)new VergilDefaultChargeAction()); } if (state3 != null) { FsmUtil.InsertAction(state3, 1, (FsmStateAction)(object)new VergilChargeLungeBeginAction()); } FsmState state4 = FsmUtil.GetState(__instance, "Set Finished"); if (state4 != null) { FsmUtil.InsertAction(state4, 0, (FsmStateAction)(object)new VergilChargeVariantClearAction()); } vergilNailArtsMarker.CaptureAnticFinish(state); if (state3 != null) { FsmUtil.InsertAction(state3, 2, (FsmStateAction)(object)new ImcBeastChargeAction()); } HeroController instance2 = HeroController.instance; WandererChargeAttackModule.AttachHeroLungeDriver((instance2 != null) ? ((Component)instance2).gameObject : null); instance._log.LogInfo((object)"[InsectMayCry][Vergil] Nail Arts FSM hooked (D/V/N charge variants)."); } catch (Exception ex) { instance._log.LogWarning((object)("[InsectMayCry][Vergil] FSM hook failed: " + ex.GetType().Name + ": " + ex.Message)); } } } private static void InstallCrossStitchTrigger(VergilCrestFeature feature, PlayMakerFSM fsm) { if ((Object)(object)((Component)fsm).gameObject == (Object)null || !((Object)((Component)fsm).gameObject).name.StartsWith("Hero_Hornet", StringComparison.Ordinal) || (Object)(object)((Component)fsm).GetComponent() != (Object)null) { return; } try { FsmState state = FsmUtil.GetState(fsm, "Parry Stance"); if (state == null) { feature._log.LogWarning((object)"[InsectMayCry][Vergil] Silk Specials 'Parry Stance' not found; cross-stitch trigger skipped."); return; } FsmUtil.InsertAction(state, 0, (FsmStateAction)(object)new CrossStitchTriggerAction()); FsmState state2 = FsmUtil.GetState(fsm, "Change Facing?"); if (state2 == null) { feature._log.LogWarning((object)"[InsectMayCry][Vergil] Silk Specials 'Change Facing?' not found; rune-rage redirect disabled."); } else { FsmUtil.InsertAction(state2, 0, (FsmStateAction)(object)new CrossStitchEffectRedirectAction()); } ((Component)fsm).gameObject.AddComponent(); feature._log.LogInfo((object)"[InsectMayCry][Vergil] Silk Specials FSM hooked: D charge cross-stitch trigger (+ rune-rage redirect)."); } catch (Exception ex) { feature._log.LogWarning((object)("[InsectMayCry][Vergil] Silk Specials hook failed: " + ex.GetType().Name + ": " + ex.Message)); } } private static void InstallAirSprintDash(VergilCrestFeature feature, PlayMakerFSM fsm) { if ((Object)(object)((Component)fsm).gameObject == (Object)null || !((Object)((Component)fsm).gameObject).name.StartsWith("Hero_Hornet", StringComparison.Ordinal) || (Object)(object)((Component)fsm).GetComponent() != (Object)null) { return; } try { SprintAirDashMarker sprintAirDashMarker = ((Component)fsm).gameObject.AddComponent(); int num = 0; string[] array = new string[3] { "Air Sprint L", "Air Sprint R", "Air Sprint Loop" }; foreach (string text in array) { FsmState state = FsmUtil.GetState(fsm, text); if (state != null && sprintAirDashMarker.CaptureAttackTarget(state)) { FsmUtil.InsertAction(state, 0, (FsmStateAction)(object)new AirDashGateAction()); num++; } } if (num > 0) { feature._log.LogInfo((object)$"[InsectMayCry][Vergil] Sprint FSM hooked: air dash attack enabled for D/V/N ({num}/3 states)."); } else { feature._log.LogWarning((object)"[InsectMayCry][Vergil] Sprint air states not found; air dash attack disabled."); } } catch (Exception ex) { feature._log.LogWarning((object)("[InsectMayCry][Vergil] Sprint air-dash hook failed: " + ex.GetType().Name + ": " + ex.Message)); } } private Sprite?[] LoadSprites(string iconResource) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) Assembly assembly = typeof(VergilCrestFeature).Assembly; return (Sprite?[])(object)new Sprite[3] { LoadSprite(assembly, iconResource, new Vector2(0.5f, 0.44f), 300f), LoadSprite(assembly, "VergilCrestSilhouette.png", new Vector2(0.5f, 0.44f), 100f), LoadSprite(assembly, "VergilCrestGlow.png", new Vector2(0.5f, 0.44f), 140f) }; } private static Sprite? LoadSprite(Assembly asm, string resourceName, Vector2 pivot, float ppu) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) try { string text = Array.Find(asm.GetManifestResourceNames(), (string n) => n.EndsWith(resourceName, StringComparison.OrdinalIgnoreCase)); if (text == null) { return null; } using Stream stream = asm.GetManifestResourceStream(text); if (stream == null) { return null; } byte[] array = new byte[stream.Length]; stream.Read(array, 0, array.Length); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, array)) { return null; } return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), pivot, ppu); } catch (Exception) { return null; } } public void Dispose() { if (_disposed) { return; } _disposed = true; foreach (CrestSpec spec in _specs) { try { CrestData? crest = spec.Crest; if (((crest != null) ? crest.Moveset : null) != null && spec.MovesetHandler != null) { spec.Crest.Moveset.OnInitialized -= spec.MovesetHandler; } } catch (Exception ex) { _log.LogWarning((object)("[InsectMayCry][Vergil] unsubscribe moveset failed (" + spec.Id + "): " + ex.GetType().Name + ": " + ex.Message)); } } _specs.Clear(); _specById.Clear(); if (_instance == this) { _instance = null; } } private static void SetWillThrowTool(HeroController hero, ToolItem tool) { if (_willThrowToolField == null) { _willThrowToolField = AccessTools.Field(typeof(HeroController), "willThrowTool"); } if (_willThrowToolField == null) { WarnThrowTool("HeroController.willThrowTool field not found."); } else { _willThrowToolField.SetValue(hero, tool); } } private static void ThrowToolReflect(HeroController hero, bool isAutoThrow) { if (_throwToolMethod == null) { _throwToolMethod = AccessTools.Method(typeof(HeroController), "ThrowTool", new Type[1] { typeof(bool) }, (Type[])null); } if (_throwToolMethod == null) { WarnThrowTool("HeroController.ThrowTool(bool) not found."); return; } _throwToolMethod.Invoke(hero, new object[1] { isAutoThrow }); } private static void WarnThrowTool(string message) { if (!_throwToolWarned) { _throwToolWarned = true; VergilCrestFeature? instance = _instance; if (instance != null) { instance._log.LogWarning((object)("[InsectMayCry][Vergil] throw tool: " + message)); } } } private static void ApplyBeastChargeVelocity(HeroController hero) { //IL_0043: 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) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) Rigidbody2D component = ((Component)hero).GetComponent(); if (!((Object)(object)component == (Object)null)) { float num = _options?.DanteBeastChargeSpeedX ?? 50f; float num2 = _options?.DanteBeastChargeSpeedY ?? 20f; float num3 = ((((Component)hero).transform.localScale.x > 0f) ? 1f : (-1f)); Vector2 linearVelocity = component.linearVelocity; linearVelocity.x = num3 * (0f - num); linearVelocity.y = 0f - num2; component.linearVelocity = linearVelocity; BeastChargeDecelDriver beastChargeDecelDriver = ((Component)hero).GetComponent(); if ((Object)(object)beastChargeDecelDriver == (Object)null) { beastChargeDecelDriver = ((Component)hero).gameObject.AddComponent(); } beastChargeDecelDriver.Deceleration = _options?.DanteBeastChargeDeceleration ?? 0.85f; beastChargeDecelDriver.MaxDurationSeconds = _options?.DanteBeastChargeMaxDurationSeconds ?? 0.6f; beastChargeDecelDriver.StopThreshold = _options?.DanteBeastChargeStopThreshold ?? 1f; beastChargeDecelDriver.Arm(); } } } public enum ChargeVariant { None, Default, Up, Down, Dash } public static class VergilCrestRules { public const string DanteCrestId = "Dante"; public const string VergilCrestId = "Vergil"; public const string NeroCrestId = "Nero"; public const string CrestId = "Vergil"; public static readonly string[] FamilyCrestIds = new string[3] { "Dante", "Vergil", "Nero" }; public static bool IsVergilCrest(string? crestId) { return string.Equals(crestId, "Vergil", StringComparison.Ordinal); } public static bool IsDanteCrest(string? crestId) { return string.Equals(crestId, "Dante", StringComparison.Ordinal); } public static bool IsNeroCrest(string? crestId) { return string.Equals(crestId, "Nero", StringComparison.Ordinal); } public static bool IsImcCrest(string? crestId) { if (!IsDanteCrest(crestId) && !IsVergilCrest(crestId)) { return IsNeroCrest(crestId); } return true; } public static bool IsHunterDashCrest(string? crestId) { return IsDanteCrest(crestId); } public static ChargeVariant ResolveChargeVariant(bool dash, bool up, bool down) { if (!dash) { if (!up) { if (!down) { return ChargeVariant.Default; } return ChargeVariant.Down; } return ChargeVariant.Up; } return ChargeVariant.Dash; } public static string ResolveAirDashTarget(string? crestId, bool shamanDashSlashEnabled, bool shamanAnticAvailable, bool neroStartAvailable) { if (IsDanteCrest(crestId)) { return "Dash Stab Dir"; } if (IsVergilCrest(crestId)) { if (!(shamanDashSlashEnabled && shamanAnticAvailable)) { return "Dash Stab Dir"; } return "Shaman Antic"; } if (IsNeroCrest(crestId)) { if (!neroStartAvailable) { return "Dash Stab Dir"; } return "Nero Start"; } return ""; } } public static class VergilCrestTextPatch { private static readonly Dictionary _textsZh; private static readonly Dictionary _textsEn; private static bool _installed; static VergilCrestTextPatch() { _textsZh = new Dictionary(StringComparer.OrdinalIgnoreCase); _textsEn = new Dictionary(StringComparer.OrdinalIgnoreCase); AddZh("Vergil", "Vergil", "Vergil:猎手普攻、萨满下劈、女巫 3 连冲刺;蓄力变体(默认=免费跳蚤蜜酿、上=萨满远程剑气、下=女巫放大转圈、冲刺=萨满回旋斩加强)。"); AddZh("Dante", "Dante", "Dante:野兽普攻、垂直野兽下劈(无前进速度)、猎手穿透冲刺;蓄力变体(默认=免费十字绣、上=猎手双风刃、下=野兽蓄力斩、冲刺=漫步者突进)。"); AddZh("Nero", "Nero", "Nero:收割者普攻、漫步者下劈、收割者冲刺;蓄力变体(默认=免费丝弹1(熔炉之女)+命中燃爆、上=收割者蓄力斩放大、下=十字斩、冲刺=建筑师钻头突进)。"); AddEn("Vergil", "Vergil", "Vergil: Hunter slash, Shaman downslash, Witch 3-hit dash; charge variants (default=free Flea Brew, up=Shaman long-range wave, down=Witch amplified spin, dash=Shaman spin slash enhanced)."); AddEn("Dante", "Dante", "Dante: Beast slash, vertical Beast downslash (no forward velocity), Hunter piercing dash; charge variants (default=free Cross Stitch, up=Hunter wind blades, down=Beast charge slash, dash=Wanderer lunge)."); AddEn("Nero", "Nero", "Nero: Reaper slash, Wanderer downslash, Reaper dash; charge variants (default=free silk-shot 1 (Forge Daughter) + explosion on hit, up=Reaper charge slash amplified, down=Cross Slash, dash=Architect drill lunge)."); } private static void AddZh(string name, string display, string desc) { _textsZh["Mods.local.crestsevolve|" + name] = display; _textsZh["Mods.local.crestsevolve|" + name + "Desc"] = desc; } private static void AddEn(string name, string display, string desc) { _textsEn["Mods.local.crestsevolve|" + name] = display; _textsEn["Mods.local.crestsevolve|" + name + "Desc"] = desc; } public static void Install(Harmony harmony, ManualLogSource log) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown if (_installed) { return; } _installed = true; MethodInfo methodInfo = AccessTools.Method(typeof(Language), "Has", new Type[2] { typeof(string), typeof(string) }, (Type[])null); if (methodInfo == null) { log.LogWarning((object)"[InsectMayCry][Vergil] Language.Has(string,string) not found; crest text fallback disabled."); return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilCrestTextPatch), "HasPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo2 = AccessTools.Method(typeof(Language), "Get", new Type[2] { typeof(string), typeof(string) }, (Type[])null); if (methodInfo2 == null) { log.LogWarning((object)"[InsectMayCry][Vergil] Language.Get(string,string) not found; crest text fallback disabled."); return; } harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilCrestTextPatch), "GetPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); log.LogInfo((object)"[InsectMayCry][Vergil] crest display text fallback installed (V/D/N, Has+Get)."); } public static bool HasPrefix(string key, string sheetTitle, ref bool __result) { if (_textsZh.Count == 0) { return true; } if (IsKnownKey(sheetTitle, key)) { __result = true; return false; } return true; } public static bool GetPrefix(string key, string sheetTitle, ref string __result) { if (_textsZh.Count == 0) { return true; } if ((IsChineseLanguage() ? _textsZh : _textsEn).TryGetValue(sheetTitle + "|" + key, out string value)) { __result = value; return false; } return true; } private static bool IsKnownKey(string? sheetTitle, string? key) { if (sheetTitle == null || key == null) { return false; } if (!string.Equals(sheetTitle, "Mods.local.crestsevolve", StringComparison.OrdinalIgnoreCase)) { return false; } if (!string.Equals(key, "Vergil", StringComparison.OrdinalIgnoreCase) && !string.Equals(key, "VergilDesc", StringComparison.OrdinalIgnoreCase) && !string.Equals(key, "Dante", StringComparison.OrdinalIgnoreCase) && !string.Equals(key, "DanteDesc", StringComparison.OrdinalIgnoreCase) && !string.Equals(key, "Nero", StringComparison.OrdinalIgnoreCase)) { return string.Equals(key, "NeroDesc", StringComparison.OrdinalIgnoreCase); } return true; } private static bool IsChineseLanguage() { //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) try { string text = PlayerPrefs.GetString("language"); if (string.IsNullOrEmpty(text)) { text = ((object)Application.systemLanguage/*cast due to .constrained prefix*/).ToString(); } return string.Equals(text, "ZH", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "ZH_TW", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Chinese", StringComparison.OrdinalIgnoreCase); } catch (Exception) { return true; } } } public static class VergilFreeToolSlotGuardRules { public static string SanitizeSlotValue(string? currentValue, string? incomingValue, string? freeToolName) { string text = incomingValue ?? string.Empty; if (string.IsNullOrEmpty(freeToolName)) { return text; } string text2 = currentValue ?? string.Empty; if (string.Equals(text, freeToolName, StringComparison.Ordinal) && !string.Equals(text2, freeToolName, StringComparison.Ordinal)) { return text2; } return text; } public static List SanitizeEquippedList(IReadOnlyList? currentSlots, IReadOnlyList? incoming, string? freeToolName) { List list = new List(); if (incoming == null) { return list; } for (int i = 0; i < incoming.Count; i++) { string currentValue = ((currentSlots != null && i < currentSlots.Count) ? currentSlots[i] : null); list.Add(SanitizeSlotValue(currentValue, incoming[i], freeToolName)); } return list; } } } namespace CrestsEvolve.InsectMayCry.Features.SecondToolSet { public enum SecondSlotKind { Red, Skill } public enum UtilitySlotKind { Yellow, Blue } public sealed record SecondSlotSpec(int Index, SecondToolBinding Binding, SecondSlotKind Kind, float X, float Y, bool IsLocked); public sealed record UtilitySlotSpec(int Index, UtilitySlotKind Kind, float X, float Y, bool IsLocked); public enum SecondSpecialAction { ShamanBind } public sealed record SecondSpecialBinding(SecondToolBinding Binding, SecondSpecialAction Action); public enum SecondToolBinding { Up, Neutral, Down } public sealed class SecondToolSetFeature : IDisposable { private readonly SecondToolSetHudDriver? _hud; public SecondToolSetFeature(ManualLogSource log, Harmony harmony, FoundationServices? services) { SecondToolSetPatches.Install(harmony, log); if (services != null) { StatusIconFeature statusIcons = services.StatusIcons; if (((statusIcons != null) ? new bool?(statusIcons.Enabled) : ((bool?)null)) == true && services.StatusLedger != null && services.StatusOptions != null && services.StatusTextures != null && services.Events != null) { _hud = new SecondToolSetHudDriver(log, services.StatusLedger, services.StatusOptions, services.StatusTextures, services.Events, SecondToolSetGameBridge.CurrentCrestId, SecondToolSetGameBridge.GetEquippedToolName, SecondToolSetGameBridge.GetToolSnapshot); _hud.Start(); } } } public void Dispose() { _hud?.Dispose(); SecondToolSetPatches.Reset(); } } public sealed class SecondToolSetHudDriver : IDisposable { public const string IconOwner = "imc.second-toolset"; public const string RedRingColor = "#B33A3A"; public const string SkillRingColor = "#FFFFFF"; public const float SecondIconScale = 1.5f; private static readonly (SecondToolBinding Binding, string Key)[] Slots = new(SecondToolBinding, string)[3] { (SecondToolBinding.Up, "imc.second.up"), (SecondToolBinding.Neutral, "imc.second.neutral"), (SecondToolBinding.Down, "imc.second.down") }; private readonly ManualLogSource _log; private readonly StatusIconLedger _ledger; private readonly StatusIconOptions _options; private readonly StatusIconTextureService _textures; private readonly GameEventHub _events; private readonly Func _currentCrestId; private readonly Func _getEquippedToolName; private readonly Func _getToolSnapshot; private readonly Dictionary _lastSprite = new Dictionary(StringComparer.Ordinal); private bool _started; private bool _disposed; public SecondToolSetHudDriver(ManualLogSource log, StatusIconLedger ledger, StatusIconOptions options, StatusIconTextureService textures, GameEventHub events, Func currentCrestId, Func getEquippedToolName, Func getToolSnapshot) { _log = log ?? throw new ArgumentNullException("log"); _ledger = ledger ?? throw new ArgumentNullException("ledger"); _options = options ?? throw new ArgumentNullException("options"); _textures = textures ?? throw new ArgumentNullException("textures"); _events = events ?? throw new ArgumentNullException("events"); _currentCrestId = currentCrestId ?? throw new ArgumentNullException("currentCrestId"); _getEquippedToolName = getEquippedToolName ?? throw new ArgumentNullException("getEquippedToolName"); _getToolSnapshot = getToolSnapshot ?? throw new ArgumentNullException("getToolSnapshot"); } public void Start() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown if (!_started && !_disposed) { _started = true; Dictionary dictionary = new Dictionary(StringComparer.Ordinal); (SecondToolBinding, string)[] slots = Slots; for (int i = 0; i < slots.Length; i++) { string item = slots[i].Item2; dictionary[item] = new StatusIconEntryOptions(true, 1, "#FFFFFF", "placeholder", "#FFFFFF"); } _options.RegisterIconOptions((IEnumerable>)dictionary); slots = Slots; for (int i = 0; i < slots.Length; i++) { string item2 = slots[i].Item2; _ledger.Register(new StatusIconDefinition(item2, "imc.second-toolset", 1)); } _events.ToolEquipsChanged += Refresh; _events.CrestChanged += OnCrestChanged; _events.ToolUsed += OnToolUsed; _events.SceneChanged += OnSceneChanged; _events.SilkChanged += OnSilkChanged; Refresh(); _log.LogInfo((object)"[InsectMayCry][SecondToolSet] HUD driver started (dormant until a crest layout is registered)."); } } public void Refresh() { if (!_started || _disposed) { return; } try { string text = _currentCrestId(); SecondToolSetLayout secondToolSetLayout = (string.IsNullOrEmpty(text) ? null : SecondToolSetRegistry.TryGet(text)); (SecondToolBinding, string)[] slots = Slots; for (int i = 0; i < slots.Length; i++) { (SecondToolBinding, string) tuple = slots[i]; SecondToolBinding item = tuple.Item1; string item2 = tuple.Item2; SecondSlotSpec secondSlotSpec = ((secondToolSetLayout == null) ? null : SecondToolSetRules.FindSecondSlot(secondToolSetLayout, item)); if (secondSlotSpec == null || text == null) { Hide(item2); continue; } string text2 = _getEquippedToolName(text, secondSlotSpec.Index - 1); SecondToolSnapshot secondToolSnapshot = ((text2 == null) ? null : _getToolSnapshot(text2)); if (secondToolSnapshot == null) { Hide(item2); continue; } int num = ((secondSlotSpec.Kind != SecondSlotKind.Red) ? 1 : Math.Max(1, secondToolSnapshot.Storage)); int num2 = ((secondSlotSpec.Kind != SecondSlotKind.Red) ? 1 : Math.Clamp(secondToolSnapshot.AmountLeft, 0, num)); _ledger.SetMax(item2, "imc.second-toolset", num); _ledger.Set(item2, "imc.second-toolset", num2); _ledger.SetVisible(item2, "imc.second-toolset", true); _ledger.SetColorOverride(item2, "imc.second-toolset", (secondSlotSpec.Kind == SecondSlotKind.Red) ? "#B33A3A" : "#FFFFFF"); _ledger.SetDimmed(item2, "imc.second-toolset", secondSlotSpec.Kind == SecondSlotKind.Skill && !secondToolSnapshot.SilkAffordable); _textures.SetDynamicIconSprite(item2, secondToolSnapshot.Sprite); _textures.SetDynamicIconScale(item2, (float?)1.5f); if (GetLastSprite(item2) != secondToolSnapshot.Sprite) { _lastSprite[item2] = secondToolSnapshot.Sprite; _ledger.Touch(item2, "imc.second-toolset"); } } } catch (Exception ex) { _log.LogWarning((object)("[InsectMayCry][SecondToolSet] HUD refresh failed: " + ex.GetType().Name + ": " + ex.Message)); } } public void Dispose() { if (!_disposed) { _disposed = true; _events.ToolEquipsChanged -= Refresh; _events.CrestChanged -= OnCrestChanged; _events.ToolUsed -= OnToolUsed; _events.SceneChanged -= OnSceneChanged; _events.SilkChanged -= OnSilkChanged; (SecondToolBinding, string)[] slots = Slots; for (int i = 0; i < slots.Length; i++) { string item = slots[i].Item2; Hide(item); } } } private void OnCrestChanged(CrestChangedEvent _) { Refresh(); } private void OnToolUsed(ToolUsedEvent _) { Refresh(); } private void OnSceneChanged(string _) { Refresh(); } private void OnSilkChanged(ResourceEvent _) { Refresh(); } private void Hide(string key) { _ledger.SetVisible(key, "imc.second-toolset", false); _ledger.Set(key, "imc.second-toolset", 0); _ledger.SetColorOverride(key, "imc.second-toolset", (string)null); _ledger.SetDimmed(key, "imc.second-toolset", false); _textures.SetDynamicIconSprite(key, (Sprite)null); _textures.SetDynamicIconScale(key, (float?)null); _lastSprite[key] = null; } private Sprite? GetLastSprite(string key) { if (!_lastSprite.TryGetValue(key, out Sprite value)) { return null; } return value; } } public sealed record SecondToolSetLayout(string CrestId, IReadOnlyList SecondSlots, IReadOnlyList? NativeSlots = null, IReadOnlyList? SpecialBindings = null, IReadOnlyList? UtilitySlots = null, bool ReplaceBind = true); public static class SecondToolSetRegistry { private static readonly Dictionary _layouts = new Dictionary(StringComparer.Ordinal); public static void Register(SecondToolSetLayout layout, Action? warn = null) { IReadOnlyList readOnlyList = SecondToolSetRules.Validate(layout); if (readOnlyList.Count > 0) { foreach (string item in readOnlyList) { warn?.Invoke("[SecondToolSet] layout rejected for '" + (layout?.CrestId ?? "") + "': " + item); } return; } _layouts[layout.CrestId] = layout; } public static SecondToolSetLayout? TryGet(string? crestId) { if (string.IsNullOrEmpty(crestId)) { return null; } if (!_layouts.TryGetValue(crestId, out SecondToolSetLayout value)) { return null; } return value; } public static bool IsActive(string? crestId) { SecondToolSetLayout secondToolSetLayout = TryGet(crestId); if ((object)secondToolSetLayout != null) { IReadOnlyList secondSlots = secondToolSetLayout.SecondSlots; if (secondSlots != null) { return secondSlots.Count > 0; } } return false; } public static bool HasLayout(string? crestId) { return TryGet(crestId) != null; } public static void Clear() { _layouts.Clear(); } } public static class SecondToolSetRules { public const int NativeFirstIndex = 1; public const int NativeLastIndex = 3; public const int SecondFirstIndex = 4; public const int SecondLastIndex = 6; public const int MaxRedSkillSlotCount = 6; public static bool IsSecondSlotIndex(int index) { if (index >= 4) { return index <= 6; } return false; } public static IReadOnlyList Validate(SecondToolSetLayout? layout) { List errors = new List(); if (layout == null) { errors.Add("layout is null."); return errors; } if (string.IsNullOrWhiteSpace(layout.CrestId)) { errors.Add("CrestId is required."); } HashSet allIndexes = new HashSet(); ValidateAttackGroup(layout.NativeSlots, 1, 3, "Native"); ValidateAttackGroup(layout.SecondSlots, 4, 6, "Second"); ValidateSpecialBindings(layout); ValidateUtilityGroup(layout.UtilitySlots); int num = CountRedSkill(layout.NativeSlots); int num2 = CountRedSkill(layout.SecondSlots); int num3 = num + num2; if (num3 > 6) { errors.Add($"Red+Skill slot total {num3} exceeds the limit of {6}."); } return errors; static int CountRedSkill(IReadOnlyList? slots) { if (slots == null) { return 0; } int num4 = 0; foreach (SecondSlotSpec slot in slots) { if (slot != null && (slot.Kind == SecondSlotKind.Red || slot.Kind == SecondSlotKind.Skill)) { num4++; } } return num4; } void ValidateAttackGroup(IReadOnlyList? slots, int firstIndex, int lastIndex, string groupName) { if (slots == null || slots.Count == 0) { return; } if (slots.Count > lastIndex - firstIndex + 1) { errors.Add($"{groupName} slots cannot exceed {lastIndex - firstIndex + 1} entries " + $"(slots {firstIndex}..{lastIndex})."); } HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(); foreach (SecondSlotSpec slot2 in slots) { if (slot2 == null) { errors.Add(groupName + " slots contains a null entry."); } else { if (slot2.Index < firstIndex || slot2.Index > lastIndex) { errors.Add($"{groupName} slot index {slot2.Index} is outside {firstIndex}..{lastIndex}."); } else if (!hashSet.Add(slot2.Index)) { errors.Add($"{groupName} slot index {slot2.Index} is duplicated."); } if (!hashSet2.Add(slot2.Binding)) { errors.Add($"{groupName} binding {slot2.Binding} appears more than once."); } if (!allIndexes.Add(slot2.Index)) { errors.Add($"Slot index {slot2.Index} is duplicated across groups."); } } } } void ValidateSpecialBindings(SecondToolSetLayout current) { if (current.SpecialBindings == null || current.SpecialBindings.Count == 0) { return; } HashSet hashSet = new HashSet(); foreach (SecondSpecialBinding specialBinding in current.SpecialBindings) { if (specialBinding == null) { errors.Add("SpecialBindings contains a null entry."); } else { if (!hashSet.Add(specialBinding.Binding)) { errors.Add($"Special binding {specialBinding.Binding} appears more than once."); } if (FindSecondSlot(current, specialBinding.Binding) != null) { errors.Add($"Binding {specialBinding.Binding} has both a second slot and a special action."); } } } } void ValidateUtilityGroup(IReadOnlyList? slots) { if (slots == null || slots.Count == 0) { return; } HashSet hashSet = new HashSet(); foreach (UtilitySlotSpec slot3 in slots) { if (slot3 == null) { errors.Add("Utility slots contains a null entry."); } else { if (slot3.Index < 1) { errors.Add($"Utility slot index {slot3.Index} must be positive."); } else if (!hashSet.Add(slot3.Index)) { errors.Add($"Utility slot index {slot3.Index} is duplicated."); } if (!allIndexes.Add(slot3.Index)) { errors.Add($"Slot index {slot3.Index} is duplicated across groups."); } } } } } public static SecondSlotSpec? FindSecondSlot(SecondToolSetLayout? layout, SecondToolBinding binding) { if (layout?.SecondSlots == null) { return null; } foreach (SecondSlotSpec secondSlot in layout.SecondSlots) { if (secondSlot != null && secondSlot.Binding == binding) { return secondSlot; } } return null; } public static SecondSpecialAction? FindSpecialAction(SecondToolSetLayout? layout, SecondToolBinding binding) { if (layout?.SpecialBindings == null) { return null; } foreach (SecondSpecialBinding specialBinding in layout.SpecialBindings) { if (specialBinding != null && specialBinding.Binding == binding) { return specialBinding.Action; } } return null; } public static bool ShouldSuppressBind(SecondToolSetLayout? layout) { if (layout != null && layout.ReplaceBind && layout.SecondSlots != null) { return layout.SecondSlots.Count > 0; } return false; } } public sealed record SecondToolSnapshot(Sprite? Sprite, int AmountLeft, int Storage, bool IsSkill, bool SilkAffordable = true); } namespace CrestsEvolve.InsectMayCry.Features.CrossStitch { public static class CrossStitchEffectCatalog { public static readonly IReadOnlyList Modules = new CrossStitchEffectModule[4] { new CrossStitchEffectModule("cross-stitch.normal", "正常十字绣(免费)", CrossStitchEffectKind.Normal, null, true, false, "Dante"), new CrossStitchEffectModule("cross-stitch.rune-rage", "十字绣接符文之怒", CrossStitchEffectKind.RuneRage, "Initial Silk Cost", false, false, "Dante"), new CrossStitchEffectModule("cross-stitch.sharpdart", "十字绣接丝刃镖", CrossStitchEffectKind.Sharpdart, "Silk Charge Begin", true, false, "Dante"), new CrossStitchEffectModule("cross-stitch.thread-storm", "十字绣接灵丝风暴", CrossStitchEffectKind.ThreadStorm, "Do Sphere", true, false, "Dante") }; public static CrossStitchEffectModule? FindById(string? id) { if (id == null) { return null; } foreach (CrossStitchEffectModule module in Modules) { if (module.Id == id) { return module; } } return null; } public static CrossStitchEffectModule? FindByKind(CrossStitchEffectKind kind) { foreach (CrossStitchEffectModule module in Modules) { if (module.EffectKind == kind) { return module; } } return null; } } public enum CrossStitchEffectKind { Normal, RuneRage, Sharpdart, ThreadStorm } public sealed class CrossStitchEffectModule { public string Id { get; } public string DisplayName { get; } public CrossStitchEffectKind EffectKind { get; } public IReadOnlyList CrestIds { get; } public string? RedirectStateName { get; } public bool RequiresFreeWindow { get; } public bool RequireRealParry { get; } public CrossStitchEffectModule(string id, string displayName, CrossStitchEffectKind effectKind, string? redirectStateName, bool requiresFreeWindow, bool requireRealParry = false, params string[] crestIds) { Id = id ?? throw new ArgumentNullException("id"); DisplayName = displayName ?? throw new ArgumentNullException("displayName"); EffectKind = effectKind; RedirectStateName = redirectStateName; RequiresFreeWindow = requiresFreeWindow; RequireRealParry = requireRealParry; CrestIds = crestIds ?? throw new ArgumentNullException("crestIds"); } public bool IsActiveFor(string? crestId) { foreach (string crestId2 in CrestIds) { if (string.Equals(crestId2, crestId, StringComparison.Ordinal)) { return true; } } return false; } } public static class CrossStitchEffectRules { public static CrossStitchEffectModule? ResolveModule(IReadOnlyList modules, CrossStitchEffectKind kind) { if (modules == null) { return null; } foreach (CrossStitchEffectModule module in modules) { if (module != null && module.EffectKind == kind) { return module; } } return null; } public static bool IsAvailable(CrossStitchEffectModule? module, string? crestId) { return module?.IsActiveFor(crestId) ?? false; } public static bool RequiresRedirect(CrossStitchEffectKind kind) { return kind != CrossStitchEffectKind.Normal; } } } namespace CrestsEvolve.InsectMayCry.Features.AttackModules { public sealed class AttackModuleCatalog : IDisposable { private readonly ManualLogSource _log; private readonly IReadOnlyList _modules; private bool _disposed; public IReadOnlyList Modules => _modules; public AttackModuleCatalog(ManualLogSource log, Harmony harmony, AttackModuleOptions options) { _log = log ?? throw new ArgumentNullException("log"); _modules = new IAttackModule[11] { new HunterDashAttackModule(log, options), new HunterWindBladeModule(log, options), new WandererChargeAttackModule(log, options), new ShamanChargeSlashModule(log, options), new ShamanDashSlashModule(log, options), new BeastDownslashModule(log, options), new ReaperDashSlashModule(log, options), new VergilGroundDashChargeModule(log, options), new WitchDashAttackModule(log, options), new WitchChargeModule(log, options), new WitchCrossSlashModule(log, options) }; int num = 0; foreach (IAttackModule module in _modules) { if (module.Enabled) { module.Install(harmony); num++; log.LogInfo((object)("[InsectMayCry][AttackModule] " + module.Id + " installed (crests: " + string.Join(",", module.CrestIds) + ").")); } } log.LogInfo((object)$"[InsectMayCry][AttackModule] catalog ready ({_modules.Count} modules, {num} enabled)."); } public void Dispose() { if (_disposed) { return; } _disposed = true; for (int num = _modules.Count - 1; num >= 0; num--) { try { _modules[num].Dispose(); } catch (Exception ex) { _log.LogWarning((object)("[InsectMayCry][AttackModule] dispose " + _modules[num].Id + " failed: " + ex.Message)); } } } } public static class AttackModuleCodes { public const string A1 = "A1"; public const string A2 = "A2"; public const string A3 = "A3"; public const string B1 = "B1"; public const string B2 = "B2"; public const string B3 = "B3"; public const string B4 = "B4"; public const string B5 = "B5"; public const string B6 = "B6"; public const string C4 = "C4"; } public sealed class AttackModuleOptions { public bool HunterDashEnabled { get; } public float HunterDashSpeedMultiplier { get; } public float HunterDashDistanceMultiplier { get; } public float HunterDashHitRangeMultiplier { get; } public bool HunterDashPierceEnemies { get; } public double HunterDashInvulnerabilitySeconds { get; } public bool SprintImmediateDashAttack { get; } public bool WindBladeEnabled { get; } public int WindBladeCount { get; } public float WindBladeSpeed { get; } public double WindBladeDamage { get; } public float WindBladeLifetime { get; } public float WindBladeSpreadDegrees { get; } public float WindBladeSpawnForwardOffset { get; } public float WindBladeBladeDelaySeconds { get; } public bool WandererChargeEnabled { get; } public float WandererChargeDistanceMultiplier { get; } public float WandererChargeHitRangeMultiplier { get; } public float WandererChargeDamageMultiplier { get; } public bool ShamanChargeSlashEnabled { get; } public float ShamanChargeSlashTravelDistance { get; } public float ShamanChargeSlashTravelDuration { get; } public bool WitchDashEnabled { get; } public int WitchDashSlashCount { get; } public float WitchAttackSpeedMultiplier { get; } public bool WitchDashSkipAntic { get; } public bool WitchChargeEnabled { get; } public float WitchChargeDurationMultiplier { get; } public float WitchChargeDamageMultiplier { get; } public float WitchChargeSizeMultiplier { get; } public bool VergilDashChargeEnabled { get; } public int VergilDashChargeRounds { get; } public float VergilDashChargeSpeedMultiplier { get; } public double VergilDashChargeInvulnerabilitySeconds { get; } public bool WitchCrossSlashEnabled { get; } public float WitchCrossSlashScale { get; } public float WitchCrossSlashDamageMultiplier { get; } public bool ShamanDashSlashEnabled { get; } public float ShamanDashSlashRangeMultiplier { get; } public float ShamanDashSlashDamageMultiplier { get; } public double ShamanDashSlashDurationSeconds { get; } public double ShamanDashSlashHitResetIntervalSeconds { get; } public string NeroChargeShotToolName { get; } public bool NeroChargeShotExplosionOnHit { get; } public double NeroChargeShotExplosionWindowSeconds { get; } public double ChargeToolCooldownSeconds { get; } public float DanteChargeCrossStitchParryWindowSeconds { get; } public double DanteChargeCrossStitchRequestTimeoutSeconds { get; } public float DanteBeastChargeSpeedX { get; } public float DanteBeastChargeSpeedY { get; } public float DanteBeastChargeDeceleration { get; } public float DanteBeastChargeMaxDurationSeconds { get; } public float DanteBeastChargeStopThreshold { get; } public double DanteCrossStitchSharpdartFreeWindowSeconds { get; } public float NeroChargeSlashReaperScaleMultiplier { get; } public bool DanteCrossStitchRuneEnabled { get; } public int DanteCrossStitchRuneCasts { get; } public double DanteCrossStitchRuneInvulnerabilitySeconds { get; } public double DanteCrossStitchRuneRequestTimeoutSeconds { get; } public float DanteCrossStitchRuneMinSpawnRadius { get; } public float DanteCrossStitchRuneMaxSpawnRadius { get; } public float DanteCrossStitchRuneSilkBombCooldownSeconds { get; } public AttackModuleOptions(ConfigFile config) { HunterDashEnabled = config.Bind("Hunter.DashAttack", "Enabled", true, "猎手冲刺攻击:位移 ×3.9、速度 ×1.6、范围 ×2.2、击中不刹车+穿透+无敌(默认: true)").Value; HunterDashSpeedMultiplier = ClampMultiplier(config.Bind("Hunter.DashAttack", "SpeedMultiplier", 1.6f, "猎手冲刺整体速度倍率(时间感放缓;默认: 1.6)").Value); HunterDashDistanceMultiplier = ClampMultiplier(config.Bind("Hunter.DashAttack", "DistanceMultiplier", 3.9f, "猎手冲刺位移距离倍率(默认: 3.9;时长倍率自动 = 距离÷速度 = 2.4375)").Value); HunterDashHitRangeMultiplier = ClampMultiplier(config.Bind("Hunter.DashAttack", "HitRangeMultiplier", 2.2f, "猎手冲刺攻击命中范围倍率(localScale.x;默认: 2.2)").Value); HunterDashPierceEnemies = config.Bind("Hunter.DashAttack", "PierceEnemies", true, "猎手冲刺击中敌人不刹车、继续前进并穿透沿途敌人(默认: true)").Value; HunterDashInvulnerabilitySeconds = Math.Max(0.0, config.Bind("Hunter.DashAttack", "InvulnerabilitySeconds", 0.6, "猎手冲刺期间无敌秒数(覆盖冲刺+收招;默认: 0.6)").Value); SprintImmediateDashAttack = config.Bind("Sprint", "ImmediateDashAttack", true, "冲刺中按攻击立即使用冲刺攻击,不等冲刺放完(修改 Sprint FSM;默认: true)").Value; WindBladeEnabled = config.Bind("Hunter.WindBlade", "Enabled", true, "猎手蓄力斩后释放风刃弹幕(默认: true)").Value; WindBladeCount = Math.Max(1, config.Bind("Hunter.WindBlade", "BladeCount", 2, "每次蓄力斩释放的风刃数量(默认: 2,依次发射两枚)").Value); WindBladeSpeed = Math.Max(1f, config.Bind("Hunter.WindBlade", "Speed", 22f, "风刃飞行速度(默认: 22)").Value); WindBladeDamage = Math.Max(0.0, config.Bind("Hunter.WindBlade", "Damage", 2.0, "风刃伤害(玩家近战伤害倍率;默认 2)").Value); WindBladeLifetime = Math.Max(0.1f, config.Bind("Hunter.WindBlade", "Lifetime", 1.6f, "风刃存活时长(秒)(默认: 1.6)").Value); WindBladeSpreadDegrees = Math.Max(0f, config.Bind("Hunter.WindBlade", "SpreadDegrees", 0f, "风刃扇形展开角度(总角)(默认: 0,多枚同向依次发射)").Value); WindBladeSpawnForwardOffset = config.Bind("Hunter.WindBlade", "SpawnForwardOffset", 1.2f, "风刃出生点相对角色的前方偏移(默认: 1.2)").Value; WindBladeBladeDelaySeconds = Math.Max(0f, config.Bind("Hunter.WindBlade", "BladeDelaySeconds", 0.12f, "多枚风刃依次发射的间隔秒数(默认: 0.12,0=同帧扇形发射)").Value); WandererChargeEnabled = config.Bind("Wanderer.ChargeAttack", "Enabled", true, "漫步者蓄力攻击:位移 ×3、范围 ×1.5、每下伤害 ×2(默认: true)").Value; WandererChargeDistanceMultiplier = ClampMultiplier(config.Bind("Wanderer.ChargeAttack", "DistanceMultiplier", 3f, "漫步者蓄力突进初速度倍率(位移距离 ∝ 初速度;默认: 3)").Value); WandererChargeHitRangeMultiplier = ClampMultiplier(config.Bind("Wanderer.ChargeAttack", "HitRangeMultiplier", 1.5f, "漫步者蓄力攻击命中范围倍率(localScale.x;默认: 1.5)").Value); WandererChargeDamageMultiplier = ClampMultiplier(config.Bind("Wanderer.ChargeAttack", "DamageMultiplier", 2f, "漫步者蓄力每下伤害倍率(nailDamageMultiplier ×2;默认: 2)").Value); ShamanChargeSlashEnabled = config.Bind("Shaman.ChargeSlashRange", "Enabled", true, "萨满蓄力斩风刃远距离化(默认: true)").Value; ShamanChargeSlashTravelDistance = Math.Max(0f, config.Bind("Shaman.ChargeSlashRange", "TravelDistance", 52.8f, "萨满蓄力斩风刃飞行距离(米),默认 52.8(如需对齐猎手风刃 27×1.6 可设 43.2;0=保持原版短距离)").Value); ShamanChargeSlashTravelDuration = Math.Max(0.1f, config.Bind("Shaman.ChargeSlashRange", "TravelDuration", 1.6f, "萨满蓄力斩风刃飞行时长(秒),同时作为 DisableAfterTime 停用延迟").Value); WitchDashEnabled = config.Bind("Witch.DashAttack", "Enabled", true, "女巫冲刺攻击:原版 2 连挥刀重复多轮 + 跳过前摇 + 整体速度倍率(关闭=恢复原版)").Value; WitchDashSlashCount = Math.Clamp(config.Bind("Witch.DashAttack", "SlashCount", 6, "连续挥砍次数(默认 6 = 3 轮 × 2 连;必须为偶数,2 = 原版 2 次;范围 2~18)").Value, 2, 18); if (WitchDashSlashCount % 2 != 0) { WitchDashSlashCount--; } WitchAttackSpeedMultiplier = Math.Max(1f, config.Bind("Witch.DashAttack", "SpeedMultiplier", 3f, "冲刺攻击整体速度倍率(默认 3;前摇已跳过,主要作用于每段突进时长 DashStabTime,不影响普通攻击)").Value); WitchDashSkipAntic = config.Bind("Witch.DashAttack", "SkipAntic", true, "跳过冲刺攻击前摇动画(默认 true;关闭=保留原版前摇,用于对照测试)").Value; WitchChargeEnabled = config.Bind("Witch.ChargeAttack", "Enabled", true, "蓄力攻击 C4(女巫放大):持续×2 / 每下伤害×2 / 大小×1.3(Vergil 默认蓄力;默认: true)").Value; WitchChargeDurationMultiplier = Math.Max(1f, config.Bind("Witch.ChargeAttack", "DurationMultiplier", 2f, "蓄力攻击持续倍率(ClipFps÷倍率;默认: 2)").Value); WitchChargeDamageMultiplier = Math.Max(1f, config.Bind("Witch.ChargeAttack", "DamageMultiplier", 2f, "蓄力攻击每下伤害倍率(nailDamageMultiplier;默认: 2)").Value); WitchChargeSizeMultiplier = Math.Max(1f, config.Bind("Witch.ChargeAttack", "SizeMultiplier", 1.3f, "蓄力攻击大小倍率(localScale;默认: 1.3)").Value); VergilDashChargeEnabled = config.Bind("Vergil.DashCharge", "Enabled", true, "V 地面冲刺蓄力 B2:冲刺式 4 连穿透(完全替换漫步者蓄力+突进;默认: true)").Value; VergilDashChargeRounds = Math.Clamp(config.Bind("Vergil.DashCharge", "Rounds", 4, "2 连挥刀重复轮数(默认 4 轮 × 2 连 = 8 刀;1~9)").Value, 1, 9); VergilDashChargeSpeedMultiplier = Math.Max(1f, config.Bind("Vergil.DashCharge", "SpeedMultiplier", 3f, "冲刺式 4 连每段时长倍率(读原始 dashStabTime=0.15 后 ÷3 ≈0.05s;默认: 3)").Value); VergilDashChargeInvulnerabilitySeconds = Math.Max(0.0, config.Bind("Vergil.DashCharge", "InvulnerabilitySeconds", 0.0, "冲刺式 4 连全程无敌秒数(默认 0=关;可配)").Value); WitchCrossSlashEnabled = config.Bind("Witch.CrossSlash", "Enabled", true, "女巫蓄力十字斩:蓄力斩替换为 Song Knight CrossSlash Friendly(默认: true)").Value; WitchCrossSlashScale = Math.Max(0.1f, config.Bind("Witch.CrossSlash", "Scale", 1.3f, "十字斩大小倍率(用户指定 1.3;默认: 1.3)").Value); WitchCrossSlashDamageMultiplier = Math.Max(0.1f, config.Bind("Witch.CrossSlash", "DamageMultiplier", 2.3f, "十字斩伤害倍率(nailDamageMultiplier;参考 ReaperBalance 默认 2.3,待用户确认)").Value); ShamanDashSlashEnabled = config.Bind("Shaman.DashSlash", "Enabled", true, "萨满冲刺斩模块(B4,回旋斩):接入 V 空中冲刺攻击;关闭=V 空中冲刺回退通用冲刺攻击(默认: true)").Value; ShamanDashSlashRangeMultiplier = Math.Max(1f, config.Bind("Shaman.DashSlash", "RangeMultiplier", 1.25f, "回旋斩范围倍率(默认 1.25,与相位猛冲一致)").Value); ShamanDashSlashDamageMultiplier = Math.Max(1f, config.Bind("Shaman.DashSlash", "DamageMultiplier", 1.25f, "回旋斩伤害倍率(DamageEnemies.DamageMultiplier;默认 1.25)").Value); ShamanDashSlashDurationSeconds = ShamanDashSlashRules.ClampDurationSeconds(config.Bind("Shaman.DashSlash", "DurationSeconds", 1.5, "回旋斩脱手后原地持续旋转秒数(0=关闭残留、保持原版收招;默认 1.5)").Value); ShamanDashSlashHitResetIntervalSeconds = Math.Max(0.0, config.Bind("Shaman.DashSlash", "HitResetIntervalSeconds", 0.4, "回旋斩命中集周期重置间隔(秒)(0=关闭;默认 0.4)").Value); NeroChargeShotToolName = config.Bind("Nero.ChargeShot", "ToolName", "WebShot Forge", "N 默认蓄力使用的丝弹工具内部 ID(丝弹1 = WebShot Forge / Silkshot-Forge_Daughter;E1:toolitems.bundle + FSM dump,见查证笔记 48)").Value; NeroChargeShotExplosionOnHit = config.Bind("Nero.ChargeShot", "ExplosionOnHit", true, "N 默认蓄力丝弹命中敌人触发原版燃爆(Tool_pinpilo_explosion;默认: true)").Value; NeroChargeShotExplosionWindowSeconds = Math.Max(0.0, config.Bind("Nero.ChargeShot", "ExplosionWindowSeconds", 5.0, "N 默认蓄力丝弹命中燃爆窗口秒数(默认: 5.0;0=关闭燃爆窗口)").Value); ChargeToolCooldownSeconds = Math.Max(0.0, config.Bind("Charge", "ToolCooldownSeconds", 0.0, "D/V/N 默认蓄力工具释放前设置的冷却秒数(默认: 0)").Value); DanteChargeCrossStitchParryWindowSeconds = Math.Max(0.05f, config.Bind("Dante.ChargeCrossStitch", "ParryWindowSeconds", 0.25f, "效果模块声明 RequireRealParry=true 时 Parry Stance 的格挡窗口秒数(默认: 0.25)").Value); DanteChargeCrossStitchRequestTimeoutSeconds = Math.Max(0.1, config.Bind("Dante.ChargeCrossStitch", "RequestTimeoutSeconds", 2.0, "十字绣请求有效窗口秒数,超时自动清除(默认: 2.0)").Value); DanteBeastChargeSpeedX = Math.Max(0f, config.Bind("Dante.BeastCharge", "SpeedX", 50f, "D 下+蓄力野兽蓄力斩水平速度大小(面向方向;默认: 50)").Value); DanteBeastChargeSpeedY = Math.Max(0f, config.Bind("Dante.BeastCharge", "SpeedY", 20f, "D 下+蓄力野兽蓄力斩垂直下落速度大小(默认: 20)").Value); DanteBeastChargeDeceleration = Math.Clamp(config.Bind("Dante.BeastCharge", "Deceleration", 0.85f, "野兽蓄力突进每物理帧水平速度衰减系数(默认: 0.85)").Value, 0.01f, 1f); DanteBeastChargeMaxDurationSeconds = Math.Max(0.05f, config.Bind("Dante.BeastCharge", "MaxDurationSeconds", 0.6f, "野兽蓄力减速最多持续秒数(默认: 0.6)").Value); DanteBeastChargeStopThreshold = Math.Max(0f, config.Bind("Dante.BeastCharge", "StopThreshold", 1f, "水平速度低于该值停止减速(默认: 1)").Value); DanteCrossStitchSharpdartFreeWindowSeconds = Math.Max(0.0, config.Bind("Dante.CrossStitchSharpdart", "FreeWindowSeconds", 0.5, "丝刃镖跳转后免消耗窗口秒数(默认: 0.5;0=关闭免消耗窗口)").Value); NeroChargeSlashReaperScaleMultiplier = Math.Max(0.1f, config.Bind("Nero.ChargeSlash", "ReaperScaleMultiplier", 1.3f, "N 上+蓄力收割者蓄力对象大小倍率(默认: 1.3)").Value); DanteCrossStitchRuneEnabled = config.Bind("Dante.CrossStitchRune", "Enabled", false, "D 蓄力十字绣触发后改为符文之怒特效(开启时替换丝刃镖模块):跳过前摇直接声呐环+3段爆炸,不扣丝、无视冷却(默认: false,D 默认走丝刃镖)").Value; DanteCrossStitchRuneCasts = Math.Clamp(config.Bind("Dante.CrossStitchRune", "Casts", 1, "符文之怒特效轮数(1=单轮:1 个声呐环 + 3 段随机爆炸;1~3)").Value, 1, 3); DanteCrossStitchRuneInvulnerabilitySeconds = Math.Max(0.0, config.Bind("Dante.CrossStitchRune", "InvulnerabilitySeconds", 1.0, "符文之怒特效期间无敌秒数(0=关闭;默认 1)").Value); DanteCrossStitchRuneRequestTimeoutSeconds = Math.Max(0.1, config.Bind("Dante.CrossStitchRune", "RequestTimeoutSeconds", 2.0, "符文之怒跳转请求有效窗口秒数,超时自动清除(默认: 2.0)").Value); DanteCrossStitchRuneMinSpawnRadius = Math.Max(0f, config.Bind("Dante.CrossStitchRune", "MinSpawnRadius", DanteCrossStitchRuneRules.FirstCastMinSpawnRadius, "符文之怒首轮爆炸最小生成半径(默认: 3.5)").Value); DanteCrossStitchRuneMaxSpawnRadius = Math.Max(0f, config.Bind("Dante.CrossStitchRune", "MaxSpawnRadius", DanteCrossStitchRuneRules.FirstCastMaxSpawnRadius, "符文之怒首轮爆炸最大生成半径(默认: 8)").Value); DanteCrossStitchRuneSilkBombCooldownSeconds = Math.Max(0f, config.Bind("Dante.CrossStitchRune", "SilkBombCooldownSeconds", 0f, "符文之怒 SILK BOMB 冷却秒数(默认: 0 = 无视冷却)").Value); } private static float ClampMultiplier(float value) { return Math.Max(1f, value); } } public static class AttackModuleRules { public static readonly string[] SprintAttackStates = new string[6] { "Ground Sprint R", "Ground Sprint L", "Air Sprint R", "Air Sprint L", "Air Sprint Loop", "Turn Anim" }; public static bool IsHunterCrest(string? crestId) { if (!string.Equals(crestId, "Hunter", StringComparison.Ordinal) && !string.Equals(crestId, "Hunter_v2", StringComparison.Ordinal)) { return string.Equals(crestId, "Hunter_v3", StringComparison.Ordinal); } return true; } public static bool IsWandererCrest(string? crestId) { return string.Equals(crestId, "Wanderer", StringComparison.Ordinal); } public static bool IsShamanCrest(string? crestId) { return string.Equals(crestId, "Spell", StringComparison.OrdinalIgnoreCase); } public static bool IsWitchCrest(string? crestId) { return string.Equals(crestId, "Witch", StringComparison.OrdinalIgnoreCase); } public static int ComputeWitchDashSlashCount(int rounds) { return Math.Max(1, rounds) * 2; } public static bool IsVergilGroundDashCharge(bool enabled, string? crestId, bool isDashVariant, bool onGround) { return enabled && VergilCrestRules.IsVergilCrest(crestId) && isDashVariant && onGround; } public static bool IsWitchDashStabObject(string? objectName) { if (!string.Equals(objectName, "Dash Stab 1", StringComparison.Ordinal)) { return string.Equals(objectName, "Dash Stab 2", StringComparison.Ordinal); } return true; } public static bool IsHunterDashObject(string? objectName) { return string.Equals(objectName, "Dash Stab", StringComparison.Ordinal); } public static bool IsWandererChargeObject(string? objectName) { return string.Equals(objectName, "Charge Slash Wanderer", StringComparison.Ordinal); } public static float ComputeShamanChargeSlashTravelX(float travelDistance, float heroScaleX, float waveLossyScaleX) { return (0f - Math.Abs(travelDistance)) * (float)Math.Sign(heroScaleX) * (float)Math.Sign(waveLossyScaleX); } public static float ApplyMultiplier(float value, float multiplier) { if (!(multiplier > 0f)) { return value; } return value * multiplier; } public static float ComputeTimeMultiplier(float travelDistanceMultiplier, float speedMultiplier) { if (!(speedMultiplier > 0f)) { return 1f; } return Math.Max(0.1f, travelDistanceMultiplier / speedMultiplier); } public static bool ShouldPierceHunterDash(bool enabled, string? crestId, string? objectName) { if (enabled && IsHunterCrest(crestId)) { return IsHunterDashObject(objectName); } return false; } public static bool IsSprintAttackState(string? stateName) { if (stateName != null) { return Array.IndexOf(SprintAttackStates, stateName) >= 0; } return false; } } public sealed class BeastDownslashModule : IAttackModule, IDisposable { private readonly ManualLogSource _log; private readonly AttackModuleOptions _options; private static BeastDownslashModule? _instance; private static GameObject? _spinSlashClone; private bool _disposed; public string Id => "B5"; public string DisplayName => "下劈攻击(野兽 SpinBall)"; public bool Enabled => true; public IReadOnlyList CrestIds { get; } = new string[1] { "Dante" }; public bool IsActiveFor(string? crestId) { return VergilCrestRules.IsDanteCrest(crestId); } public BeastDownslashModule(ManualLogSource log, AttackModuleOptions options) { _log = log ?? throw new ArgumentNullException("log"); _options = options ?? throw new ArgumentNullException("options"); } public void Install(Harmony harmony) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Expected O, but got Unknown if (harmony == null) { throw new ArgumentNullException("harmony"); } if (_instance != null) { return; } _instance = this; MethodInfo methodInfo = AccessTools.Method(typeof(PlayMakerFSM), "Start", (Type[])null, (Type[])null); if (methodInfo == null) { _log.LogWarning((object)"[AttackModule:beast.downslash] PlayMakerFSM.Start not found; feature disabled."); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(BeastDownslashModule), "OnFsmStart", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo2 = AccessTools.Method(typeof(HeroController), "Update", (Type[])null, (Type[])null); if (methodInfo2 == null) { _log.LogWarning((object)"[AttackModule:beast.downslash] HeroController.Update not found; pogo disabled."); } else { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(BeastDownslashModule), "OnHeroUpdate", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } _log.LogInfo((object)"[InsectMayCry][AttackModule:beast.downslash] installed (D beast SpinBall downslash + pogo)."); } public static void SetUpMoveset(string crestId, Transform root, HeroController hero) { if (!VergilCrestRules.IsDanteCrest(crestId) || (Object)(object)root == (Object)null || (Object)(object)hero == (Object)null) { return; } Transform val = ((Component)hero).transform.Find("Attacks/Warrior/SpinSlash"); if ((Object)(object)val == (Object)null) { BeastDownslashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)"[AttackModule:beast.downslash] Attacks/Warrior/SpinSlash not found."); } return; } _spinSlashClone = Object.Instantiate(((Component)val).gameObject, root); ((Object)_spinSlashClone).name = crestId + " SpinSlash"; _spinSlashClone.SetActive(true); BeastDownslashModule? instance2 = _instance; if (instance2 != null) { instance2._log.LogInfo((object)"[AttackModule:beast.downslash] spin slash clone ready."); } } public static void DownSlashFsmEdit(PlayMakerFSM fsm, FsmState startState, out FsmState[] endStates) { try { FsmState state = FsmUtil.GetState(fsm, "SpinBall Antic"); if (state != null) { FsmUtil.AddTransition(startState, "FINISHED", state.Name); } else { BeastDownslashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)"[AttackModule:beast.downslash] crest_attacks 'SpinBall Antic' not found; Dante downslash falls back to End."); } } ApplySpinSlashVariable(fsm); } catch (Exception ex) { BeastDownslashModule? instance2 = _instance; if (instance2 != null) { instance2._log.LogWarning((object)("[AttackModule:beast.downslash] FSM edit failed: " + ex.GetType().Name + ": " + ex.Message)); } } endStates = Array.Empty(); } public static void OnFsmStart(PlayMakerFSM __instance) { if (_instance != null && !((Object)(object)__instance == (Object)null) && __instance.Fsm != null && !(__instance.FsmName != "crest_attacks")) { ApplySpinSlashVariable(__instance); } } private static void ApplySpinSlashVariable(PlayMakerFSM fsm) { if ((Object)(object)_spinSlashClone == (Object)null || (Object)(object)fsm == (Object)null) { return; } FsmVariables fsmVariables = fsm.FsmVariables; FsmGameObject val = ((fsmVariables != null) ? fsmVariables.GetFsmGameObject("SpinSlash") : null); if (val == null) { BeastDownslashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)"[AttackModule:beast.downslash] crest_attacks 'SpinSlash' variable not found."); } return; } val.Value = _spinSlashClone; FsmVariables fsmVariables2 = fsm.FsmVariables; FsmGameObject val2 = ((fsmVariables2 != null) ? fsmVariables2.GetFsmGameObject("SpinSlashRage") : null); if (val2 != null) { val2.Value = _spinSlashClone; } } public static void OnHeroUpdate(HeroController __instance) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)__instance == (Object)null || __instance.playerData == null || !VergilCrestRules.IsDanteCrest(__instance.playerData.CurrentCrestID)) { return; } PlayMakerFSM crestAttacksFSM = __instance.crestAttacksFSM; ApplySpinSlashVariable(crestAttacksFSM); object obj; if (crestAttacksFSM == null) { obj = null; } else { FsmVariables fsmVariables = crestAttacksFSM.FsmVariables; obj = ((fsmVariables != null) ? fsmVariables.GetFsmBool("In Crest Attack") : null); } FsmBool val = (FsmBool)obj; if (val == null || !val.Value) { return; } Rigidbody2D component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null)) { Vector2 linearVelocity = component.linearVelocity; if (linearVelocity.x != 0f) { linearVelocity.x = 0f; component.linearVelocity = linearVelocity; } } } catch (Exception ex) { BeastDownslashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)("[AttackModule:beast.downslash] pogo failed: " + ex.GetType().Name + ": " + ex.Message)); } } } public void Dispose() { if (!_disposed) { _disposed = true; _spinSlashClone = null; if (_instance == this) { _instance = null; } } } } public sealed class HunterDashAttackModule : IAttackModule, IDisposable { private sealed class SkipDashAttackAnticAction : FsmStateAction { public override void OnEnter() { try { HunterDashAttackModule instance = _instance; if (instance != null && instance.IsHunterDashPierceActive()) { Fsm fsm = ((FsmStateAction)this).Fsm; if (fsm != null && fsm.Variables != null) { FsmFloat fsmFloat = fsm.Variables.GetFsmFloat("Attack Speed"); FsmFloat fsmFloat2 = fsm.Variables.GetFsmFloat("X Scale"); FsmFloat fsmFloat3 = fsm.Variables.GetFsmFloat("Attack Speed Crt"); if (fsmFloat != null && fsmFloat2 != null && fsmFloat3 != null) { fsmFloat3.Value = fsmFloat.Value * fsmFloat2.Value; } } ((FsmStateAction)this).Fsm.Event("FINISHED"); } } catch (Exception) { } ((FsmStateAction)this).Finish(); } } private sealed class DashStabDirInvulnAction : FsmStateAction { public override void OnEnter() { try { HunterDashAttackModule instance = _instance; if (instance != null && instance.IsHunterDashPierceActive() && instance._options.HunterDashInvulnerabilitySeconds > 0.0) { FoundationServices current = FoundationServices.Current; if (current != null) { IGameApi game = current.Game; if (game != null) { game.AcquireInvulnerability("hunter.dash-pierce", instance._options.HunterDashInvulnerabilitySeconds); } } } } catch (Exception) { } ((FsmStateAction)this).Finish(); } } private sealed class SprintAttackBoostMarker : MonoBehaviour { private readonly List _restores = new List(); public void AddRestore(Action restore) { _restores.Add(restore); } public void Restore() { foreach (Action restore in _restores) { try { restore(); } catch (Exception) { } } _restores.Clear(); } private void OnDestroy() { Restore(); } } private readonly ManualLogSource _log; private readonly AttackModuleOptions _options; private static HunterDashAttackModule? _instance; private static FsmFloat? _attackCooldown; private bool _disposed; private static bool _loggedHunterSpeed; private static bool _loggedHunterTime; public string Id => "A1"; public string DisplayName => "冲刺攻击(猎手穿透)"; public bool Enabled => _options.HunterDashEnabled; public IReadOnlyList CrestIds { get; } = new string[1] { "Dante" }; public bool IsActiveFor(string? crestId) { return VergilCrestRules.IsHunterDashCrest(crestId); } public HunterDashAttackModule(ManualLogSource log, AttackModuleOptions options) { _log = log ?? throw new ArgumentNullException("log"); _options = options ?? throw new ArgumentNullException("options"); } public void Install(Harmony harmony) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Expected O, but got Unknown //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Expected O, but got Unknown //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Expected O, but got Unknown //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Expected O, but got Unknown if (harmony == null) { throw new ArgumentNullException("harmony"); } if (_instance == null) { _instance = this; MethodInfo methodInfo = AccessTools.PropertyGetter(typeof(HeroControllerConfig), "DashStabSpeed"); if (methodInfo == null) { _log.LogWarning((object)"[AttackModule:hunter.dash] DashStabSpeed getter not found; travel boost disabled."); } else { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(HunterDashAttackModule), "OnDashStabSpeedGetter", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.PropertyGetter(typeof(HeroControllerConfig), "DashStabTime"); if (methodInfo2 == null) { _log.LogWarning((object)"[AttackModule:hunter.dash] DashStabTime getter not found; travel time boost disabled."); } else { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(HunterDashAttackModule), "OnDashStabTimeGetter", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo3 = AccessTools.Method(typeof(NailAttackBase), "OnSlashStarting", (Type[])null, (Type[])null); if (methodInfo3 == null) { _log.LogWarning((object)"[AttackModule:hunter.dash] NailAttackBase.OnSlashStarting not found; range boost disabled."); } else { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(HunterDashAttackModule), "OnSlashStarting", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo4 = AccessTools.Method(typeof(DashStabNailAttack), "DoRecoilHit", (Type[])null, (Type[])null); if (methodInfo4 == null) { _log.LogWarning((object)"[AttackModule:hunter.dash] DashStabNailAttack.DoRecoilHit not found; pierce disabled."); } else { harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(HunterDashAttackModule), "OnDashStabHitRecoil", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo5 = AccessTools.Method(typeof(PlayMakerFSM), "Start", (Type[])null, (Type[])null); if (methodInfo5 == null) { _log.LogWarning((object)"[AttackModule:hunter.dash] PlayMakerFSM.Start not found; Sprint FSM edits disabled."); } else { harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(HunterDashAttackModule), "OnFsmStart", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo6 = AccessTools.Method(typeof(HeroController), "Update", (Type[])null, (Type[])null); if (methodInfo6 == null) { _log.LogWarning((object)"[AttackModule:hunter.dash] HeroController.Update not found; vergil immediate dash disabled."); } else { harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(HunterDashAttackModule), "OnHeroUpdate", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } _log.LogInfo((object)"[InsectMayCry][AttackModule:hunter.dash] installed (D/N 3.9/1.6/2.2 pierce+invuln)."); } } public static void OnDashStabSpeedGetter(HeroControllerConfig __instance, ref float __result) { HunterDashAttackModule instance = _instance; if (instance == null || !instance._options.HunterDashEnabled || instance._options.HunterDashSpeedMultiplier <= 1f) { return; } HeroController instance2 = HeroController.instance; if (!((Object)(object)instance2 == (Object)null) && instance2.playerData != null && VergilCrestRules.IsHunterDashCrest(instance2.playerData.CurrentCrestID) && instance2.Config == __instance) { __result = AttackModuleRules.ApplyMultiplier(__result, instance._options.HunterDashSpeedMultiplier); if (!_loggedHunterSpeed) { _loggedHunterSpeed = true; instance._log.LogInfo((object)("[InsectMayCry][AttackModule:hunter.dash] DashStabSpeed boosted to " + $"{__result} (x{instance._options.HunterDashSpeedMultiplier}).")); } } } public static void OnDashStabTimeGetter(HeroControllerConfig __instance, ref float __result) { HunterDashAttackModule instance = _instance; if (instance == null || !instance._options.HunterDashEnabled || instance._options.HunterDashDistanceMultiplier <= 1f) { return; } HeroController instance2 = HeroController.instance; if (!((Object)(object)instance2 == (Object)null) && instance2.playerData != null && VergilCrestRules.IsHunterDashCrest(instance2.playerData.CurrentCrestID) && instance2.Config == __instance) { float num = AttackModuleRules.ComputeTimeMultiplier(instance._options.HunterDashDistanceMultiplier, instance._options.HunterDashSpeedMultiplier); __result = AttackModuleRules.ApplyMultiplier(__result, num); if (!_loggedHunterTime) { _loggedHunterTime = true; instance._log.LogInfo((object)("[InsectMayCry][AttackModule:hunter.dash] DashStabTime boosted to " + $"{__result} (x{num}).")); } } } public static void OnSlashStarting(NailAttackBase __instance) { //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) HunterDashAttackModule instance = _instance; if (instance == null || (Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).transform == (Object)null) { return; } HeroController instance2 = HeroController.instance; if ((Object)(object)instance2 == (Object)null || instance2.playerData == null) { return; } string currentCrestID = instance2.playerData.CurrentCrestID; string name = ((Object)((Component)__instance).transform).name; if (instance.IsHunterDashPierceActive() && AttackModuleRules.IsHunterDashObject(name) && instance._options.HunterDashInvulnerabilitySeconds > 0.0) { FoundationServices current = FoundationServices.Current; if (current != null) { IGameApi game = current.Game; if (game != null) { game.AcquireInvulnerability("hunter.dash-pierce", instance._options.HunterDashInvulnerabilitySeconds); } } } if (instance._options.HunterDashEnabled && !(instance._options.HunterDashHitRangeMultiplier <= 1f) && VergilCrestRules.IsHunterDashCrest(currentCrestID) && AttackModuleRules.IsHunterDashObject(name)) { Vector3 localScale = ((Component)__instance).transform.localScale; ((Component)__instance).transform.localScale = new Vector3(localScale.x * instance._options.HunterDashHitRangeMultiplier, localScale.y, localScale.z); } } public static bool OnDashStabHitRecoil(DashStabNailAttack __instance) { HunterDashAttackModule instance = _instance; if (instance == null) { return true; } if (!instance._options.HunterDashEnabled || !instance._options.HunterDashPierceEnemies) { return true; } if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).transform == (Object)null) { return true; } HeroController instance2 = HeroController.instance; if ((Object)(object)instance2 == (Object)null || instance2.playerData == null) { return true; } if (!instance.IsHunterDashPierceActive() || !AttackModuleRules.IsHunterDashObject(((Object)((Component)__instance).transform).name)) { return true; } return false; } public static void OnFsmStart(PlayMakerFSM __instance) { HunterDashAttackModule instance = _instance; if (instance != null && !((Object)(object)__instance == (Object)null) && __instance.Fsm != null && !(__instance.FsmName != "Sprint") && !((Object)(object)((Component)__instance).gameObject == (Object)null) && ((Object)((Component)__instance).gameObject).name.StartsWith("Hero_Hornet", StringComparison.Ordinal)) { _attackCooldown = __instance.FsmVariables.GetFsmFloat("Attack Cooldown"); instance.InstallSprintAttackBoost(__instance); } } private void InstallSprintAttackBoost(PlayMakerFSM fsm) { if (!_options.SprintImmediateDashAttack || (Object)(object)((Component)fsm).GetComponent() != (Object)null) { return; } try { SprintAttackBoostMarker marker = ((Component)fsm).gameObject.AddComponent(); InstallAttackAnticSkip(fsm, marker); InstallDashStabDirInvuln(fsm, marker); _log.LogInfo((object)"[InsectMayCry][AttackModule:hunter.dash] Sprint FSM hooked: D/N antic skip + invuln-on-press."); } catch (Exception ex) { _log.LogWarning((object)("[AttackModule:hunter.dash] Sprint FSM hook failed: " + ex.GetType().Name + ": " + ex.Message)); } } private bool IsHunterDashPierceActive() { if (!_options.HunterDashEnabled || !_options.HunterDashPierceEnemies) { return false; } HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null || instance.playerData == null) { return false; } return VergilCrestRules.IsHunterDashCrest(instance.playerData.CurrentCrestID); } public static void OnHeroUpdate() { HunterDashAttackModule instance = _instance; if (instance != null && instance._options.HunterDashEnabled && instance._options.SprintImmediateDashAttack) { HeroController instance2 = HeroController.instance; if (!((Object)(object)instance2 == (Object)null) && instance2.cState != null && instance2.playerData != null && instance2.cState.isSprinting && VergilCrestRules.IsHunterDashCrest(instance2.playerData.CurrentCrestID) && _attackCooldown != null) { _attackCooldown.Value = 0f; } } } private void InstallAttackAnticSkip(PlayMakerFSM fsm, SprintAttackBoostMarker marker) { FsmState state = FsmUtil.GetState(fsm, "Attack Antic"); if (state == null) { _log.LogWarning((object)"[AttackModule:hunter.dash] Sprint 'Attack Antic' not found; antic skip skipped."); return; } SkipDashAttackAnticAction action = new SkipDashAttackAnticAction(); FsmUtil.InsertAction(state, 0, (FsmStateAction)(object)action); marker.AddRestore(delegate { if (state.Actions != null) { state.Actions = state.Actions.Where((FsmStateAction a) => (object)a != action).ToArray(); } }); _log.LogInfo((object)"[InsectMayCry][AttackModule:hunter.dash] hunter dash attack antic skip installed."); } private void InstallDashStabDirInvuln(PlayMakerFSM fsm, SprintAttackBoostMarker marker) { FsmState state = FsmUtil.GetState(fsm, "Dash Stab Dir"); if (state == null) { _log.LogWarning((object)"[AttackModule:hunter.dash] Sprint 'Dash Stab Dir' not found; invuln-on-press skipped."); return; } DashStabDirInvulnAction action = new DashStabDirInvulnAction(); FsmUtil.InsertAction(state, 0, (FsmStateAction)(object)action); marker.AddRestore(delegate { if (state.Actions != null) { state.Actions = state.Actions.Where((FsmStateAction a) => (object)a != action).ToArray(); } }); _log.LogInfo((object)"[InsectMayCry][AttackModule:hunter.dash] hunter dash attack invuln-on-press installed."); } public void Dispose() { if (_disposed) { return; } _disposed = true; try { HeroController instance = HeroController.instance; if (instance != null) { ((Component)instance).GetComponent()?.Restore(); } } catch (Exception ex) { _log.LogWarning((object)("[AttackModule:hunter.dash] FSM restore failed: " + ex.GetType().Name + ": " + ex.Message)); } if (_instance == this) { _instance = null; } } } public sealed class HunterWindBladeModule : IAttackModule, IDisposable { private readonly ManualLogSource _log; private readonly AttackModuleOptions _options; private HunterWindBladeFeature? _feature; public string Id => "A3"; public string DisplayName => "上蓄力攻击(双风刃)"; public bool Enabled => _options.WindBladeEnabled; public IReadOnlyList CrestIds { get; } = new string[1] { "Dante" }; public bool IsActiveFor(string? crestId) { if (VergilCrestRules.IsDanteCrest(crestId)) { return VergilChargeVariantState.Current == ChargeVariant.Up; } return false; } public HunterWindBladeModule(ManualLogSource log, AttackModuleOptions options) { _log = log ?? throw new ArgumentNullException("log"); _options = options ?? throw new ArgumentNullException("options"); } public void Install(Harmony harmony) { if (_feature == null) { _feature = HunterWindBladeFeature.Install(_log, harmony, _options); _log.LogInfo((object)"[InsectMayCry][AttackModule:hunter.windblade] installed (speed 27)."); } } public void Dispose() { _feature?.Dispose(); _feature = null; } } public interface IAttackModule : IDisposable { string Id { get; } string DisplayName { get; } bool Enabled { get; } IReadOnlyList CrestIds { get; } bool IsActiveFor(string? crestId); void Install(Harmony harmony); } public sealed class ReaperDashSlashModule : IAttackModule, IDisposable { private readonly ManualLogSource _log; private readonly AttackModuleOptions _options; private static ReaperDashSlashModule? _instance; private static GameObject? _dashUpperSlashClone; private static FsmGameObject? _reaperSlashVar; private static GameObject? _originalReaperSlash; private bool _disposed; public string Id => "B6"; public string DisplayName => "冲刺攻击(收割者上挑冲刺斩)"; public bool Enabled => true; public IReadOnlyList CrestIds { get; } = new string[1] { "Nero" }; public bool IsActiveFor(string? crestId) { return VergilCrestRules.IsNeroCrest(crestId); } public ReaperDashSlashModule(ManualLogSource log, AttackModuleOptions options) { _log = log ?? throw new ArgumentNullException("log"); _options = options ?? throw new ArgumentNullException("options"); } public void Install(Harmony harmony) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Expected O, but got Unknown if (harmony == null) { throw new ArgumentNullException("harmony"); } if (_instance != null) { return; } _instance = this; MethodInfo methodInfo = AccessTools.Method(typeof(PlayMakerFSM), "Start", (Type[])null, (Type[])null); if (methodInfo == null) { _log.LogWarning((object)"[AttackModule:reaper.dash-slash] PlayMakerFSM.Start not found; feature disabled."); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ReaperDashSlashModule), "OnFsmStart", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo2 = AccessTools.Method(typeof(HeroController), "Update", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ReaperDashSlashModule), "OnHeroUpdate", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } _log.LogInfo((object)"[InsectMayCry][AttackModule:reaper.dash-slash] installed (N reaper dash slash)."); } public static void SetUpMoveset(string crestId, Transform root, HeroController hero) { if (!VergilCrestRules.IsNeroCrest(crestId) || (Object)(object)root == (Object)null || (Object)(object)hero == (Object)null) { return; } Transform val = ((Component)hero).transform.Find("Attacks/Scythe/DashUpper Slash"); if ((Object)(object)val == (Object)null) { ReaperDashSlashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)"[AttackModule:reaper.dash-slash] Attacks/Scythe/DashUpper Slash not found."); } return; } _dashUpperSlashClone = Object.Instantiate(((Component)val).gameObject, root); ((Object)_dashUpperSlashClone).name = crestId + " DashUpper Slash"; _dashUpperSlashClone.SetActive(true); ReaperDashSlashModule? instance2 = _instance; if (instance2 != null) { instance2._log.LogInfo((object)"[AttackModule:reaper.dash-slash] dash upper slash clone ready."); } } public static void DashSlashFsmEdit(PlayMakerFSM fsm, FsmState startState, out FsmState[] endStates) { try { FsmState state = FsmUtil.GetState(fsm, "Reaper Antic"); if (state != null) { FsmUtil.AddTransition(startState, "FINISHED", state.Name); } else { ReaperDashSlashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)"[AttackModule:reaper.dash-slash] Sprint FSM 'Reaper Antic' not found; Nero dash falls back to Regain Control Normal."); } } CacheReaperSlashVariable(fsm); } catch (Exception ex) { ReaperDashSlashModule? instance2 = _instance; if (instance2 != null) { instance2._log.LogWarning((object)("[AttackModule:reaper.dash-slash] FSM edit failed: " + ex.GetType().Name + ": " + ex.Message)); } } endStates = Array.Empty(); } public static void OnFsmStart(PlayMakerFSM __instance) { if (_instance != null && !((Object)(object)__instance == (Object)null) && __instance.Fsm != null && !(__instance.FsmName != "Sprint") && !((Object)(object)((Component)__instance).gameObject == (Object)null) && ((Object)((Component)__instance).gameObject).name.StartsWith("Hero_Hornet", StringComparison.Ordinal)) { CacheReaperSlashVariable(__instance); } } private static void CacheReaperSlashVariable(PlayMakerFSM fsm) { FsmVariables fsmVariables = fsm.FsmVariables; FsmGameObject val = ((fsmVariables != null) ? fsmVariables.GetFsmGameObject("Reaper Slash") : null); if (val == null) { ReaperDashSlashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)"[AttackModule:reaper.dash-slash] Sprint FSM 'Reaper Slash' variable not found."); } } else { _reaperSlashVar = val; _originalReaperSlash = null; ApplyReaperSlashRedirect(); } } public static void OnHeroUpdate() { if (_reaperSlashVar != null) { ApplyReaperSlashRedirect(); } } private static void ApplyReaperSlashRedirect() { FsmGameObject reaperSlashVar = _reaperSlashVar; if (reaperSlashVar == null) { return; } HeroController instance = HeroController.instance; if (!((Object)(object)instance == (Object)null)) { string crestId = instance.playerData?.CurrentCrestID; GameObject val = ResolveOriginalReaperSlash(instance); if (VergilCrestRules.IsNeroCrest(crestId) && (Object)(object)_dashUpperSlashClone != (Object)null) { val = _dashUpperSlashClone; } if (!((Object)(object)val == (Object)null) && reaperSlashVar.Value != val) { reaperSlashVar.Value = val; } } } private static GameObject? ResolveOriginalReaperSlash(HeroController hero) { if ((Object)(object)_originalReaperSlash == (Object)null) { object originalReaperSlash; if (hero == null) { originalReaperSlash = null; } else { Transform obj = ((Component)hero).transform.Find("Attacks/Scythe/DashUpper Slash"); originalReaperSlash = ((obj != null) ? ((Component)obj).gameObject : null); } _originalReaperSlash = (GameObject?)originalReaperSlash; } return _originalReaperSlash; } public void Dispose() { if (!_disposed) { _disposed = true; _dashUpperSlashClone = null; if (_instance == this) { _instance = null; } } } } public sealed class ShamanChargeSlashModule : IAttackModule, IDisposable { private sealed class ShamanChargeSlashFsmMarker : MonoBehaviour { } private sealed class ShamanChargeSlashRangeAction : FsmStateAction { public override void OnEnter() { try { ExtendRange(); } catch (Exception ex) { ShamanChargeSlashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)("[AttackModule:shaman.charge-slash] action threw: " + ex.GetType().Name + ": " + ex.Message)); } } ((FsmStateAction)this).Finish(); } } private readonly ManualLogSource _log; private readonly AttackModuleOptions _options; private static ShamanChargeSlashModule? _instance; private bool _disposed; private static bool _warned; private static bool _hookWarned; private static readonly Dictionary _targets = new Dictionary(StringComparer.Ordinal); private static FieldInfo? _travelDistanceField; private static FieldInfo? _travelDurationField; private static FieldInfo? _waitTimeField; private static FieldInfo? _timeLeftField; public string Id => "B3"; public string DisplayName => "蓄力下攻击(萨满远程)"; public bool Enabled => _options.ShamanChargeSlashEnabled; public IReadOnlyList CrestIds { get; } = new string[1] { "Vergil" }; public bool IsActiveFor(string? crestId) { return IsShamanLongRangeActive(crestId); } private static bool IsShamanLongRangeActive(string? crestId) { if (VergilCrestRules.IsVergilCrest(crestId)) { return VergilChargeVariantState.Current == ChargeVariant.Up; } return false; } public static void RegisterTarget(string crestId, Transform target) { if (!string.IsNullOrEmpty(crestId) && !((Object)(object)target == (Object)null)) { _targets[crestId] = target; } } public ShamanChargeSlashModule(ManualLogSource log, AttackModuleOptions options) { _log = log ?? throw new ArgumentNullException("log"); _options = options ?? throw new ArgumentNullException("options"); } public void Install(Harmony harmony) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown if (harmony == null) { throw new ArgumentNullException("harmony"); } if (_instance == null) { _instance = this; _warned = false; _hookWarned = false; CacheFields(); MethodInfo methodInfo = AccessTools.Method(typeof(PlayMakerFSM), "Start", (Type[])null, (Type[])null); if (methodInfo == null) { _log.LogWarning((object)"[AttackModule:shaman.charge-slash] PlayMakerFSM.Start not found; feature disabled."); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ShamanChargeSlashModule), "OnFsmStart", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _log.LogInfo((object)"[InsectMayCry][AttackModule:shaman.charge-slash] installed (D/V/N down-variant long-range wave)."); } } private void CacheFields() { _travelDistanceField = AccessTools.Field(typeof(NailSlashTravel), "travelDistance"); _travelDurationField = AccessTools.Field(typeof(NailSlashTravel), "travelDuration"); _waitTimeField = AccessTools.Field(typeof(DisableAfterTime), "waitTime"); _timeLeftField = AccessTools.Field(typeof(DisableAfterTime), "timeLeft"); if (_travelDistanceField == null || _travelDurationField == null || _waitTimeField == null || _timeLeftField == null) { _log.LogWarning((object)"[AttackModule:shaman.charge-slash] one or more fields not found; feature disabled."); } } public static void OnFsmStart(PlayMakerFSM __instance) { ShamanChargeSlashModule instance = _instance; if (instance == null || (Object)(object)__instance == (Object)null || __instance.Fsm == null || __instance.FsmName != "Nail Arts" || (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, "Do Slash"); if (state == null) { if (!_hookWarned) { _hookWarned = true; instance._log.LogWarning((object)"[AttackModule:shaman.charge-slash] Nail Arts 'Do Slash' state not found."); } } else { FsmUtil.InsertAction(state, state.Actions.Length, (FsmStateAction)(object)new ShamanChargeSlashRangeAction()); ((Component)__instance).gameObject.AddComponent(); instance._log.LogInfo((object)"[AttackModule:shaman.charge-slash] Nail Arts FSM hook installed."); } } catch (Exception ex) { instance._log.LogWarning((object)("[AttackModule:shaman.charge-slash] FSM hook failed: " + ex.GetType().Name + ": " + ex.Message)); } } private static void ExtendRange() { //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) ShamanChargeSlashModule instance = _instance; if (instance == null || !instance._options.ShamanChargeSlashEnabled) { return; } HeroController instance2 = HeroController.instance; if ((Object)(object)instance2 == (Object)null || instance2.playerData == null || _travelDistanceField == null || _travelDurationField == null) { return; } string currentCrestID = instance2.playerData.CurrentCrestID; if (!IsShamanLongRangeActive(currentCrestID)) { instance._log.LogDebug((object)"[AttackModule:shaman.charge-slash] skipped: not V-up variant."); return; } Transform value = null; if (currentCrestID != null) { _targets.TryGetValue(currentCrestID, out value); } object obj2; if (!((Object)(object)value != (Object)null)) { Transform obj = ((Component)instance2).transform.Find("Attacks/Charge Slash Shaman"); obj2 = ((obj != null) ? ((Component)obj).gameObject : null); } else { obj2 = ((Component)value).gameObject; } GameObject val = (GameObject)obj2; if ((Object)(object)val == (Object)null) { WarnOnce(instance, "Charge Slash Shaman child not found under Hero_Hornet/Attacks."); return; } NailSlashTravel component = val.GetComponent(); if ((Object)(object)component == (Object)null) { WarnOnce(instance, "NailSlashTravel not found on Charge Slash Shaman."); return; } Vector2 val2 = (Vector2)(_travelDistanceField.GetValue(component) ?? ((object)Vector2.zero)); float num = AttackModuleRules.ComputeShamanChargeSlashTravelX(instance._options.ShamanChargeSlashTravelDistance, ((Component)instance2).transform.localScale.x, val.transform.lossyScale.x); _travelDistanceField.SetValue(component, (object)new Vector2(num, val2.y)); _travelDurationField.SetValue(component, instance._options.ShamanChargeSlashTravelDuration); DisableAfterTime component2 = val.GetComponent(); if ((Object)(object)component2 != (Object)null && _waitTimeField != null && _timeLeftField != null) { _waitTimeField.SetValue(component2, instance._options.ShamanChargeSlashTravelDuration); _timeLeftField.SetValue(component2, instance._options.ShamanChargeSlashTravelDuration); } instance._log.LogInfo((object)("[AttackModule:shaman.charge-slash] extended travel: " + $"distance={instance._options.ShamanChargeSlashTravelDistance} " + $"duration={instance._options.ShamanChargeSlashTravelDuration}.")); } private static void WarnOnce(ShamanChargeSlashModule module, string message) { if (!_warned) { _warned = true; module._log.LogWarning((object)("[AttackModule:shaman.charge-slash] " + message)); } } public void Dispose() { if (!_disposed) { _disposed = true; _targets.Clear(); if (_instance == this) { _instance = null; } } } } public sealed class ShamanDashSlashModule : IAttackModule, IDisposable { private sealed class ShamanDashSlashFsmMarker : MonoBehaviour { } private sealed class SpinSlashOriginalsMarker : MonoBehaviour { public Vector3 OriginalScale; public float OriginalDamageMultiplier = 1f; } private sealed class ShamanDashSlashStartAction : FsmStateAction { private const float LeapYSpeed = 14.5f; public override void OnEnter() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) try { HeroController instance = HeroController.instance; if (instance?.playerData == null || !VergilChargeVariantState.IsImcEquipped() || VergilChargeVariantState.Current != ChargeVariant.Dash || !VergilCrestRules.IsVergilCrest(instance.playerData.CurrentCrestID)) { return; } Rigidbody2D component = ((Component)instance).GetComponent(); if ((Object)(object)component != (Object)null) { Vector2 linearVelocity = component.linearVelocity; linearVelocity.y = 14.5f; component.linearVelocity = linearVelocity; } Fsm fsm = ((FsmStateAction)this).Fsm; object obj; if (fsm == null) { obj = null; } else { FsmVariables variables = fsm.Variables; if (variables == null) { obj = null; } else { FsmGameObject fsmGameObject = variables.GetFsmGameObject("Current Charge Slash"); obj = ((fsmGameObject != null) ? fsmGameObject.Value : null); } } GameObject val = (GameObject)obj; if ((Object)(object)val != (Object)null) { val.SendMessage("StartSlash", (SendMessageOptions)1); } else { string currentCrestID = instance.playerData.CurrentCrestID; if (currentCrestID != null && _targets.TryGetValue(currentCrestID, out Transform value) && (Object)(object)value != (Object)null) { ((Component)value).gameObject.SendMessage("StartSlash", (SendMessageOptions)1); } } } catch (Exception) { } ((FsmStateAction)this).Finish(); } } private sealed class ShamanDashSlashLingerDriver : MonoBehaviour { private Transform? _clone; private Transform? _originalParent; private double _endTime; private bool _armed; public bool IsArmed => _armed; public void Arm(Transform clone, Transform? originalParent, double durationSeconds, float releaseDirection) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) _clone = clone; _originalParent = originalParent; _armed = durationSeconds > 0.0; if (_armed && (Object)(object)_clone != (Object)null) { _clone.SetParent((Transform)null, true); ((Component)_clone).gameObject.SetActive(true); Vector3 localScale = _clone.localScale; _clone.localScale = new Vector3((0f - releaseDirection) * Mathf.Abs(localScale.x), localScale.y, localScale.z); NailSlash component = ((Component)_clone).GetComponent(); DamageEnemies val = ((component != null) ? ((NailAttackBase)component).EnemyDamager : null); if ((Object)(object)val != (Object)null) { val.SetDirection((releaseDirection > 0f) ? 0f : 180f); } } _endTime = Time.timeAsDouble + Math.Max(0.0, durationSeconds); } private void Update() { if (_armed && !((Object)(object)_clone == (Object)null) && !((Object)(object)((Component)_clone).gameObject == (Object)null)) { if (Time.timeAsDouble >= _endTime) { EndLinger(); } else { ((Component)_clone).gameObject.SetActive(true); } } } private void EndLinger() { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) if (!_armed && (Object)(object)_clone == (Object)null) { return; } _armed = false; Transform clone = _clone; Transform originalParent = _originalParent; if ((Object)(object)clone != (Object)null && (Object)(object)((Component)clone).gameObject != (Object)null) { NailSlash component = ((Component)clone).GetComponent(); if ((Object)(object)component != (Object)null) { component.CancelAttack(true); } SpinSlashHitResetDriver component2 = ((Component)clone).GetComponent(); if ((Object)(object)component2 != (Object)null) { Object.Destroy((Object)(object)component2); } if ((Object)(object)originalParent != (Object)null) { clone.SetParent(originalParent, false); clone.localPosition = _slotLocalPosition; clone.localRotation = _slotLocalRotation; clone.localScale = _slotLocalScale; } ((Component)clone).gameObject.SetActive(true); } _clone = null; } private void OnDestroy() { EndLinger(); } } private sealed class SpinSlashHitResetDriver : MonoBehaviour { public float Interval = 0.4f; public DamageEnemies[] Damagers = Array.Empty(); private float _timer; private float _startTime; private void Awake() { _startTime = Time.time; } private void Update() { try { if (Time.time - _startTime > 35f) { Object.Destroy((Object)(object)this); return; } _timer += Time.deltaTime; if (!(_timer < Interval)) { _timer = 0f; DamageEnemies[] damagers = Damagers; for (int i = 0; i < damagers.Length; i++) { ResetHitState(damagers[i]); } } } catch (Exception) { Object.Destroy((Object)(object)this); } } private static void ResetHitState(DamageEnemies damager) { if ((Object)(object)damager == (Object)null || _damagedCollidersField == null || _damagePreventedField == null) { return; } if (_hasSharedDamageGroupField != null) { object value = _hasSharedDamageGroupField.GetValue(damager); if (value is bool && (bool)value) { return; } } if (_isProcessingBufferField != null) { object value = _isProcessingBufferField.GetValue(damager); if (value is bool && (bool)value) { return; } } object value2 = _damagedCollidersField.GetValue(damager); _hashSetClear?.Invoke(value2, null); object value3 = _damagePreventedField.GetValue(damager); _damagePreventedField.FieldType.GetMethod("Clear", Type.EmptyTypes)?.Invoke(value3, null); } } private const string SpinClipName = "Dash Attack Slash"; private readonly ManualLogSource _log; private readonly AttackModuleOptions _options; private static ShamanDashSlashModule? _instance; private static bool _hookWarned; private static bool _mergedLibWarned; private static bool _mergedLibLogged; private static bool _redirectLogged; private static bool _lingerLogged; private static readonly Dictionary _targets = new Dictionary(StringComparer.Ordinal); private static FsmGameObject? _shamanDashSlashVar; private static GameObject? _originalShamanDashSlash; private static Transform? _originalParent; private static tk2dSpriteAnimation? _mergedAnimLib; private static GameObject? _mergedAnimLibHost; private static tk2dSpriteAnimation? _spinLoopLib; private static GameObject? _spinLoopLibHost; private static bool _spinLoopLibWarned; private static Vector3 _slotLocalPosition; private static Quaternion _slotLocalRotation; private static Vector3 _slotLocalScale; private static FieldInfo? _animTriggerCounterField; private static FieldInfo? _damagedCollidersField; private static FieldInfo? _damagePreventedField; private static FieldInfo? _isProcessingBufferField; private static FieldInfo? _hasSharedDamageGroupField; private static MethodInfo? _hashSetClear; private static bool _resetBindingWarned; private static ShamanDashSlashLingerDriver? _lingerDriver; private bool _disposed; public string Id => "B4"; public string DisplayName => "萨满冲刺斩(回旋斩,接入 V)"; public bool Enabled => _options.ShamanDashSlashEnabled; public IReadOnlyList CrestIds { get; } = new string[1] { "Vergil" }; public bool IsActiveFor(string? crestId) { return VergilCrestRules.IsVergilCrest(crestId); } public ShamanDashSlashModule(ManualLogSource log, AttackModuleOptions options) { _log = log ?? throw new ArgumentNullException("log"); _options = options ?? throw new ArgumentNullException("options"); } public void Install(Harmony harmony) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Expected O, but got Unknown //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Expected O, but got Unknown //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Expected O, but got Unknown //IL_01b6: Expected O, but got Unknown //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Expected O, but got Unknown if (harmony == null) { throw new ArgumentNullException("harmony"); } if (_instance != null) { return; } _instance = this; _hookWarned = false; _mergedLibWarned = false; _mergedLibLogged = false; _redirectLogged = false; _lingerLogged = false; MethodInfo methodInfo = AccessTools.Method(typeof(PlayMakerFSM), "Start", (Type[])null, (Type[])null); if (methodInfo == null) { _log.LogWarning((object)"[AttackModule:shaman.dash-slash] PlayMakerFSM.Start not found; feature disabled."); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ShamanDashSlashModule), "OnFsmStart", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo2 = AccessTools.Method(typeof(HeroController), "Update", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ShamanDashSlashModule), "OnHeroUpdate", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo3 = AccessTools.Method(typeof(NailAttackBase), "OnSlashStarting", (Type[])null, (Type[])null); if (methodInfo3 == null) { _log.LogWarning((object)"[AttackModule:shaman.dash-slash] NailAttackBase.OnSlashStarting not found; range/damage/linger disabled."); } else { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ShamanDashSlashModule), "OnSpinSlashStarting", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo4 = AccessTools.Method(typeof(NailSlash), "CancelAttack", new Type[1] { typeof(bool) }, (Type[])null); if (methodInfo4 == null) { _log.LogWarning((object)"[AttackModule:shaman.dash-slash] NailSlash.CancelAttack(bool) not found; linger cancel guard disabled."); } else { harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ShamanDashSlashModule), "OnSpinSlashCancel", (Type[])null, (Type[])null)), new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ShamanDashSlashModule), "OnSpinSlashCancelPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo5 = AccessTools.Method(typeof(NailSlash), "OnAnimationEventTriggered", (Type[])null, (Type[])null); _animTriggerCounterField = AccessTools.Field(typeof(NailSlash), "animTriggerCounter"); if (methodInfo5 == null || _animTriggerCounterField == null) { _log.LogWarning((object)"[AttackModule:shaman.dash-slash] NailSlash clip-event hook unavailable; linger damage may stop after the first spin cycle."); } else { harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(ShamanDashSlashModule), "OnSpinSlashClipEvent", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } _log.LogInfo((object)"[InsectMayCry][AttackModule:shaman.dash-slash] installed (V air-dash released spin slash)."); } public static void RegisterTarget(string crestId, Transform target) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(crestId) || (Object)(object)target == (Object)null) { return; } ((Component)target).gameObject.SetActive(true); if (VergilCrestRules.IsVergilCrest(crestId)) { object originalParent; if (!((Object)(object)target.parent != (Object)null)) { HeroController instance = HeroController.instance; originalParent = ((instance != null) ? ((Component)instance).transform : null); } else { originalParent = target.parent; } _originalParent = (Transform?)originalParent; _slotLocalPosition = target.localPosition; _slotLocalRotation = target.localRotation; _slotLocalScale = target.localScale; InstallSpinLoopLibrary(target); } _targets[crestId] = target; } private static void InstallSpinLoopLibrary(Transform target) { try { tk2dSpriteAnimator component = ((Component)target).GetComponent(); NailSlash component2 = ((Component)target).GetComponent(); string clipName = (((Object)(object)component2 != (Object)null && !string.IsNullOrEmpty(component2.animName)) ? component2.animName : "Dash Attack Slash"); tk2dSpriteAnimation val = (((Object)(object)component != (Object)null) ? component.Library : null); if ((Object)(object)component == (Object)null || (Object)(object)val == (Object)null) { WarnSpinLoopLib("animator/library missing on V spin clone."); return; } if ((Object)(object)_spinLoopLib == (Object)null) { _spinLoopLib = BuildSpinLoopLibrary(val, clipName); } if (!((Object)(object)_spinLoopLib == (Object)null)) { component.Library = _spinLoopLib; } } catch (Exception ex) { WarnSpinLoopLib(ex.GetType().Name + ": " + ex.Message); } } private static tk2dSpriteAnimation? BuildSpinLoopLibrary(tk2dSpriteAnimation sourceLib, string clipName) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown if (sourceLib?.clips == null || sourceLib.clips.Length == 0) { return null; } tk2dSpriteAnimationClip val = Array.Find(sourceLib.clips, (tk2dSpriteAnimationClip c) => c != null && c.name == clipName); if (val == null || val.frames == null || val.frames.Length == 0) { WarnSpinLoopLib("clip '" + clipName + "' not found in weapon library."); return null; } tk2dSpriteAnimationClip val2 = new tk2dSpriteAnimationClip { name = clipName, fps = val.fps, loopStart = 0, wrapMode = (WrapMode)0, frames = val.frames }; tk2dSpriteAnimationClip[] array = (tk2dSpriteAnimationClip[])(object)new tk2dSpriteAnimationClip[sourceLib.clips.Length]; for (int num = 0; num < sourceLib.clips.Length; num++) { tk2dSpriteAnimationClip val3 = sourceLib.clips[num]; array[num] = ((val3 != null && val3.name == clipName) ? val2 : val3); } GameObject val4 = new GameObject("IMC_Vergil_SpinLoopLib"); Object.DontDestroyOnLoad((Object)(object)val4); ((Object)val4).hideFlags = (HideFlags)61; tk2dSpriteAnimation obj = val4.AddComponent(); obj.clips = array; obj.ValidateLookup(); _spinLoopLibHost = val4; return obj; } private static void WarnSpinLoopLib(string message) { if (!_spinLoopLibWarned) { _spinLoopLibWarned = true; ShamanDashSlashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)("[AttackModule:shaman.dash-slash] spin loop lib: " + message)); } } } public static void InstallMergedHeroAnimLib(HeroControllerConfig targetCfg, tk2dSpriteAnimation witchLib) { try { if (_instance == null || (Object)(object)targetCfg == (Object)null || (Object)(object)witchLib == (Object)null) { return; } tk2dSpriteAnimation orCreateMergedLib = GetOrCreateMergedLib(witchLib, ResolveShamanAnimLib()); if ((Object)(object)orCreateMergedLib == (Object)null) { WarnMergedLib("merged hero anim library could not be built (witch/shaman clips missing)."); return; } FieldInfo fieldInfo = AccessTools.Field(typeof(HeroControllerConfig), "heroAnimOverrideLib"); if (fieldInfo == null) { WarnMergedLib("heroAnimOverrideLib field not found."); return; } fieldInfo.SetValue(targetCfg, orCreateMergedLib); if (!_mergedLibLogged) { _mergedLibLogged = true; ShamanDashSlashModule? instance = _instance; if (instance != null) { ManualLogSource log = instance._log; tk2dSpriteAnimationClip[] clips = orCreateMergedLib.clips; log.LogInfo((object)("[AttackModule:shaman.dash-slash] merged hero anim lib installed " + $"({((clips != null) ? clips.Length : 0)} clips: witch+shaman).")); } } } catch (Exception ex) { WarnMergedLib(ex.GetType().Name + ": " + ex.Message); } } private static tk2dSpriteAnimation? ResolveShamanAnimLib() { try { ToolCrest crestByName = ToolItemManager.GetCrestByName("Spell"); HeroControllerConfig val = ((crestByName != null) ? crestByName.HeroConfig : null); if ((Object)(object)val == (Object)null) { return null; } object? obj = AccessTools.Field(typeof(HeroControllerConfig), "heroAnimOverrideLib")?.GetValue(val); return (tk2dSpriteAnimation?)((obj is tk2dSpriteAnimation) ? obj : null); } catch (Exception ex) { WarnMergedLib("resolve shaman anim lib failed: " + ex.GetType().Name + ": " + ex.Message); return null; } } private static tk2dSpriteAnimation? GetOrCreateMergedLib(tk2dSpriteAnimation witchLib, tk2dSpriteAnimation? shamanLib) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown if ((Object)(object)_mergedAnimLib != (Object)null) { return _mergedAnimLib; } List list = new List(); HashSet names = new HashSet(StringComparer.Ordinal); AddClips(list, names, witchLib?.clips); AddClips(list, names, shamanLib?.clips); if (list.Count == 0) { return null; } GameObject val = new GameObject("IMC_Vergil_MergedHeroAnimLib"); Object.DontDestroyOnLoad((Object)(object)val); ((Object)val).hideFlags = (HideFlags)61; tk2dSpriteAnimation obj = val.AddComponent(); obj.clips = list.ToArray(); obj.ValidateLookup(); _mergedAnimLib = obj; _mergedAnimLibHost = val; return obj; } private static void AddClips(List clips, HashSet names, tk2dSpriteAnimationClip[]? source) { if (source == null) { return; } foreach (tk2dSpriteAnimationClip val in source) { if (val != null && !string.IsNullOrEmpty(val.name) && names.Add(val.name)) { clips.Add(val); } } } private static void WarnMergedLib(string message) { if (!_mergedLibWarned) { _mergedLibWarned = true; ShamanDashSlashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)("[AttackModule:shaman.dash-slash] merged anim lib: " + message)); } } } public static void OnFsmStart(PlayMakerFSM __instance) { ShamanDashSlashModule instance = _instance; if (instance == null || !instance._options.ShamanDashSlashEnabled || (Object)(object)__instance == (Object)null || __instance.Fsm == null || (Object)(object)((Component)__instance).gameObject == (Object)null || !((Object)((Component)__instance).gameObject).name.StartsWith("Hero_Hornet", StringComparison.Ordinal)) { return; } if (__instance.FsmName == "Sprint") { CacheShamanDashSlashVariable(__instance); } else { if (__instance.FsmName != "Nail Arts" || (Object)(object)((Component)__instance).GetComponent() != (Object)null) { return; } try { FsmState state = FsmUtil.GetState(__instance, "Do Slash"); if (state == null) { if (!_hookWarned) { _hookWarned = true; instance._log.LogWarning((object)"[AttackModule:shaman.dash-slash] Nail Arts 'Do Slash' not found."); } } else { FsmUtil.InsertAction(state, state.Actions.Length, (FsmStateAction)(object)new ShamanDashSlashStartAction()); ((Component)__instance).gameObject.AddComponent(); instance._log.LogInfo((object)"[AttackModule:shaman.dash-slash] Nail Arts 'Do Slash' hook installed."); } } catch (Exception ex) { instance._log.LogWarning((object)("[AttackModule:shaman.dash-slash] FSM hook failed: " + ex.GetType().Name + ": " + ex.Message)); } } } private static void CacheShamanDashSlashVariable(PlayMakerFSM fsm) { FsmVariables fsmVariables = fsm.FsmVariables; FsmGameObject val = ((fsmVariables != null) ? fsmVariables.GetFsmGameObject("Shaman Dash Slash") : null); if (val == null) { ShamanDashSlashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)"[AttackModule:shaman.dash-slash] Sprint FSM 'Shaman Dash Slash' variable not found."); } } else { _shamanDashSlashVar = val; _originalShamanDashSlash = null; ApplyShamanDashSlashRedirect(); } } public static void OnHeroUpdate() { if (_shamanDashSlashVar != null) { ApplyShamanDashSlashRedirect(); } } private static void ApplyShamanDashSlashRedirect() { FsmGameObject shamanDashSlashVar = _shamanDashSlashVar; if (shamanDashSlashVar == null) { return; } HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null) { return; } string crestId = instance.playerData?.CurrentCrestID; GameObject val = ResolveOriginalShamanDashSlash(instance); if (VergilCrestRules.IsVergilCrest(crestId) && _targets.TryGetValue("Vergil", out Transform value) && (Object)(object)value != (Object)null) { val = ((Component)value).gameObject; if (!((Component)value).gameObject.activeSelf) { ((Component)value).gameObject.SetActive(true); } } if ((Object)(object)val == (Object)null || shamanDashSlashVar.Value == val) { return; } shamanDashSlashVar.Value = val; if (!_redirectLogged) { _redirectLogged = true; ShamanDashSlashModule? instance2 = _instance; if (instance2 != null) { instance2._log.LogInfo((object)("[AttackModule:shaman.dash-slash] Shaman Dash Slash -> " + $"'{((Object)val).name}' active={val.activeInHierarchy} (first redirect).")); } } } private static GameObject? ResolveOriginalShamanDashSlash(HeroController hero) { if ((Object)(object)_originalShamanDashSlash == (Object)null) { object originalShamanDashSlash; if (hero == null) { originalShamanDashSlash = null; } else { Transform obj = ((Component)hero).transform.Find("Attacks/Shaman/DashSlash"); originalShamanDashSlash = ((obj != null) ? ((Component)obj).gameObject : null); } _originalShamanDashSlash = (GameObject?)originalShamanDashSlash; } return _originalShamanDashSlash; } private static bool IsVergilSpinSlashClone(Transform transform) { if (_targets.TryGetValue("Vergil", out Transform value) && (Object)(object)value != (Object)null) { return transform == value; } return false; } public static void OnSpinSlashStarting(NailAttackBase __instance) { //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) ShamanDashSlashModule instance = _instance; if (instance == null || !instance._options.ShamanDashSlashEnabled || (Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).transform == (Object)null || !IsVergilSpinSlashClone(((Component)__instance).transform)) { return; } AttackModuleOptions options = instance._options; SpinSlashOriginalsMarker spinSlashOriginalsMarker = ((Component)__instance).GetComponent(); if ((Object)(object)spinSlashOriginalsMarker == (Object)null) { spinSlashOriginalsMarker = ((Component)__instance).gameObject.AddComponent(); spinSlashOriginalsMarker.OriginalScale = ((Component)__instance).transform.localScale; spinSlashOriginalsMarker.OriginalDamageMultiplier = (((Object)(object)__instance.EnemyDamager != (Object)null) ? __instance.EnemyDamager.DamageMultiplier : 1f); } Vector3 originalScale = spinSlashOriginalsMarker.OriginalScale; ((Component)__instance).transform.localScale = new Vector3(originalScale.x * options.ShamanDashSlashRangeMultiplier, originalScale.y * options.ShamanDashSlashRangeMultiplier, originalScale.z); if ((Object)(object)__instance.EnemyDamager != (Object)null) { __instance.EnemyDamager.DamageMultiplier = spinSlashOriginalsMarker.OriginalDamageMultiplier * options.ShamanDashSlashDamageMultiplier; } EnsureHitResetDriver(__instance, options.ShamanDashSlashHitResetIntervalSeconds); HeroController instance2 = HeroController.instance; if (!((Object)(object)instance2 == (Object)null)) { ShamanDashSlashLingerDriver shamanDashSlashLingerDriver = ((Component)instance2).GetComponent(); if ((Object)(object)shamanDashSlashLingerDriver == (Object)null) { shamanDashSlashLingerDriver = ((Component)instance2).gameObject.AddComponent(); } _lingerDriver = shamanDashSlashLingerDriver; ShamanDashSlashLingerDriver shamanDashSlashLingerDriver2 = shamanDashSlashLingerDriver; Transform transform = ((Component)__instance).transform; Transform? originalParent = _originalParent; double shamanDashSlashDurationSeconds = options.ShamanDashSlashDurationSeconds; Rigidbody2D component = ((Component)instance2).GetComponent(); shamanDashSlashLingerDriver2.Arm(transform, originalParent, shamanDashSlashDurationSeconds, ShamanDashSlashRules.ResolveReleaseDirection((component != null) ? component.linearVelocity.x : 0f, ((Component)instance2).transform.localScale.x)); if (!_lingerLogged) { _lingerLogged = true; instance._log.LogInfo((object)("[AttackModule:shaman.dash-slash] linger armed: duration=" + $"{options.ShamanDashSlashDurationSeconds}s range=" + $"{options.ShamanDashSlashRangeMultiplier} damage=" + $"{options.ShamanDashSlashDamageMultiplier} hitReset=" + $"{options.ShamanDashSlashHitResetIntervalSeconds}s")); } } } public static bool OnSpinSlashCancel(NailSlash __instance) { ShamanDashSlashModule instance = _instance; if (instance == null || !instance._options.ShamanDashSlashEnabled) { return true; } if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).transform == (Object)null) { return true; } if (!IsVergilSpinSlashClone(((Component)__instance).transform)) { return true; } HeroController instance2 = HeroController.instance; ShamanDashSlashLingerDriver shamanDashSlashLingerDriver = ((instance2 != null) ? ((Component)instance2).GetComponent() : null); if ((Object)(object)shamanDashSlashLingerDriver != (Object)null && shamanDashSlashLingerDriver.IsArmed) { return false; } return true; } public static void OnSpinSlashCancelPostfix(NailSlash __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).transform == (Object)null || !IsVergilSpinSlashClone(((Component)__instance).transform)) { return; } HeroController instance = HeroController.instance; ShamanDashSlashLingerDriver shamanDashSlashLingerDriver = ((instance != null) ? ((Component)instance).GetComponent() : null); if ((Object)(object)shamanDashSlashLingerDriver == (Object)null || !shamanDashSlashLingerDriver.IsArmed) { SpinSlashHitResetDriver component = ((Component)__instance).GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } } } public static bool OnSpinSlashClipEvent(NailSlash __instance) { ShamanDashSlashModule instance = _instance; if (instance == null || !instance._options.ShamanDashSlashEnabled) { return true; } if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).transform == (Object)null) { return true; } if (!IsVergilSpinSlashClone(((Component)__instance).transform)) { return true; } HeroController instance2 = HeroController.instance; ShamanDashSlashLingerDriver shamanDashSlashLingerDriver = ((instance2 != null) ? ((Component)instance2).GetComponent() : null); if ((Object)(object)shamanDashSlashLingerDriver == (Object)null || !shamanDashSlashLingerDriver.IsArmed) { return true; } if (_animTriggerCounterField == null) { return true; } return ((_animTriggerCounterField.GetValue(__instance) is int num) ? num : (-1)) != 2; } private static void EnsureHitResetDriver(NailAttackBase attack, double intervalSeconds) { if (intervalSeconds <= 0.0001 || !TryResolveHitResetBinding()) { return; } SpinSlashHitResetDriver spinSlashHitResetDriver = ((Component)attack).GetComponent(); if ((Object)(object)spinSlashHitResetDriver == (Object)null) { spinSlashHitResetDriver = ((Component)attack).gameObject.AddComponent(); } List list = new List(); if ((Object)(object)attack.EnemyDamager != (Object)null) { list.Add(attack.EnemyDamager); } object? obj = AccessTools.Property(typeof(NailAttackBase), "ExtraDamager")?.GetValue(attack); GameObject val = (GameObject)((obj is GameObject) ? obj : null); if (val != null && (Object)(object)val != (Object)null) { DamageEnemies component = val.GetComponent(); if ((Object)(object)component != (Object)null) { list.Add(component); } } spinSlashHitResetDriver.Interval = (float)intervalSeconds; spinSlashHitResetDriver.Damagers = list.ToArray(); } private static bool TryResolveHitResetBinding() { if (_damagedCollidersField != null && _damagePreventedField != null) { return true; } if (_resetBindingWarned) { return false; } _damagedCollidersField = AccessTools.Field(typeof(DamageEnemies), "damagedColliders"); _damagePreventedField = AccessTools.Field(typeof(DamageEnemies), "damagePrevented"); _isProcessingBufferField = AccessTools.Field(typeof(DamageEnemies), "isProcessingBuffer"); _hasSharedDamageGroupField = AccessTools.Field(typeof(DamageEnemies), "hasSharedDamageGroup"); if (_damagedCollidersField != null) { _hashSetClear = _damagedCollidersField.FieldType.GetMethod("Clear", Type.EmptyTypes); } if (_damagedCollidersField == null || _damagePreventedField == null || _hashSetClear == null) { _resetBindingWarned = true; ShamanDashSlashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)"[AttackModule:shaman.dash-slash] hit-reset binding missing (damagedColliders/damagePrevented); repeated hits disabled, other boosts keep working."); } return false; } return true; } public void Dispose() { if (!_disposed) { _disposed = true; _targets.Clear(); _originalParent = null; if ((Object)(object)_lingerDriver != (Object)null) { Object.Destroy((Object)(object)_lingerDriver); _lingerDriver = null; } if ((Object)(object)_mergedAnimLibHost != (Object)null) { Object.Destroy((Object)(object)_mergedAnimLibHost); _mergedAnimLibHost = null; _mergedAnimLib = null; } if ((Object)(object)_spinLoopLibHost != (Object)null) { Object.Destroy((Object)(object)_spinLoopLibHost); _spinLoopLibHost = null; _spinLoopLib = null; } if (_instance == this) { _instance = null; } } } } public static class ShamanDashSlashRules { public const double MaxDurationSeconds = 30.0; public const float DirectionVelocityThreshold = 0.1f; public static double ClampDurationSeconds(double value) { return Math.Clamp(value, 0.0, 30.0); } public static float ResolveReleaseDirection(float velocityX, float heroScaleX) { if (Math.Abs(velocityX) >= 0.1f) { if (!(velocityX > 0f)) { return -1f; } return 1f; } if (!(heroScaleX < 0f)) { return -1f; } return 1f; } } public sealed class VergilGroundDashChargeModule : IAttackModule, IDisposable { private sealed class GroundDashChargeAction : FsmStateAction { public override void OnEnter() { try { VergilGroundDashChargeModule instance = _instance; if (instance == null || !instance._options.VergilDashChargeEnabled) { return; } HeroController instance2 = HeroController.instance; if ((Object)(object)instance2 == (Object)null || instance2.playerData == null || instance2.cState == null || !AttackModuleRules.IsVergilGroundDashCharge(enabled: true, instance2.playerData.CurrentCrestID, VergilChargeVariantState.Current == ChargeVariant.Dash, instance2.cState.onGround)) { return; } Fsm fsm = ((FsmStateAction)this).Fsm; if (fsm != null && fsm.Variables != null && !((Object)(object)_placeholder == (Object)null)) { FsmGameObject fsmGameObject = fsm.Variables.GetFsmGameObject("Current Charge Slash"); if (fsmGameObject != null) { fsmGameObject.Value = _placeholder; } VergilChargeVariantState.LungeActive = false; GroundDashChargeDriver groundDashChargeDriver = ((Component)instance2).GetComponent(); if ((Object)(object)groundDashChargeDriver == (Object)null) { groundDashChargeDriver = ((Component)instance2).gameObject.AddComponent(); } groundDashChargeDriver.Arm(instance._options); } } catch (Exception ex) { VergilGroundDashChargeModule? instance3 = _instance; if (instance3 != null) { instance3._log.LogWarning((object)("[AttackModule:B2] action threw: " + ex.GetType().Name + ": " + ex.Message)); } } finally { ((FsmStateAction)this).Finish(); } } } private sealed class GroundDashChargeDriver : MonoBehaviour { private AttackModuleOptions? _options; private Coroutine? _routine; private bool _armed; private float _startTime; public bool IsActive => _armed; public void Arm(AttackModuleOptions options) { _options = options; if (_routine != null) { ((MonoBehaviour)this).StopCoroutine(_routine); _routine = null; } _armed = true; _startTime = Time.time; _routine = ((MonoBehaviour)this).StartCoroutine(RunChain()); } public void Abort() { if (_routine != null) { ((MonoBehaviour)this).StopCoroutine(_routine); _routine = null; } FinishCleanup(); } private void Update() { if (_armed) { VergilChargeVariantState.LungeActive = false; } } private IEnumerator RunChain() { HeroController instance = HeroController.instance; Transform parent = _registeredParent; AttackModuleOptions options = _options; if ((Object)(object)instance == (Object)null || (Object)(object)parent == (Object)null || options == null) { FinishCleanup(); yield break; } int rounds = Math.Max(1, options.VergilDashChargeRounds); float num = ReadRawDashStabTime(instance); float stepTime = Math.Max(0.01f, num / Math.Max(1f, options.VergilDashChargeSpeedMultiplier)); float speed = Math.Abs(((Object)(object)instance.Config != (Object)null) ? instance.Config.DashStabSpeed : (-30f)); float sign = ((((Component)instance).transform.localScale.x < 0f) ? 1f : (-1f)); Rigidbody2D rb = ((Component)instance).GetComponent(); float total = (float)(rounds * 2) * stepTime; if (options.VergilDashChargeInvulnerabilitySeconds > 0.0) { FoundationServices current = FoundationServices.Current; if (current != null) { IGameApi game = current.Game; if (game != null) { game.AcquireInvulnerability("B2.dash-charge", options.VergilDashChargeInvulnerabilitySeconds); } } } try { for (int round = 0; round < rounds; round++) { for (int step = 1; step <= 2; step++) { if (Time.time - _startTime > total + 1f) { yield break; } Transform child = parent.Find("Dash Stab " + step); if (!((Object)(object)child == (Object)null)) { ActivateStep(child, sign); if ((Object)(object)rb != (Object)null) { Vector2 linearVelocity = rb.linearVelocity; linearVelocity.x = sign * speed; rb.linearVelocity = linearVelocity; } yield return (object)new WaitForSeconds(stepTime); DeactivateStep(child); } } } } finally { FinishCleanup(); } } private float ReadRawDashStabTime(HeroController hero) { if ((Object)(object)hero.Config != (Object)null && _dashStabTimeField != null && _dashStabTimeField.GetValue(hero.Config) is float num && num > 0f) { return num; } if (!((Object)(object)hero.Config != (Object)null)) { return 0.15f; } return hero.Config.DashStabTime; } private static void ActivateStep(Transform child, float sign) { if (!((Component)child).gameObject.activeSelf) { ((Component)child).gameObject.SetActive(true); } DamageEnemies component = ((Component)child).GetComponent(); if ((Object)(object)component != (Object)null) { component.SetDirection((sign > 0f) ? 0f : 180f); } NailAttackBase component2 = ((Component)child).GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.OnCancelAttack(); component2.OnSlashStarting(); component2.OnPlaySlash(); } Collider2D component3 = ((Component)child).GetComponent(); if ((Object)(object)component3 != (Object)null) { ((Behaviour)component3).enabled = true; } Renderer[] componentsInChildren = ((Component)child).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].enabled = true; } } private static void DeactivateStep(Transform child) { Collider2D component = ((Component)child).GetComponent(); if ((Object)(object)component != (Object)null) { ((Behaviour)component).enabled = false; } Renderer[] componentsInChildren = ((Component)child).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].enabled = false; } } private void FinishCleanup() { _armed = false; _routine = null; Transform registeredParent = _registeredParent; if (!((Object)(object)registeredParent != (Object)null)) { return; } for (int i = 1; i <= 2; i++) { Transform val = registeredParent.Find("Dash Stab " + i); if ((Object)(object)val != (Object)null) { DeactivateStep(val); } } } private void OnDestroy() { if (_routine != null) { ((MonoBehaviour)this).StopCoroutine(_routine); _routine = null; } FinishCleanup(); } } private sealed class GroundDashChargeFsmMarker : MonoBehaviour { private readonly List _restores = new List(); public void AddRestore(Action restore) { _restores.Add(restore); } public void Restore() { foreach (Action restore in _restores) { try { restore(); } catch (Exception) { } } _restores.Clear(); } private void OnDestroy() { Restore(); } } private const string PlaceholderName = "CE V Ground DashCharge Placeholder"; private const string InvulnerabilitySource = "B2.dash-charge"; private readonly ManualLogSource _log; private readonly AttackModuleOptions _options; private static VergilGroundDashChargeModule? _instance; private static Transform? _registeredParent; private static GameObject? _placeholder; private static bool _warnedTarget; private static bool _warnedFsm; private static FieldInfo? _dashStabTimeField; private bool _disposed; public string Id => "B2"; public string DisplayName => "地面冲刺蓄力(冲刺式 4 连穿透)"; public bool Enabled => _options.VergilDashChargeEnabled; public IReadOnlyList CrestIds { get; } = new string[1] { "Vergil" }; public bool IsActiveFor(string? crestId) { return VergilCrestRules.IsVergilCrest(crestId); } public VergilGroundDashChargeModule(ManualLogSource log, AttackModuleOptions options) { _log = log ?? throw new ArgumentNullException("log"); _options = options ?? throw new ArgumentNullException("options"); } public void Install(Harmony harmony) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Expected O, but got Unknown if (harmony == null) { throw new ArgumentNullException("harmony"); } if (_instance == null) { _instance = this; _warnedTarget = false; _warnedFsm = false; _dashStabTimeField = AccessTools.Field(typeof(HeroControllerConfig), "dashStabTime"); MethodInfo methodInfo = AccessTools.Method(typeof(DashStabNailAttack), "DoRecoilHit", (Type[])null, (Type[])null); if (methodInfo == null) { _log.LogWarning((object)"[AttackModule:B2] DashStabNailAttack.DoRecoilHit not found; pierce disabled."); } else { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilGroundDashChargeModule), "OnDoRecoilHit", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.Method(typeof(PlayMakerFSM), "Start", (Type[])null, (Type[])null); if (methodInfo2 == null) { _log.LogWarning((object)"[AttackModule:B2] PlayMakerFSM.Start not found; FSM hook disabled."); } else { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(VergilGroundDashChargeModule), "OnFsmStart", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } _log.LogInfo((object)("[InsectMayCry][AttackModule:B2] installed (vergil ground dash-charge; " + $"rounds={_options.VergilDashChargeRounds}, speed x{_options.VergilDashChargeSpeedMultiplier}).")); } } public static void RegisterTarget(string crestId, Transform dashStabParent) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown VergilGroundDashChargeModule instance = _instance; if (instance == null || (Object)(object)dashStabParent == (Object)null || !VergilCrestRules.IsVergilCrest(crestId)) { return; } _registeredParent = dashStabParent; if ((Object)(object)_placeholder == (Object)null) { Transform parent = dashStabParent.parent; if ((Object)(object)parent == (Object)null) { if (!_warnedTarget) { _warnedTarget = true; instance._log.LogWarning((object)"[AttackModule:B2] no parent for placeholder; vanilla charge may flash."); } } else { GameObject val = new GameObject("CE V Ground DashCharge Placeholder"); val.transform.SetParent(parent, false); val.SetActive(false); _placeholder = val; } } instance._log.LogInfo((object)"[AttackModule:B2] target registered (Vergil Dash Stab Parent)."); } public static void OnFsmStart(PlayMakerFSM __instance) { VergilGroundDashChargeModule instance = _instance; if (instance == null || (Object)(object)__instance == (Object)null || __instance.Fsm == null || __instance.FsmName != "Nail Arts" || (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 doSlash = FsmUtil.GetState(__instance, "Do Slash"); if (doSlash == null) { if (!_warnedFsm) { _warnedFsm = true; instance._log.LogWarning((object)"[AttackModule:B2] Nail Arts 'Do Slash' not found."); } return; } GroundDashChargeAction action = new GroundDashChargeAction(); FsmUtil.InsertAction(doSlash, 0, (FsmStateAction)(object)action); ((Component)__instance).gameObject.AddComponent().AddRestore(delegate { RemoveAction(doSlash, (FsmStateAction)(object)action); }); instance._log.LogInfo((object)"[AttackModule:B2] Nail Arts 'Do Slash' hooked (vergil ground dash-charge)."); } catch (Exception ex) { instance._log.LogWarning((object)("[AttackModule:B2] FSM hook failed: " + ex.GetType().Name + ": " + ex.Message)); } } private static void RemoveAction(FsmState state, FsmStateAction action) { if (((state != null) ? state.Actions : null) != null) { state.Actions = state.Actions.Where((FsmStateAction a) => a != action).ToArray(); } } public static bool OnDoRecoilHit(DashStabNailAttack __instance) { VergilGroundDashChargeModule instance = _instance; if (instance == null) { return true; } if (!instance._options.VergilDashChargeEnabled) { return true; } if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).transform == (Object)null) { return true; } if ((Object)(object)_registeredParent == (Object)null) { return true; } if (!((Component)__instance).transform.IsChildOf(_registeredParent)) { return true; } if (!AttackModuleRules.IsWitchDashStabObject(((Object)((Component)__instance).transform).name)) { return true; } HeroController instance2 = HeroController.instance; if ((Object)(object)instance2 == (Object)null) { return true; } GroundDashChargeDriver component = ((Component)instance2).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsActive) { return true; } return false; } public void Dispose() { if (_disposed) { return; } _disposed = true; try { HeroController instance = HeroController.instance; if (instance != null) { ((Component)instance).GetComponent()?.Restore(); } if (instance != null) { ((Component)instance).GetComponent()?.Abort(); } } catch (Exception ex) { _log.LogWarning((object)("[AttackModule:B2] restore failed: " + ex.GetType().Name + ": " + ex.Message)); } _registeredParent = null; _placeholder = null; if (_instance == this) { _instance = null; } } } public sealed class WandererChargeAttackModule : IAttackModule, IDisposable { private sealed class WandererChargeRangeScaler : MonoBehaviour { private Vector3 _original; private Vector3 _stopperOriginalScale; private Vector3 _stopperOriginalPosition; private Transform? _stopper; private DamageEnemies? _damager; private float _damageOriginal = 1f; private bool _captured; private float _rangeMultiplier = 1f; private float _damageMultiplier = 1f; public void Configure(float rangeMultiplier, float damageMultiplier) { _rangeMultiplier = Math.Max(1f, rangeMultiplier); _damageMultiplier = Math.Max(1f, damageMultiplier); if (!_captured) { CaptureOriginals(); } if (((Component)this).gameObject.activeInHierarchy) { Apply(); } } private void OnEnable() { if (!_captured) { CaptureOriginals(); } Apply(); } private void OnDisable() { if (_captured) { RestoreInternal(); } } public void Restore() { if (_captured) { RestoreInternal(); } } private void CaptureOriginals() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) _original = ((Component)this).transform.localScale; _stopper = ((Component)this).transform.Find("Lunge Stopper"); if ((Object)(object)_stopper != (Object)null) { _stopperOriginalScale = _stopper.localScale; _stopperOriginalPosition = _stopper.localPosition; } _damager = ((Component)this).GetComponent(); if ((Object)(object)_damager != (Object)null) { _damageOriginal = _damager.nailDamageMultiplier; } _captured = true; } private void Apply() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) ((Component)this).transform.localScale = new Vector3(_original.x * _rangeMultiplier, _original.y, _original.z); if ((Object)(object)_stopper != (Object)null) { _stopper.localScale = new Vector3(_stopperOriginalScale.x / _rangeMultiplier, _stopperOriginalScale.y, _stopperOriginalScale.z); _stopper.localPosition = new Vector3(_stopperOriginalPosition.x / _rangeMultiplier, _stopperOriginalPosition.y, _stopperOriginalPosition.z); } if ((Object)(object)_damager != (Object)null && _damageMultiplier > 1f) { _damager.nailDamageMultiplier = _damageOriginal * _damageMultiplier; } } private void RestoreInternal() { //IL_0007: 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_0037: Unknown result type (might be due to invalid IL or missing references) ((Component)this).transform.localScale = _original; if ((Object)(object)_stopper != (Object)null) { _stopper.localScale = _stopperOriginalScale; _stopper.localPosition = _stopperOriginalPosition; } if ((Object)(object)_damager != (Object)null) { _damager.nailDamageMultiplier = _damageOriginal; } } } private sealed class ChargeDashLungeDriver : MonoBehaviour { private const float BaseSpeed = -188.99998f; private const float Deceleration = 0.78f; private float _speed; private Rigidbody2D? _rb; private void Awake() { _rb = ((Component)this).GetComponent(); } private void Update() { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (!VergilChargeVariantState.LungeActive) { return; } HeroController instance = HeroController.instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)_rb == (Object)null)) { if (_speed == 0f) { float num = ((((Component)instance).transform.localScale.x > 0f) ? 1f : (-1f)); _speed = num * -188.99998f; } Vector2 linearVelocity = _rb.linearVelocity; linearVelocity.x = _speed; _rb.linearVelocity = linearVelocity; } } private void FixedUpdate() { if (VergilChargeVariantState.LungeActive) { _speed *= 0.78f; if (Mathf.Abs(_speed) < 1f) { _speed = 0f; VergilChargeVariantState.LungeActive = false; } } } } private readonly ManualLogSource _log; private readonly AttackModuleOptions _options; private static readonly List _attached = new List(); private static AttackModuleOptions? _optionsCache; private bool _disposed; public string Id => "A2"; public string DisplayName => "蓄力冲刺攻击(漫步者加强)"; public bool Enabled => _options.WandererChargeEnabled; public IReadOnlyList CrestIds { get; } = new string[1] { "Dante" }; public bool IsActiveFor(string? crestId) { if (VergilCrestRules.IsDanteCrest(crestId)) { return VergilChargeVariantState.Current == ChargeVariant.Dash; } return false; } public WandererChargeAttackModule(ManualLogSource log, AttackModuleOptions options) { _log = log ?? throw new ArgumentNullException("log"); _options = options ?? throw new ArgumentNullException("options"); _optionsCache = options; } public void Install(Harmony harmony) { _log.LogInfo((object)"[InsectMayCry][AttackModule:wanderer.charge] installed (D/V/N-only; no vanilla edits)."); } public static void AttachImcInstance(GameObject instance) { WandererChargeRangeScaler wandererChargeRangeScaler = instance.GetComponent(); if ((Object)(object)wandererChargeRangeScaler == (Object)null) { wandererChargeRangeScaler = instance.AddComponent(); _attached.Add(wandererChargeRangeScaler); } wandererChargeRangeScaler.Configure(_optionsCache?.WandererChargeHitRangeMultiplier ?? 1.5f, _optionsCache?.WandererChargeDamageMultiplier ?? 2f); } public static void AttachHeroLungeDriver(GameObject? hero) { if (!((Object)(object)hero == (Object)null) && _optionsCache != null && _optionsCache.WandererChargeEnabled && (Object)(object)hero.GetComponent() == (Object)null) { hero.AddComponent(); } } public void Dispose() { if (_disposed) { return; } _disposed = true; foreach (WandererChargeRangeScaler item in _attached) { try { item?.Restore(); } catch (Exception) { } } _attached.Clear(); } } public sealed class WitchChargeModule : IAttackModule, IDisposable { private sealed class WitchChargeScaler : MonoBehaviour { private float _durationMultiplier = 2f; private float _damageMultiplier = 2f; private float _sizeMultiplier = 1.3f; private Vector3 _originalScale; private tk2dSpriteAnimator[] _animators = Array.Empty(); private float[] _originalFps = Array.Empty(); private DamageEnemies[] _damagers = Array.Empty(); private float[] _originalDamage = Array.Empty(); private bool _captured; public void Configure(float durationMultiplier, float damageMultiplier, float sizeMultiplier) { _durationMultiplier = Math.Max(1f, durationMultiplier); _damageMultiplier = Math.Max(1f, damageMultiplier); _sizeMultiplier = Math.Max(1f, sizeMultiplier); if (!_captured) { CaptureOriginals(); } if (((Component)this).gameObject.activeInHierarchy) { Apply(); } } private void OnEnable() { if (!_captured) { CaptureOriginals(); } Apply(); } private void OnDisable() { if (_captured) { RestoreInternal(); } } public void Restore() { if (_captured) { RestoreInternal(); } } private void CaptureOriginals() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) _originalScale = ((Component)this).transform.localScale; _animators = ((Component)this).GetComponentsInChildren(true); _originalFps = new float[_animators.Length]; for (int i = 0; i < _animators.Length; i++) { _originalFps[i] = _animators[i].ClipFps; } _damagers = ((Component)this).GetComponentsInChildren(true); _originalDamage = new float[_damagers.Length]; for (int j = 0; j < _damagers.Length; j++) { _originalDamage[j] = _damagers[j].nailDamageMultiplier; } _captured = true; } private void Apply() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) ((Component)this).transform.localScale = new Vector3(_originalScale.x * _sizeMultiplier, _originalScale.y * _sizeMultiplier, _originalScale.z); for (int i = 0; i < _animators.Length; i++) { if ((Object)(object)_animators[i] != (Object)null && _originalFps[i] > 0f) { _animators[i].ClipFps = _originalFps[i] / _durationMultiplier; } } for (int j = 0; j < _damagers.Length; j++) { if ((Object)(object)_damagers[j] != (Object)null) { _damagers[j].nailDamageMultiplier = _originalDamage[j] * _damageMultiplier; } } } private void RestoreInternal() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) ((Component)this).transform.localScale = _originalScale; for (int i = 0; i < _animators.Length; i++) { if ((Object)(object)_animators[i] != (Object)null) { _animators[i].ClipFps = _originalFps[i]; } } for (int j = 0; j < _damagers.Length; j++) { if ((Object)(object)_damagers[j] != (Object)null) { _damagers[j].nailDamageMultiplier = _originalDamage[j]; } } } } private readonly ManualLogSource _log; private readonly AttackModuleOptions _options; private static readonly List _attached = new List(); private static AttackModuleOptions? _optionsCache; private bool _disposed; public string Id => "C4"; public string DisplayName => "蓄力攻击(女巫放大)"; public bool Enabled => _options.WitchChargeEnabled; public IReadOnlyList CrestIds { get; } = new string[1] { "Vergil" }; public bool IsActiveFor(string? crestId) { return VergilCrestRules.IsVergilCrest(crestId); } public WitchChargeModule(ManualLogSource log, AttackModuleOptions options) { _log = log ?? throw new ArgumentNullException("log"); _options = options ?? throw new ArgumentNullException("options"); _optionsCache = options; } public void Install(Harmony harmony) { _log.LogInfo((object)"[InsectMayCry][AttackModule:C4/witch-charge] installed (D/V/N default charge; no vanilla edits)."); } public static void AttachImcInstance(GameObject instance) { if (!((Object)(object)instance == (Object)null)) { WitchChargeScaler witchChargeScaler = instance.GetComponent(); if ((Object)(object)witchChargeScaler == (Object)null) { witchChargeScaler = instance.AddComponent(); _attached.Add(witchChargeScaler); } witchChargeScaler.Configure(_optionsCache?.WitchChargeDurationMultiplier ?? 2f, _optionsCache?.WitchChargeDamageMultiplier ?? 2f, _optionsCache?.WitchChargeSizeMultiplier ?? 1.3f); } } public void Dispose() { if (_disposed) { return; } _disposed = true; foreach (WitchChargeScaler item in _attached) { try { item?.Restore(); } catch (Exception) { } } _attached.Clear(); } } public sealed class WitchCrossSlashModule : IAttackModule, IDisposable { private sealed class ConditionalActivateAction : FsmStateAction { private readonly ActivateGameObject _original; public ConditionalActivateAction(ActivateGameObject original) { _original = original ?? throw new ArgumentNullException("original"); } public override void OnEnter() { try { if (!IsCrossSlashActive()) { ((FsmStateAction)_original).OnEnter(); } } catch (Exception ex) { WitchCrossSlashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)("[AttackModule:witch.cross-slash] activate fallback threw: " + ex.GetType().Name + ": " + ex.Message)); } } ((FsmStateAction)this).Finish(); } } private sealed class ConditionalSpawnAction : FsmStateAction { private readonly SendMessageV2 _original; public ConditionalSpawnAction(SendMessageV2 original) { _original = original ?? throw new ArgumentNullException("original"); } public override void OnEnter() { try { if (IsCrossSlashActive()) { _instance?.SpawnCrossSlash(); } else { ((FsmStateAction)_original).OnEnter(); } } catch (Exception ex) { WitchCrossSlashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)("[AttackModule:witch.cross-slash] spawn fallback threw: " + ex.GetType().Name + ": " + ex.Message)); } } ((FsmStateAction)this).Finish(); } } private sealed class WitchCrossSlashMarker : MonoBehaviour { private FsmState? _state; private FsmStateAction[]? _original; public void StoreOriginal(FsmState state, FsmStateAction[] original) { _state = state; _original = original; } public void Restore() { if (_state != null && _original != null) { _state.Actions = _original; } _state = null; _original = null; } private void OnDestroy() { Restore(); } } private const string BundleFileName = "localpoolprefabs_assets_areasong.bundle"; private const string BundleRelPath = "aa/StandaloneWindows64/localpoolprefabs_assets_areasong.bundle"; private const string PrefabAssetPath = "Assets/Prefabs/Hornet Enemies/Song Knight CrossSlash Friendly.prefab"; private const string PrefabName = "Song Knight CrossSlash Friendly"; private const string TemplateName = "CE Witch CrossSlash Template"; private readonly ManualLogSource _log; private readonly AttackModuleOptions _options; private static WitchCrossSlashModule? _instance; private static AssetBundle? _bundle; private static GameObject? _template; private static bool _prefabTried; private static bool _warnedNoPrefab; private static bool _warnedFsm; private static bool _warnedDamagers; private static FieldInfo? _isHeroDamageField; private static FieldInfo? _directionSourceOverrideField; private bool _disposed; public string Id => "witch.cross-slash"; public string DisplayName => "女巫蓄力十字斩"; public bool Enabled => _options.WitchCrossSlashEnabled; public IReadOnlyList CrestIds { get; } = new string[2] { "Witch", "Nero" }; public bool IsActiveFor(string? crestId) { if (!AttackModuleRules.IsWitchCrest(crestId)) { return VergilCrestRules.IsNeroCrest(crestId); } return true; } public WitchCrossSlashModule(ManualLogSource log, AttackModuleOptions options) { _log = log ?? throw new ArgumentNullException("log"); _options = options ?? throw new ArgumentNullException("options"); } public void Install(Harmony harmony) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Expected O, but got Unknown if (harmony == null) { throw new ArgumentNullException("harmony"); } if (_instance == null) { _instance = this; _warnedFsm = false; _warnedDamagers = false; _isHeroDamageField = AccessTools.Field(typeof(DamageEnemies), "isHeroDamage"); _directionSourceOverrideField = AccessTools.Field(typeof(DamageEnemies), "directionSourceOverride"); if (_isHeroDamageField == null || _directionSourceOverrideField == null) { _log.LogWarning((object)"[AttackModule:witch.cross-slash] DamageEnemies private fields not found; damager config will be partial."); } MethodInfo methodInfo = AccessTools.Method(typeof(PlayMakerFSM), "Start", (Type[])null, (Type[])null); if (methodInfo == null) { _log.LogWarning((object)"[AttackModule:witch.cross-slash] PlayMakerFSM.Start not found; FSM hook disabled."); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(WitchCrossSlashModule), "OnFsmStart", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _log.LogInfo((object)("[InsectMayCry][AttackModule:witch.cross-slash] installed (witch-only; " + $"scale x{_options.WitchCrossSlashScale}, damage x{_options.WitchCrossSlashDamageMultiplier}).")); } } public static void OnFsmStart(PlayMakerFSM __instance) { WitchCrossSlashModule instance = _instance; if (instance == null || (Object)(object)__instance == (Object)null || __instance.Fsm == null || __instance.FsmName != "Nail Arts" || (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, "Do Slash"); if (state == null) { if (!_warnedFsm) { _warnedFsm = true; instance._log.LogWarning((object)"[AttackModule:witch.cross-slash] Nail Arts 'Do Slash' not found."); } return; } FsmStateAction[] actions = state.Actions; if (actions == null) { return; } ((Component)__instance).gameObject.AddComponent().StoreOriginal(state, actions); FsmStateAction[] array = (FsmStateAction[])(object)new FsmStateAction[actions.Length]; for (int i = 0; i < actions.Length; i++) { FsmStateAction val = actions[i]; ActivateGameObject val2 = (ActivateGameObject)(object)((val is ActivateGameObject) ? val : null); if (val2 != null) { array[i] = (FsmStateAction)(object)new ConditionalActivateAction(val2); continue; } SendMessageV2 val3 = (SendMessageV2)(object)((val is SendMessageV2) ? val : null); if (val3 != null && val3.functionCall?.FunctionName == "OnSlashStarting") { array[i] = (FsmStateAction)(object)new ConditionalSpawnAction(val3); } else { array[i] = val; } } state.Actions = array; instance._log.LogInfo((object)"[AttackModule:witch.cross-slash] Nail Arts 'Do Slash' hooked (witch/nero-gated)."); } catch (Exception ex) { instance._log.LogWarning((object)("[AttackModule:witch.cross-slash] FSM hook failed: " + ex.GetType().Name + ": " + ex.Message)); } } public static bool IsCrossSlashActive() { WitchCrossSlashModule instance = _instance; if (instance == null || !instance._options.WitchCrossSlashEnabled) { return false; } HeroController instance2 = HeroController.instance; if ((Object)(object)instance2 == (Object)null || instance2.playerData == null) { return false; } string currentCrestID = instance2.playerData.CurrentCrestID; if (AttackModuleRules.IsWitchCrest(currentCrestID)) { return true; } if (VergilCrestRules.IsNeroCrest(currentCrestID)) { return VergilChargeVariantState.Current == ChargeVariant.Down; } return false; } private void EnsurePrefabLoaded() { //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_template != (Object)null || _prefabTried) { return; } _prefabTried = true; try { _bundle = FindLoadedBundleWithPrefab(out string assetPath); if ((Object)(object)_bundle == (Object)null || assetPath == null) { _log.LogWarning((object)"[AttackModule:witch.cross-slash] cross-slash bundle not loaded yet; module will retry on next charge slash."); return; } GameObject val = _bundle.LoadAsset(assetPath); if ((Object)(object)val == (Object)null) { _log.LogWarning((object)"[AttackModule:witch.cross-slash] Song Knight CrossSlash Friendly prefab not found."); return; } _template = Object.Instantiate(val); ((Object)_template).name = "CE Witch CrossSlash Template"; _template.SetActive(false); _template.transform.localScale = Vector3.one * _options.WitchCrossSlashScale; ConfigureDamagers(_template); _log.LogInfo((object)("[AttackModule:witch.cross-slash] loaded Song Knight CrossSlash Friendly template " + $"(scale x{_options.WitchCrossSlashScale}).")); } catch (Exception ex) { _log.LogWarning((object)("[AttackModule:witch.cross-slash] prefab load failed: " + ex.GetType().Name + ": " + ex.Message)); } } private static AssetBundle? FindLoadedBundleWithPrefab(out string? assetPath) { assetPath = null; try { foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles()) { if ((Object)(object)allLoadedAssetBundle == (Object)null) { continue; } string[] allAssetNames = allLoadedAssetBundle.GetAllAssetNames(); if (allAssetNames == null) { continue; } string[] array = allAssetNames; foreach (string text in array) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); if (fileNameWithoutExtension.IndexOf("CrossSlash", StringComparison.OrdinalIgnoreCase) >= 0 && fileNameWithoutExtension.IndexOf("Friendly", StringComparison.OrdinalIgnoreCase) >= 0) { assetPath = text; return allLoadedAssetBundle; } } } } catch (Exception ex) { WitchCrossSlashModule? instance = _instance; if (instance != null) { instance._log.LogWarning((object)("[AttackModule:witch.cross-slash] loaded-bundle search failed: " + ex.Message)); } } return null; } private void ConfigureDamagers(GameObject root) { Transform val = root.transform.Find("Damager1"); Transform val2 = root.transform.Find("Damager2"); if ((Object)(object)val != (Object)null) { ConfigureSingleDamager(((Component)val).gameObject, "Damager1"); } if ((Object)(object)val2 != (Object)null) { ConfigureSingleDamager(((Component)val2).gameObject, "Damager2"); } if ((Object)(object)val == (Object)null && (Object)(object)val2 == (Object)null && !_warnedDamagers) { _warnedDamagers = true; _log.LogWarning((object)"[AttackModule:witch.cross-slash] Damager1/Damager2 not found on template."); } } private void ConfigureSingleDamager(GameObject damagerObject, string damagerName) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) DamageEnemies component = damagerObject.GetComponent(); if ((Object)(object)component == (Object)null) { _log.LogWarning((object)("[AttackModule:witch.cross-slash] DamageEnemies not found on " + damagerName + ".")); return; } damagerObject.tag = "Nail Attack"; component.useNailDamage = true; component.nailDamageMultiplier = _options.WitchCrossSlashDamageMultiplier; component.attackType = (AttackTypes)16; component.stunDamage = 1f; component.canWeakHit = false; component.magnitudeMult = 1f; component.direction = 0f; component.moveDirection = false; component.ignoreInvuln = false; if (_isHeroDamageField != null) { _isHeroDamageField.SetValue(component, true); } if (_directionSourceOverrideField != null) { _directionSourceOverrideField.SetValue(component, Enum.ToObject(_directionSourceOverrideField.FieldType, 1)); } } private void SpawnCrossSlash() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) EnsurePrefabLoaded(); if ((Object)(object)_template == (Object)null) { if (!_warnedNoPrefab) { _warnedNoPrefab = true; _log.LogWarning((object)"[AttackModule:witch.cross-slash] template unavailable; cross slash skipped."); } return; } HeroController instance = HeroController.instance; if (!((Object)(object)instance == (Object)null)) { GameObject obj = Object.Instantiate(_template, ((Component)instance).transform.position, GetSpawnRotation(instance)); obj.transform.SetParent((Transform)null); obj.SetActive(true); _log.LogInfo((object)"[AttackModule:witch.cross-slash] spawned cross slash."); } } private static Quaternion GetSpawnRotation(HeroController hero) { //IL_0006: 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) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!(((Component)hero).transform.localScale.x < 0f)) { return Quaternion.identity; } return Quaternion.Euler(0f, 180f, 0f); } public void Dispose() { if (_disposed) { return; } _disposed = true; try { HeroController instance = HeroController.instance; if (instance != null) { ((Component)instance).GetComponent()?.Restore(); } } catch (Exception ex) { _log.LogWarning((object)("[AttackModule:witch.cross-slash] FSM restore failed: " + ex.GetType().Name + ": " + ex.Message)); } if (_instance == this) { _instance = null; } } } public sealed class WitchDashAttackModule : IAttackModule, IDisposable { private sealed class ResetWitchDashCycleAction : FsmStateAction { public override void OnEnter() { try { HeroController instance = HeroController.instance; if (IsCurrentVergilConfig((instance != null) ? instance.Config : null)) { Fsm fsm = ((FsmStateAction)this).Fsm; object obj; if (fsm == null) { obj = null; } else { GameObject gameObject = fsm.GameObject; obj = ((gameObject != null) ? gameObject.GetComponent() : null); } WitchDashRepeatMarker witchDashRepeatMarker = (WitchDashRepeatMarker)obj; if ((Object)(object)witchDashRepeatMarker != (Object)null) { witchDashRepeatMarker.CompletedCycles = 0; } } } catch (Exception ex) { WitchDashAttackModule? instance2 = _instance; if (instance2 != null) { instance2._log.LogWarning((object)("[AttackModule:witch.dash] reset action threw: " + ex.GetType().Name + ": " + ex.Message)); } } finally { ((FsmStateAction)this).Finish(); } } } private sealed class RepeatWitchDashChainAction : FsmStateAction { public override void OnEnter() { try { WitchDashAttackModule instance = _instance; if (instance == null || !instance._options.WitchDashEnabled) { return; } HeroController instance2 = HeroController.instance; if (!IsCurrentVergilConfig((instance2 != null) ? instance2.Config : null)) { return; } Fsm fsm = ((FsmStateAction)this).Fsm; if (fsm == null || fsm.Variables == null) { return; } FsmInt fsmInt = fsm.Variables.GetFsmInt("Attack Step"); FsmInt fsmInt2 = fsm.Variables.GetFsmInt("Attack Steps"); if (fsmInt == null || fsmInt2 == null || fsmInt2.Value <= 1) { return; } GameObject gameObject = fsm.GameObject; WitchDashRepeatMarker witchDashRepeatMarker = ((gameObject != null) ? gameObject.GetComponent() : null); if ((Object)(object)witchDashRepeatMarker == (Object)null || witchDashRepeatMarker.TargetRounds <= 1 || fsmInt.Value < fsmInt2.Value) { return; } if (witchDashRepeatMarker.CompletedCycles < witchDashRepeatMarker.TargetRounds - 1) { witchDashRepeatMarker.CompletedCycles++; if (!witchDashRepeatMarker.LoggedRepeat) { witchDashRepeatMarker.LoggedRepeat = true; WitchDashAttackModule? instance3 = _instance; if (instance3 != null) { instance3._log.LogInfo((object)"[AttackModule:witch.dash] 2-hit chain repeat active (first repeat)."); } } fsmInt.Value = 0; } else { witchDashRepeatMarker.CompletedCycles = 0; } } catch (Exception ex) { WitchDashAttackModule? instance4 = _instance; if (instance4 != null) { instance4._log.LogWarning((object)("[AttackModule:witch.dash] repeat action threw: " + ex.GetType().Name + ": " + ex.Message)); } } finally { ((FsmStateAction)this).Finish(); } } } private sealed class SkipWitchDashAnticAction : FsmStateAction { public override void OnEnter() { try { WitchDashAttackModule instance = _instance; if (instance == null || !instance._options.WitchDashEnabled || !instance._options.WitchDashSkipAntic) { return; } HeroController instance2 = HeroController.instance; if (!IsCurrentVergilConfig((instance2 != null) ? instance2.Config : null)) { return; } Fsm fsm = ((FsmStateAction)this).Fsm; if (fsm == null || fsm.Variables == null) { return; } FsmFloat fsmFloat = fsm.Variables.GetFsmFloat("Attack Speed"); FsmFloat fsmFloat2 = fsm.Variables.GetFsmFloat("X Scale"); FsmFloat fsmFloat3 = fsm.Variables.GetFsmFloat("Attack Speed Crt"); if (fsmFloat != null && fsmFloat2 != null && fsmFloat3 != null) { fsmFloat3.Value = fsmFloat.Value * fsmFloat2.Value; } GameObject gameObject = fsm.GameObject; WitchDashRepeatMarker witchDashRepeatMarker = ((gameObject != null) ? gameObject.GetComponent() : null); if ((Object)(object)witchDashRepeatMarker != (Object)null && !witchDashRepeatMarker.LoggedAnticSkip) { witchDashRepeatMarker.LoggedAnticSkip = true; WitchDashAttackModule? instance3 = _instance; if (instance3 != null) { instance3._log.LogInfo((object)"[AttackModule:witch.dash] antic skip active (first hit)."); } } ((FsmStateAction)this).Fsm.Event("FINISHED"); } catch (Exception ex) { WitchDashAttackModule? instance4 = _instance; if (instance4 != null) { instance4._log.LogWarning((object)("[AttackModule:witch.dash] antic skip threw: " + ex.GetType().Name + ": " + ex.Message)); } } finally { ((FsmStateAction)this).Finish(); } } } private sealed class WitchDashRepeatMarker : MonoBehaviour { private readonly List _restores = new List(); public int TargetRounds = 3; public int CompletedCycles; public bool LoggedAnticSkip; public bool LoggedRepeat; public void AddRestore(Action restore) { _restores.Add(restore); } public void Restore() { foreach (Action restore in _restores) { try { restore(); } catch (Exception) { } } _restores.Clear(); } private void OnDestroy() { Restore(); } } private readonly ManualLogSource _log; private readonly AttackModuleOptions _options; private static WitchDashAttackModule? _instance; private static bool _warnedLoop; private static bool _warnedStartAttack; private static bool _warnedAttackAntic; private bool _disposed; public string Id => "B1"; public string DisplayName => "女巫冲刺攻击(连续挥砍 / 跳过前摇 / 整体速度可配)"; public bool Enabled => _options.WitchDashEnabled; public IReadOnlyList CrestIds { get; } = new string[1] { "Vergil" }; public bool IsActiveFor(string? crestId) { return VergilCrestRules.IsVergilCrest(crestId); } public WitchDashAttackModule(ManualLogSource log, AttackModuleOptions options) { _log = log ?? throw new ArgumentNullException("log"); _options = options ?? throw new ArgumentNullException("options"); } public void Install(Harmony harmony) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown if (harmony == null) { throw new ArgumentNullException("harmony"); } if (_instance == null) { _instance = this; _warnedLoop = false; _warnedStartAttack = false; _warnedAttackAntic = false; MethodInfo methodInfo = AccessTools.PropertyGetter(typeof(HeroControllerConfig), "DashStabTime"); if (methodInfo == null) { _log.LogWarning((object)"[AttackModule:witch.dash] DashStabTime getter not found; dash speed boost disabled."); } else { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(WitchDashAttackModule), "OnDashStabTimeGetter", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.Method(typeof(PlayMakerFSM), "Start", (Type[])null, (Type[])null); if (methodInfo2 == null) { _log.LogWarning((object)"[AttackModule:witch.dash] PlayMakerFSM.Start not found; FSM edits disabled."); } else { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(WitchDashAttackModule), "OnFsmStart", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } _log.LogInfo((object)("[InsectMayCry][AttackModule:witch.dash] installed v4 (vergil-only; witch/curse restored; " + $"slashes={_options.WitchDashSlashCount}, speed x{_options.WitchAttackSpeedMultiplier}, " + $"skipAntic={_options.WitchDashSkipAntic}).")); } } public static void OnDashStabTimeGetter(HeroControllerConfig __instance, ref float __result) { WitchDashAttackModule instance = _instance; if (instance != null && instance._options.WitchDashEnabled && IsCurrentVergilConfig(__instance)) { float num = Math.Max(1f, instance._options.WitchAttackSpeedMultiplier); __result = AttackModuleRules.ApplyMultiplier(__result, 1f / num); } } public static void OnFsmStart(PlayMakerFSM __instance) { WitchDashAttackModule instance = _instance; if (instance == null || (Object)(object)__instance == (Object)null || __instance.Fsm == null || __instance.FsmName != "Sprint" || (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 { WitchDashRepeatMarker witchDashRepeatMarker = ((Component)__instance).gameObject.AddComponent(); witchDashRepeatMarker.TargetRounds = Math.Max(1, instance._options.WitchDashSlashCount / 2); int num = 0; FsmState loop = FsmUtil.GetState(__instance, "Loop?"); if (loop == null) { if (!_warnedLoop) { _warnedLoop = true; instance._log.LogWarning((object)"[AttackModule:witch.dash] Sprint 'Loop?' not found; dash repeat disabled."); } } else { RepeatWitchDashChainAction repeat = new RepeatWitchDashChainAction(); FsmUtil.InsertAction(loop, 0, (FsmStateAction)(object)repeat); witchDashRepeatMarker.AddRestore(delegate { RemoveAction(loop, (FsmStateAction)(object)repeat); }); num++; } FsmState startAttack = FsmUtil.GetState(__instance, "Start Attack"); if (startAttack == null) { if (!_warnedStartAttack) { _warnedStartAttack = true; instance._log.LogWarning((object)"[AttackModule:witch.dash] Sprint 'Start Attack' not found; cycle reset disabled."); } } else { ResetWitchDashCycleAction reset = new ResetWitchDashCycleAction(); FsmUtil.InsertAction(startAttack, 0, (FsmStateAction)(object)reset); witchDashRepeatMarker.AddRestore(delegate { RemoveAction(startAttack, (FsmStateAction)(object)reset); }); num++; } FsmState attackAntic = FsmUtil.GetState(__instance, "Attack Antic"); if (attackAntic == null) { if (!_warnedAttackAntic) { _warnedAttackAntic = true; instance._log.LogWarning((object)"[AttackModule:witch.dash] Sprint 'Attack Antic' not found; antic skip disabled."); } } else { SkipWitchDashAnticAction skip = new SkipWitchDashAnticAction(); FsmUtil.InsertAction(attackAntic, 0, (FsmStateAction)(object)skip); witchDashRepeatMarker.AddRestore(delegate { RemoveAction(attackAntic, (FsmStateAction)(object)skip); }); num++; } if (num > 0) { instance._log.LogInfo((object)($"[AttackModule:witch.dash] Sprint FSM hooked: antic skip + 2-hit chain x{witchDashRepeatMarker.TargetRounds} " + $"({num}/3 states).")); } } catch (Exception ex) { instance._log.LogWarning((object)("[AttackModule:witch.dash] Sprint FSM hook failed: " + ex.GetType().Name + ": " + ex.Message)); } } private static void RemoveAction(FsmState state, FsmStateAction action) { if (((state != null) ? state.Actions : null) != null) { state.Actions = state.Actions.Where((FsmStateAction a) => a != action).ToArray(); } } private static bool IsCurrentVergilConfig(HeroControllerConfig? config) { HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.Config == (Object)null || instance.playerData == null) { return false; } if (instance.Config != config) { return false; } return VergilCrestRules.IsVergilCrest(instance.playerData.CurrentCrestID); } public void Dispose() { if (_disposed) { return; } _disposed = true; try { HeroController instance = HeroController.instance; if (instance != null) { ((Component)instance).GetComponent()?.Restore(); } } catch (Exception ex) { _log.LogWarning((object)("[AttackModule:witch.dash] FSM restore failed: " + ex.GetType().Name + ": " + ex.Message)); } if (_instance == this) { _instance = null; } } } } namespace CrestsEvolve.Core.State { public enum SwapOrigin { Standing, Moving, Airborne, Attack, Charge, Dash, BackDash, DashAttack, Dive, Cutscene, Unknown } public sealed record SwapRequest(long RequestId, string FromCrest, string ToCrest, SwapOrigin Origin, int RequestedFrame, double RequestedUnscaledTime, int EarliestFrame, double TimeoutSeconds, bool PreserveSilk, bool PreserveMarkedHealth, string Reason); public sealed class CrestSwapQueue { private SwapRequest? _current; private bool _dashOriginSeen; public SwapRequest? Current => _current; public bool HasRequest => (object)_current != null; public bool IsDashOrigin => _dashOriginSeen; public void Enqueue(SwapRequest request) { if ((object)request == null) { throw new ArgumentNullException("request"); } if (string.IsNullOrWhiteSpace(request.ToCrest)) { throw new ArgumentException("Target crest is required.", "request"); } if (request.TimeoutSeconds <= 0.0) { throw new ArgumentOutOfRangeException("request"); } if (request.RequestedFrame < 0) { throw new ArgumentOutOfRangeException("request"); } _dashOriginSeen = IsDashOriginRequest(request) || ((object)_current != null && _dashOriginSeen); _current = request; } public SwapRequest? TryDequeue(int frame, double unscaledTime, Func? safetyGate = null) { SwapRequest current = _current; if ((object)current == null) { return null; } if (frame < current.EarliestFrame) { return null; } if (unscaledTime - current.RequestedUnscaledTime > current.TimeoutSeconds) { Cancel(); return null; } if (safetyGate != null && !safetyGate(current)) { return null; } _current = null; _dashOriginSeen = false; return current; } public void Cancel() { _current = null; _dashOriginSeen = false; } private static bool IsDashOriginRequest(SwapRequest request) { SwapOrigin origin = request.Origin; if ((uint)(origin - 5) <= 2u) { return true; } return false; } } } namespace CrestsEvolve.Core.CrestSwitching { public sealed class DashSafetyPolicy { private readonly Func _dashSettleFramesProvider; private int _lastDashFrame = -1073741824; private int _lastUnsafeFrame = -1073741824; public int LastDashFrame => _lastDashFrame; public int LastUnsafeFrame => _lastUnsafeFrame; public DashSafetyPolicy(Func dashSettleFramesProvider) { _dashSettleFramesProvider = dashSettleFramesProvider ?? throw new ArgumentNullException("dashSettleFramesProvider"); } public void RecordFrame(int frame, bool dashActive, bool unsafeNow) { if (dashActive) { _lastDashFrame = frame; } if (unsafeNow) { _lastUnsafeFrame = frame; } } public bool HasDashSettled(int currentFrame) { int num = Math.Max(1, _dashSettleFramesProvider()); return currentFrame - _lastDashFrame >= num; } public int EarliestFrame(int currentFrame) { return Math.Max(currentFrame + 1, _lastUnsafeFrame + 1); } public void Reset() { _lastDashFrame = -1073741824; _lastUnsafeFrame = -1073741824; } } }