Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of CombatFlow v0.1.1
CombatFlow.Core.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("CombatFlow.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.1.0")] [assembly: AssemblyInformationalVersion("0.1.1+ae7cbc76414b0819a0294580cc820e6d873af136")] [assembly: AssemblyProduct("CombatFlow.Core")] [assembly: AssemblyTitle("CombatFlow.Core")] [assembly: AssemblyVersion("0.1.1.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [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; } } internal static class IsExternalInit { } } namespace CombatFlow.Core { public static class AttackClassifier { public static AttackKind Classify(string? attackAnimation) { return attackAnimation switch { "swing_longsword" => AttackKind.SwordCombo, "sword_secondary" => AttackKind.SwordSecondary, "dualaxes" => AttackKind.AxesCombo, "dualaxes_secondary" => AttackKind.AxesSecondary, _ => AttackKind.None, }; } } public static class AttackClear { public const float Window = 0.1f; public const float Never = float.NegativeInfinity; public static bool Unexplained(float now, float lastSeen) { return now - lastSeen > 0.1001f; } public static string Callers(IEnumerable<(string? ns, string? type, string? method)> frames, int max) { StringBuilder stringBuilder = new StringBuilder(); int num = 0; foreach (var (ns, text, text2) in frames) { if (num >= max) { break; } if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(text2) && !Skipped(ns) && !text.StartsWith("DMD<", StringComparison.Ordinal) && !text2.StartsWith("DMD<", StringComparison.Ordinal)) { if (num > 0) { stringBuilder.Append('>'); } stringBuilder.Append(text).Append('.').Append(text2); num++; } } if (num != 0) { return stringBuilder.ToString(); } return "-"; } private static bool Skipped(string? ns) { if (ns != null) { if (!Starts(ns, "CombatFlow") && !Starts(ns, "HarmonyLib")) { return Starts(ns, "MonoMod"); } return true; } return false; } private static bool Starts(string ns, string root) { if (!(ns == root)) { return ns.StartsWith(root + ".", StringComparison.Ordinal); } return true; } } public enum AttackKind { None, SwordCombo, SwordSecondary, AxesCombo, AxesSecondary } public static class AutoParry { public const string Force = "force"; public const float PerfectBlockTimer = 0f; public static bool IsOn(CancelSettings s) { if (s.Enabled) { return s.AutoParry; } return false; } public static bool FromFront(float dotDirForward) { return !(dotDirForward > 0f); } public static bool ShouldForce(bool enabled, bool autoParry, bool isLocalPlayer, bool blockable, bool fromFront, bool hasBlocker, bool inDodge, bool inPlaceMode, bool encumbered, bool inMinorAction, bool staggering) { return Decide(enabled, autoParry, isLocalPlayer, blockable, fromFront, hasBlocker, inDodge, inPlaceMode, encumbered, inMinorAction, staggering) == "force"; } public static string Decide(bool enabled, bool autoParry, bool isLocalPlayer, bool blockable, bool fromFront, bool hasBlocker, bool inDodge, bool inPlaceMode, bool encumbered, bool inMinorAction, bool staggering) { if (!enabled || !autoParry) { return "off"; } if (!isLocalPlayer) { return "not_local"; } if (!blockable) { return "not_blockable"; } if (!fromFront) { return "behind"; } if (!hasBlocker) { return "no_blocker"; } if (inDodge) { return "dodge"; } if (inPlaceMode) { return "place_mode"; } if (encumbered) { return "encumbered"; } if (inMinorAction) { return "minor_action"; } if (staggering) { return "staggering"; } return "force"; } public static bool ForcedBlocking(bool inDodge, bool inPlaceMode, bool encumbered, bool inMinorAction, bool staggering) { if (!inDodge && !inPlaceMode && !encumbered && !inMinorAction) { return !staggering; } return false; } public static string Outcome(float timedBlockBonus, bool guardBroken, bool hasAttacker) { if (guardBroken) { return "guard_broken"; } if (!(timedBlockBonus > 1f && hasAttacker)) { return "blocked"; } return "perfect"; } } public static class CancelEmotes { public static string Trigger(CancelEmote e) { if (e != CancelEmote.Cheer) { return "emote_nonono"; } return "emote_cheer"; } public static float BlendSeconds(CancelEmote e) { if (e != CancelEmote.Cheer) { return 0.11f; } return 0.19f; } } public readonly struct CancelDecision { public bool Cancel { get; } public string Reason { get; } public CancelDecision(bool cancel, string reason) { Cancel = cancel; Reason = reason; } } public static class CancelPolicy { public static CancelDecision ShouldCancel(AttackKind kind, ClipTiming timing, int damageEventsSeen, float stateSpeed, CancelSettings s) { if (!s.Enabled) { return new CancelDecision(cancel: false, "disabled"); } if (kind == AttackKind.None) { return new CancelDecision(cancel: false, "not_handled"); } IReadOnlyList<float> damageEventTimes = timing.DamageEventTimes; if (damageEventTimes.Count == 0 || damageEventsSeen != damageEventTimes.Count) { return new CancelDecision(cancel: false, "not_last_hit"); } float? chainTime = timing.ChainTime; if (chainTime.HasValue) { float valueOrDefault = chainTime.GetValueOrDefault(); if (!(damageEventTimes[damageEventTimes.Count - 1] + CancelEmotes.BlendSeconds(s.Emote) * stateSpeed < valueOrDefault)) { return new CancelDecision(cancel: false, "chain_faster"); } } return new CancelDecision(cancel: true, "ok"); } public static CancelDecision ShouldFireDelayed(bool enabled, bool sameAttack, bool staggering, bool inAttack) { if (!enabled) { return new CancelDecision(cancel: false, "disabled"); } if (!sameAttack || staggering || !inAttack) { return new CancelDecision(cancel: false, "superseded"); } return new CancelDecision(cancel: true, "ok"); } public static CancelDecision ShouldFireNow(CancelDecision d, bool staggering) { if (!(d.Cancel && staggering)) { return d; } return new CancelDecision(cancel: false, "superseded"); } public static CancelDecision ShouldCancelDodge(CancelSettings s) { if (!s.Enabled) { return new CancelDecision(cancel: false, "disabled"); } if (!s.Dodge) { return new CancelDecision(cancel: false, "toggle_off"); } return new CancelDecision(cancel: true, "ok"); } } public enum CancelEmote { NoNoNo, Cheer } public enum MaxComboMode { Cycle, Loop } public sealed record CancelSettings(bool Enabled, bool Dodge, CancelEmote Emote, float DelayAfterHit, bool DebugLog, bool MaxCombo = false, MaxComboMode MaxComboMode = MaxComboMode.Cycle, int MaxComboLoopCap = 0, bool SwapButtons = false, bool AutoParry = false, bool AxesMaxCombo = false) { public static CancelSettings Default { get; } = new CancelSettings(Enabled: true, Dodge: true, CancelEmote.NoNoNo, 0f, DebugLog: false, MaxCombo: true, MaxComboMode.Loop, 0, SwapButtons: false, AutoParry: false, AxesMaxCombo: true); [CompilerGenerated] private CancelSettings(CancelSettings original) { Enabled = original.Enabled; Dodge = original.Dodge; Emote = original.Emote; DelayAfterHit = original.DelayAfterHit; DebugLog = original.DebugLog; MaxCombo = original.MaxCombo; MaxComboMode = original.MaxComboMode; MaxComboLoopCap = original.MaxComboLoopCap; SwapButtons = original.SwapButtons; AutoParry = original.AutoParry; AxesMaxCombo = original.AxesMaxCombo; } } public enum ClipPickResult { Current, Next, Ambiguous } public static class ClipPick { public const float Tolerance = 0.05f; public static ClipPickResult Choose(ClipTiming? current, float currentT, float currentLength, ClipTiming? next, float nextT, float nextLength) { if (next == null) { return ClipPickResult.Current; } if (current == null) { return ClipPickResult.Next; } bool flag = HasDamageNear(current, currentT, currentLength); bool flag2 = HasDamageNear(next, nextT, nextLength); if (flag == flag2) { return ClipPickResult.Ambiguous; } if (!flag) { return ClipPickResult.Next; } return ClipPickResult.Current; } public static bool UseNext(ClipPickResult r) { return r == ClipPickResult.Next; } public static bool HasDamageNear(ClipTiming t, float clipT, float clipLength, float tolerance = 0.05f) { foreach (float damageEventTime in t.DamageEventTimes) { float num = Math.Abs(damageEventTime - clipT); if (clipLength > 0f) { num %= clipLength; num = Math.Min(num, clipLength - num); } if (num <= tolerance + 0.0001f) { return true; } } return false; } public static string LogValue(ClipPickResult r) { return r switch { ClipPickResult.Next => "next", ClipPickResult.Ambiguous => "ambiguous", _ => "current", }; } } public sealed class ClipTiming { public IReadOnlyList<float> DamageEventTimes { get; } public float? ChainTime { get; } public ClipTiming(IReadOnlyList<float> damageEventTimes, float? chainTime) { DamageEventTimes = damageEventTimes; ChainTime = chainTime; } public static ClipTiming FromEvents(IEnumerable<(float time, string functionName)> events) { List<float> list = new List<float>(); float? num = null; foreach (var @event in events) { var (num2, _) = @event; switch (@event.functionName) { case "Hit": case "OnAttackTrigger": list.Add(num2); break; case "Chain": if (!num.HasValue || num2 < num) { num = num2; } break; } } list.Sort(); return new ClipTiming(list, num); } } public sealed class DamageCounter { private object? _current; private int _seen; public int Register(object attackInstance) { if (attackInstance != _current) { _current = attackInstance; _seen = 0; } return ++_seen; } } public static class DebugLine { public static string Format(double t, int frame, string ev, IReadOnlyList<(string k, string v)> kv) { StringBuilder stringBuilder = new StringBuilder(64); stringBuilder.Append("AC t=").Append(t.ToString("0.000", CultureInfo.InvariantCulture)).Append(" f=") .Append(frame.ToString(CultureInfo.InvariantCulture)) .Append(" ev=") .Append(ev); for (int i = 0; i < kv.Count; i++) { stringBuilder.Append(' ').Append(kv[i].k).Append('=') .Append(Token(kv[i].v)); } return stringBuilder.ToString(); } public static string Num(double v) { return v.ToString("0.###", CultureInfo.InvariantCulture); } public static string Bool(bool b) { if (!b) { return "false"; } return "true"; } private static string Token(string? v) { if (!string.IsNullOrEmpty(v)) { return v.Replace(' ', '_'); } return "-"; } } public static class HoldDrop { public const float WatchWindow = 0.5f; public static bool ShouldWatch(bool inFlight, float now, float lastCancelTime) { if (!inFlight) { if (now - lastCancelTime >= 0f) { return now - lastCancelTime <= 0.5f; } return false; } return true; } public static bool WasDropped(bool incomingHold, bool holdAfter) { if (incomingHold) { return !holdAfter; } return false; } } public static class LabLine { public static string Format(double t, int frame, int ff, string ev, IReadOnlyList<(string k, string v)> kv) { StringBuilder stringBuilder = new StringBuilder(80); stringBuilder.Append("CL t=").Append(t.ToString("0.000", CultureInfo.InvariantCulture)).Append(" f=") .Append(frame.ToString(CultureInfo.InvariantCulture)) .Append(" ff=") .Append(ff.ToString(CultureInfo.InvariantCulture)) .Append(" ev=") .Append(ev); for (int i = 0; i < kv.Count; i++) { stringBuilder.Append(' ').Append(kv[i].k).Append('=') .Append(Token(kv[i].v)); } return stringBuilder.ToString(); } public static string Dp(double v, int decimals) { return v.ToString("F" + decimals.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); } public static string Bit(bool b) { if (!b) { return "0"; } return "1"; } public static bool IsLastChain(int lvl, int levels) { if (levels > 1) { return lvl == levels - 1; } return false; } public static float SwingMultiplier(float damageMultiplier, float lastChainMultiplier, int lvl, int levels) { if (!IsLastChain(lvl, levels) || !(lastChainMultiplier > 1f)) { return damageMultiplier; } return damageMultiplier * lastChainMultiplier; } public static int InputMask(bool attack, bool attackHold, bool secondary, bool secondaryHold, bool block, bool blockHold) { return (int)((attack ? 1u : 0u) | (uint)(attackHold ? 2 : 0) | (uint)(secondary ? 4 : 0) | (uint)(secondaryHold ? 8 : 0) | (uint)(block ? 16 : 0)) | (blockHold ? 32 : 0); } public static string TagName(int tag, int attack, int emote, int dodge, int idle) { if (tag == attack) { return "attack"; } if (tag == emote) { return "emote"; } if (tag == dodge) { return "dodge"; } if (tag == idle) { return "idle"; } return "other"; } private static string Token(string? v) { if (!string.IsNullOrEmpty(v)) { return v.Replace(' ', '_'); } return "-"; } } public enum LogKind { Review, Lab, Info } public static class LogGate { public const bool ComboLabLogDefault = false; public static bool Allows(LogKind kind, bool debugLog, bool comboLabLog) { if (debugLog) { return kind != LogKind.Lab || comboLabLog; } return false; } } public enum MaxComboActionKind { None, TriggerSwing1, TriggerSwing2, CancelNow, Abort, Progress } public readonly struct MaxComboAction { public MaxComboActionKind Kind { get; } public string Reason { get; } public static MaxComboAction None => new MaxComboAction(MaxComboActionKind.None, ""); public static MaxComboAction TriggerSwing1 => new MaxComboAction(MaxComboActionKind.TriggerSwing1, ""); public static MaxComboAction TriggerSwing2 => new MaxComboAction(MaxComboActionKind.TriggerSwing2, ""); public static MaxComboAction CancelNow => new MaxComboAction(MaxComboActionKind.CancelNow, ""); public static MaxComboAction Progress => new MaxComboAction(MaxComboActionKind.Progress, ""); private MaxComboAction(MaxComboActionKind kind, string reason) { Kind = kind; Reason = reason; } public static MaxComboAction Abort(string reason) { return new MaxComboAction(MaxComboActionKind.Abort, reason); } public static MaxComboAction End(string reason) { return new MaxComboAction(MaxComboActionKind.CancelNow, reason); } } public sealed record MaxComboState(int Step, int InstanceId, float StepTime, int Loop = 0) { public static MaxComboState Idle { get; } = new MaxComboState(0, 0, 0f); public bool Running => Step > 0; [CompilerGenerated] private MaxComboState(MaxComboState original) { Step = original.Step; InstanceId = original.InstanceId; StepTime = original.StepTime; Loop = original.Loop; } } public static class MaxComboPolicy { public const float Timeout = 1f; public const int DefaultLoopCap = 0; public const int MinLoopCap = 0; public const int MaxLoopCap = 1000; public const string SecondaryAnimation = "sword_secondary"; public const string PrimaryAnimation = "swing_longsword"; public const int MinPrimaryLevels = 3; public const string SecondaryClip = "Sword-Attack-R4"; public const string Swing1Clip = "Attack2"; public const string Swing2Clip = "Attack3"; public const string Swing1Trigger = "swing_longsword1"; public const string Swing2Trigger = "swing_longsword2"; public static readonly string[] SwingTriggers = new string[3] { "swing_longsword0", "swing_longsword1", "swing_longsword2" }; public static readonly string[] AbortTriggers = new string[2] { "swing_longsword1", "swing_longsword2" }; public static bool Eligible(string? attackAnimation, string? primaryAnimation, int primaryChainLevels) { if (attackAnimation == "sword_secondary" && primaryAnimation == "swing_longsword") { return primaryChainLevels >= 3; } return false; } public static bool Eligible(MaxComboProfile profile, string? attackAnimation, int attackLevel, int attackLevels, string? primaryAnimation, int primaryLevels, string? secondaryAnimation) { return profile.Eligible(attackAnimation, attackLevel, attackLevels, primaryAnimation, primaryLevels, secondaryAnimation); } public static bool Routes(AttackKind kind, CancelSettings s, bool eligible) { return Routes(MaxComboProfile.Sword, kind, s, eligible); } public static bool Routes(MaxComboProfile profile, AttackKind kind, CancelSettings s, bool eligible) { return s.Enabled && profile.IsOn(s) && kind == profile.Kind && eligible; } public static bool Acts(MaxComboActionKind k) { if (k != MaxComboActionKind.TriggerSwing1 && k != MaxComboActionKind.TriggerSwing2 && k != MaxComboActionKind.CancelNow) { return k == MaxComboActionKind.Progress; } return true; } public static string SkipReason(MaxComboProfile profile, MaxComboState state, string? firedClip, int swingHit, CancelSettings s, bool eligible, bool staggering) { if (state.Running) { string expected = ((state.Step == 1) ? profile.Swing1Clip : profile.Swing2Clip); if (!MaxComboProfile.SameClip(firedClip, expected)) { return "clip"; } return "-"; } if (!s.Enabled || !profile.IsOn(s)) { return "off"; } if (!eligible) { return "not_eligible"; } if (staggering) { return "staggering"; } if (!MaxComboProfile.SameClip(firedClip, profile.StartClip)) { return "clip"; } if (swingHit < profile.StartHits) { return "not_last_hit"; } return "-"; } public static (string k, string v)[] SkipFields(string reason, string? clip, int swingHit, int step) { int num = 0; string text = clip ?? ""; for (int i = 0; i < text.Length; i++) { if (text[i] == ' ') { num++; } } return new(string, string)[5] { ("reason", reason), ("clip", string.IsNullOrEmpty(clip) ? "-" : clip), ("clipSpaces", num.ToString(CultureInfo.InvariantCulture)), ("hit", swingHit.ToString(CultureInfo.InvariantCulture)), ("step", step.ToString(CultureInfo.InvariantCulture)) }; } public static MaxComboAction Decide(MaxComboState state, string? firedClip, bool sameInstance, CancelSettings s, float sinceLastStep, bool eligible, bool staggering, bool held = false) { return Decide(MaxComboProfile.Sword, state, firedClip, 1, sameInstance, s, sinceLastStep, eligible, staggering, held); } public static MaxComboAction Decide(MaxComboProfile profile, MaxComboState state, string? firedClip, int swingHit, bool sameInstance, CancelSettings s, float sinceLastStep, bool eligible, bool staggering, bool held = false) { bool flag = s.Enabled && profile.IsOn(s); if (state.Running) { MaxComboAction result = AbortReason(flag, sameInstance, staggering, dodging: false, sinceLastStep); if (result.Kind == MaxComboActionKind.Abort) { return result; } bool flag2 = s.MaxComboMode == MaxComboMode.Loop; if (state.Step == 1 && MaxComboProfile.SameClip(firedClip, profile.Swing1Clip)) { if (swingHit < profile.Swing1Hits) { return MaxComboAction.Progress; } if (!flag2 || state.Loop <= 0) { return MaxComboAction.TriggerSwing2; } return LoopHit(state, s, held, MaxComboAction.TriggerSwing2); } if (state.Step == 2 && MaxComboProfile.SameClip(firedClip, profile.Swing2Clip)) { if (swingHit < profile.Swing2Hits) { return MaxComboAction.Progress; } if (!flag2) { return MaxComboAction.CancelNow; } return LoopHit(state, s, held, MaxComboAction.TriggerSwing1); } return MaxComboAction.None; } if (!flag || !eligible || staggering || !MaxComboProfile.SameClip(firedClip, profile.StartClip) || swingHit < profile.StartHits) { return MaxComboAction.None; } return MaxComboAction.TriggerSwing1; } public static MaxComboAction Watch(MaxComboState state, bool sameInstance, CancelSettings s, float sinceLastStep, bool staggering, bool dodging) { return Watch(MaxComboProfile.Sword, state, sameInstance, s, sinceLastStep, staggering, dodging); } public static MaxComboAction Watch(MaxComboProfile profile, MaxComboState state, bool sameInstance, CancelSettings s, float sinceLastStep, bool staggering, bool dodging) { if (!state.Running) { return MaxComboAction.None; } return AbortReason(s.Enabled && profile.IsOn(s), sameInstance, staggering, dodging, sinceLastStep); } public static MaxComboState Next(MaxComboState state, MaxComboAction a, int instanceId, float now) { switch (a.Kind) { case MaxComboActionKind.TriggerSwing1: if (!state.Running) { return new MaxComboState(1, instanceId, now); } return new MaxComboState(1, state.InstanceId, now, state.Loop + 1); case MaxComboActionKind.TriggerSwing2: return new MaxComboState(2, state.InstanceId, now, (state.Loop > 0) ? (state.Loop + 1) : 0); case MaxComboActionKind.Progress: if (!state.Running) { return state; } return state with { StepTime = now }; case MaxComboActionKind.CancelNow: case MaxComboActionKind.Abort: return MaxComboState.Idle; default: return state; } } public static int StepNumber(MaxComboActionKind k) { return k switch { MaxComboActionKind.TriggerSwing1 => 1, MaxComboActionKind.TriggerSwing2 => 2, MaxComboActionKind.CancelNow => 3, _ => 0, }; } public static string? TriggerFor(MaxComboActionKind k) { return k switch { MaxComboActionKind.TriggerSwing1 => "swing_longsword1", MaxComboActionKind.TriggerSwing2 => "swing_longsword2", _ => null, }; } public static (string k, string v)[] StepFields(int step, string? clip, int instanceId) { return new(string, string)[3] { ("step", step.ToString(CultureInfo.InvariantCulture)), ("clip", string.IsNullOrEmpty(clip) ? "-" : clip), ("attack", ShortId(instanceId)) }; } public static (string k, string v)[] RunFields(int loop, string? plan, float stamina) { return new(string, string)[3] { ("loop", loop.ToString(CultureInfo.InvariantCulture)), ("plan", string.IsNullOrEmpty(plan) ? "-" : plan), ("stamina", stamina.ToString("0.0", CultureInfo.InvariantCulture)) }; } public static (string k, string v)[] WeaponFields(MaxComboProfile profile) { return new(string, string)[1] { ("weapon", profile.Weapon) }; } public static (string k, string v)[] EndFields(string reason) { return new(string, string)[1] { ("reason", reason) }; } public static (string k, string v)[] AbortFields(string reason) { return new(string, string)[1] { ("reason", reason) }; } public static (string k, string v)[] ConfigFields(CancelSettings s) { return new(string, string)[5] { ("enabled", DebugLine.Bool(s.Enabled)), ("maxCombo", DebugLine.Bool(s.MaxCombo)), ("maxComboMode", s.MaxComboMode.ToString()), ("maxComboLoopCap", s.MaxComboLoopCap.ToString(CultureInfo.InvariantCulture)), ("axesMaxCombo", DebugLine.Bool(s.AxesMaxCombo)) }; } public static string ShortId(int id) { return (id & 0xFFFF).ToString("x4", CultureInfo.InvariantCulture); } private static MaxComboAction LoopHit(MaxComboState state, CancelSettings s, bool held, MaxComboAction next) { if (!held) { return MaxComboAction.End("released"); } if (s.MaxComboLoopCap > 0 && state.Loop >= s.MaxComboLoopCap) { return MaxComboAction.End("cap"); } return next; } private static MaxComboAction AbortReason(bool on, bool sameInstance, bool staggering, bool dodging, float since) { if (!on) { return MaxComboAction.Abort("disabled"); } if (!sameInstance) { return MaxComboAction.Abort("instance"); } if (staggering) { return MaxComboAction.Abort("stagger"); } if (dodging) { return MaxComboAction.Abort("dodge"); } if (since > 1f) { return MaxComboAction.Abort("timeout"); } return MaxComboAction.None; } } public sealed class MaxComboProfile { private readonly bool _axes; public const string AxesPrimaryAnimation = "dualaxes"; public const string AxesSecondaryAnimation = "dualaxes_secondary"; public const int AxesMinChainLevels = 4; public const string AxesFinisherClip = "DualAxes_Attack_4"; public const string AxesSwing1Clip = "DualAxes_Attack_3_2"; public string Weapon { get; } public AttackKind Kind { get; } public string StartClip { get; } public string Swing1Clip { get; } public string Swing2Clip { get; } public int StartHits { get; } public int Swing1Hits { get; } public int Swing2Hits { get; } public string Swing1Trigger { get; } public string Swing2Trigger { get; } public string[] SwingTriggers { get; } public string[] AbortTriggers { get; } public bool HoldPrimary { get; } public static MaxComboProfile Sword { get; } = new MaxComboProfile("sword", AttackKind.SwordSecondary, "Sword-Attack-R4", 1, "Attack2", 1, "Attack3", 1, "swing_longsword1", "swing_longsword2", MaxComboPolicy.SwingTriggers, holdPrimary: false, axes: false); public static MaxComboProfile Axes { get; } = new MaxComboProfile("axes", AttackKind.AxesCombo, "DualAxes_Attack_4", 2, "DualAxes_Attack_3_2", 2, "DualAxes_Attack_4", 2, "dualaxes2", "dualaxes3", new string[4] { "dualaxes0", "dualaxes1", "dualaxes2", "dualaxes3" }, holdPrimary: true, axes: true); private MaxComboProfile(string weapon, AttackKind kind, string startClip, int startHits, string swing1Clip, int swing1Hits, string swing2Clip, int swing2Hits, string swing1Trigger, string swing2Trigger, string[] swingTriggers, bool holdPrimary, bool axes) { Weapon = weapon; Kind = kind; StartClip = startClip; StartHits = startHits; Swing1Clip = swing1Clip; Swing1Hits = swing1Hits; Swing2Clip = swing2Clip; Swing2Hits = swing2Hits; Swing1Trigger = swing1Trigger; Swing2Trigger = swing2Trigger; SwingTriggers = swingTriggers; AbortTriggers = new string[2] { swing1Trigger, swing2Trigger }; HoldPrimary = holdPrimary; _axes = axes; } public static MaxComboProfile? For(AttackKind kind) { return kind switch { AttackKind.SwordSecondary => Sword, AttackKind.AxesCombo => Axes, _ => null, }; } public static bool SameClip(string? fired, string expected) { if (!string.IsNullOrEmpty(fired) && fired.Length == expected.Length) { return string.Equals(fired.Replace(' ', '_'), expected.Replace(' ', '_'), StringComparison.Ordinal); } return false; } public bool IsOn(CancelSettings s) { if (!_axes) { return s.MaxCombo; } return s.AxesMaxCombo; } public string? TriggerFor(MaxComboActionKind k) { return k switch { MaxComboActionKind.TriggerSwing1 => Swing1Trigger, MaxComboActionKind.TriggerSwing2 => Swing2Trigger, _ => null, }; } internal bool Eligible(string? attackAnimation, int attackLevel, int attackLevels, string? primaryAnimation, int primaryLevels, string? secondaryAnimation) { if (!_axes) { return MaxComboPolicy.Eligible(attackAnimation, primaryAnimation, primaryLevels); } if (attackAnimation == "dualaxes" && primaryAnimation == "dualaxes" && secondaryAnimation == "dualaxes_secondary" && attackLevels >= 4) { return attackLevel == attackLevels - 1; } return false; } } public sealed class SwingHitCounter { private int _instance; private string? _clip; private int _count; public int Register(int instanceId, string? clip) { if (_count == 0 || instanceId != _instance || clip != _clip) { _instance = instanceId; _clip = clip; _count = 0; } return ++_count; } public void Reset() { _count = 0; _clip = null; _instance = 0; } } public enum RecoveryAction { None, Stop, Expire } public readonly struct RecoveryDecision { public RecoveryAction Action { get; } public string Reason { get; } public RecoveryDecision(RecoveryAction action, string reason) { Action = action; Reason = reason; } } public static class RecoveryPolicy { public const float HeldGrace = 0.1f; public const float Expiry = 1.5f; private static readonly RecoveryDecision Wait = new RecoveryDecision(RecoveryAction.None, ""); public static RecoveryDecision Decide(bool inFlight, bool currentTagIsEmote, bool inTransition, bool attackHeld, float fullyInEmoteFor, float sinceCancel) { if (!inFlight) { return Wait; } bool flag = currentTagIsEmote && !inTransition; if (flag && !attackHeld) { return new RecoveryDecision(RecoveryAction.Stop, "released"); } if (flag && fullyInEmoteFor > 0.1f) { return new RecoveryDecision(RecoveryAction.Stop, "held_timeout"); } if (sinceCancel > 1.5f) { return new RecoveryDecision(flag ? RecoveryAction.Stop : RecoveryAction.Expire, "expired"); } return Wait; } public static bool EmoteTriggerSpent(bool currentTagIsEmote, bool nextTagIsEmote) { return currentTagIsEmote || nextTagIsEmote; } public static bool StopOnDisable(bool inFlight, bool currentTagIsEmote, bool nextTagIsEmote) { if (inFlight) { return currentTagIsEmote || nextTagIsEmote; } return false; } } public static class RelaunchBlock { public const float Window = 0.5f; public static bool ShouldStart(float dtSinceCancel) { if (dtSinceCancel >= 0f) { return dtSinceCancel < 0.5f; } return false; } public static bool AttackBegun(bool inTransition, bool currentIsAttack, bool nextIsAttack, bool currentIsCancelled) { if (!inTransition) { if (currentIsAttack) { return !currentIsCancelled; } return false; } return nextIsAttack; } public static bool ShouldLift(bool active, float sinceCancel, bool layer0Attack) { if (active) { if (!layer0Attack && !(sinceCancel < 0f)) { return sinceCancel >= 0.5f; } return true; } return false; } public static bool ShouldRefuse(bool isLocalPlayer, bool active, float sinceCancel, bool layer0Attack) { if (isLocalPlayer && active) { return !ShouldLift(active, sinceCancel, layer0Attack); } return false; } } public static class SpeedPlan { public static (bool setAnimatorSpeed, bool setPauseRestore) For(float pauseTimer) { if (!(pauseTimer > 0f)) { return (setAnimatorSpeed: true, setPauseRestore: false); } return (setAnimatorSpeed: false, setPauseRestore: true); } } public static class SpeedSkip { public const float Window = 0.5f; public static bool ShouldSkip(int? cancelledState, int currentState, int nextState, float sinceCancel) { return ShouldSkip(cancelledState, relaunched: false, currentState, nextState, sinceCancel); } public static bool ShouldSkip(int? cancelledState, bool relaunched, int currentState, int nextState, float sinceCancel) { if (cancelledState.HasValue && sinceCancel <= 0.5f) { if (currentState != cancelledState.Value) { if (!relaunched) { return nextState == cancelledState.Value; } return false; } return true; } return false; } public static bool ShouldForget(int? cancelledState, int currentState, int nextState) { return ShouldForget(cancelledState, relaunched: false, currentState, nextState); } public static bool ShouldForget(int? cancelledState, bool relaunched, int currentState, int nextState) { if (cancelledState.HasValue && currentState != cancelledState.Value) { if (!relaunched) { return nextState != cancelledState.Value; } return true; } return false; } } public readonly struct AttackInputs : IEquatable<AttackInputs> { public bool Attack { get; } public bool AttackHold { get; } public bool Secondary { get; } public bool SecondaryHold { get; } public AttackInputs(bool attack, bool attackHold, bool secondary, bool secondaryHold) { Attack = attack; AttackHold = attackHold; Secondary = secondary; SecondaryHold = secondaryHold; } public AttackInputs Swapped() { return new AttackInputs(Secondary, SecondaryHold, Attack, AttackHold); } public bool Equals(AttackInputs o) { if (Attack == o.Attack && AttackHold == o.AttackHold && Secondary == o.Secondary) { return SecondaryHold == o.SecondaryHold; } return false; } public override bool Equals(object? obj) { if (obj is AttackInputs o) { return Equals(o); } return false; } public override int GetHashCode() { return (int)((Attack ? 1u : 0u) | (uint)(AttackHold ? 2 : 0) | (uint)(Secondary ? 4 : 0)) | (SecondaryHold ? 8 : 0); } } public static class SwapButtons { public static bool IsOn(CancelSettings s) { if (s.Enabled) { return s.SwapButtons; } return false; } public static bool IsSword(string? secondaryAnimation, string? primaryAnimation, int primaryChainLevels) { return MaxComboPolicy.Eligible(secondaryAnimation, primaryAnimation, primaryChainLevels); } public static bool ShouldSwap(bool enabled, bool swordEquipped, bool inPlaceMode) { if (enabled && swordEquipped) { return !inPlaceMode; } return false; } public static (string k, string v)[] LabFields(bool swapped) { if (!swapped) { return Array.Empty<(string, string)>(); } return new(string, string)[1] { ("swapped", "1") }; } } }
CombatFlow.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CombatFlow.Core; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] 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; } } } public sealed class ConfigurationManagerAttributes { public int? Order; public bool? IsAdvanced; public bool? Browsable; public bool? ReadOnly; public string? Category; public string? DispName; public string? Description; public bool? ShowRangeAsPercent; public bool? HideDefaultButton; public bool? HideSettingName; public Action<ConfigEntryBase>? CustomDrawer; } namespace CombatFlow { internal static class AttackClearLog { private const int CallerCount = 3; private static float _lastSeen = float.NegativeInfinity; private static readonly HashSet<string> Reported = new HashSet<string>(); internal static void OnUnequip(ItemData item) { _lastSeen = Time.time; ReviewLog.Write("attack_cleared", ("via", "unequip"), ("item", (item.m_shared != null) ? item.m_shared.m_name : "-"), ("caller", Callers())); } internal static void OnStartFailed(Player p, bool secondary, bool inAttackBefore) { _lastSeen = Time.time; ReviewLog.Write("attack_cleared", ("via", "start_failed"), ("secondary", DebugLine.Bool(secondary)), ("stamina", Get((Func<float>)p.GetStamina)), ("need", Need(p, secondary)), ("inAttack", DebugLine.Bool(inAttackBefore)), ("inDodge", Get((Func<bool>)((Character)p).InDodge)), ("canMove", Get((Func<bool>)((Character)p).CanMove)), ("staggering", Get((Func<bool>)((Character)p).IsStaggering)), ("minorAction", Get((Func<bool>)((Character)p).InMinorAction)), ("caller", Callers())); } internal static void OnInstanceAbort(Player p) { if (AttackClear.Unexplained(Time.time, _lastSeen)) { ReviewLog.Write("attack_cleared", ("via", "unknown"), ("current", ComboLab.Anim(((Humanoid)p).m_currentAttack))); } } internal static void Report(string where, Exception e) { if (Reported.Add(where)) { ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}.{1}: {2}", "AttackClearLog", where, e)); } } } private static string Need(Player p, bool secondary) { try { ItemData currentWeapon = ((Humanoid)p).GetCurrentWeapon(); Attack val = ((currentWeapon?.m_shared == null) ? null : (secondary ? currentWeapon.m_shared.m_secondaryAttack : currentWeapon.m_shared.m_attack)); if (val == null) { return "-"; } Attack obj = val.Clone(); obj.m_character = (Humanoid)(object)p; obj.m_weapon = currentWeapon; return Dp1(obj.GetAttackStamina()); } catch (Exception e) { Report("Need", e); return "?"; } } private static string Get(Func<bool> getter) { try { return DebugLine.Bool(getter()); } catch (Exception e) { Report(getter.Method.Name, e); return "?"; } } private static string Get(Func<float> getter) { try { return Dp1(getter()); } catch (Exception e) { Report(getter.Method.Name, e); return "?"; } } private static string Dp1(float v) { return v.ToString("0.0", CultureInfo.InvariantCulture); } private static string Callers() { try { StackFrame[] obj = new StackTrace(1, fNeedFileInfo: false).GetFrames() ?? Array.Empty<StackFrame>(); List<(string, string, string)> list = new List<(string, string, string)>(obj.Length); StackFrame[] array = obj; for (int i = 0; i < array.Length; i++) { MethodBase method = array[i].GetMethod(); Type type = method?.DeclaringType; list.Add((type?.Namespace, type?.Name, method?.Name)); } return AttackClear.Callers((IEnumerable<ValueTuple<string, string, string>>)list, 3); } catch (Exception e) { Report("Callers", e); return "?"; } } } internal static class AttackTracker { private static readonly ClipTiming NoTiming = new ClipTiming((IReadOnlyList<float>)Array.Empty<float>(), (float?)null); private static readonly DamageCounter Counter = new DamageCounter(); private static Player? _pendingPlayer; private static Attack? _pendingAttack; private static CancelEmote _pendingEmote; private static float _pendingDue; private static bool _tickReported; internal unsafe static void OnAttackStarted(Player player) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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) Attack currentAttack = ((Humanoid)player).m_currentAttack; float dtSinceCancel; bool flag = Canceller.TryConsumeRelaunch(out dtSinceCancel); if (currentAttack == null || !ReviewLog.On) { return; } AttackKind val = AttackClassifier.Classify(currentAttack.m_attackAnimation); if ((int)val != 0) { string item = currentAttack.m_currentAttackCainLevel.ToString(); ReviewLog.Write("attack_start", ("kind", ((object)(*(AttackKind*)(&val))/*cast due to .constrained prefix*/).ToString()), ("lvl", item), ("levels", currentAttack.m_attackChainLevels.ToString()), ("clip", "-"), ("stateSpeed", "-"), ("trigger", TriggerName(currentAttack))); if (flag) { ReviewLog.Write("relaunch", ("kind", ((object)(*(AttackKind*)(&val))/*cast due to .constrained prefix*/).ToString()), ("lvl", item), ("dtSinceCancel", DebugLine.Num((double)dtSinceCancel))); } } } internal unsafe static void OnDamageEvent(Player player, CancelSettings s) { //IL_0027: 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_002d: 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_0084: 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) //IL_0093: 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_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) Attack currentAttack = ((Humanoid)player).m_currentAttack; if (currentAttack == null || ((Humanoid)player).GetCurrentWeapon() == null || (Object)(object)((Character)player).m_animator == (Object)null) { return; } AttackKind val = AttackClassifier.Classify(currentAttack.m_attackAnimation); if ((int)val == 0) { return; } int num = Counter.Register((object)currentAttack); float stateSpeed; string clipName; float clipT; string clipPick; ClipTiming val2 = ClipTimingCache.ForCurrent(((Character)player).m_animator, out stateSpeed, out clipName, out clipT, out clipPick) ?? NoTiming; int currentAttackCainLevel = currentAttack.m_currentAttackCainLevel; bool clipIsNext = clipPick == ClipPick.LogValue((ClipPickResult)1); bool cancelNow; int swingHit; CancelDecision val3 = (CancelDecision)(MaxComboRun.OnHit(player, currentAttack, val, s, clipName, clipIsNext, out cancelNow, out swingHit) ? new CancelDecision(cancelNow, cancelNow ? "ok" : "maxcombo") : CancelPolicy.ShouldCancel(val, val2, (swingHit > 0) ? swingHit : num, stateSpeed, s)); if (s.DelayAfterHit <= 0f) { val3 = CancelPolicy.ShouldFireNow(val3, ((Character)player).IsStaggering()); } if (ReviewLog.On) { ReviewLog.Write("damage", ("kind", ((object)(*(AttackKind*)(&val))/*cast due to .constrained prefix*/).ToString()), ("lvl", currentAttackCainLevel.ToString()), ("n", $"{num}/{val2.DamageEventTimes.Count}"), ("clipT", DebugLine.Num((double)clipT)), ("clip", clipName), ("stateSpeed", DebugLine.Num((double)stateSpeed)), ("clipPick", clipPick)); LogDecision(val, currentAttackCainLevel, ((CancelDecision)(ref val3)).Cancel, ((CancelDecision)(ref val3)).Reason); } if (((CancelDecision)(ref val3)).Cancel) { if (s.DelayAfterHit > 0f) { _pendingPlayer = player; _pendingAttack = currentAttack; _pendingEmote = s.Emote; _pendingDue = Time.time + s.DelayAfterHit; } else { Canceller.Cancel(player, s.Emote); } } } internal static void Tick() { try { TickCore(); } catch (Exception arg) { if (!_tickReported) { _tickReported = true; ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}.{1}: {2}", "AttackTracker", "Tick", arg)); } } } } private static void TickCore() { //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) //IL_009f: 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_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) MaxComboRun.Tick(Plugin.Current); if (!Plugin.Current.Enabled) { _pendingPlayer = null; _pendingAttack = null; Canceller.Disable(); return; } Canceller.TickRecovery(); if (_pendingAttack == null || Time.time < _pendingDue) { return; } Player pendingPlayer = _pendingPlayer; Attack pendingAttack = _pendingAttack; CancelEmote pendingEmote = _pendingEmote; _pendingPlayer = null; _pendingAttack = null; bool flag = (Object)(object)pendingPlayer != (Object)null && (Object)(object)pendingPlayer == (Object)(object)Player.m_localPlayer; CancelDecision val = CancelPolicy.ShouldFireDelayed(true, flag && ((Humanoid)pendingPlayer).m_currentAttack == pendingAttack, flag && ((Character)pendingPlayer).IsStaggering(), flag && ((Character)pendingPlayer).InAttack()); if (!((CancelDecision)(ref val)).Cancel) { if (ReviewLog.On) { LogDecision(AttackClassifier.Classify(pendingAttack.m_attackAnimation), pendingAttack.m_currentAttackCainLevel, cancel: false, ((CancelDecision)(ref val)).Reason); } } else { Canceller.Cancel(pendingPlayer, pendingEmote); } } private unsafe static void LogDecision(AttackKind kind, int lvl, bool cancel, string reason) { ReviewLog.Write("decision", ("kind", ((object)(*(AttackKind*)(&kind))/*cast due to .constrained prefix*/).ToString()), ("lvl", lvl.ToString()), ("cancel", DebugLine.Bool(cancel)), ("reason", reason)); } private static string TriggerName(Attack atk) { if (atk.m_attackChainLevels <= 1) { return atk.m_attackAnimation; } return atk.m_attackAnimation + atk.m_currentAttackCainLevel; } } internal static class Canceller { private static readonly int TagAttack = Animator.StringToHash("attack"); private static readonly int TagEmote = Animator.StringToHash("emote"); private const string EmoteStop = "emote_stop"; internal static bool InFlight; internal static float LastCancelTime; internal static int? CancelledState; internal static bool Relaunched; private static float _fullyInEmoteSince = -1f; private static string? _emoteTrigger; private static bool _blockActive; private static int _blocked; public unsafe static void Cancel(Player p, CancelEmote e, int? stateTag = null, string? kindOverride = null) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //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_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_012d: 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) Animator animator = ((Character)p).m_animator; if ((Object)(object)animator == (Object)null) { return; } float speed = animator.speed; float num = (((Object)(object)((Character)p).m_animEvent != (Object)null) ? ((Character)p).m_animEvent.m_pauseTimer : 0f); AnimatorStateInfo val; int fullPathHash; if (animator.IsInTransition(0)) { val = animator.GetNextAnimatorStateInfo(0); if (((AnimatorStateInfo)(ref val)).tagHash == (stateTag ?? TagAttack)) { val = animator.GetNextAnimatorStateInfo(0); fullPathHash = ((AnimatorStateInfo)(ref val)).fullPathHash; goto IL_0097; } } val = animator.GetCurrentAnimatorStateInfo(0); fullPathHash = ((AnimatorStateInfo)(ref val)).fullPathHash; goto IL_0097; IL_0097: CancelledState = fullPathHash; Relaunched = false; LiftBlock("cancel"); animator.ResetTrigger("emote_stop"); _emoteTrigger = CancelEmotes.Trigger(e); animator.SetTrigger(_emoteTrigger); string item = ApplySpeedPlan(p, animator, num); InFlight = true; LastCancelTime = Time.time; _fullyInEmoteSince = -1f; if (ReviewLog.On) { Attack currentAttack = ((Humanoid)p).m_currentAttack; ReviewLog.Write("cancel", ("kind", kindOverride ?? ((object)AttackClassifier.Classify(currentAttack?.m_attackAnimation)/*cast due to .constrained prefix*/).ToString()), ("lvl", (currentAttack?.m_currentAttackCainLevel ?? 0).ToString()), ("emote", ((object)(*(CancelEmote*)(&e))/*cast due to .constrained prefix*/).ToString()), ("pauseTimer", DebugLine.Num((double)num)), ("animSpeedBefore", DebugLine.Num((double)speed)), ("plan", item)); } } internal static string? Chain(Player p, string trigger, bool useNext, string[] staleTriggers) { //IL_0041: 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_0052: 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) Animator animator = ((Character)p).m_animator; if ((Object)(object)animator == (Object)null) { return null; } float pauseTimer = (((Object)(object)((Character)p).m_animEvent != (Object)null) ? ((Character)p).m_animEvent.m_pauseTimer : 0f); AnimatorStateInfo val; int fullPathHash; if (!useNext || !animator.IsInTransition(0)) { val = animator.GetCurrentAnimatorStateInfo(0); fullPathHash = ((AnimatorStateInfo)(ref val)).fullPathHash; } else { val = animator.GetNextAnimatorStateInfo(0); fullPathHash = ((AnimatorStateInfo)(ref val)).fullPathHash; } CancelledState = fullPathHash; Relaunched = false; LiftBlock("cancel"); animator.ResetTrigger("emote_stop"); ResetEmoteTrigger(animator); foreach (string text in staleTriggers) { animator.ResetTrigger(text); } animator.SetTrigger(trigger); string result = ApplySpeedPlan(p, animator, pauseTimer); LastCancelTime = Time.time; return result; } internal static bool TryConsumeRelaunch(out float dtSinceCancel) { dtSinceCancel = 0f; if (CancelledState.HasValue) { Relaunched = true; } if (!InFlight) { return false; } InFlight = false; _fullyInEmoteSince = -1f; dtSinceCancel = Time.time - LastCancelTime; _blockActive = RelaunchBlock.ShouldStart(dtSinceCancel); _blocked = 0; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && (Object)(object)((Character)localPlayer).m_animator != (Object)null) { ((Character)localPlayer).m_animator.ResetTrigger("emote_stop"); ResetEmoteTrigger(((Character)localPlayer).m_animator); } return true; } internal static void Disable() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: 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_007e: Unknown result type (might be due to invalid IL or missing references) LiftBlock("disabled"); if (!InFlight && !CancelledState.HasValue && _emoteTrigger == null) { return; } Player localPlayer = Player.m_localPlayer; Animator val = (((Object)(object)localPlayer != (Object)null) ? ((Character)localPlayer).m_animator : null); if ((Object)(object)localPlayer != (Object)null && (Object)(object)val != (Object)null) { bool num = val.IsInTransition(0); AnimatorStateInfo val2 = val.GetCurrentAnimatorStateInfo(0); bool flag = ((AnimatorStateInfo)(ref val2)).tagHash == TagEmote; int num2; if (num) { val2 = val.GetNextAnimatorStateInfo(0); num2 = ((((AnimatorStateInfo)(ref val2)).tagHash == TagEmote) ? 1 : 0); } else { num2 = 0; } bool flag2 = (byte)num2 != 0; if (RecoveryPolicy.StopOnDisable(InFlight, flag, flag2)) { SendEmoteStop(localPlayer, val); if (ReviewLog.On) { ReviewLog.Write("recovery_stop", ("dtSinceCancel", DebugLine.Num((double)(Time.time - LastCancelTime))), ("reason", "disabled")); } } ResetEmoteTrigger(val); } Reset(); } internal static void Reset() { InFlight = false; CancelledState = null; Relaunched = false; _fullyInEmoteSince = -1f; _emoteTrigger = null; _blockActive = false; _blocked = 0; } internal static bool RefuseStartAttack(Humanoid h) { if (!_blockActive) { return false; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)((Character)localPlayer).m_animator == (Object)null) { LiftBlock("no_player"); return false; } bool flag = RelaunchedAttackBegun(((Character)localPlayer).m_animator); float num = Time.time - LastCancelTime; if (RelaunchBlock.ShouldLift(_blockActive, num, flag)) { LiftBlock(flag ? "attack" : "window"); return false; } if (!RelaunchBlock.ShouldRefuse((Object)(object)h == (Object)(object)localPlayer, _blockActive, num, flag)) { return false; } _blocked++; return true; } private static void TickBlock(Player p) { if (_blockActive && !((Object)(object)((Character)p).m_animator == (Object)null)) { bool flag = RelaunchedAttackBegun(((Character)p).m_animator); if (RelaunchBlock.ShouldLift(_blockActive, Time.time - LastCancelTime, flag)) { LiftBlock(flag ? "attack" : "window"); } } } private static bool RelaunchedAttackBegun(Animator animator) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) bool num = animator.IsInTransition(0); AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(0); int num2; if (num) { AnimatorStateInfo nextAnimatorStateInfo = animator.GetNextAnimatorStateInfo(0); num2 = ((((AnimatorStateInfo)(ref nextAnimatorStateInfo)).tagHash == TagAttack) ? 1 : 0); } else { num2 = 0; } bool flag = (byte)num2 != 0; bool flag2 = CancelledState.HasValue && ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash == CancelledState.Value; return RelaunchBlock.AttackBegun(num, ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).tagHash == TagAttack, flag, flag2); } private static void LiftBlock(string reason) { if (_blockActive) { _blockActive = false; if (ReviewLog.On) { ReviewLog.Write("relaunch_block", ("blocked", _blocked.ToString()), ("reason", reason), ("dtSinceCancel", DebugLine.Num((double)(Time.time - LastCancelTime)))); } _blocked = 0; } } internal static void TickRecovery() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Invalid comparison between Unknown and I4 //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Invalid comparison between Unknown and I4 if (!InFlight && !CancelledState.HasValue && !_blockActive) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)((Character)localPlayer).m_animator == (Object)null) { Reset(); return; } TickBlock(localPlayer); Animator animator = ((Character)localPlayer).m_animator; bool flag = animator.IsInTransition(0); AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(0); AnimatorStateInfo nextAnimatorStateInfo; if (CancelledState.HasValue) { int num; if (!flag) { num = 0; } else { nextAnimatorStateInfo = animator.GetNextAnimatorStateInfo(0); num = ((AnimatorStateInfo)(ref nextAnimatorStateInfo)).fullPathHash; } int num2 = num; if (SpeedSkip.ShouldForget(CancelledState, Relaunched, ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash, num2)) { CancelledState = null; Relaunched = false; } } if (!InFlight) { return; } bool flag2 = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).tagHash == TagEmote; int num3; if (flag) { nextAnimatorStateInfo = animator.GetNextAnimatorStateInfo(0); num3 = ((((AnimatorStateInfo)(ref nextAnimatorStateInfo)).tagHash == TagEmote) ? 1 : 0); } else { num3 = 0; } bool flag3 = (byte)num3 != 0; if (_emoteTrigger != null && RecoveryPolicy.EmoteTriggerSpent(flag2, flag3)) { ResetEmoteTrigger(animator); } float time = Time.time; if (flag2 && !flag) { if (_fullyInEmoteSince < 0f) { _fullyInEmoteSince = time; } } else { _fullyInEmoteSince = -1f; } bool flag4 = ((Character)localPlayer).m_attackHold || ((Character)localPlayer).m_secondaryAttackHold; float num4 = ((_fullyInEmoteSince < 0f) ? 0f : (time - _fullyInEmoteSince)); RecoveryDecision val = RecoveryPolicy.Decide(InFlight, flag2, flag, flag4, num4, time - LastCancelTime); if ((int)((RecoveryDecision)(ref val)).Action != 0) { if ((int)((RecoveryDecision)(ref val)).Action == 1) { SendEmoteStop(localPlayer, animator); } ResetEmoteTrigger(animator); InFlight = false; _fullyInEmoteSince = -1f; if (ReviewLog.On) { ReviewLog.Write(((int)((RecoveryDecision)(ref val)).Action == 1) ? "recovery_stop" : "recovery_expire", ("dtSinceCancel", DebugLine.Num((double)(time - LastCancelTime))), ("reason", ((RecoveryDecision)(ref val)).Reason)); } } } private static void ResetEmoteTrigger(Animator animator) { if (_emoteTrigger != null) { animator.ResetTrigger(_emoteTrigger); _emoteTrigger = null; } } private static void SendEmoteStop(Player p, Animator animator) { animator.SetTrigger("emote_stop"); float pauseTimer = (((Object)(object)((Character)p).m_animEvent != (Object)null) ? ((Character)p).m_animEvent.m_pauseTimer : 0f); ApplySpeedPlan(p, animator, pauseTimer); } private static string ApplySpeedPlan(Player p, Animator animator, float pauseTimer) { if (SpeedPlan.For(pauseTimer).Item1 || (Object)(object)((Character)p).m_animEvent == (Object)null) { animator.speed = 1f; return "speed"; } ((Character)p).m_animEvent.m_pauseSpeed = 1f; return "restore"; } } internal static class ClipTimingCache { private readonly struct Read { public readonly ClipTiming Timing; public readonly float StateSpeed; public readonly string ClipName; public readonly float ClipT; public readonly float Length; public Read(ClipTiming timing, float stateSpeed, string clipName, float clipT, float length) { Timing = timing; StateSpeed = stateSpeed; ClipName = clipName; ClipT = clipT; Length = length; } } private static readonly Dictionary<string, ClipTiming> Cache = new Dictionary<string, ClipTiming>(); private static readonly List<AnimatorClipInfo> Clips = new List<AnimatorClipInfo>(); public static ClipTiming? ForCurrent(Animator a, out float stateSpeed, out string clipName, out float clipT, out string clipPick) { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: 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) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) stateSpeed = 1f; clipName = ""; clipT = 0f; clipPick = ClipPick.LogValue((ClipPickResult)0); Read? read = ReadState(a, next: false); Read? read2 = (a.IsInTransition(0) ? ReadState(a, next: true) : ((Read?)null)); ClipPickResult val = ClipPick.Choose(read?.Timing, read?.ClipT ?? 0f, read?.Length ?? 0f, read2?.Timing, read2?.ClipT ?? 0f, read2?.Length ?? 0f); clipPick = ClipPick.LogValue(val); Read? read3 = ((ClipPick.UseNext(val) || !read.HasValue) ? read2 : read); if (!read3.HasValue) { return null; } Read value = read3.Value; stateSpeed = value.StateSpeed; clipName = value.ClipName; clipT = value.ClipT; return value.Timing; } private static Read? ReadState(Animator a, bool next) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_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) AnimatorStateInfo val = (next ? a.GetNextAnimatorStateInfo(0) : a.GetCurrentAnimatorStateInfo(0)); Clips.Clear(); if (next) { a.GetNextAnimatorClipInfo(0, Clips); } else { a.GetCurrentAnimatorClipInfo(0, Clips); } AnimationClip val2 = null; float num = float.NegativeInfinity; foreach (AnimatorClipInfo clip in Clips) { AnimatorClipInfo current = clip; if ((Object)(object)((AnimatorClipInfo)(ref current)).clip != (Object)null && ((AnimatorClipInfo)(ref current)).weight > num) { val2 = ((AnimatorClipInfo)(ref current)).clip; num = ((AnimatorClipInfo)(ref current)).weight; } } if ((Object)(object)val2 == (Object)null) { return null; } string name = ((Object)val2).name; float normalizedTime = ((AnimatorStateInfo)(ref val)).normalizedTime; float clipT = (normalizedTime - Mathf.Floor(normalizedTime)) * val2.length; if (!Cache.TryGetValue(name, out ClipTiming value)) { value = ClipTiming.FromEvents(val2.events.Select((AnimationEvent e) => (time: e.time, functionName: e.functionName))); Cache[name] = value; } return new Read(value, ((AnimatorStateInfo)(ref val)).speed, name, clipT, val2.length); } } internal static class ComboLab { private static readonly int TagAttack = Animator.StringToHash("attack"); private static readonly int TagEmote = Animator.StringToHash("emote"); private static readonly int TagDodge = Animator.StringToHash("dodge"); private static readonly int TagIdle = Animator.StringToHash("idle"); private static Player? _player; private static int _inputMask = -1; private static int _block = -1; private static bool _stateKnown; private static int _cur; private static int _next; private static readonly HashSet<string> Reported = new HashSet<string>(); internal static bool On { get { if (ReviewLog.Log != null) { return LogGate.Allows((LogKind)1, Plugin.Current.DebugLog, Settings.ComboLabLog); } return false; } } internal static void Write(string ev, params (string k, string v)[] kv) { if (On) { ReviewLog.Log.LogInfo((object)LabLine.Format((double)Time.time, Time.frameCount, MonoUpdaters.UpdateCount, ev, (IReadOnlyList<ValueTuple<string, string>>)kv)); } } internal static Player? Local() { Player localPlayer = Player.m_localPlayer; if (localPlayer != _player) { _player = localPlayer; _inputMask = -1; _block = -1; _stateKnown = false; } if (!((Object)(object)localPlayer == (Object)null)) { return localPlayer; } return null; } internal static void Tick() { //IL_003b: 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_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) try { if (!On) { _player = null; return; } Player val = Local(); Animator val2 = (((Object)(object)val != (Object)null) ? ((Character)val).m_animator : null); if (!((Object)(object)val2 == (Object)null)) { AnimatorStateInfo currentAnimatorStateInfo = val2.GetCurrentAnimatorStateInfo(0); int num; if (!val2.IsInTransition(0)) { num = 0; } else { AnimatorStateInfo nextAnimatorStateInfo = val2.GetNextAnimatorStateInfo(0); num = ((AnimatorStateInfo)(ref nextAnimatorStateInfo)).fullPathHash; } int num2 = num; if (!_stateKnown || ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash != _cur || num2 != _next) { _stateKnown = true; _cur = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash; _next = num2; Write("state", ("cur", _cur.ToString()), ("next", _next.ToString()), ("curTag", LabLine.TagName(((AnimatorStateInfo)(ref currentAnimatorStateInfo)).tagHash, TagAttack, TagEmote, TagDodge, TagIdle)), ("t01", LabLine.Dp((double)((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime, 2))); } } } catch (Exception e) { Report("Tick", e); } } internal static void OnInput(bool attack, bool attackHold, bool secondary, bool secondaryHold, bool block, bool blockHold, bool swapped) { int num = LabLine.InputMask(attack, attackHold, secondary, secondaryHold, block, blockHold) | (swapped ? 64 : 0); if (num != _inputMask) { _inputMask = num; (string, string)[] array = new(string, string)[6] { ("attack", LabLine.Bit(attack)), ("attackHold", LabLine.Bit(attackHold)), ("secondary", LabLine.Bit(secondary)), ("secondaryHold", LabLine.Bit(secondaryHold)), ("block", LabLine.Bit(block)), ("blockHold", LabLine.Bit(blockHold)) }; Write("input", swapped ? array.Concat<(string, string)>(SwapButtons.LabFields(true)).ToArray() : array); } } internal static void OnBlocking(bool on) { int num = (on ? 1 : 0); if (num != _block) { _block = num; Write("block", ("on", LabLine.Bit(on))); } } internal static bool InAttackNoCache(Player p, out bool cached) { //IL_0041: 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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) cached = p.m_cachedFrame == MonoUpdaters.UpdateCount; if (cached) { return p.m_cachedAttack; } Animator animator = ((Character)p).m_animator; for (int i = 0; i < animator.layerCount; i++) { AnimatorStateInfo val; int tagHash; if (!animator.IsInTransition(i)) { val = animator.GetCurrentAnimatorStateInfo(i); tagHash = ((AnimatorStateInfo)(ref val)).tagHash; } else { val = animator.GetNextAnimatorStateInfo(i); tagHash = ((AnimatorStateInfo)(ref val)).tagHash; } if (tagHash == TagAttack) { return true; } } return false; } internal static string Anim(Attack? atk) { if (atk == null) { return "-"; } return atk.m_attackAnimation; } internal static void Report(string where, Exception e) { if (Reported.Add(where)) { ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}.{1}: {2}", "ComboLab", where, e)); } } } } internal static class InputGate { private static bool _imguiHasFocus; public static void SampleGuiFocus() { _imguiHasFocus = GUIUtility.keyboardControl != 0; } public static bool AcceptsHotkey() { return Blocker() == null; } public static string? Blocker() { if (!Player.m_localPlayerExists || (Object)(object)Player.m_localPlayer == (Object)null) { return "no local player"; } if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) { return "chat has focus"; } if (Console.IsVisible()) { return "console is open"; } if (TextInput.IsVisible()) { return "a text prompt is open"; } if (Menu.IsVisible()) { return "the menu is open"; } if (InventoryGui.IsVisible()) { return "the inventory is open"; } if (_imguiHasFocus) { return "an IMGUI text field has focus"; } return null; } } internal static class MaxComboRun { private static readonly int TagDodge = Animator.StringToHash("dodge"); private static readonly SwingHitCounter SwingHits = new SwingHitCounter(); private static MaxComboState _state = MaxComboState.Idle; private static MaxComboProfile _profile = MaxComboProfile.Sword; private static Attack? _attack; private static bool _hitReported; private static bool _tickReported; internal static bool OnHit(Player p, Attack atk, AttackKind kind, CancelSettings s, string clip, bool clipIsNext, out bool cancelNow, out int swingHit) { //IL_0008: 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_00d4: 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_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Invalid comparison between Unknown and I4 //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Expected I4, but got Unknown //IL_00f2: 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) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) cancelNow = false; swingHit = 0; MaxComboProfile val = MaxComboProfile.For(kind); if (!_state.Running && (val == null || !val.IsOn(s))) { return false; } bool running = _state.Running; MaxComboProfile val2 = (MaxComboProfile)(running ? ((object)_profile) : ((object)val)); bool flag = atk == _attack; int step = _state.Step; try { bool flag2 = val != null && Eligible(val, p, atk); if ((val == null || !MaxComboPolicy.Routes(val, kind, s, flag2)) && !running) { return false; } float time = Time.time; bool flag3 = ((Character)p).IsStaggering(); int hashCode = RuntimeHelpers.GetHashCode(atk); swingHit = SwingHits.Register(hashCode, clip); MaxComboProfile val3 = val2; MaxComboAction val4 = MaxComboPolicy.Decide(val3, _state, clip, swingHit, flag, s, time - _state.StepTime, flag2, flag3, Held(val3, p)); if ((int)((MaxComboAction)(ref val4)).Kind == 4) { Abort(p, ((MaxComboAction)(ref val4)).Reason); val4 = MaxComboAction.None; if (val != null) { val3 = val; val4 = MaxComboPolicy.Decide(val3, _state, clip, swingHit, false, s, 0f, flag2, flag3, Held(val3, p)); } } MaxComboActionKind kind2 = ((MaxComboAction)(ref val4)).Kind; switch (kind2 - 1) { case 0: case 1: { string trigger = val3.TriggerFor(((MaxComboAction)(ref val4)).Kind); string text = Canceller.Chain(p, trigger, clipIsNext, val3.SwingTriggers); if (text == null) { LogSkip(val3, "no_animator", clip, swingHit, step); return false; } _attack = atk; _profile = val3; _state = MaxComboPolicy.Next(_state, val4, hashCode, time); LogStep(p, ((MaxComboAction)(ref val4)).Kind, clip, _state.InstanceId, _state.Loop, text); break; } case 4: _state = MaxComboPolicy.Next(_state, val4, hashCode, time); break; case 2: { int loop = _state.Loop; int instanceId = _state.InstanceId; _state = MaxComboPolicy.Next(_state, val4, hashCode, time); _attack = null; if (((MaxComboAction)(ref val4)).Reason.Length == 0) { LogStep(p, ((MaxComboAction)(ref val4)).Kind, clip, instanceId, loop, null); } else { LogEnd(p, ((MaxComboAction)(ref val4)).Reason, clip, instanceId, loop); } cancelNow = true; break; } default: LogSkip(val3, MaxComboPolicy.SkipReason(val3, _state, clip, swingHit, s, flag2, flag3), clip, swingHit, _state.Step); break; } return MaxComboPolicy.Acts(((MaxComboAction)(ref val4)).Kind); } catch (Exception arg) { if (!_hitReported) { _hitReported = true; ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}.{1}: {2}", "MaxComboRun", "OnHit", arg)); } } SafeAbort(p, "error"); cancelNow = false; swingHit = 0; return false; } } internal static void Tick(CancelSettings s) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: 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_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Invalid comparison between Unknown and I4 //IL_0064: 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) if (!_state.Running) { return; } try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { _state = MaxComboState.Idle; _attack = null; return; } Animator animator = ((Character)localPlayer).m_animator; int num; if ((Object)(object)animator != (Object)null) { AnimatorStateInfo val = animator.GetCurrentAnimatorStateInfo(0); if (((AnimatorStateInfo)(ref val)).tagHash != TagDodge) { if (animator.IsInTransition(0)) { val = animator.GetNextAnimatorStateInfo(0); num = ((((AnimatorStateInfo)(ref val)).tagHash == TagDodge) ? 1 : 0); } else { num = 0; } } else { num = 1; } } else { num = 0; } bool flag = (byte)num != 0; MaxComboAction val2 = MaxComboPolicy.Watch(_profile, _state, ((Humanoid)localPlayer).m_currentAttack == _attack, s, Time.time - _state.StepTime, ((Character)localPlayer).IsStaggering(), flag); if ((int)((MaxComboAction)(ref val2)).Kind == 4) { Abort(localPlayer, ((MaxComboAction)(ref val2)).Reason); } } catch (Exception arg) { if (!_tickReported) { _tickReported = true; ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}.{1}: {2}", "MaxComboRun", "Tick", arg)); } } SafeAbort(Player.m_localPlayer, "error"); } } private static bool Eligible(MaxComboProfile profile, Player p, Attack atk) { SharedData val = ((Humanoid)p).GetCurrentWeapon()?.m_shared; Attack val2 = val?.m_attack; if (val2 != null) { return MaxComboPolicy.Eligible(profile, atk.m_attackAnimation, atk.m_currentAttackCainLevel, atk.m_attackChainLevels, val2.m_attackAnimation, val2.m_attackChainLevels, val.m_secondaryAttack?.m_attackAnimation); } return false; } private static bool Held(MaxComboProfile profile, Player p) { if (!profile.HoldPrimary) { return ((Character)p).m_secondaryAttackHold; } return ((Character)p).m_attackHold; } private static void SafeAbort(Player? p, string reason) { try { if ((Object)(object)p != (Object)null) { Abort(p, reason); } } catch (Exception) { } finally { _state = MaxComboState.Idle; _attack = null; SwingHits.Reset(); } } private static void Abort(Player p, string reason) { int loop = _state.Loop; _state = MaxComboState.Idle; _attack = null; Animator animator = ((Character)p).m_animator; if ((Object)(object)animator != (Object)null) { string[] abortTriggers = _profile.AbortTriggers; foreach (string text in abortTriggers) { animator.ResetTrigger(text); } } if (ReviewLog.On) { ReviewLog.Write("maxcombo_abort", Concat(MaxComboPolicy.AbortFields(reason), MaxComboPolicy.RunFields(loop, (string)null, p.GetStamina()), MaxComboPolicy.WeaponFields(_profile))); } if (reason == "instance" && ReviewLog.On) { AttackClearLog.OnInstanceAbort(p); } } private static void LogSkip(MaxComboProfile profile, string reason, string clip, int swingHit, int step) { if (ReviewLog.On) { ReviewLog.Write("maxcombo_skip", Concat(MaxComboPolicy.SkipFields(reason, clip, swingHit, step), MaxComboPolicy.WeaponFields(profile))); } } private static void LogStep(Player p, MaxComboActionKind k, string clip, int id, int loop, string? plan) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (ReviewLog.On) { ReviewLog.Write("maxcombo", Concat(MaxComboPolicy.StepFields(MaxComboPolicy.StepNumber(k), clip, id), MaxComboPolicy.RunFields(loop, plan, p.GetStamina()), MaxComboPolicy.WeaponFields(_profile))); } } private static void LogEnd(Player p, string reason, string clip, int id, int loop) { if (ReviewLog.On) { ReviewLog.Write("maxcombo_end", Concat(MaxComboPolicy.EndFields(reason), new(string, string)[2] { ("clip", clip), ("attack", MaxComboPolicy.ShortId(id)) }, MaxComboPolicy.RunFields(loop, (string)null, p.GetStamina()), MaxComboPolicy.WeaponFields(_profile))); } } private static (string k, string v)[] Concat(params (string k, string v)[][] parts) { int num = 0; (string, string)[][] array = parts; foreach ((string, string)[] array2 in array) { num += array2.Length; } (string, string)[] array3 = new(string, string)[num]; int num2 = 0; array = parts; foreach ((string, string)[] array4 in array) { array4.CopyTo(array3, num2); num2 += array4.Length; } return array3; } } [BepInPlugin("I3_rett.CombatFlow", "CombatFlow", "0.1.1")] public sealed class Plugin : BaseUnityPlugin { private Harmony? _harmony; public static CancelSettings Current => Settings.Snapshot(); private void Awake() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown Settings.Bind(((BaseUnityPlugin)this).Config); ReviewLog.Log = ((BaseUnityPlugin)this).Logger; _harmony = new Harmony("I3_rett.CombatFlow"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"CombatFlow 0.1.1 loaded"); Settings.LogConfig(); } private void OnDestroy() { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void OnGUI() { InputGate.SampleGuiFocus(); } private void Update() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) AttackTracker.Tick(); ComboLab.Tick(); KeyboardShortcut value = Settings.ToggleKey.Value; if (((KeyboardShortcut)(ref value)).IsDown() && InputGate.AcceptsHotkey()) { Settings.Enabled.Value = !Settings.Enabled.Value; string text = (Settings.Enabled.Value ? "ON" : "OFF"); if (ReviewLog.On) { ReviewLog.Write("toggle", ("enabled", DebugLine.Bool(Settings.Enabled.Value))); } if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, "CombatFlow " + text, 0, (Sprite)null, false, true); } } } } internal static class PluginInfo { public const string Guid = "I3_rett.CombatFlow"; public const string Name = "CombatFlow"; public const string Version = "0.1.1"; } internal static class ReviewLog { internal static ManualLogSource? Log; internal static bool On { get { if (Log != null) { return LogGate.Allows((LogKind)0, Plugin.Current.DebugLog, Settings.ComboLabLog); } return false; } } internal static void Write(string ev, params (string k, string v)[] kv) { if (On) { Log.LogInfo((object)DebugLine.Format((double)Time.time, Time.frameCount, ev, (IReadOnlyList<ValueTuple<string, string>>)kv)); } } } internal static class Settings { private static CancelSettings _snapshot = CancelSettings.Default; private static ConfigEntry<bool> _maxCombo = null; private static ConfigEntry<MaxComboMode> _maxComboMode = null; private static ConfigEntry<int> _maxComboLoopCap = null; private static ConfigEntry<bool> _swapButtons = null; private static ConfigEntry<bool> _axesMaxCombo = null; private static ConfigEntry<bool> _dodge = null; private static ConfigEntry<bool> _autoParry = null; private static ConfigEntry<CancelEmote> _emote = null; private static ConfigEntry<float> _delayAfterHit = null; private static ConfigEntry<bool> _debugLog = null; private static ConfigEntry<bool> _comboLab = null; private static bool _configReported; internal static ConfigEntry<bool> Enabled { get; private set; } = null; internal static ConfigEntry<KeyboardShortcut> ToggleKey { get; private set; } = null; internal static bool ComboLabLog { get; private set; } internal static CancelSettings Snapshot() { return _snapshot; } internal static void Bind(ConfigFile config) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0093: 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) //IL_00f5: Expected O, but got Unknown //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Expected O, but got Unknown CancelSettings val = CancelSettings.Default; Enabled = config.Bind<bool>("General", "Enabled", val.Enabled, Describe("Master switch for animation cancelling.", 100)); ToggleKey = config.Bind<KeyboardShortcut>("General", "Toggle key", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), Describe("Turns animation cancelling on or off in game and shows a short message.", 90)); _maxCombo = config.Bind<bool>("Sword", "Max combo (auto)", val.MaxCombo, Describe("While the secondary attack plays, chain the second and third primary swings into it, then cancel or loop (see Max combo mode). The final cancel honours Delay after hit. Releasing the button takes effect at the next hit, so one more swing may play. Needs a sword whose primary is the three-swing combo. On by default.", 85)); _maxComboMode = config.Bind<MaxComboMode>("Sword", "Max combo mode", val.MaxComboMode, Describe("Used by Max combo (auto) of the sword and of the Berserker axes. Sword, Cycle: secondary, second swing, third swing, then cancel (every relaunched secondary costs stamina). Sword, Loop: after the third swing, keep alternating the second and third swings while the secondary button is held; only the first secondary costs stamina. Berserker axes: the same with the combo finisher and the third and fourth blows, while the primary button is held. Releasing the button cancels at the end of the swing playing (one more swing may play). After Max combo loop cap extra swings a still-held button starts a new attack, which costs stamina again.", 84)); _maxComboLoopCap = config.Bind<int>("Sword", "Max combo loop cap", val.MaxComboLoopCap, new ConfigDescription("Max combo mode Loop only, sword and Berserker axes: how many extra swings are chained past the first pass before the run ends with a cancel, from 0 to 1000. A still-held button then starts a new attack, which costs stamina again. 0 means no cap: the loop goes on until the button is released or the run is interrupted.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), new object[1] { new ConfigurationManagerAttributes { Order = 83 } })); _swapButtons = config.Bind<bool>("Sword", "Swap attack buttons", val.SwapButtons, Describe("With a sword only, swap the primary and secondary attack buttons (press and hold), so the primary button starts the secondary and its Max combo loop and the secondary button the light combo. Block, dodge, jump and movement are unchanged; other weapons, tools and build mode are untouched. Works with a gamepad. Needs Enabled on. Off by default.", 82)); _axesMaxCombo = config.Bind<bool>("Berserker axes", "Max combo (auto)", val.AxesMaxCombo, Describe("After the last hit of the primary combo finisher, chain the third and fourth blows into it, then cancel or loop, with Max combo mode and Max combo loop cap of the Sword section. The chained blows keep the finisher's bonus damage. Loop mode keeps going while the primary button is held; releasing it cancels at the end of the swing playing. On by default.", 85)); _dodge = config.Bind<bool>("Dodge", "Cancel after invincibility", val.Dodge, Describe("End the dodge roll as soon as its invincibility window is over.", 100)); _autoParry = config.Bind<bool>("Combat", "Auto perfect parry", val.AutoParry, Describe("A blockable hit from the front is parried as a perfect block, even mid-attack and without the block button. Blocks with the shield, weapon or bare hands, as the game does; hits from behind and during a dodge are unchanged. The game's own block rules apply (stamina, guard break, attacker stagger). Applies to every attacker, creatures and players. Needs Enabled on. Off by default.", 100)); _emote = config.Bind<CancelEmote>("Advanced", "Cancel emote", val.Emote, Describe("The emote the game plays under the hood to cut an animation short, NoNoNo or Cheer.", 100, advanced: true)); _delayAfterHit = config.Bind<float>("Advanced", "Delay after hit (s)", val.DelayAfterHit, new ConfigDescription("Seconds to wait after the hit lands before cancelling, from 0 to 0.3.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.3f), new object[1] { new ConfigurationManagerAttributes { Order = 90, IsAdvanced = true } })); _debugLog = config.Bind<bool>("Advanced", "Debug log", val.DebugLog, Describe("Log the timestamp of every damage event and cancel, for measuring DPS. Off: the mod writes nothing to the log apart from its startup line and errors. Off by default.", 80, advanced: true)); _comboLab = config.Bind<bool>("Advanced", "Combo lab log", false, Describe("Measurement only: log inputs, attack starts, animator states, blocking and hits of the local player (CL lines), even with Enabled off. Needs Debug log on as well. Off by default.", 70, advanced: true)); config.SettingChanged += delegate { Rebuild(); LogConfig(); }; Rebuild(); } internal static void LogConfig() { try { if (ReviewLog.Log != null) { (string, string)[] kv = MaxComboPolicy.ConfigFields(_snapshot); if (ReviewLog.On) { ReviewLog.Write("config", kv); } if (ComboLab.On) { ComboLab.Write("config", kv); } } } catch (Exception arg) { if (!_configReported) { _configReported = true; ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}.{1}: {2}", "Settings", "LogConfig", arg)); } } } } private static ConfigDescription Describe(string text, int order, bool advanced = false) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown return new ConfigDescription(text, (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = order, IsAdvanced = (advanced ? new bool?(true) : ((bool?)null)) } }); } private static void Rebuild() { //IL_0028: 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_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown ComboLabLog = _comboLab.Value; _snapshot = new CancelSettings(Enabled.Value, _dodge.Value, _emote.Value, _delayAfterHit.Value, _debugLog.Value, _maxCombo.Value, _maxComboMode.Value, _maxComboLoopCap.Value, _swapButtons.Value, _autoParry.Value, _axesMaxCombo.Value); } } } namespace CombatFlow.Patches { [HarmonyPatch(typeof(Humanoid), "OnAttackTrigger")] internal static class AttackTriggerPatch { private static bool _reported; private static void Postfix(Humanoid __instance) { try { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && __instance.m_currentAttack != null) { CancelSettings current = Plugin.Current; if (current.Enabled) { AttackTracker.OnDamageEvent(Player.m_localPlayer, current); } } } catch (Exception arg) { if (!_reported) { _reported = true; ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}: {1}", "AttackTriggerPatch", arg)); } } } } } [HarmonyPatch(typeof(Character), "RPC_Damage")] internal static class AutoParryPatch { internal struct State { internal bool Counted; internal Player? Player; internal bool Active; internal float Timer; internal float TimedBlockBonus; internal float StaggerBefore; internal bool HasAttacker; internal bool Log; internal string? Result; internal string? Attacker; internal bool Blockable; internal bool Front; internal float DmgBefore; } internal static Humanoid? Forced; internal static int Depth; internal static bool ForcedTrue; private static bool _reported; private static void Prefix(Character __instance, HitData hit, out State __state) { //IL_0061: 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) __state = default(State); try { CancelSettings current = Plugin.Current; if (!current.Enabled || hit == null) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null || val != Player.m_localPlayer) { return; } Depth++; __state.Counted = true; bool flag = ReviewLog.On; if (Depth == 1 && (current.AutoParry || flag)) { bool flag2 = AutoParry.FromFront(Vector3.Dot(hit.m_dir, ((Component)val).transform.forward)); Character attacker = hit.GetAttacker(); string text = "off"; ItemData val2 = null; if (current.AutoParry) { val2 = ((Humanoid)val).GetCurrentBlocker(); text = AutoParry.Decide(current.Enabled, current.AutoParry, true, hit.m_blockable, flag2, val2 != null, ((Character)val).InDodge(), ((Character)val).InPlaceMode(), ((Character)val).IsEncumbered(), ((Character)val).InMinorAction(), ((Character)val).IsStaggering()); } __state.Player = val; __state.Result = text; __state.HasAttacker = (Object)(object)attacker != (Object)null; if (flag) { __state.Log = true; __state.Attacker = AttackerName(attacker); __state.Blockable = hit.m_blockable; __state.Front = flag2; __state.DmgBefore = hit.GetTotalDamage(); } if (!(text != "force") && val2 != null) { __state.Timer = ((Humanoid)val).m_blockTimer; __state.TimedBlockBonus = val2.m_shared.m_timedBlockBonus; __state.StaggerBefore = ((Character)val).GetStaggerPercentage(); ForcedTrue = false; __state.Active = true; ((Humanoid)val).m_blockTimer = 0f; Forced = (Humanoid?)(object)val; } } } catch (Exception e) { if (__state.Active && (Object)(object)__state.Player != (Object)null) { ((Humanoid)__state.Player).m_blockTimer = __state.Timer; Forced = null; } bool counted = __state.Counted; __state = default(State); __state.Counted = counted; Report(e); } } private static void Finalizer(HitData hit, State __state) { if (__state.Counted) { Depth--; } try { Player player = __state.Player; if (!((Object)(object)player == (Object)null)) { string text = __state.Result; if (__state.Active) { Forced = null; ((Humanoid)player).m_blockTimer = __state.Timer; bool flag = !((Character)player).HaveStamina(0f) || (__state.StaggerBefore < 1f && ((Character)player).GetStaggerPercentage() >= 1f); text = (ForcedTrue ? AutoParry.Outcome(__state.TimedBlockBonus, flag, __state.HasAttacker) : "not_forced"); } if (__state.Log && ReviewLog.On) { ReviewLog.Write("parry", ("attacker", __state.Attacker ?? "-"), ("blockable", DebugLine.Bool(__state.Blockable)), ("front", DebugLine.Bool(__state.Front)), ("dmgBefore", DebugLine.Num((double)__state.DmgBefore)), ("dmgAfter", (hit != null) ? DebugLine.Num((double)hit.GetTotalDamage()) : "-"), ("result", text ?? "-")); } } } catch (Exception e) { Forced = null; Report(e); } } private static string AttackerName(Character? attacker) { if ((Object)(object)attacker == (Object)null) { return "-"; } Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null) { return val.GetPlayerName(); } if (!string.IsNullOrEmpty(attacker.m_name)) { return attacker.m_name; } return ((Object)((Component)attacker).gameObject).name; } internal static void Report(Exception e) { if (!_reported) { _reported = true; ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}: {1}", "AutoParryPatch", e)); } } } } [HarmonyPatch(typeof(Humanoid), "IsBlocking")] internal static class AutoParryBlockingPatch { [HarmonyPriority(600)] private static void Postfix(Humanoid __instance, ref bool __result) { try { if (!((Object)(object)AutoParryPatch.Forced == (Object)null) && __instance == AutoParryPatch.Forced && AutoParryPatch.Depth == 1 && (!((Character)__instance).m_nview.IsValid() || ((Character)__instance).m_nview.IsOwner())) { __result = AutoParry.ForcedBlocking(((Character)__instance).InDodge(), ((Character)__instance).InPlaceMode(), ((Character)__instance).IsEncumbered(), ((Character)__instance).InMinorAction(), ((Character)__instance).IsStaggering()); AutoParryPatch.ForcedTrue |= __result; } } catch (Exception e) { AutoParryPatch.Report(e); } } } [HarmonyPatch(typeof(Player), "SetControls")] internal static class LabSetControlsPatch { [HarmonyPriority(800)] private static bool Prefix(Player __instance, bool attack, bool attackHold, bool secondaryAttack, bool secondaryAttackHold, bool block, bool blockHold) { try { if (!ComboLab.On || __instance != ComboLab.Local()) { return true; } ComboLab.OnInput(attack, attackHold, secondaryAttack, secondaryAttackHold, block, blockHold, SwapButtonsPatch.SwappedThisCall); } catch (Exception e) { ComboLab.Report("LabSetControlsPatch", e); } return true; } } [HarmonyPatch(typeof(Humanoid), "StartAttack", new Type[] { typeof(Character), typeof(bool) })] internal static class LabStartAttackPatch { internal struct Before { public bool Watch; public bool InAttack; public bool Cached; public bool QueuedChain; public float SinceLast; public string PrevAnim; } [HarmonyPriority(800)] private static bool Prefix(Humanoid __instance, out Before __state) { __state = default(Before); try { if (!ComboLab.On) { return true; } Player val = ComboLab.Local(); if ((Object)(object)val == (Object)null || (object)__instance != val) { return true; } bool cached; bool inAttack = ComboLab.InAttackNoCache(val, out cached); __state = new Before { Watch = true, InAttack = inAttack, Cached = cached, QueuedChain = ((Humanoid)val).HaveQueuedChain(), SinceLast = ((Humanoid)val).m_timeSinceLastAttack, PrevAnim = ComboLab.Anim(((Humanoid)val).m_currentAttack) }; } catch (Exception e) { ComboLab.Report("LabStartAttackPatch.Prefix", e); } return true; } [HarmonyPriority(0)] private static void Postfix(Humanoid __instance, bool secondaryAttack, bool __result, Before __state) { try { if (__state.Watch) { Attack val = (__result ? __instance.m_currentAttack : null); ComboLab.Write("start", ("secondary", LabLine.Bit(secondaryAttack)), ("result", LabLine.Bit(__result)), ("inAttackBefore", LabLine.Bit(__state.InAttack)), ("cached", LabLine.Bit(__state.Cached)), ("queuedChain", LabLine.Bit(__state.QueuedChain)), ("sinceLast", LabLine.Dp((double)__state.SinceLast, 3)), ("anim", ComboLab.Anim(val)), ("lvl", (val != null) ? val.m_currentAttackCainLevel.ToString() : "-"), ("levels", (val != null) ? val.m_attackChainLevels.ToString() : "-"), ("prevAnim", __state.PrevAnim)); } } catch (Exception e) { ComboLab.Report("LabStartAttackPatch.Postfix", e); } } } [HarmonyPatch(typeof(ZSyncAnimation), "SetTrigger", new Type[] { typeof(string) })] internal static class LabTriggerPatch { [HarmonyPriority(800)] private static bool Prefix(ZSyncAnimation __instance, string name) { try { if (!ComboLab.On) { return true; } Player val = ComboLab.Local(); if ((Object)(object)val == (Object)null || __instance != ((Character)val).m_zanim) { return true; } ComboLab.Write("trigger", ("name", name)); } catch (Exception e) { ComboLab.Report("LabTriggerPatch", e); } return true; } } [HarmonyPatch(typeof(Humanoid), "OnAttackTrigger")] internal static class LabSwingPatch { [HarmonyPriority(0)] private static void Postfix(Humanoid __instance) { //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_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) try { if (!ComboLab.On || (object)__instance != ComboLab.Local()) { return; } Attack currentAttack = __instance.m_currentAttack; if (currentAttack == null) { return; } int currentAttackCainLevel = currentAttack.m_currentAttackCainLevel; int attackChainLevels = currentAttack.m_attackChainLevels; float num = LabLine.SwingMultiplier(currentAttack.m_damageMultiplier, currentAttack.m_lastChainDamageMultiplier, currentAttackCainLevel, attackChainLevels); int num2 = 0; int num3 = 0; Animator animator = ((Character)__instance).m_animator; if ((Object)(object)animator != (Object)null) { AnimatorStateInfo val = animator.GetCurrentAnimatorStateInfo(0); num2 = ((AnimatorStateInfo)(ref val)).fullPathHash; int num4; if (!animator.IsInTransition(0)) { num4 = 0; } else { val = animator.GetNextAnimatorStateInfo(0); num4 = ((AnimatorStateInfo)(ref val)).fullPathHash; } num3 = num4; } ComboLab.Write("swing", ("anim", ComboLab.Anim(currentAttack)), ("secondary", LabLine.Bit(__instance.m_currentAttackIsSecondary)), ("lvl", currentAttackCainLevel.ToString()), ("levels", attackChainLevels.ToString()), ("mult", LabLine.Dp((double)num, 2)), ("state", num2.ToString()), ("next", num3.ToString())); } catch (Exception e) { ComboLab.Report("LabSwingPatch", e); } } } [HarmonyPatch(typeof(Humanoid), "IsBlocking")] internal static class LabBlockingPatch { [HarmonyPriority(0)] private static void Postfix(Humanoid __instance, bool __result) { try { if (ComboLab.On && (object)__instance == ComboLab.Local()) { ComboLab.OnBlocking(__result); } } catch (Exception e) { ComboLab.Report("LabBlockingPatch", e); } } } [HarmonyPatch(typeof(Character), "Damage", new Type[] { typeof(HitData) })] internal static class LabHitPatch { [HarmonyPriority(800)] private static bool Prefix(Character __instance, HitData hit) { try { if (!ComboLab.On || hit == null) { return true; } Player val = ComboLab.Local(); if ((Object)(object)val == (Object)null || (object)hit.GetAttacker() != val) { return true; } Attack currentAttack = ((Humanoid)val).m_currentAttack; int num = currentAttack?.m_currentAttackCainLevel ?? (-1); int num2 = currentAttack?.m_attackChainLevels ?? 0; string item = (string.IsNullOrEmpty(__instance.m_name) ? ((Object)((Component)__instance).gameObject).name : __instance.m_name); ComboLab.Write("hit", ("dmg", LabLine.Dp((double)hit.GetTotalDamage(), 1)), ("target", item), ("anim", ComboLab.Anim(currentAttack)), ("lvl", (currentAttack != null) ? num.ToString() : "-"), ("levels", (currentAttack != null) ? num2.ToString() : "-"), ("secondary", LabLine.Bit(((Humanoid)val).m_currentAttackIsSecondary)), ("lastChain", LabLine.Bit(LabLine.IsLastChain(num, num2)))); } catch (Exception e) { ComboLab.Report("LabHitPatch", e); } return true; } } [HarmonyPatch(typeof(Player), "OnDodgeMortal")] internal static class DodgePatch { private const string Kind = "Dodge"; private static readonly int TagDodge = Animator.StringToHash("dodge"); private static readonly List<AnimatorClipInfo> Clips = new List<AnimatorClipInfo>(); private static bool _reported; private static void Postfix(Player __instance) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } CancelSettings current = Plugin.Current; if (!current.Enabled) { return; } CancelDecision val = CancelPolicy.ShouldCancelDodge(current); Animator animator = ((Character)__instance).m_animator; if (((CancelDecision)(ref val)).Cancel && ((Object)(object)animator == (Object)null || !IsDodging(animator))) { ((CancelDecision)(ref val))..ctor(false, "not_dodging"); } if (ReviewLog.On) { ReviewLog.Write("decision", ("kind", "Dodge"), ("lvl", "0"), ("cancel", DebugLine.Bool(((CancelDecision)(ref val)).Cancel)), ("reason", ((CancelDecision)(ref val)).Reason)); } if (((CancelDecision)(ref val)).Cancel && !((Object)(object)animator == (Object)null)) { float num = (ReviewLog.On ? ClipTime(animator) : 0f); Canceller.Cancel(__instance, current.Emote, TagDodge, "Dodge"); if (ReviewLog.On) { ReviewLog.Write("dodge_cancel", ("clipT", DebugLine.Num((double)num))); } } } catch (Exception arg) { if (!_reported) { _reported = true; ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}: {1}", "DodgePatch", arg)); } } } } private static bool IsDodging(Animator a) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: 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_0026: Unknown result type (might be due to invalid IL or missing references) AnimatorStateInfo val = a.GetCurrentAnimatorStateInfo(0); if (((AnimatorStateInfo)(ref val)).tagHash != TagDodge) { if (a.IsInTransition(0)) { val = a.GetNextAnimatorStateInfo(0); return ((AnimatorStateInfo)(ref val)).tagHash == TagDodge; } return false; } return true; } private static float ClipTime(Animator a) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_0079: Unknown result type (might be due to invalid IL or missing references) int num; AnimatorStateInfo val; if (a.IsInTransition(0)) { AnimatorStateInfo nextAnimatorStateInfo = a.GetNextAnimatorStateInfo(0); num = ((((AnimatorStateInfo)(ref nextAnimatorStateInfo)).tagHash == TagDodge) ? 1 : 0); if (num != 0) { val = a.GetNextAnimatorStateInfo(0); goto IL_0035; } } else { num = 0; } val = a.GetCurrentAnimatorStateInfo(0); goto IL_0035; IL_0035: AnimatorStateInfo val2 = val; Clips.Clear(); if (num != 0) { a.GetNextAnimatorClipInfo(0, Clips); } else { a.GetCurrentAnimatorClipInfo(0, Clips); } AnimationClip val3 = null; float num2 = float.NegativeInfinity; foreach (AnimatorClipInfo clip in Clips) { AnimatorClipInfo current = clip; if ((Object)(object)((AnimatorClipInfo)(ref current)).clip != (Object)null && ((AnimatorClipInfo)(ref current)).weight > num2) { val3 = ((AnimatorClipInfo)(ref current)).clip; num2 = ((AnimatorClipInfo)(ref current)).weight; } } if ((Object)(object)val3 == (Object)null) { return 0f; } float normalizedTime = ((AnimatorStateInfo)(ref val2)).normalizedTime; return (normalizedTime - Mathf.Floor(normalizedTime)) * val3.length; } } [HarmonyPatch(typeof(Player), "SetControls")] internal static class SetControlsPatch { internal struct State { public bool Watch; public bool Attack; public bool Secondary; } private static bool _reported; [HarmonyPriority(400)] private static bool Prefix(Player __instance, bool attackHold, bool secondaryAttackHold, out State __state) { __state = default(State); try { if (!ReviewLog.On || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return true; } if (HoldDrop.ShouldWatch(Canceller.InFlight, Time.time, Canceller.LastCancelTime)) { __state = new State { Watch = true, Attack = attackHold, Secondary = secondaryAttackHold }; } } catch (Exception e) { Report(e); } return true; } private static void Postfix(Player __instance, State __state) { try { if (__state.Watch) { if (HoldDrop.WasDropped(__state.Attack, ((Character)__instance).m_attackHold)) { ReviewLog.Write("hold_drop", ("which", "attack")); } if (HoldDrop.WasDropped(__state.Secondary, ((Character)__instance).m_secondaryAttackHold)) { ReviewLog.Write("hold_drop", ("which", "secondary")); } } } catch (Exception e) { Report(e); } } private static void Report(Exception e) { if (!_reported) { _reported = true; ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}: {1}", "SetControlsPatch", e)); } } } } [HarmonyPatch(typeof(CharacterAnimEvent), "Speed", new Type[] { typeof(float) })] internal static class SpeedEventPatch { private static bool _reported; private static bool Prefix(CharacterAnimEvent __instance, float speedScale) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) try { if (!Canceller.CancelledState.HasValue || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)__instance.m_character != (Object)(object)Player.m_localPlayer) { return true; } Animator animator = __instance.m_animator; if ((Object)(object)animator == (Object)null) { return true; } int num; AnimatorStateInfo val; if (!animator.IsInTransition(0)) { num = 0; } else { val = animator.GetNextAnimatorStateInfo(0); num = ((AnimatorStateInfo)(ref val)).fullPathHash; } int num2 = num; int? cancelledState = Canceller.CancelledState; bool relaunched = Canceller.Relaunched; val = animator.GetCurrentAnimatorStateInfo(0); if (!SpeedSkip.ShouldSkip(cancelledState, relaunched, ((AnimatorStateInfo)(ref val)).fullPathHash, num2, Time.time - Canceller.LastCancelTime)) { return true; } if (ReviewLog.On) { ReviewLog.Write("speed_skip", ("value", DebugLine.Num((double)speedScale)), ("relaunched", DebugLine.Bool(Canceller.Relaunched))); } return false; } catch (Exception arg) { if (!_reported) { _reported = true; ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}: {1}", "SpeedEventPatch", arg)); } } return true; } } } [HarmonyPatch(typeof(Humanoid), "StartAttack", new Type[] { typeof(Character), typeof(bool) })] internal static class StartAttackPatch { internal struct Before { public bool HadAttack; public bool InAttack; } private static bool _reported; private static bool _prefixReported; private static bool Prefix(Humanoid __instance, ref bool __result, out Before __state) { __state = default(Before); try { if (ReviewLog.On && __instance.m_currentAttack != null && (Object)(object)__instance == (Object)(object)Player.m_localPlayer) { Player localPlayer = Player.m_localPlayer; bool cached; bool inAttack = (Object)(object)((Character)localPlayer).m_animator != (Object)null && ComboLab.InAttackNoCache(localPlayer, out cached); __state = new Before { HadAttack = true, InAttack = inAttack }; } } catch (Exception e) { AttackClearLog.Report("StartAttackPatch.Prefix", e); } try { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || !Plugin.Current.Enabled || !Canceller.RefuseStartAttack(__instance)) { return true; } __result = false; return false; } catch (Exception arg) { if (!_prefixReported) { _prefixReported = true; ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}.{1}: {2}", "StartAttackPatch", "Prefix", arg)); } } return true; } } private static void Postfix(Humanoid __instance, bool secondaryAttack, bool __result, Before __state) { try { if (__state.HadAttack && !__result && __instance.m_currentAttack == null && (Object)(object)__instance == (Object)(object)Player.m_localPlayer) { AttackClearLog.OnStartFailed(Player.m_localPlayer, secondaryAttack, __state.InAttack); } } catch (Exception e) { AttackClearLog.Report("StartAttackPatch.Postfix", e); } try { if (__result && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && Plugin.Current.Enabled) { AttackTracker.OnAttackStarted(Player.m_localPlayer); } } catch (Exception arg) { if (!_reported) { _reported = true; ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}: {1}", "StartAttackPatch", arg)); } } } } } [HarmonyPatch(typeof(Player), "SetControls")] internal static class SwapButtonsPatch { private static bool _reported; internal const int Order = 900; internal static bool SwappedThisCall { get; private set; } [HarmonyPriority(900)] private static bool Prefix(Player __instance, ref bool attack, ref bool attackHold, ref bool secondaryAttack, ref bool secondaryAttackHold) { //IL_009e: 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_00a7: 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) SwappedThisCall = false; try { if (!SwapButtons.IsOn(Settings.Snapshot()) || __instance != Player.m_localPlayer) { return true; } Attack val = ((Humanoid)__instance).GetCurrentWeapon()?.m_shared?.m_attack; Attack val2 = ((Humanoid)__instance).GetCurrentWeapon()?.m_shared?.m_secondaryAttack; bool flag = val != null && val2 != null && SwapButtons.IsSword(val2.m_attackAnimation, val.m_attackAnimation, val.m_attackChainLevels); if (!SwapButtons.ShouldSwap(true, flag, ((Character)__instance).InPlaceMode())) { return true; } AttackInputs val3 = new AttackInputs(attack, attackHold, secondaryAttack, secondaryAttackHold); AttackInputs val4 = ((AttackInputs)(ref val3)).Swapped(); attack = ((AttackInputs)(ref val4)).Attack; attackHold = ((AttackInputs)(ref val4)).AttackHold; secondaryAttack = ((AttackInputs)(ref val4)).Secondary; secondaryAttackHold = ((AttackInputs)(ref val4)).SecondaryHold; SwappedThisCall = true; } catch (Exception arg) { if (!_reported) { _reported = true; ManualLogSource? log = ReviewLog.Log; if (log != null) { log.LogError((object)string.Format("{0}: {1}", "SwapButtonsPatch", arg)); } } } return true; } } [HarmonyPatch(typeof(Humanoid), "UnequipItem", new Type[] { typeof(ItemData), typeof(bool) })] internal static class UnequipItemPatch { private static void Prefix(Humanoid __instance, ItemData item, out bool __state) { __state = false; try { if (ReviewLog.On && item != null && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { Attack currentAttack = __instance.m_currentAttack; __state = currentAttack != null && currentAttack.GetWeapon() == item; } } catch (Exception e) { AttackClearLog.Report("UnequipItemPatch.Prefix", e); } } private static void Postfix(Humanoid __instance, ItemData item, bool __state) { try { if (__state && __instance.m_currentAttack == null) { AttackClearLog.OnUnequip(item); } } catch (Exception e) { AttackClearLog.Report("UnequipItemPatch.Postfix", e); } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }