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 WiresCombatFlow v2.1.0
WiresCombatFlow.dll
Decompiled 4 days agousing System; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("WiresCombatFlow")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("WiresCombatFlow")] [assembly: AssemblyTitle("WiresCombatFlow")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace WiresCombatFlow { internal static class AutoVerticalAim { private const int MaxColliders = 48; private static readonly Collider[] Nearby = (Collider[])(object)new Collider[48]; private static readonly int CharacterMask = LayerMask.GetMask(new string[6] { "character", "character_net", "character_ghost", "hitbox", "character_noenv", "vehicle" }); private static readonly int WorldMask = LayerMask.GetMask(new string[6] { "Default", "Default_small", "static_solid", "piece", "terrain", "blocker" }); private static readonly FieldRef<Attack, Humanoid> Attacker = AccessTools.FieldRefAccess<Attack, Humanoid>("m_character"); internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> MaxCorrection; internal static ConfigEntry<float> HorizontalCone; [ThreadStatic] private static Attack _evaluatingHit; internal static void Bind(ConfigFile file) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown Enabled = file.Bind<bool>("Vertical Melee Aim", "Enabled", true, "Automatically correct only the invisible vertical hit direction toward an enemy in the existing melee attack arc."); MaxCorrection = file.Bind<float>("Vertical Melee Aim", "Maximum Correction Angle", 65f, new ConfigDescription("Maximum absolute up/down hit angle in degrees. No extra reach or wider horizontal attack arc.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 85f), Array.Empty<object>())); HorizontalCone = file.Bind<float>("Vertical Melee Aim", "Search Cone Half Angle", 25f, new ConfigDescription("Maximum sideways angle for identifying a target. It never exceeds the weapon's normal horizontal attack arc.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 45f), Array.Empty<object>())); } internal static void BeginHit(Attack attack) { _evaluatingHit = attack; } internal static void EndHit() { _evaluatingHit = null; } internal static void CorrectDirection(Attack attack, Transform originJoint, ref Vector3 direction) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Invalid comparison between Unknown and I4 //IL_00bb: 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_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Invalid comparison between Unknown and I4 //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_05ce: Unknown result type (might be due to invalid IL or missing references) //IL_05d0: Unknown result type (might be due to invalid IL or missing references) //IL_05d2: Unknown result type (might be due to invalid IL or missing references) //IL_05d7: Unknown result type (might be due to invalid IL or missing references) //IL_05d9: Unknown result type (might be due to invalid IL or missing references) //IL_05e5: Unknown result type (might be due to invalid IL or missing references) //IL_05ec: Unknown result type (might be due to invalid IL or missing references) //IL_05f3: Unknown result type (might be due to invalid IL or missing references) //IL_05f8: Unknown result type (might be due to invalid IL or missing references) //IL_0617: Unknown result type (might be due to invalid IL or missing references) //IL_0627: Unknown result type (might be due to invalid IL or missing references) //IL_0630: Unknown result type (might be due to invalid IL or missing references) //IL_0635: Unknown result type (might be due to invalid IL or missing references) //IL_05ab: Unknown result type (might be due to invalid IL or missing references) //IL_05ad: Unknown result type (might be due to invalid IL or missing references) //IL_05bc: Unknown result type (might be due to invalid IL or missing references) //IL_05be: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Unknown result type (might be due to invalid IL or missing references) //IL_0323: Unknown result type (might be due to invalid IL or missing references) //IL_032f: Unknown result type (might be due to invalid IL or missing references) //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_0393: Unknown result type (might be due to invalid IL or missing references) //IL_0395: Unknown result type (might be due to invalid IL or missing references) //IL_039a: Unknown result type (might be due to invalid IL or missing references) //IL_039c: Unknown result type (might be due to invalid IL or missing references) //IL_039e: Unknown result type (might be due to invalid IL or missing references) //IL_03a0: Unknown result type (might be due to invalid IL or missing references) //IL_03a5: Unknown result type (might be due to invalid IL or missing references) //IL_03b5: Unknown result type (might be due to invalid IL or missing references) //IL_03b7: Unknown result type (might be due to invalid IL or missing references) //IL_03b9: Unknown result type (might be due to invalid IL or missing references) //IL_03be: Unknown result type (might be due to invalid IL or missing references) //IL_03c9: Unknown result type (might be due to invalid IL or missing references) //IL_03ce: Unknown result type (might be due to invalid IL or missing references) //IL_03d3: Unknown result type (might be due to invalid IL or missing references) //IL_03e1: Unknown result type (might be due to invalid IL or missing references) //IL_03e3: Unknown result type (might be due to invalid IL or missing references) //IL_03e5: Unknown result type (might be due to invalid IL or missing references) //IL_03ea: Unknown result type (might be due to invalid IL or missing references) //IL_03f5: Unknown result type (might be due to invalid IL or missing references) //IL_03fa: Unknown result type (might be due to invalid IL or missing references) //IL_03ff: Unknown result type (might be due to invalid IL or missing references) //IL_0423: Unknown result type (might be due to invalid IL or missing references) //IL_0441: Unknown result type (might be due to invalid IL or missing references) //IL_0452: Unknown result type (might be due to invalid IL or missing references) //IL_0461: Unknown result type (might be due to invalid IL or missing references) //IL_0475: Unknown result type (might be due to invalid IL or missing references) //IL_0477: Unknown result type (might be due to invalid IL or missing references) //IL_047c: Unknown result type (might be due to invalid IL or missing references) //IL_047e: Unknown result type (might be due to invalid IL or missing references) //IL_0483: Unknown result type (might be due to invalid IL or missing references) //IL_04c4: Unknown result type (might be due to invalid IL or missing references) //IL_051f: Unknown result type (might be due to invalid IL or missing references) //IL_0521: Unknown result type (might be due to invalid IL or missing references) //IL_0524: Unknown result type (might be due to invalid IL or missing references) //IL_0529: Unknown result type (might be due to invalid IL or missing references) //IL_052e: Unknown result type (might be due to invalid IL or missing references) //IL_0535: Unknown result type (might be due to invalid IL or missing references) //IL_053d: Unknown result type (might be due to invalid IL or missing references) //IL_0542: Unknown result type (might be due to invalid IL or missing references) //IL_0547: Unknown result type (might be due to invalid IL or missing references) //IL_0549: Unknown result type (might be due to invalid IL or missing references) //IL_054d: Unknown result type (might be due to invalid IL or missing references) //IL_0557: Unknown result type (might be due to invalid IL or missing references) //IL_055c: Unknown result type (might be due to invalid IL or missing references) //IL_0562: Unknown result type (might be due to invalid IL or missing references) //IL_056e: Unknown result type (might be due to invalid IL or missing references) //IL_057d: Unknown result type (might be due to invalid IL or missing references) if (_evaluatingHit != attack || Enabled == null || !Enabled.Value || MaxCorrection.Value <= 0f || (Object)(object)originJoint == (Object)null || attack == null || ((int)attack.m_attackType != 0 && (int)attack.m_attackType != 1) || attack.m_isHomeItem || attack.m_harvest || attack.m_pickaxeSpecial) { return; } Humanoid obj = Attacker.Invoke(attack); Player val = (Player)(object)((obj is Player) ? obj : null); if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer || ((Character)val).IsDead() || ((Character)val).InPlaceMode()) { return; } ItemData weapon = attack.GetWeapon(); if (weapon == null || weapon.m_shared.m_tamedOnly) { return; } Transform transform = ((Component)val).transform; Vector3 forward = transform.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude < 0.001f) { return; } ((Vector3)(ref forward)).Normalize(); Vector3 val2 = originJoint.position + Vector3.up * attack.m_attackHeight + transform.right * attack.m_attackOffset; float num = Mathf.Max(0f, attack.m_attackRange); if (num <= 0.05f) { return; } int characterMask = CharacterMask; float num2 = Mathf.Max(attack.m_attackRayWidth, attack.m_attackRayWidth + attack.m_attackRayWidthCharExtra); num2 = Mathf.Max(0f, num2); float num3 = num + num2 + Mathf.Max(Mathf.Abs(attack.m_attackHeightChar1), Mathf.Abs(attack.m_attackHeightChar2)); float num4 = (((int)attack.m_attackType == 1) ? 0f : Mathf.Min(HorizontalCone.Value, Mathf.Max(0f, attack.m_attackAngle * 0.5f))); int num5 = Physics.OverlapSphereNonAlloc(val2, num3, Nearby, characterMask, (QueryTriggerInteraction)1); if (num5 <= 0 || num5 >= Nearby.Length) { return; } float num6 = float.MaxValue; Vector3 val3 = Vector3.zero; Vector3 val4 = Vector3.zero; Vector3 zero = Vector3.zero; bool flag = false; for (int i = 0; i < num5; i++) { Collider val5 = Nearby[i]; if ((Object)(object)val5 == (Object)null || !val5.enabled) { continue; } GameObject val6 = Projectile.FindHitObject(val5); Character val7 = (((Object)(object)val6 != (Object)null) ? val6.GetComponent<Character>() : null); if ((Object)(object)val7 == (Object)null) { val7 = ((Component)val5).GetComponentInParent<Character>(); } if ((Object)(object)val7 == (Object)null || (Object)(object)val7 == (Object)(object)val || val7.IsDead()) { continue; } BaseAI baseAI = val7.GetBaseAI(); if (!BaseAI.IsEnemy((Character)(object)val, val7) && ((Object)(object)baseAI == (Object)null || !baseAI.IsAggravatable())) { continue; } Bounds bounds = val5.bounds; Vector3 val8 = ((Bounds)(ref bounds)).center - val2; val8.y = 0f; float sqrMagnitude = ((Vector3)(ref val8)).sqrMagnitude; if (sqrMagnitude < 0.0004f) { continue; } float num7 = Vector3.Dot(val8, forward); if (num7 <= 0.01f) { continue; } float num8 = Mathf.Abs(Vector3.Dot(val8, transform.right)); float num9 = Mathf.Abs(transform.right.x) * ((Bounds)(ref bounds)).extents.x + Mathf.Abs(transform.right.z) * ((Bounds)(ref bounds)).extents.z + num2; float num10 = Mathf.Atan2(num8, num7) * 57.29578f; float num11 = Mathf.Atan2(num9, Mathf.Max(0.1f, num7)) * 57.29578f; if (num10 > num4 + num11) { continue; } float num12 = num7 / Mathf.Sqrt(sqrMagnitude); Vector3 val9 = val5.ClosestPoint(val2); Vector3 val10 = val9 - val2; float num13 = num + num2; float sqrMagnitude2 = ((Vector3)(ref val10)).sqrMagnitude; Vector3 val11 = val9 - val2 - Vector3.up * attack.m_attackHeightChar1; float num14 = Mathf.Min(sqrMagnitude2, ((Vector3)(ref val11)).sqrMagnitude); val11 = val9 - val2 - Vector3.up * attack.m_attackHeightChar2; if (Mathf.Min(num14, ((Vector3)(ref val11)).sqrMagnitude) > num13 * num13) { continue; } float num15 = Mathf.Min(0.1f, Mathf.Max(0f, ((Bounds)(ref bounds)).size.y * 0.2f)); float num16 = ((Bounds)(ref bounds)).min.y + num15; float num17 = ((Bounds)(ref bounds)).max.y - num15; float num18 = Mathf.Clamp(val2.y, num16, num17); Vector3 val12 = ((Bounds)(ref bounds)).ClosestPoint(val2) - val2; val12.y = 0f; float num19 = ((Vector3)(ref val12)).magnitude; if (num19 < 0.1f) { num19 = Mathf.Sqrt(sqrMagnitude); } num19 = Mathf.Min(Mathf.Sqrt(sqrMagnitude), num19 + 0.08f); if (!(Mathf.Abs(Mathf.Atan2(num18 - val2.y, Mathf.Max(0.1f, num19)) * 57.29578f) > MaxCorrection.Value)) { float num20 = ((Vector3)(ref val10)).sqrMagnitude * (1f + (1f - num12) * 5f); if (!(num20 >= num6)) { num6 = num20; val3 = val2 + forward * num19 + Vector3.up * (num18 - val2.y); val4 = Vector3.Lerp(val9, ((Bounds)(ref bounds)).center, 0.65f); ((Vector3)(ref zero))..ctor(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).max.y - num15, ((Bounds)(ref bounds)).center.z); flag = true; } } } if (flag && (attack.m_hitThroughWalls || !Physics.Linecast(val2, val4, WorldMask, (QueryTriggerInteraction)1) || !Physics.Linecast(val2, zero, WorldMask, (QueryTriggerInteraction)1))) { Vector3 val13 = val3 - val2; float y = val13.y; Vector2 val14 = new Vector2(val13.x, val13.z); float num21 = Mathf.Atan2(y, Mathf.Max(0.1f, ((Vector2)(ref val14)).magnitude)); float num22 = Mathf.Cos(num21); direction = new Vector3(forward.x * num22, Mathf.Sin(num21), forward.z * num22); } } } [HarmonyPatch(typeof(Attack), "DoMeleeAttack")] internal static class MeleeHitContextPatch { [HarmonyPrefix] private static void Prefix(Attack __instance) { AutoVerticalAim.BeginHit(__instance); } [HarmonyPostfix] private static void Postfix() { AutoVerticalAim.EndHit(); } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception) { AutoVerticalAim.EndHit(); return __exception; } } [HarmonyPatch(typeof(Attack), "GetMeleeAttackDir")] internal static class MeleeHitDirectionPatch { [HarmonyPostfix] private static void Postfix(Attack __instance, Transform originJoint, ref Vector3 attackDir) { AutoVerticalAim.CorrectDirection(__instance, originJoint, ref attackDir); } } internal static class FlowEnhancements { private sealed class InputState { internal float JumpBufferUntil = -100f; internal float SprintBufferUntil = -100f; internal bool WasRunning; internal Animator Animator; internal int[] ReturnStateHashes; } private static ConditionalWeakTable<Player, InputState> States = new ConditionalWeakTable<Player, InputState>(); internal static void Reset() { States = new ConditionalWeakTable<Player, InputState>(); } internal static void OnAttackStarted(Player player) { if (CombatFlow.IsLocal(player)) { CaptureNeutralAnimation(player); } } internal static void CaptureNeutralAnimation(Player player) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if (!CombatFlow.IsLocal(player) || !Plugin.Enabled.Value || ((Character)player).IsDead() || ((Character)player).IsStaggering() || ((Character)player).InDodge()) { return; } InputState orCreateValue = States.GetOrCreateValue(player); Animator val = orCreateValue.Animator; if ((Object)(object)val == (Object)null) { val = (orCreateValue.Animator = ((Component)player).GetComponentInChildren<Animator>(true)); } if ((Object)(object)val == (Object)null || ((Character)player).InAttack()) { return; } if (orCreateValue.ReturnStateHashes == null || orCreateValue.ReturnStateHashes.Length != val.layerCount) { orCreateValue.ReturnStateHashes = new int[val.layerCount]; } for (int i = 0; i < val.layerCount; i++) { if (!val.IsInTransition(i)) { AnimatorStateInfo currentAnimatorStateInfo = val.GetCurrentAnimatorStateInfo(i); if (((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash != 0 && !((AnimatorStateInfo)(ref currentAnimatorStateInfo)).IsTag("attack") && !((AnimatorStateInfo)(ref currentAnimatorStateInfo)).IsTag("dodge") && !((AnimatorStateInfo)(ref currentAnimatorStateInfo)).IsTag("stagger")) { orCreateValue.ReturnStateHashes[i] = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash; } } } } internal static void OnCancelled(Player player, DefensiveCancel kind) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) if (!CombatFlow.IsLocal(player)) { return; } InputState orCreateValue = States.GetOrCreateValue(player); Animator val = orCreateValue.Animator; if ((Object)(object)val == (Object)null) { val = (orCreateValue.Animator = ((Component)player).GetComponentInChildren<Animator>(true)); } int[] returnStateHashes = orCreateValue.ReturnStateHashes; if ((Object)(object)val == (Object)null || returnStateHashes == null) { return; } float value = Plugin.CancelBlendTime.Value; int num = Mathf.Min(val.layerCount, returnStateHashes.Length); for (int i = 0; i < num; i++) { int num2 = returnStateHashes[i]; if (num2 != 0 && val.HasState(i, num2)) { AnimatorStateInfo currentAnimatorStateInfo = val.GetCurrentAnimatorStateInfo(i); AnimatorStateInfo val2 = (val.IsInTransition(i) ? val.GetNextAnimatorStateInfo(i) : currentAnimatorStateInfo); if (((AnimatorStateInfo)(ref currentAnimatorStateInfo)).IsTag("attack") || ((AnimatorStateInfo)(ref val2)).IsTag("attack")) { val.CrossFadeInFixedTime(num2, value, i); } } } orCreateValue.JumpBufferUntil = -100f; orCreateValue.SprintBufferUntil = -100f; } internal static void BeforeControls(Player player, Vector3 movedir, bool blockHold, ref bool jump, bool run, bool dodge) { if (!CombatFlow.IsLocal(player) || !Plugin.Enabled.Value) { return; } InputState orCreateValue = States.GetOrCreateValue(player); if (InventoryGui.IsVisible() || Menu.IsVisible() || Console.IsVisible() || Hud.InRadial()) { orCreateValue.JumpBufferUntil = -100f; orCreateValue.SprintBufferUntil = -100f; orCreateValue.WasRunning = false; return; } float time = Time.time; bool flag = ((Vector3)(ref movedir)).sqrMagnitude > 0.04f; bool num = run && !orCreateValue.WasRunning && flag; orCreateValue.WasRunning = run; bool flag2 = CombatFlow.IsWindup(player); bool flag3 = CombatFlow.IsCommittedRecovery(player); float value = Plugin.InputBufferDuration.Value; float num2 = (flag3 ? CombatFlow.RecoveryElapsed(player) : (-1f)); bool flag4 = flag3 && num2 < Plugin.RecoveryCancelDelay.Value; if (jump && (flag2 || flag4) && !dodge && !blockHold && !((Character)player).IsCrouching()) { float num3 = (flag4 ? (Plugin.RecoveryCancelDelay.Value - num2 + 0.03f) : 0f); orCreateValue.JumpBufferUntil = time + Mathf.Max(value, num3); } else if (jump) { orCreateValue.JumpBufferUntil = -100f; } if (!jump && orCreateValue.JumpBufferUntil >= time && flag3 && ((Character)player).IsOnGround() && CombatFlow.RecoveryElapsed(player) >= Plugin.RecoveryCancelDelay.Value) { jump = true; orCreateValue.JumpBufferUntil = -100f; } else if (orCreateValue.JumpBufferUntil < time) { orCreateValue.JumpBufferUntil = -100f; } if (num && (flag2 || flag3)) { float num4 = (flag4 ? (Plugin.RecoveryCancelDelay.Value - num2 + 0.03f) : 0f); orCreateValue.SprintBufferUntil = time + Mathf.Max(value, num4); } if (flag && run && flag3 && !dodge && !blockHold && orCreateValue.SprintBufferUntil >= time && CombatFlow.RecoveryElapsed(player) >= Plugin.RecoveryCancelDelay.Value) { CombatFlow.BeforeSprint(player); orCreateValue.SprintBufferUntil = -100f; } else if (!run || !flag || orCreateValue.SprintBufferUntil < time) { orCreateValue.SprintBufferUntil = -100f; } } internal static void SmoothMovement(Player player, ref float movementFactor, bool turning) { if (!CombatFlow.IsLocal(player) || !Plugin.Enabled.Value) { return; } float num = Mathf.Max(0.45f, Plugin.MovementRecoveryTime.Value); float num2 = CombatFlow.SprintReleaseElapsed(player); if (!turning && num2 >= 0f && ((Character)player).IsRunning()) { float num3 = Mathf.Clamp01(num2 / num); num3 = num3 * num3 * (3f - 2f * num3); float num4 = Mathf.Lerp(0.24f, 1f, num3); movementFactor = Mathf.Min(movementFactor, num4); return; } float num5 = CombatFlow.RecoveryElapsed(player); float value = Plugin.RecoveryCancelDelay.Value; if (!(num5 <= value) && !(movementFactor >= 1f)) { float num6 = Mathf.Clamp01((num5 - value) / num); num6 = num6 * num6 * (3f - 2f * num6); if (turning) { movementFactor = Mathf.Min(1f, movementFactor + (1f - movementFactor) * 0.35f * num6); } else { movementFactor = Mathf.Min(1f, movementFactor * (1f + 0.35f * num6)); } } } } [HarmonyPatch(typeof(Player), "SetControls")] internal static class InputBufferPatch { [HarmonyPrefix] private static void Prefix(Player __instance, Vector3 movedir, bool blockHold, ref bool jump, bool run, bool dodge) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) FlowEnhancements.BeforeControls(__instance, movedir, blockHold, ref jump, run, dodge); } } [HarmonyPatch(typeof(PlayerController), "FixedUpdate")] internal static class NeutralAnimationCapturePatch { [HarmonyPostfix] private static void Postfix() { if ((Object)(object)Player.m_localPlayer != (Object)null) { FlowEnhancements.CaptureNeutralAnimation(Player.m_localPlayer); } } } [HarmonyPatch(typeof(Humanoid), "GetAttackSpeedFactorMovement")] internal static class MovementRecoveryPatch { [HarmonyPostfix] private static void Postfix(Humanoid __instance, ref float __result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val != (Object)null) { FlowEnhancements.SmoothMovement(val, ref __result, turning: false); } } } [HarmonyPatch(typeof(Humanoid), "GetAttackSpeedFactorRotation")] internal static class RotationRecoveryPatch { [HarmonyPostfix] private static void Postfix(Humanoid __instance, ref float __result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val != (Object)null) { FlowEnhancements.SmoothMovement(val, ref __result, turning: true); } } } [HarmonyPatch(typeof(Humanoid), "OnWeaponTrailStart")] internal static class CancelledTrailGuardPatch { [HarmonyPrefix] private static bool Prefix(Humanoid __instance) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null)) { return !CombatFlow.BlocksAttackTrigger(val); } return true; } } [HarmonyPatch(typeof(Attack), "OnAttackTrigger")] internal static class CancelledHitGuardPatch { [HarmonyPrefix] private static bool Prefix(Attack __instance) { return !CombatFlow.BlocksCancelledAttack(__instance); } } [HarmonyPatch(typeof(Attack), "Start")] internal static class ComboGracePatch { [HarmonyPrefix] private static void Prefix(Attack __instance, Humanoid character, Attack previousAttack, ref float timeSinceLastAttack) { Player val = (Player)(object)((character is Player) ? character : null); if (val != null && CombatFlow.IsLocal(val) && Plugin.Enabled.Value && __instance.m_attackChainLevels > 1 && previousAttack != null && !(previousAttack.m_attackAnimation != __instance.m_attackAnimation)) { float value = Plugin.ComboGrace.Value; if (timeSinceLastAttack > 0.2f && timeSinceLastAttack <= 0.2f + value) { timeSinceLastAttack = 0.199f; } } } } [BepInPlugin("wire.valheim.combatflow", "Wire's Combat Flow", "2.1.0")] [BepInProcess("valheim.exe")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "wire.valheim.combatflow"; public const string Name = "Wire's Combat Flow"; public const string Version = "2.1.0"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> RecoveryCancelDelay; internal static ConfigEntry<float> BlockCancelCost; internal static ConfigEntry<float> DodgeCancelCost; internal static ConfigEntry<float> JumpCancelCost; internal static ConfigEntry<float> SecondaryExtraCost; internal static ConfigEntry<float> SprintCancelCost; internal static ConfigEntry<float> InputBufferDuration; internal static ConfigEntry<float> ComboGrace; internal static ConfigEntry<float> MovementRecoveryTime; internal static ConfigEntry<float> CancelBlendTime; private Harmony _harmony; private void Awake() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Expected O, but got Unknown //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Expected O, but got Unknown //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Expected O, but got Unknown //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Expected O, but got Unknown //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Expected O, but got Unknown //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Expected O, but got Unknown //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Expected O, but got Unknown //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable responsive defensive recovery cancels."); RecoveryCancelDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Recovery Cancelling", "Delay After Hit", 0.16f, new ConfigDescription("Follow-through time after the attack's actual hit/projectile trigger before recovery may be cancelled.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.4f), Array.Empty<object>())); BlockCancelCost = ((BaseUnityPlugin)this).Config.Bind<float>("Recovery Cancelling", "Block Cancel Extra Stamina", 4f, new ConfigDescription("Extra stamina charged when cancelling attack recovery into block.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 40f), Array.Empty<object>())); DodgeCancelCost = ((BaseUnityPlugin)this).Config.Bind<float>("Recovery Cancelling", "Dodge Cancel Extra Stamina", 8f, new ConfigDescription("Extra stamina charged when cancelling attack recovery into dodge. The normal dodge stamina cost is still paid too.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 40f), Array.Empty<object>())); JumpCancelCost = ((BaseUnityPlugin)this).Config.Bind<float>("Recovery Cancelling", "Jump Cancel Extra Stamina", 6f, new ConfigDescription("Extra stamina charged for jumping out of committed attack recovery, in addition to the normal jump stamina.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 40f), Array.Empty<object>())); SecondaryExtraCost = ((BaseUnityPlugin)this).Config.Bind<float>("Recovery Cancelling", "Secondary Attack Extra Stamina", 4f, new ConfigDescription("Additional cancel surcharge after a secondary/special attack.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 40f), Array.Empty<object>())); SprintCancelCost = ((BaseUnityPlugin)this).Config.Bind<float>("Recovery Cancelling", "Sprint Cancel Extra Stamina", 6f, new ConfigDescription("Extra stamina for sprinting out of recovery.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 40f), Array.Empty<object>())); InputBufferDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Flow", "Input Buffer Seconds", 0.15f, new ConfigDescription("Brief grace period for early jump/sprint inputs.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 0.3f), Array.Empty<object>())); ComboGrace = ((BaseUnityPlugin)this).Config.Bind<float>("Flow", "Extra Combo Grace Seconds", 0.12f, new ConfigDescription("Extra time for continuing the same melee combo after the previous attack ends.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.3f), Array.Empty<object>())); MovementRecoveryTime = ((BaseUnityPlugin)this).Config.Bind<float>("Flow", "Movement Recovery Seconds", 0.5f, new ConfigDescription("Duration of gradual post-hit movement and turning easing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 1f), Array.Empty<object>())); CancelBlendTime = ((BaseUnityPlugin)this).Config.Bind<float>("Flow", "Cancel Animation Blend Seconds", 0.2f, new ConfigDescription("Smooth transition duration when leaving a cancelled attack.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.08f, 0.35f), Array.Empty<object>())); AutoVerticalAim.Bind(((BaseUnityPlugin)this).Config); _harmony = new Harmony("wire.valheim.combatflow"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Wire's Combat Flow 2.1.0 loaded."); } private void OnDestroy() { CombatFlow.Reset(); if (_harmony != null) { _harmony.UnpatchSelf(); } Log = null; } } internal enum DefensiveCancel { None, Block, Dodge, Jump, Sprint } internal static class CombatFlow { private sealed class State { internal Attack Attack; internal bool Secondary; internal bool Committed; internal float CommitTime; internal bool SuppressAttack; internal Attack CancelledAttack; internal float SuppressUntil; internal float SprintCancelledAt = -100f; internal DefensiveCancel Cancel; } private static ConditionalWeakTable<Player, State> States = new ConditionalWeakTable<Player, State>(); private static readonly FieldRef<Humanoid, Attack> CurrentAttack = AccessTools.FieldRefAccess<Humanoid, Attack>("m_currentAttack"); private static readonly FieldRef<Humanoid, bool> CurrentAttackIsSecondary = AccessTools.FieldRefAccess<Humanoid, bool>("m_currentAttackIsSecondary"); private static readonly FieldRef<Player, float> QueuedDodgeTimer = AccessTools.FieldRefAccess<Player, float>("m_queuedDodgeTimer"); private static readonly FieldRef<Player, float> QueuedAttackTimer = AccessTools.FieldRefAccess<Player, float>("m_queuedAttackTimer"); private static readonly FieldRef<Player, float> QueuedSecondAttackTimer = AccessTools.FieldRefAccess<Player, float>("m_queuedSecondAttackTimer"); private static readonly FieldRef<Character, bool> Blocking = AccessTools.FieldRefAccess<Character, bool>("m_blocking"); private static readonly MethodInfo GetDodgeStaminaUseMethod = AccessTools.Method(typeof(Player), "GetDodgeStaminaUse", (Type[])null, (Type[])null); internal static bool IsLocal(Player player) { if ((Object)(object)player != (Object)null) { return (Object)(object)player == (Object)(object)Player.m_localPlayer; } return false; } internal static void AttackStarted(Player player) { if (IsLocal(player)) { State orCreateValue = States.GetOrCreateValue(player); orCreateValue.Attack = CurrentAttack.Invoke((Humanoid)(object)player); orCreateValue.Secondary = CurrentAttackIsSecondary.Invoke((Humanoid)(object)player); orCreateValue.Committed = false; orCreateValue.CommitTime = 0f; orCreateValue.SuppressAttack = false; orCreateValue.CancelledAttack = null; orCreateValue.SuppressUntil = 0f; orCreateValue.SprintCancelledAt = -100f; orCreateValue.Cancel = DefensiveCancel.None; FlowEnhancements.OnAttackStarted(player); } } internal static void AttackTriggered(Player player) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 if (!IsLocal(player)) { return; } Attack val = CurrentAttack.Invoke((Humanoid)(object)player); if (val != null && (int)val.m_attackType != 3) { State orCreateValue = States.GetOrCreateValue(player); if (orCreateValue.Attack != val) { orCreateValue.Attack = val; orCreateValue.Secondary = CurrentAttackIsSecondary.Invoke((Humanoid)(object)player); orCreateValue.Committed = false; orCreateValue.SuppressAttack = false; orCreateValue.CancelledAttack = null; orCreateValue.Cancel = DefensiveCancel.None; } if (!orCreateValue.Committed) { orCreateValue.Committed = true; orCreateValue.CommitTime = Time.time; } } } internal static void BeforeDodge(Player player) { if (IsLocal(player) && Plugin.Enabled != null && Plugin.Enabled.Value && !(QueuedDodgeTimer.Invoke(player) <= 0f)) { TryCancel(player, DefensiveCancel.Dodge); } } internal static void BeforeJump(Player player, bool force) { if (!(!IsLocal(player) || force) && Plugin.Enabled != null && Plugin.Enabled.Value && !(QueuedDodgeTimer.Invoke(player) > 0f) && ((Character)player).IsOnGround() && !((Character)player).IsEncumbered() && !((Character)player).IsDead() && !((Character)player).InDodge() && !((Character)player).IsKnockedBack() && !((Character)player).IsStaggering() && !((Character)player).IsAttached()) { TryCancel(player, DefensiveCancel.Jump); } } internal static void BeforeBlock(Player player) { if (IsLocal(player) && Plugin.Enabled != null && Plugin.Enabled.Value && !(QueuedDodgeTimer.Invoke(player) > 0f) && Blocking.Invoke((Character)(object)player)) { TryCancel(player, DefensiveCancel.Block); } } internal static bool IsWindup(Player player) { if (!IsLocal(player) || !Plugin.Enabled.Value) { return false; } if (States.TryGetValue(player, out var value) && value.Attack != null && !value.Attack.IsDone() && !value.Committed && !value.SuppressAttack) { return CurrentAttack.Invoke((Humanoid)(object)player) == value.Attack; } return false; } internal static bool IsCommittedRecovery(Player player) { if (!IsLocal(player) || !Plugin.Enabled.Value) { return false; } if (States.TryGetValue(player, out var value) && value.Committed && value.Attack != null && !value.Attack.IsDone() && !value.SuppressAttack) { return CurrentAttack.Invoke((Humanoid)(object)player) == value.Attack; } return false; } internal static float RecoveryElapsed(Player player) { if (!IsCommittedRecovery(player) || !States.TryGetValue(player, out var value)) { return -1f; } return Mathf.Max(0f, Time.time - value.CommitTime); } internal static float SprintReleaseElapsed(Player player) { if (!IsLocal(player)) { return -1f; } if (!States.TryGetValue(player, out var value)) { return -1f; } float num = Time.time - value.SprintCancelledAt; if (!(num >= 0f) || !(num <= Mathf.Max(0.45f, Plugin.MovementRecoveryTime.Value))) { return -1f; } return num; } internal static bool BlocksAttackTrigger(Player player) { if (!IsLocal(player)) { return false; } if (States.TryGetValue(player, out var value) && value.CancelledAttack != null) { return CurrentAttack.Invoke((Humanoid)(object)player) == value.CancelledAttack; } return false; } internal static bool BlocksCancelledAttack(Attack attack) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || attack == null) { return false; } if (States.TryGetValue(localPlayer, out var value)) { return value.CancelledAttack == attack; } return false; } internal static void BeforeSprint(Player player) { if (IsCommittedRecovery(player) && QueuedDodgeTimer.Invoke(player) <= 0f && !Blocking.Invoke((Character)(object)player)) { TryCancel(player, DefensiveCancel.Sprint); } } private static void TryCancel(Player player, DefensiveCancel cancel) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Invalid comparison between Unknown and I4 State orCreateValue = States.GetOrCreateValue(player); Attack val = CurrentAttack.Invoke((Humanoid)(object)player); if (orCreateValue.SuppressAttack || val == null || val != orCreateValue.Attack || val.IsDone() || (int)val.m_attackType == 3 || val.m_attackKillsSelf || ((Character)player).InPlaceMode() || ((Character)player).IsDead() || ((Character)player).InDodge() || ((Character)player).IsStaggering() || ((Character)player).IsKnockedBack() || !orCreateValue.Committed || Time.time - orCreateValue.CommitTime < Plugin.RecoveryCancelDelay.Value) { return; } float num = cancel switch { DefensiveCancel.Dodge => Plugin.DodgeCancelCost.Value, DefensiveCancel.Jump => Plugin.JumpCancelCost.Value, DefensiveCancel.Sprint => Plugin.SprintCancelCost.Value, _ => Plugin.BlockCancelCost.Value, }; if (orCreateValue.Secondary) { num += Plugin.SecondaryExtraCost.Value; } switch (cancel) { case DefensiveCancel.Dodge: { float normalDodgeCost = GetNormalDodgeCost(player); if (!((Character)player).HaveStamina(normalDodgeCost + num + 0.01f)) { return; } break; } case DefensiveCancel.Jump: if (!((Character)player).HaveStamina(((Character)player).m_jumpStaminaUsage + num + 0.01f)) { return; } break; default: if (!((Character)player).HaveStamina(num + 0.01f)) { return; } break; } if (num > 0f) { ((Character)player).UseStamina(num); } val.Stop(); QueuedAttackTimer.Invoke(player) = 0f; QueuedSecondAttackTimer.Invoke(player) = 0f; orCreateValue.SuppressAttack = true; orCreateValue.CancelledAttack = val; orCreateValue.SuppressUntil = Time.time + Plugin.CancelBlendTime.Value + 0.03f; orCreateValue.SprintCancelledAt = ((cancel == DefensiveCancel.Sprint) ? Time.time : (-100f)); orCreateValue.Cancel = cancel; FlowEnhancements.OnCancelled(player, cancel); } private static float GetNormalDodgeCost(Player player) { if (GetDodgeStaminaUseMethod == null) { return player.m_dodgeStaminaUsage; } object obj = GetDodgeStaminaUseMethod.Invoke(player, null); if (!(obj is float)) { return player.m_dodgeStaminaUsage; } return (float)obj; } internal static void OverrideInAttack(Player player, ref bool result) { if (IsLocal(player) && States.TryGetValue(player, out var value) && value.SuppressAttack) { if (!result && Time.time >= value.SuppressUntil) { value.SuppressAttack = false; value.Cancel = DefensiveCancel.None; } else { result = false; } } } internal static bool ShouldRejectNewAttack(Player player) { if (!IsLocal(player)) { return false; } if (States.TryGetValue(player, out var value)) { return value.SuppressAttack; } return false; } internal static void Reset() { States = new ConditionalWeakTable<Player, State>(); FlowEnhancements.Reset(); } } [HarmonyPatch(typeof(Humanoid), "StartAttack")] internal static class StartAttackPatch { [HarmonyPrefix] private static bool Prefix(Humanoid __instance, ref bool __result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val != (Object)null && CombatFlow.ShouldRejectNewAttack(val)) { __result = false; return false; } return true; } [HarmonyPostfix] private static void Postfix(Humanoid __instance, bool __result) { if (__result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val != (Object)null) { CombatFlow.AttackStarted(val); } } } } [HarmonyPatch(typeof(Humanoid), "OnAttackTrigger")] internal static class AttackTriggerPatch { [HarmonyPrefix] private static bool Prefix(Humanoid __instance) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null)) { return !CombatFlow.BlocksAttackTrigger(val); } return true; } [HarmonyPostfix] private static void Postfix(Humanoid __instance) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val != (Object)null && !CombatFlow.BlocksAttackTrigger(val)) { CombatFlow.AttackTriggered(val); } } } [HarmonyPatch(typeof(Player), "UpdateDodge")] internal static class UpdateDodgePatch { [HarmonyPrefix] private static void Prefix(Player __instance) { CombatFlow.BeforeDodge(__instance); } } [HarmonyPatch(typeof(Humanoid), "UpdateBlock")] internal static class UpdateBlockPatch { [HarmonyPrefix] private static void Prefix(Humanoid __instance) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val != (Object)null) { CombatFlow.BeforeBlock(val); } } } [HarmonyPatch(typeof(Character), "Jump")] internal static class JumpCancelPatch { [HarmonyPrefix] private static void Prefix(Character __instance, bool force) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val != (Object)null) { CombatFlow.BeforeJump(val, force); } } } [HarmonyPatch(typeof(Player), "InAttack")] internal static class PlayerInAttackPatch { [HarmonyPostfix] private static void Postfix(Player __instance, ref bool __result) { CombatFlow.OverrideInAttack(__instance, ref __result); } } }