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}");
}
}
}