Decompiled source of Parrying v1.0.3

plugins/Parrying/Parrying.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HBS;
using HBS.Net;
using HBS.Util;
using HarmonyLib;
using KoMiKoZa.Necropolis.JSONLocalizeAPI;
using Necro;
using Necro.UI;
using UnityEngine;

[assembly: AssemblyCompany("KoMiKoZa")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyTitle("Parrying")]
[assembly: AssemblyDescription("Adds a precise parry system with multiplayer synchronization to Necropolis")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyProduct("Parrying")]
[assembly: AssemblyCopyright("Copyright © KoMiKoZa 2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("248157f3-a5c1-4968-a7a8-51e56b173c73")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: CompilationRelaxations(8)]
[assembly: AssemblyVersion("1.0.1.0")]
namespace Komikoza.Necropolis.Parrying;

[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInPlugin("komikoza.necropolis.parrying", "Parrying", "1.0.1")]
public class ParryPlugin : BaseUnityPlugin
{
	public const string PLUGIN_GUID = "komikoza.necropolis.parrying";

	public const string PLUGIN_NAME = "Parrying";

	public const string PLUGIN_VERSION = "1.0.1";

	public const float PARRY_WINDOW_DURATION = 0.15f;

	public const uint ROUTER_VALUE = 1347571282u;

	internal static ManualLogSource Log;

	internal static ConfigEntry<bool> ModEnabled;

	internal static ConfigEntry<bool> PlayParryAudio;

	internal static ConfigEntry<bool> ShowParryToast;

	internal static ConfigEntry<bool> ShowBrazenHeadDialogue;

	internal static ConfigEntry<bool> DebugMode;

	internal static MethodInfo cachedNotifyWeaponKnockdown;

	internal static MethodInfo cachedNotifyWeaponHitBlockReact;

	internal static FieldInfo cachedHitReactFeedbackValue;

	private void Awake()
	{
		//IL_018e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0194: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ModEnabled", true, "Enable or disable this mod");
		PlayParryAudio = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio & Visual", "PlayParryAudio", true, "Play parry sound");
		ShowParryToast = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio & Visual", "ShowParryToast", true, "Show parry toast notification");
		ShowBrazenHeadDialogue = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio & Visual", "ShowBrazenHeadDialogue", true, "Show Brazen Head dialogue after consecutive parries");
		DebugMode = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugMode", false, "Enable debug logging");
		if (!ModEnabled.Value)
		{
			Log.LogInfo((object)string.Format("[{0}] Disabled", "Parrying"));
			return;
		}
		try
		{
			cachedNotifyWeaponKnockdown = typeof(Actor).GetMethod("NotifyWeaponKnockdown", BindingFlags.Instance | BindingFlags.NonPublic);
			cachedNotifyWeaponHitBlockReact = typeof(Actor).GetMethod("NotifyWeaponHitBlockReact", BindingFlags.Instance | BindingFlags.NonPublic);
			cachedHitReactFeedbackValue = typeof(Actor).GetField("hitReactFeedbackValue", BindingFlags.Instance | BindingFlags.NonPublic);
			if ((object)cachedNotifyWeaponKnockdown == null)
			{
				Log.LogWarning((object)"[PARRY] Could not cache NotifyWeaponKnockdown method");
			}
			if ((object)cachedNotifyWeaponHitBlockReact == null)
			{
				Log.LogWarning((object)"[PARRY] Could not cache NotifyWeaponHitBlockReact method");
			}
			if ((object)cachedHitReactFeedbackValue == null)
			{
				Log.LogWarning((object)"[PARRY] Could not cache hitReactFeedbackValue field");
			}
			Harmony val = new Harmony("komikoza.necropolis.parrying");
			val.PatchAll();
			ParryNetworkReceiver.Initialize();
			Log.LogInfo((object)"================================================");
			Log.LogInfo((object)string.Format("[{0}] v{1} loaded!", "Parrying", "1.0.1"));
			Log.LogInfo((object)" - Parry window: 0.15s after block");
			Log.LogInfo((object)"================================================");
		}
		catch (Exception arg)
		{
			Log.LogError((object)string.Format("[{0}] Failed to load: {1}", "Parrying", arg));
		}
	}
}
public class ParryNetworkReceiver : ObjectDataReceiver
{
	private const float CACHE_REBUILD_INTERVAL = 2f;

	private static ParryNetworkReceiver instance;

	private static Dictionary<uint, Actor> actorCache = new Dictionary<uint, Actor>();

	private static float lastCacheRebuild = 0f;

	public static ParryNetworkReceiver Instance => instance;

	public static void Initialize()
	{
		if (instance == null)
		{
			instance = new ParryNetworkReceiver();
			ObjectDataRouter.Singleton.Register(1347571282u, (ObjectDataReceiver)(object)instance);
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogInfo((object)"[PARRY][NET] Network receiver registered");
			}
		}
	}

	public void Receive(SerializationStream stream)
	{
		//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)
		//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0155: Unknown result type (might be due to invalid IL or missing references)
		//IL_015a: Unknown result type (might be due to invalid IL or missing references)
		//IL_015d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_0136: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (!ParryPlugin.ModEnabled.Value)
			{
				return;
			}
			byte @byte = stream.GetByte();
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogInfo((object)$"[PARRY][NET] Received message type {@byte}");
			}
			switch (@byte)
			{
			case 1:
			{
				uint uInt3 = stream.GetUInt();
				Vector3 vector3 = stream.GetVector3();
				Actor val2 = FindActorById(uInt3);
				if ((Object)(object)val2 == (Object)null)
				{
					if (ParryPlugin.DebugMode.Value)
					{
						ParryPlugin.Log.LogWarning((object)$"[PARRY][NET] Actor {uInt3} not found");
					}
				}
				else
				{
					ApplyKnockdown(val2, vector3);
				}
				break;
			}
			case 2:
			{
				uint uInt3 = stream.GetUInt();
				Vector3 vector3 = stream.GetVector3();
				Actor val2 = FindActorById(uInt3);
				if ((Object)(object)val2 == (Object)null)
				{
					if (ParryPlugin.DebugMode.Value)
					{
						ParryPlugin.Log.LogWarning((object)$"[PARRY][NET] Actor {uInt3} not found");
					}
				}
				else
				{
					ApplyBlockReact(val2, vector3);
				}
				break;
			}
			case 3:
			{
				uint uInt = stream.GetUInt();
				uint uInt2 = stream.GetUInt();
				Vector3 vector = stream.GetVector3();
				Vector3 vector2 = stream.GetVector3();
				Actor val = FindActorById(uInt);
				if ((Object)(object)val == (Object)null)
				{
					if (ParryPlugin.DebugMode.Value)
					{
						ParryPlugin.Log.LogWarning((object)$"[PARRY][NET] Defender actor {uInt} not found");
					}
				}
				else
				{
					Actor attacker = FindActorById(uInt2);
					ApplyParryEffects(val, attacker, vector, vector2);
				}
				break;
			}
			default:
				ParryPlugin.Log.LogWarning((object)$"[PARRY][NET] Unknown message type: {@byte}");
				break;
			}
		}
		catch (Exception arg)
		{
			ParryPlugin.Log.LogError((object)$"[PARRY][NET] Receive error: {arg}");
		}
	}

	private void ApplyKnockdown(Actor actor, Vector3 force)
	{
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogInfo((object)$"[PARRY][NET] Applying knockdown to {((Object)actor).name} with force {force}");
			}
			actor.Set(Stats.Stagger, 100f);
			if ((object)ParryPlugin.cachedNotifyWeaponKnockdown != null)
			{
				ParryPlugin.cachedNotifyWeaponKnockdown.Invoke(actor, new object[1] { force });
			}
			else
			{
				ParryPlugin.Log.LogWarning((object)"[PARRY][NET] NotifyWeaponKnockdown method not cached");
			}
		}
		catch (Exception arg)
		{
			ParryPlugin.Log.LogError((object)$"[PARRY][NET] ApplyKnockdown error: {arg}");
		}
	}

	private void ApplyBlockReact(Actor actor, Vector3 force)
	{
		//IL_0024: 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)
		try
		{
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogInfo((object)$"[PARRY][NET] Applying block react to {((Object)actor).name} with force {force}");
			}
			if ((object)ParryPlugin.cachedNotifyWeaponHitBlockReact != null)
			{
				ParryPlugin.cachedNotifyWeaponHitBlockReact.Invoke(actor, new object[1] { force });
			}
			else
			{
				ParryPlugin.Log.LogWarning((object)"[PARRY][NET] NotifyWeaponHitBlockReact method not cached");
			}
		}
		catch (Exception arg)
		{
			ParryPlugin.Log.LogError((object)$"[PARRY][NET] ApplyBlockReact error: {arg}");
		}
	}

	private void ApplyParryEffects(Actor defender, Actor attacker, Vector3 impactPoint, Vector3 impactVelocity)
	{
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogInfo((object)$"[PARRY][NET] Applying parry effects for {((Object)defender).name}");
			}
			if (ParryPlugin.PlayParryAudio.Value && (Object)(object)defender.Body != (Object)null && AudioBankManager.HasInstance)
			{
				defender.Body.PostAudioEvent(defender.Body.thisAudio.onBlockStagger);
			}
			try
			{
				ImpactMaterial val = null;
				if ((Object)(object)attacker != (Object)null && (Object)(object)attacker.CurrentWeapon != (Object)null && (Object)(object)attacker.CurrentWeapon.ImpactBody != (Object)null)
				{
					val = attacker.CurrentWeapon.ImpactBody.ImpactMaterial;
				}
				LazySingletonBehavior<ImpactManager>.Instance.SpawnImpact(val, val, impactPoint, impactVelocity, Vector3.up);
				if (ParryPlugin.DebugMode.Value)
				{
					ParryPlugin.Log.LogInfo((object)string.Format("[PARRY][NET] Spawned network impact effect: {0}", ((Object)(object)val != (Object)null) ? ((Object)val).name : "default"));
				}
			}
			catch (Exception arg)
			{
				ParryPlugin.Log.LogError((object)$"[PARRY][NET] Impact VFX error: {arg}");
			}
		}
		catch (Exception arg)
		{
			ParryPlugin.Log.LogError((object)$"[PARRY][NET] ApplyParryEffects error: {arg}");
		}
	}

	private Actor FindActorById(uint id)
	{
		if (id == 0)
		{
			return null;
		}
		try
		{
			if (Time.time - lastCacheRebuild > 2f)
			{
				RebuildActorCache();
			}
			if (actorCache.TryGetValue(id, out var value))
			{
				if ((Object)(object)value != (Object)null && (Object)(object)value.networkRoutable != (Object)null)
				{
					return value;
				}
				actorCache.Remove(id);
			}
			Actor[] array = Object.FindObjectsOfType<Actor>();
			Actor[] array2 = array;
			foreach (Actor val in array2)
			{
				if ((Object)(object)val.networkRoutable != (Object)null)
				{
					uint dynamicRouterValue = val.networkRoutable.DynamicRouterValue;
					actorCache[dynamicRouterValue] = val;
					if (dynamicRouterValue == id)
					{
						return val;
					}
				}
			}
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogWarning((object)$"[PARRY][NET] Could not find actor with ID {id}");
			}
		}
		catch (Exception arg)
		{
			ParryPlugin.Log.LogError((object)$"[PARRY][NET] FindActorById error: {arg}");
		}
		return null;
	}

	private void RebuildActorCache()
	{
		actorCache.Clear();
		Actor[] array = Object.FindObjectsOfType<Actor>();
		Actor[] array2 = array;
		foreach (Actor val in array2)
		{
			if ((Object)(object)val != (Object)null && (Object)(object)val.networkRoutable != (Object)null)
			{
				actorCache[val.networkRoutable.DynamicRouterValue] = val;
			}
		}
		lastCacheRebuild = Time.time;
		if (ParryPlugin.DebugMode.Value)
		{
			ParryPlugin.Log.LogInfo((object)$"[PARRY][NET] Rebuilt actor cache: {actorCache.Count} actors");
		}
	}
}
public static class ParryNetworkSender
{
	public static void SendKnockdown(Actor actor, Vector3 force)
	{
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)actor == (Object)null)
		{
			return;
		}
		try
		{
			uint actorNetworkId = GetActorNetworkId(actor);
			int num = 17;
			SerializationStream routedBuffer = ObjectDataRouter.Singleton.GetRoutedBuffer((ObjectDataReceiver)(object)ParryNetworkReceiver.Instance, num);
			if (routedBuffer == null)
			{
				if (ParryPlugin.DebugMode.Value)
				{
					ParryPlugin.Log.LogWarning((object)"[PARRY][NET] Failed to get routed buffer");
				}
				return;
			}
			routedBuffer.PutByte((byte)1);
			routedBuffer.PutUInt(actorNetworkId);
			routedBuffer.PutVector3(force);
			SendToAll(routedBuffer);
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogInfo((object)$"[PARRY][NET] Sent knockdown for actor {actorNetworkId}");
			}
		}
		catch (Exception arg)
		{
			ParryPlugin.Log.LogError((object)$"[PARRY][NET] SendKnockdown error: {arg}");
		}
	}

	public static void SendBlockReact(Actor actor, Vector3 force)
	{
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)actor == (Object)null)
		{
			return;
		}
		try
		{
			uint actorNetworkId = GetActorNetworkId(actor);
			int num = 17;
			SerializationStream routedBuffer = ObjectDataRouter.Singleton.GetRoutedBuffer((ObjectDataReceiver)(object)ParryNetworkReceiver.Instance, num);
			if (routedBuffer == null)
			{
				if (ParryPlugin.DebugMode.Value)
				{
					ParryPlugin.Log.LogWarning((object)"[PARRY][NET] Failed to get routed buffer");
				}
				return;
			}
			routedBuffer.PutByte((byte)2);
			routedBuffer.PutUInt(actorNetworkId);
			routedBuffer.PutVector3(force);
			SendToAll(routedBuffer);
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogInfo((object)$"[PARRY][NET] Sent block react for actor {actorNetworkId}");
			}
		}
		catch (Exception arg)
		{
			ParryPlugin.Log.LogError((object)$"[PARRY][NET] SendBlockReact error: {arg}");
		}
	}

	public static void SendParryEffects(Actor defender, Actor attacker, Vector3 impactPoint, Vector3 impactVelocity)
	{
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)defender == (Object)null)
		{
			return;
		}
		try
		{
			uint actorNetworkId = GetActorNetworkId(defender);
			uint actorNetworkId2 = GetActorNetworkId(attacker);
			int num = 100;
			SerializationStream routedBuffer = ObjectDataRouter.Singleton.GetRoutedBuffer((ObjectDataReceiver)(object)ParryNetworkReceiver.Instance, num);
			if (routedBuffer == null)
			{
				if (ParryPlugin.DebugMode.Value)
				{
					ParryPlugin.Log.LogWarning((object)"[PARRY][NET] Failed to get routed buffer for parry effects");
				}
				return;
			}
			routedBuffer.PutByte((byte)3);
			routedBuffer.PutUInt(actorNetworkId);
			routedBuffer.PutUInt(actorNetworkId2);
			routedBuffer.PutVector3(impactPoint);
			routedBuffer.PutVector3(impactVelocity);
			SendToAll(routedBuffer);
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogInfo((object)$"[PARRY][NET] Sent parry effects for defender {actorNetworkId}, attacker {actorNetworkId2}");
			}
		}
		catch (Exception arg)
		{
			ParryPlugin.Log.LogError((object)$"[PARRY][NET] SendParryEffects error: {arg}");
		}
	}

	private static uint GetActorNetworkId(Actor actor)
	{
		if ((Object)(object)actor == (Object)null || (Object)(object)actor.networkRoutable == (Object)null)
		{
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogWarning((object)"[PARRY][NET] Cannot get network ID: actor or networkRoutable is null");
			}
			return 0u;
		}
		return actor.networkRoutable.DynamicRouterValue;
	}

	private static void SendToAll(SerializationStream stream)
	{
		try
		{
			LazySingletonBehavior<NetworkManager>.Instance.SendReliable(stream);
		}
		catch (Exception arg)
		{
			ParryPlugin.Log.LogError((object)$"[PARRY][NET] SendToAll error: {arg}");
		}
		finally
		{
			ObjectDataRouter.Singleton.PutRoutedBuffer(stream);
		}
	}
}
public static class ParryTracker
{
	private class BlockState
	{
		public bool wasBlocking = false;

		public float blockStartTime = 0f;
	}

	public const float BRAZEN_HEAD_COOLDOWN = 120f;

	private const float TOAST_COOLDOWN = 10f;

	private static Dictionary<Actor, BlockState> actorStates = new Dictionary<Actor, BlockState>();

	public static Actor currentParryDefender = null;

	public static Actor currentParryAttacker = null;

	public static bool isValidParry = false;

	public static int consecutiveParries = 0;

	public static bool hasTriggered5Parries = false;

	public static bool hasTriggered10Parries = false;

	public static float lastParryTime = -999f;

	public static float lastBrazenHeadSpeechTime = -999f;

	public static bool has10ParryBrazenHeadPlayed = false;

	private static float lastToastTime = -999f;

	public static bool CanShowToast()
	{
		if (Time.time - lastToastTime >= 10f)
		{
			lastToastTime = Time.time;
			return true;
		}
		return false;
	}

	public static void CheckBlockTransition(Actor actor)
	{
		if ((Object)(object)actor == (Object)null || !actor.IsPlayer)
		{
			return;
		}
		if (!actorStates.TryGetValue(actor, out var value))
		{
			value = new BlockState();
			actorStates[actor] = value;
		}
		bool isBlocking = actor.IsBlocking;
		if (isBlocking && !value.wasBlocking)
		{
			value.blockStartTime = Time.time;
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogInfo((object)$"[PARRY] {((Object)actor).name} started blocking at {Time.time:F3}s");
			}
		}
		else if (isBlocking && value.wasBlocking && value.blockStartTime > 0f)
		{
			float num = Time.time - value.blockStartTime;
			if (num > 0.15f && consecutiveParries > 0)
			{
				int num2 = consecutiveParries;
				consecutiveParries = 0;
				hasTriggered5Parries = false;
				hasTriggered10Parries = false;
				if (ParryPlugin.DebugMode.Value)
				{
					ParryPlugin.Log.LogInfo((object)$"[PARRY] Normal block detected - reset consecutive parries (was {num2})");
				}
			}
		}
		value.wasBlocking = isBlocking;
	}

	public static bool IsWithinParryWindow(Actor actor, out float timeSinceStart)
	{
		timeSinceStart = 999f;
		if ((Object)(object)actor == (Object)null)
		{
			return false;
		}
		if (!actorStates.TryGetValue(actor, out var value))
		{
			return false;
		}
		timeSinceStart = Time.time - value.blockStartTime;
		return timeSinceStart <= 0.15f && timeSinceStart >= 0f;
	}

	public static void ClearParryWindow(Actor actor)
	{
		if ((Object)(object)actor != (Object)null && actorStates.ContainsKey(actor))
		{
			actorStates[actor].blockStartTime = -999f;
		}
	}

	public static void ClearParryContext()
	{
		currentParryDefender = null;
		currentParryAttacker = null;
		isValidParry = false;
	}
}
[HarmonyPatch(typeof(Actor), "_Update")]
public static class Actor_Update_Patch
{
	private static void Postfix(Actor __instance)
	{
		try
		{
			if (ParryPlugin.ModEnabled.Value && (Object)(object)__instance != (Object)null && __instance.IsPlayer)
			{
				ParryTracker.CheckBlockTransition(__instance);
			}
		}
		catch (Exception arg)
		{
			ParryPlugin.Log.LogError((object)$"Error in _Update patch: {arg}");
		}
	}
}
[HarmonyPatch(typeof(Actor), "ApplyHitDamage")]
public static class Actor_ApplyHitDamage_Prefix
{
	private static bool Prefix(Actor __instance, EffectContext context, float damageScale)
	{
		//IL_0127: 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)
		//IL_0137: Unknown result type (might be due to invalid IL or missing references)
		//IL_013b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0140: Unknown result type (might be due to invalid IL or missing references)
		//IL_0141: Unknown result type (might be due to invalid IL or missing references)
		//IL_0147: Unknown result type (might be due to invalid IL or missing references)
		//IL_014c: Unknown result type (might be due to invalid IL or missing references)
		//IL_014e: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_020d: 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: Unknown result type (might be due to invalid IL or missing references)
		//IL_021d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0220: Unknown result type (might be due to invalid IL or missing references)
		//IL_0248: Unknown result type (might be due to invalid IL or missing references)
		//IL_02e8: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f3: 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_0302: Unknown result type (might be due to invalid IL or missing references)
		//IL_0317: Unknown result type (might be due to invalid IL or missing references)
		//IL_0318: Unknown result type (might be due to invalid IL or missing references)
		//IL_0322: Unknown result type (might be due to invalid IL or missing references)
		//IL_0327: Unknown result type (might be due to invalid IL or missing references)
		//IL_0332: Unknown result type (might be due to invalid IL or missing references)
		//IL_0334: Unknown result type (might be due to invalid IL or missing references)
		//IL_0336: Unknown result type (might be due to invalid IL or missing references)
		//IL_0386: Unknown result type (might be due to invalid IL or missing references)
		//IL_0388: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (!ParryPlugin.ModEnabled.Value)
			{
				return true;
			}
			if ((Object)(object)ParryTracker.currentParryDefender != (Object)null && (Object)(object)ParryTracker.currentParryDefender != (Object)(object)__instance)
			{
				ParryTracker.ClearParryContext();
			}
			if (!__instance.IsBlocking)
			{
				if ((Object)(object)ParryTracker.currentParryDefender != (Object)null)
				{
					ParryTracker.ClearParryContext();
				}
				return true;
			}
			if (!ParryTracker.IsWithinParryWindow(__instance, out var timeSinceStart))
			{
				if (ParryPlugin.DebugMode.Value)
				{
					ParryPlugin.Log.LogInfo((object)$"[PARRY] Outside window: {timeSinceStart:F3}s");
				}
				return true;
			}
			if (context == null || (Object)(object)context.sourceActor == (Object)null)
			{
				return true;
			}
			Actor sourceActor = context.sourceActor;
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogInfo((object)$">>> PARRY! {((Object)__instance).name} countered {((Object)sourceActor).name} (t={timeSinceStart:F3}s) <<<");
			}
			Vector3 val = sourceActor.Position - __instance.Position;
			Vector3 normalized = ((Vector3)(ref val)).normalized;
			Vector3 val2 = normalized * 50f;
			ParryNetworkSender.SendKnockdown(sourceActor, val2);
			float num = sourceActor.Get(Stats.Stagger);
			sourceActor.Set(Stats.Stagger, 100f);
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogInfo((object)$"[PARRY] Set stagger to 100 (was {num:F2})");
			}
			if ((object)ParryPlugin.cachedNotifyWeaponKnockdown != null)
			{
				ParryPlugin.cachedNotifyWeaponKnockdown.Invoke(sourceActor, new object[1] { val2 });
				if (ParryPlugin.DebugMode.Value)
				{
					ParryPlugin.Log.LogInfo((object)"[PARRY] Triggered knockdown via cached reflection");
				}
			}
			else
			{
				ParryPlugin.Log.LogError((object)"[PARRY] Cached NotifyWeaponKnockdown method is null!");
			}
			Vector3 val3 = -normalized * 10f;
			ParryNetworkSender.SendBlockReact(__instance, val3);
			if ((object)ParryPlugin.cachedNotifyWeaponHitBlockReact != null)
			{
				ParryPlugin.cachedNotifyWeaponHitBlockReact.Invoke(__instance, new object[1] { val3 });
				if (ParryPlugin.DebugMode.Value)
				{
					ParryPlugin.Log.LogInfo((object)"[PARRY] Triggered block react animation on defender");
				}
			}
			else
			{
				ParryPlugin.Log.LogWarning((object)"[PARRY] Cached NotifyWeaponHitBlockReact method is null!");
			}
			ParryTracker.currentParryDefender = __instance;
			ParryTracker.currentParryAttacker = sourceActor;
			ParryTracker.isValidParry = true;
			if ((Object)(object)context.sourceWeapon != (Object)null && (Object)(object)context.sourceWeapon.ImpactBody != (Object)null)
			{
				try
				{
					ImpactMaterial impactMaterial = context.sourceWeapon.ImpactBody.ImpactMaterial;
					Vector3 val4 = (__instance.Position + sourceActor.Position) / 2f;
					val4.y += 1.2f;
					Vector3 val5 = -normalized * 10f;
					LazySingletonBehavior<ImpactManager>.Instance.SpawnImpact(impactMaterial, impactMaterial, val4, val5, Vector3.up);
					if (ParryPlugin.DebugMode.Value)
					{
						ParryPlugin.Log.LogInfo((object)string.Format("[PARRY] Spawned impact: {0}", ((Object)(object)impactMaterial != (Object)null) ? ((Object)impactMaterial).name : "null"));
					}
					ParryNetworkSender.SendParryEffects(__instance, sourceActor, val4, val5);
				}
				catch (Exception ex)
				{
					ParryPlugin.Log.LogError((object)$"[PARRY] Impact effect error: {ex.Message}");
				}
			}
			if (__instance.IsLocalPlayer)
			{
				ParryTracker.consecutiveParries++;
				ParryTracker.lastParryTime = Time.time;
				if (ParryPlugin.DebugMode.Value)
				{
					ParryPlugin.Log.LogInfo((object)$"[PARRY] Consecutive parries: {ParryTracker.consecutiveParries}");
				}
			}
			ParryTracker.ClearParryWindow(__instance);
			try
			{
				if ((object)ParryPlugin.cachedHitReactFeedbackValue != null)
				{
					ParryPlugin.cachedHitReactFeedbackValue.SetValue(__instance, 0f);
				}
			}
			catch (Exception)
			{
			}
			return false;
		}
		catch (Exception ex)
		{
			ParryPlugin.Log.LogError((object)$"Error in Prefix: {ex}");
			return true;
		}
	}
}
[HarmonyPatch(typeof(Actor), "ApplyHitDamage")]
public static class Actor_ApplyHitDamage_Postfix
{
	private static void Postfix(Actor __instance, EffectContext context)
	{
		try
		{
			if (!ParryPlugin.ModEnabled.Value || !ParryTracker.isValidParry || (Object)(object)ParryTracker.currentParryDefender != (Object)(object)__instance)
			{
				return;
			}
			if (ParryPlugin.PlayParryAudio.Value && __instance.IsPlayer && (Object)(object)__instance.Body != (Object)null && AudioBankManager.HasInstance)
			{
				__instance.Body.PostAudioEvent(__instance.Body.thisAudio.onBlockStagger);
			}
			if (ParryPlugin.ShowParryToast.Value && __instance.IsLocalPlayer && ParryTracker.CanShowToast())
			{
				try
				{
					if (LazySingletonBehavior<UIManager>.HasInstance)
					{
						string randomFromArray = JSONLocalizeAPI.GetRandomFromArray(Assembly.GetExecutingAssembly(), "toast_messages", "PARRY!");
						LazySingletonBehavior<UIManager>.Instance.CreateToast(randomFromArray, (string)null, (object[])null);
					}
				}
				catch (Exception ex)
				{
					ParryPlugin.Log.LogError((object)$"[PARRY] Toast error: {ex.Message}");
				}
			}
			TriggerBrazenHeadSpeech();
			ParryTracker.isValidParry = false;
		}
		catch (Exception ex)
		{
			ParryPlugin.Log.LogError((object)$"Error in Postfix: {ex}");
		}
	}

	private static void TriggerBrazenHeadSpeech()
	{
		if (!ParryPlugin.ShowBrazenHeadDialogue.Value || ((Object)(object)ParryTracker.currentParryDefender != (Object)null && !ParryTracker.currentParryDefender.IsLocalPlayer))
		{
			return;
		}
		try
		{
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogInfo((object)$"[PARRY] Checking Brazen Head: count={ParryTracker.consecutiveParries}, triggered5={ParryTracker.hasTriggered5Parries}, triggered10={ParryTracker.hasTriggered10Parries}, cooldown={Time.time - ParryTracker.lastBrazenHeadSpeechTime:F1}s");
			}
			if (ParryTracker.consecutiveParries >= 10 && !ParryTracker.has10ParryBrazenHeadPlayed)
			{
				ParryTracker.has10ParryBrazenHeadPlayed = true;
				ParryTracker.hasTriggered10Parries = true;
				ParryTracker.lastBrazenHeadSpeechTime = Time.time;
				string fromObject = JSONLocalizeAPI.GetFromObject(Assembly.GetExecutingAssembly(), "brazen_head_10_parry", "I've seen this a thousand times, you're not special, but fine, I noticed.");
				MainHUD.StartConversation(fromObject, false, (AudioState_ActiveSpeaker)0, -1f);
				if (ParryPlugin.DebugMode.Value)
				{
					ParryPlugin.Log.LogInfo((object)"[PARRY] Triggered 10-parry Brazen Head speech");
				}
			}
			else if (Time.time - ParryTracker.lastBrazenHeadSpeechTime < 120f)
			{
				if (ParryPlugin.DebugMode.Value)
				{
					float num = 120f - (Time.time - ParryTracker.lastBrazenHeadSpeechTime);
					ParryPlugin.Log.LogInfo((object)$"[PARRY] Brazen Head on cooldown ({num:F0}s remaining)");
				}
			}
			else if (ParryTracker.consecutiveParries >= 5 && !ParryTracker.hasTriggered5Parries)
			{
				ParryTracker.hasTriggered5Parries = true;
				ParryTracker.lastBrazenHeadSpeechTime = Time.time;
				string fromObject = JSONLocalizeAPI.GetRandomFromArray(Assembly.GetExecutingAssembly(), "brazen_head_5_parry", "Impressive.");
				if (ParryPlugin.DebugMode.Value)
				{
					ParryPlugin.Log.LogInfo((object)$"[PARRY] Attempting Brazen Head speech: {fromObject}");
				}
				MainHUD.StartConversation(fromObject, false, (AudioState_ActiveSpeaker)0, -1f);
				if (ParryPlugin.DebugMode.Value)
				{
					ParryPlugin.Log.LogInfo((object)"[PARRY] Triggered 5-parry Brazen Head speech");
				}
			}
		}
		catch (Exception arg)
		{
			ParryPlugin.Log.LogError((object)$"[PARRY] Brazen Head speech error: {arg}");
		}
	}
}
[HarmonyPatch(typeof(Actor), "ApplyHitDamage")]
public static class Actor_ApplyHitDamage_ResetPostfix2
{
	private static void Postfix(Actor __instance, EffectContext context)
	{
		try
		{
			if (!ParryPlugin.ModEnabled.Value || context == null)
			{
				return;
			}
			bool flag = (Object)(object)context.sourceActor != (Object)null && context.sourceActor.IsPlayer && (Object)(object)__instance != (Object)null && !__instance.IsPlayer;
			float num = Time.time - ParryTracker.lastParryTime;
			bool flag2 = num < 2f;
			if (ParryPlugin.DebugMode.Value)
			{
				ParryPlugin.Log.LogInfo((object)string.Format("[RESET] Checking: instance={0}, playerAttack={1}, timeSinceParry={2:F3}s, gracePeriod={3}", ((Object)(object)__instance != (Object)null) ? ((Object)__instance).name : "null", flag, num, flag2));
			}
			if (flag && !flag2)
			{
				if (!context.sourceActor.IsLocalPlayer)
				{
					return;
				}
				int consecutiveParries = ParryTracker.consecutiveParries;
				if (consecutiveParries > 0)
				{
					ParryTracker.consecutiveParries = 0;
					ParryTracker.hasTriggered5Parries = false;
					ParryTracker.hasTriggered10Parries = false;
					if (ParryPlugin.DebugMode.Value)
					{
						ParryPlugin.Log.LogInfo((object)$"[PARRY] Player attacked (outside grace period) - reset consecutive parries (was {consecutiveParries})");
					}
				}
			}
			else if (num < 0.1f)
			{
				if (ParryPlugin.DebugMode.Value)
				{
					ParryPlugin.Log.LogInfo((object)"[RESET] Skipping reset - recent parry");
				}
			}
			else
			{
				if (!((Object)(object)__instance != (Object)null) || !__instance.IsPlayer || !__instance.IsLocalPlayer)
				{
					return;
				}
				int consecutiveParries = ParryTracker.consecutiveParries;
				if (consecutiveParries > 0)
				{
					ParryTracker.consecutiveParries = 0;
					ParryTracker.hasTriggered5Parries = false;
					ParryTracker.hasTriggered10Parries = false;
					if (ParryPlugin.DebugMode.Value)
					{
						ParryPlugin.Log.LogInfo((object)$"[PARRY] Player took damage - reset consecutive parries (was {consecutiveParries})");
					}
				}
			}
		}
		catch (Exception arg)
		{
			ParryPlugin.Log.LogError((object)$"[PARRY] Reset patch error: {arg}");
		}
	}
}