Decompiled source of ImmersiveCombat v0.5.3

plugins/ImmersiveCombat/ImmersiveCombat.dll

Decompiled a month ago
using System;
using System.Diagnostics;
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 HarmonyLib;
using ImmersiveCombat.Common;
using ImmersiveCombat.Config;
using ImmersiveCombat.Features.AttackCancel;
using ImmersiveCombat.Features.HoldAttack;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: AssemblyCompany("ImmersiveCombat")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.5.2.0")]
[assembly: AssemblyInformationalVersion("0.5.2+4cba1c5655706eff9e6ad677cccff9b72fe6cd3c")]
[assembly: AssemblyProduct("ImmersiveCombat")]
[assembly: AssemblyTitle("ImmersiveCombat")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.5.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 ImmersiveCombat
{
	[BepInPlugin("com.lubertmiliutin.immersivecombat", "ImmersiveCombat", "0.5.3")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.lubertmiliutin.immersivecombat";

		public const string PluginName = "ImmersiveCombat";

		public const string PluginVersion = "0.5.3";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("com.lubertmiliutin.immersivecombat");
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			int num = 0;
			foreach (MethodBase allPatchedMethod in Harmony.GetAllPatchedMethods())
			{
				Patches patchInfo = Harmony.GetPatchInfo(allPatchedMethod);
				if (patchInfo != null && (patchInfo.Prefixes.Any((Patch p) => p.owner == "com.lubertmiliutin.immersivecombat") || patchInfo.Postfixes.Any((Patch p) => p.owner == "com.lubertmiliutin.immersivecombat") || patchInfo.Transpilers.Any((Patch p) => p.owner == "com.lubertmiliutin.immersivecombat")))
				{
					num++;
					Log.LogInfo((object)("  [patch] " + allPatchedMethod.DeclaringType?.FullName + "::" + allPatchedMethod.Name));
				}
			}
			Log.LogInfo((object)string.Format("{0} v{1} loaded with {2} patched method(s).", "ImmersiveCombat", "0.5.3", num));
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
}
namespace ImmersiveCombat.Features.QuickDodge
{
	[HarmonyPatch(typeof(Player), "Update")]
	internal static class QuickDodgePatch
	{
		[HarmonyPrefix]
		private static void Prefix(Player __instance)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: 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_0102: Unknown result type (might be due to invalid IL or missing references)
			if (ModConfig.QuickDodgeEnabled.Value && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !IsTextInputActive() && Input.GetKeyDown(ModConfig.QuickDodgeKey.Value) && !((Character)__instance).IsDead() && !((Character)__instance).IsStaggering() && !((Character)__instance).InCutscene() && !((Character)__instance).InDodge() && __instance.GetDoodadController() == null && !HoldAttackState.IsActive)
			{
				if (((Character)__instance).InAttack() && ((Humanoid)__instance).m_currentAttack != null)
				{
					AttackCancelLogic.CancelAttack(__instance, ((Humanoid)__instance).m_currentAttack, refundStamina: false);
				}
				Vector3 val = ((Character)__instance).m_moveDir;
				if (((Vector3)(ref val)).sqrMagnitude < 0.01f)
				{
					val = -((Component)__instance).transform.forward;
				}
				else
				{
					((Vector3)(ref val)).Normalize();
				}
				QuickDodgeState.HalfStaminaActive = true;
				QuickDodgeState.IframeStartPending = true;
				__instance.Dodge(val * ModConfig.QuickDodgeDistanceScale.Value);
				if (ModConfig.DebugLog.Value)
				{
					Plugin.Log.LogInfo((object)($"[QuickDodge] queued. dir=({val.x:F2},{val.z:F2}) " + $"scale={ModConfig.QuickDodgeDistanceScale.Value:F2} " + $"staminaFrac={ModConfig.QuickDodgeStaminaFraction.Value:F2}"));
				}
			}
		}

		[HarmonyPostfix]
		private static void Postfix(Player __instance)
		{
			if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			if (QuickDodgeState.HalfStaminaActive && (((Character)__instance).InDodge() || __instance.m_queuedDodgeTimer <= 0f))
			{
				QuickDodgeState.HalfStaminaActive = false;
			}
			if (QuickDodgeState.IframeStartPending && __instance.m_dodgeInvincible)
			{
				QuickDodgeState.IframeStartPending = false;
				QuickDodgeState.IframeEndTime = Time.time + ModConfig.QuickDodgeIframeSeconds.Value;
				QuickDodgeState.ScaleRootMotion = true;
				if (ModConfig.DebugLog.Value)
				{
					Plugin.Log.LogInfo((object)($"[QuickDodge] iframes armed for {ModConfig.QuickDodgeIframeSeconds.Value:F2}s, " + $"root-motion scaling = {ModConfig.QuickDodgeDistanceScale.Value:F2}"));
				}
			}
			if (QuickDodgeState.ScaleRootMotion && ((Character)__instance).InDodge() && (Object)(object)((Character)__instance).m_animator != (Object)null)
			{
				float num = Mathf.Max(0.05f, ModConfig.QuickDodgeDistanceScale.Value);
				((Character)__instance).m_animator.speed = 1f / num;
			}
			if (QuickDodgeState.ScaleRootMotion && !((Character)__instance).InDodge())
			{
				QuickDodgeState.ScaleRootMotion = false;
				if ((Object)(object)((Character)__instance).m_animator != (Object)null)
				{
					((Character)__instance).m_animator.speed = 1f;
				}
				if (ModConfig.DebugLog.Value)
				{
					Plugin.Log.LogInfo((object)"[QuickDodge] root-motion scaling disengaged (dodge anim ended)");
				}
			}
			if (QuickDodgeState.IframeEndTime > 0f && Time.time >= QuickDodgeState.IframeEndTime)
			{
				if (__instance.m_dodgeInvincible)
				{
					__instance.OnDodgeMortal();
				}
				QuickDodgeState.IframeEndTime = 0f;
			}
		}

		private static bool IsTextInputActive()
		{
			if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus())
			{
				return true;
			}
			if (Console.IsVisible())
			{
				return true;
			}
			if (TextInput.IsVisible())
			{
				return true;
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Character), "AddRootMotion")]
	internal static class QuickDodgeRootMotionScalePatch
	{
		[HarmonyPrefix]
		private static void Prefix(Character __instance, ref Vector3 vel)
		{
			//IL_0021: 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)
			if (QuickDodgeState.ScaleRootMotion && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
			{
				vel *= ModConfig.QuickDodgeDistanceScale.Value;
			}
		}
	}
	[HarmonyPatch(typeof(Player), "GetDodgeStaminaUse")]
	internal static class QuickDodgeStaminaShimPatch
	{
		[HarmonyPostfix]
		private static void Postfix(ref float __result)
		{
			if (QuickDodgeState.HalfStaminaActive)
			{
				__result *= ModConfig.QuickDodgeStaminaFraction.Value;
			}
		}
	}
	internal static class QuickDodgeState
	{
		public static bool HalfStaminaActive;

		public static bool IframeStartPending;

		public static float IframeEndTime;

		public static bool ScaleRootMotion;

		public static void Reset()
		{
			HalfStaminaActive = false;
			IframeStartPending = false;
			IframeEndTime = 0f;
			ScaleRootMotion = false;
		}
	}
}
namespace ImmersiveCombat.Features.HoldAttack
{
	internal static class HoldAttackHelpers
	{
		public static bool IsProjectile(Attack a)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Invalid comparison between Unknown and I4
			if ((int)a.m_attackType != 2 && (int)a.m_attackType != 5)
			{
				return (Object)(object)a.m_attackProjectile != (Object)null;
			}
			return true;
		}

		public static bool IsLocalPlayer(Humanoid humanoid)
		{
			if ((Object)(object)humanoid != (Object)null)
			{
				return (Object)(object)humanoid == (Object)(object)Player.m_localPlayer;
			}
			return false;
		}

		public static CharacterAnimEvent GetAnimEvent(Humanoid humanoid)
		{
			if (!((Object)(object)humanoid == (Object)null))
			{
				return ((Component)humanoid).GetComponentInChildren<CharacterAnimEvent>();
			}
			return null;
		}
	}
	[HarmonyPatch(typeof(Player), "SetControls")]
	internal static class HoldAttackInputLockPatch
	{
		[HarmonyPrefix]
		private static void Prefix(Player __instance, ref Vector3 movedir, ref bool jump, ref bool dodge, ref bool run, ref bool autoRun)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			if (ModConfig.HoldEnabled.Value && ModConfig.HoldLockMovement.Value && HoldAttackState.IsActive && HoldAttackHelpers.IsLocalPlayer((Humanoid)(object)__instance))
			{
				movedir = Vector3.zero;
				jump = false;
				dodge = false;
				run = false;
				autoRun = false;
			}
		}
	}
	[HarmonyPatch(typeof(Player), "Update")]
	internal static class HoldAttackReleasePatch
	{
		[HarmonyPostfix]
		private static void Postfix(Player __instance)
		{
			HoldAttackState.TriggerReentryGuard = false;
			if (!HoldAttackState.IsActive || !HoldAttackHelpers.IsLocalPlayer((Humanoid)(object)__instance))
			{
				return;
			}
			if (((Humanoid)__instance).m_currentAttack != HoldAttackState.PendingAttack || ((Character)__instance).IsDead() || ((Character)__instance).IsStaggering() || ((Character)__instance).InCutscene())
			{
				if (ModConfig.DebugLog.Value)
				{
					Plugin.Log.LogInfo((object)"[Hold] safety bailout — clearing deferred attack");
				}
				HoldAttackState.Bailout();
				return;
			}
			bool flag = ValheimButtons.AttackHeld(((Humanoid)__instance).m_currentAttackIsSecondary);
			bool flag2 = Time.time - HoldAttackState.HoldStartTime >= ModConfig.HoldMaxSeconds.Value;
			if (ModConfig.HoldDrainStamina.Value && flag && !flag2)
			{
				float num = ModConfig.HoldDrainStaminaPerSecond.Value * Time.deltaTime;
				if (num > 0f)
				{
					((Character)__instance).UseStamina(num);
					if (__instance.GetStamina() <= 0f)
					{
						flag2 = true;
					}
				}
			}
			if (!flag || flag2)
			{
				Release(flag2);
			}
		}

		private static void Release(bool forced)
		{
			Attack pendingAttack = HoldAttackState.PendingAttack;
			bool frozenAtTrigger = HoldAttackState.FrozenAtTrigger;
			if (pendingAttack != null)
			{
				HoldAttackState.MarkReleased();
				if (ModConfig.DebugLog.Value)
				{
					float num = Time.time - HoldAttackState.HoldStartTime;
					string arg = (frozenAtTrigger ? "trigger-fallback" : "windup");
					Plugin.Log.LogInfo((object)$"[Hold] released after {num:F2}s (forced={forced}, path={arg})");
				}
				if (frozenAtTrigger)
				{
					pendingAttack.OnAttackTrigger();
				}
			}
		}
	}
	[HarmonyPatch(typeof(Humanoid), "StartAttack")]
	internal static class HoldAttackStartGuardPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(Humanoid __instance, ref bool __result)
		{
			if (!ModConfig.HoldEnabled.Value)
			{
				return true;
			}
			if (!HoldAttackState.IsActive)
			{
				return true;
			}
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return true;
			}
			__result = false;
			if (ModConfig.DebugLog.Value)
			{
				Plugin.Log.LogInfo((object)"[Hold/StartGuard] blocked StartAttack while holding deferred swing");
			}
			return false;
		}
	}
	internal static class HoldAttackState
	{
		public static Attack PendingAttack;

		public static Attack DecidedAttack;

		public static CharacterAnimEvent FrozenAnimEvent;

		public static float HoldStartTime;

		public static bool FrozenAtTrigger;

		private const float FreezeDurationSeconds = 600f;

		public static bool TriggerReentryGuard;

		public static bool IsActive => PendingAttack != null;

		public static void Freeze(Attack attack, CharacterAnimEvent animEvent, bool atTrigger)
		{
			PendingAttack = attack;
			DecidedAttack = attack;
			HoldStartTime = Time.time;
			FrozenAtTrigger = atTrigger;
			FrozenAnimEvent = animEvent;
			if ((Object)(object)animEvent != (Object)null)
			{
				animEvent.FreezeFrame(600f);
			}
		}

		public static void MarkLetThrough(Attack attack)
		{
			DecidedAttack = attack;
		}

		public static void MarkReleased()
		{
			Thaw();
			DecidedAttack = PendingAttack;
			PendingAttack = null;
			FrozenAnimEvent = null;
		}

		public static void Bailout()
		{
			Thaw();
			PendingAttack = null;
			DecidedAttack = null;
			FrozenAtTrigger = false;
			HoldStartTime = 0f;
			FrozenAnimEvent = null;
		}

		private static void Thaw()
		{
			if (!((Object)(object)FrozenAnimEvent == (Object)null))
			{
				if ((Object)(object)FrozenAnimEvent.m_animator != (Object)null)
				{
					float speed = ((FrozenAnimEvent.m_pauseSpeed > 0.01f) ? FrozenAnimEvent.m_pauseSpeed : 1f);
					FrozenAnimEvent.m_animator.speed = speed;
				}
				FrozenAnimEvent.m_pauseTimer = 0f;
			}
		}
	}
	internal static class HoldAttackTriggerDeferLogic
	{
		public static bool TryDefer(Humanoid attacker, Attack attack, string source)
		{
			if (!ModConfig.HoldEnabled.Value)
			{
				return true;
			}
			if (attack == null)
			{
				return true;
			}
			if (!HoldAttackHelpers.IsLocalPlayer(attacker))
			{
				return true;
			}
			if (HoldAttackState.TriggerReentryGuard)
			{
				return true;
			}
			if (HoldAttackState.PendingAttack == attack)
			{
				return true;
			}
			if (HoldAttackState.DecidedAttack == attack)
			{
				if (ModConfig.DebugLog.Value)
				{
					Plugin.Log.LogInfo((object)("[Hold/" + source + "] DAMAGE — trigger fired for already-decided attack"));
				}
				return true;
			}
			if (HoldAttackHelpers.IsProjectile(attack))
			{
				return true;
			}
			bool currentAttackIsSecondary = attacker.m_currentAttackIsSecondary;
			if (currentAttackIsSecondary && !ModConfig.HoldSecondary.Value)
			{
				return true;
			}
			if (!currentAttackIsSecondary && !ModConfig.HoldPrimary.Value)
			{
				return true;
			}
			bool flag = ValheimButtons.AttackHeld(currentAttackIsSecondary);
			if (ModConfig.DebugLog.Value)
			{
				Plugin.Log.LogInfo((object)($"[Hold/{source}] trigger fired. secondary={currentAttackIsSecondary} " + $"zinputHeld={flag} m_attackHold={((Character)attacker).m_attackHold} " + $"m_secAttackHold={((Character)attacker).m_secondaryAttackHold}"));
			}
			if (!flag)
			{
				return true;
			}
			CharacterAnimEvent animEvent = HoldAttackHelpers.GetAnimEvent(attacker);
			HoldAttackState.Freeze(attack, animEvent, atTrigger: true);
			HoldAttackState.TriggerReentryGuard = true;
			if (ModConfig.DebugLog.Value)
			{
				Plugin.Log.LogInfo((object)("[Hold/" + source + "] DEFERRED at trigger frame (fallback path)"));
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Attack), "OnAttackTrigger")]
	internal static class AttackOnAttackTriggerPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(Attack __instance)
		{
			return HoldAttackTriggerDeferLogic.TryDefer(__instance.m_character, __instance, "Attack");
		}
	}
	[HarmonyPatch(typeof(Humanoid), "OnAttackTrigger")]
	internal static class HumanoidOnAttackTriggerPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(Humanoid __instance)
		{
			return HoldAttackTriggerDeferLogic.TryDefer(__instance, __instance.m_currentAttack, "Humanoid");
		}
	}
	[HarmonyPatch(typeof(Humanoid), "UpdateAttack")]
	internal static class HoldAttackWindupFreezePatch
	{
		[HarmonyPostfix]
		private static void Postfix(Humanoid __instance)
		{
			//IL_007f: 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)
			if (!ModConfig.HoldEnabled.Value || HoldAttackState.IsActive || !HoldAttackHelpers.IsLocalPlayer(__instance))
			{
				return;
			}
			Attack currentAttack = __instance.m_currentAttack;
			if (currentAttack == null || currentAttack == HoldAttackState.DecidedAttack || HoldAttackHelpers.IsProjectile(currentAttack))
			{
				return;
			}
			bool currentAttackIsSecondary = __instance.m_currentAttackIsSecondary;
			if ((currentAttackIsSecondary && !ModConfig.HoldSecondary.Value) || (!currentAttackIsSecondary && !ModConfig.HoldPrimary.Value))
			{
				return;
			}
			Animator animator = ((Character)__instance).m_animator;
			if ((Object)(object)animator == (Object)null || animator.IsInTransition(0))
			{
				return;
			}
			AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);
			if (((AnimatorStateInfo)(ref currentAnimatorStateInfo)).tagHash != Humanoid.s_animatorTagAttack)
			{
				return;
			}
			float normalizedTime = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime;
			if (normalizedTime >= 1f)
			{
				return;
			}
			float value = ModConfig.HoldFreezeAt.Value;
			if (normalizedTime < value)
			{
				return;
			}
			if (!ValheimButtons.AttackHeld(currentAttackIsSecondary))
			{
				HoldAttackState.MarkLetThrough(currentAttack);
				if (ModConfig.DebugLog.Value)
				{
					Plugin.Log.LogInfo((object)$"[Hold/Windup] tap detected at t={normalizedTime:F2} — passing through");
				}
				return;
			}
			CharacterAnimEvent animEvent = HoldAttackHelpers.GetAnimEvent(__instance);
			HoldAttackState.Freeze(currentAttack, animEvent, atTrigger: false);
			if (ModConfig.DebugLog.Value)
			{
				Plugin.Log.LogInfo((object)$"[Hold/Windup] frozen at t={normalizedTime:F2} (threshold={value:F2})");
			}
		}
	}
}
namespace ImmersiveCombat.Features.AttackCancel
{
	internal static class AttackCancelLogic
	{
		public static void CancelAttack(Player player, Attack attack, bool refundStamina)
		{
			if (HoldAttackState.PendingAttack == attack)
			{
				HoldAttackState.Bailout();
			}
			if (refundStamina)
			{
				float num = attack.GetAttackStamina() * ModConfig.RefundStaminaPercent.Value;
				if (num > 0f)
				{
					((Character)player).AddStamina(num);
				}
			}
			attack.m_abortAttack = true;
			attack.Stop();
			((Humanoid)player).m_currentAttack = null;
			((Humanoid)player).m_currentAttackIsSecondary = false;
			((Humanoid)player).m_previousAttack = null;
			player.m_queuedAttackTimer = 0f;
			player.m_queuedSecondAttackTimer = 0f;
			Animator animator = ((Character)player).m_animator;
			if ((Object)(object)animator != (Object)null)
			{
				animator.CrossFadeInFixedTime("Movement", 0.05f, 0);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "Update")]
	internal static class AttackCancelPatch
	{
		[HarmonyPrefix]
		private static void Prefix(Player __instance)
		{
			if (!ModConfig.CancelEnabled.Value || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || !((Character)__instance).InAttack())
			{
				return;
			}
			Attack currentAttack = ((Humanoid)__instance).m_currentAttack;
			if (currentAttack == null)
			{
				return;
			}
			bool flag = ModConfig.CancelOnBlock.Value && ValheimButtons.BlockPressed();
			bool flag2 = ModConfig.CancelOnDodge.Value && ValheimButtons.DodgePressed();
			if (flag2 && HoldAttackState.IsActive && ModConfig.HoldLockMovement.Value)
			{
				flag2 = false;
			}
			if ((flag || flag2) && (!ModConfig.CancelOnlyDuringWindup.Value || !currentAttack.m_attackDone))
			{
				AttackCancelLogic.CancelAttack(__instance, currentAttack, ModConfig.RefundStamina.Value);
				if (ModConfig.DebugLog.Value)
				{
					string arg = (flag ? "Block" : "Dodge");
					Plugin.Log.LogInfo((object)$"[Cancel] Aborted swing via {arg}. attackDone={currentAttack.m_attackDone}");
				}
			}
		}
	}
}
namespace ImmersiveCombat.Config
{
	internal static class ModConfig
	{
		private const string SecDebug = "0 - Debug";

		private const string SecCancel = "1 - Attack Cancel";

		private const string SecHold = "2 - Hold To Release Attack";

		private const string SecQuickDodge = "3 - Quick Dodge (BETA)";

		public static ConfigEntry<bool> DebugLog { get; private set; }

		public static ConfigEntry<bool> CancelEnabled { get; private set; }

		public static ConfigEntry<bool> CancelOnBlock { get; private set; }

		public static ConfigEntry<bool> CancelOnDodge { get; private set; }

		public static ConfigEntry<bool> CancelOnlyDuringWindup { get; private set; }

		public static ConfigEntry<bool> RefundStamina { get; private set; }

		public static ConfigEntry<float> RefundStaminaPercent { get; private set; }

		public static ConfigEntry<bool> QuickDodgeEnabled { get; private set; }

		public static ConfigEntry<KeyCode> QuickDodgeKey { get; private set; }

		public static ConfigEntry<float> QuickDodgeDistanceScale { get; private set; }

		public static ConfigEntry<float> QuickDodgeStaminaFraction { get; private set; }

		public static ConfigEntry<float> QuickDodgeIframeSeconds { get; private set; }

		public static ConfigEntry<bool> HoldEnabled { get; private set; }

		public static ConfigEntry<bool> HoldPrimary { get; private set; }

		public static ConfigEntry<bool> HoldSecondary { get; private set; }

		public static ConfigEntry<float> HoldFreezeAt { get; private set; }

		public static ConfigEntry<float> HoldMaxSeconds { get; private set; }

		public static ConfigEntry<bool> HoldLockMovement { get; private set; }

		public static ConfigEntry<bool> HoldDrainStamina { get; private set; }

		public static ConfigEntry<float> HoldDrainStaminaPerSecond { get; private set; }

		public static void Bind(ConfigFile cfg)
		{
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Expected O, but got Unknown
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: Expected O, but got Unknown
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Expected O, but got Unknown
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Expected O, but got Unknown
			//IL_026c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0276: Expected O, but got Unknown
			//IL_02a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ae: Expected O, but got Unknown
			//IL_02dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e6: Expected O, but got Unknown
			DebugLog = cfg.Bind<bool>("0 - Debug", "DebugLog", false, "Verbose logging to BepInEx\\LogOutput.log.");
			CancelEnabled = cfg.Bind<bool>("1 - Attack Cancel", "Enabled", true, "Master toggle for the Attack Cancel feature.");
			CancelOnBlock = cfg.Bind<bool>("1 - Attack Cancel", "CancelOnBlock", true, "Pressing Block during a swing cancels the swing.");
			CancelOnDodge = cfg.Bind<bool>("1 - Attack Cancel", "CancelOnDodge", true, "Pressing Dodge (Jump key + WASD) during a swing cancels the swing.");
			CancelOnlyDuringWindup = cfg.Bind<bool>("1 - Attack Cancel", "CancelOnlyDuringWindup", false, "Only allow cancelling before the damage trigger has fired.");
			RefundStamina = cfg.Bind<bool>("1 - Attack Cancel", "RefundStamina", true, "Refund part of the stamina consumed by the cancelled swing.");
			RefundStaminaPercent = cfg.Bind<float>("1 - Attack Cancel", "RefundStaminaPercent", 0.5f, new ConfigDescription("Fraction of the attack's stamina cost to refund (0..1).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			HoldEnabled = cfg.Bind<bool>("2 - Hold To Release Attack", "Enabled", true, "Master toggle. Holding the attack button freezes the swing in wind-up; releasing it plays the strike.");
			HoldPrimary = cfg.Bind<bool>("2 - Hold To Release Attack", "HoldPrimary", true, "Apply hold-to-release to primary (LMB) attacks.");
			HoldSecondary = cfg.Bind<bool>("2 - Hold To Release Attack", "HoldSecondary", false, "Apply hold-to-release to secondary (RMB) attacks. Off by default — vanilla secondary moves are designed to fire instantly.");
			HoldFreezeAt = cfg.Bind<float>("2 - Hold To Release Attack", "FreezeAtNormalizedTime", 0.35f, new ConfigDescription("Where in the swing animation (0..1) to freeze. Lower = earlier wind-up pose.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 0.95f), Array.Empty<object>()));
			HoldMaxSeconds = cfg.Bind<float>("2 - Hold To Release Attack", "MaxHoldSeconds", 5f, new ConfigDescription("Auto-release after this many seconds (safety cap).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 30f), Array.Empty<object>()));
			HoldLockMovement = cfg.Bind<bool>("2 - Hold To Release Attack", "LockMovementWhileHeld", true, "Zero out movement / jump / dodge inputs while the wind-up is frozen, so accidental key presses don't waste stamina. Block still works (cancels).");
			HoldDrainStamina = cfg.Bind<bool>("2 - Hold To Release Attack", "DrainStaminaWhileHolding", false, "Drain stamina each second while holding. Off by default.");
			HoldDrainStaminaPerSecond = cfg.Bind<float>("2 - Hold To Release Attack", "DrainStaminaPerSecond", 4f, new ConfigDescription("Stamina per second drained while holding (only used if DrainStaminaWhileHolding).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			QuickDodgeEnabled = cfg.Bind<bool>("3 - Quick Dodge (BETA)", "Enabled", false, "BETA. Short Witcher 3-style sidestep on a dedicated key, half stamina, shortened iframes. Off by default while this is still in beta.");
			QuickDodgeKey = cfg.Bind<KeyCode>("3 - Quick Dodge (BETA)", "KeyBind", (KeyCode)308, "BETA. Key that triggers Quick Dodge. Combine with WASD to pick direction; no WASD = backward.");
			QuickDodgeDistanceScale = cfg.Bind<float>("3 - Quick Dodge (BETA)", "DistanceScale", 0.4f, new ConfigDescription("BETA. Fraction of a normal dodge's distance AND duration.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 1f), Array.Empty<object>()));
			QuickDodgeStaminaFraction = cfg.Bind<float>("3 - Quick Dodge (BETA)", "StaminaFraction", 0.5f, new ConfigDescription("BETA. Fraction of vanilla dodge stamina cost.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 1f), Array.Empty<object>()));
			QuickDodgeIframeSeconds = cfg.Bind<float>("3 - Quick Dodge (BETA)", "IframeSeconds", 0.05f, new ConfigDescription("BETA. Seconds of invincibility after dodge starts (vanilla ≈ 0.2s).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>()));
		}
	}
}
namespace ImmersiveCombat.Common
{
	internal static class AnimatorConstants
	{
		public const string MovementState = "Movement";

		public const int BodyLayer = 0;

		public const float ShortCrossFade = 0.05f;
	}
	internal static class ValheimButtons
	{
		public const string Jump = "Jump";

		public const string JumpJoy = "JoyDodge";

		public const string Block = "Block";

		public const string BlockJoy = "JoyBlock";

		public const string Attack = "Attack";

		public const string AttackJoy = "JoyAttack";

		public const string SecAttack = "SecondaryAttack";

		public const string SecAttackJoy = "JoySecondaryAttack";

		public static bool Pressed(string kb, string joy)
		{
			if (!ZInput.GetButtonDown(kb))
			{
				return ZInput.GetButtonDown(joy);
			}
			return true;
		}

		public static bool Held(string kb, string joy)
		{
			if (!ZInput.GetButton(kb))
			{
				return ZInput.GetButton(joy);
			}
			return true;
		}

		public static bool BlockPressed()
		{
			return Pressed("Block", "JoyBlock");
		}

		public static bool DodgePressed()
		{
			return Pressed("Jump", "JoyDodge");
		}

		public static bool AttackHeld(bool secondary)
		{
			if (!secondary)
			{
				return Held("Attack", "JoyAttack");
			}
			return Held("SecondaryAttack", "JoySecondaryAttack");
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		internal IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}