using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace PPR { internal static class PPRAccess { public static ItemData GetCurrentWeapon(Humanoid h) { return Traverse.Create((object)h).Method("GetCurrentWeapon", Array.Empty()).GetValue(); } public static ItemData GetCurrentBlocker(Humanoid h) { return Traverse.Create((object)h).Method("GetCurrentBlocker", Array.Empty()).GetValue(); } public static bool HaveSecondaryAttack(ItemData item) { return Traverse.Create((object)item).Method("HaveSecondaryAttack", Array.Empty()).GetValue(); } public static bool HaveStamina(Character c, float amount) { return Traverse.Create((object)c).Method("HaveStamina", new Type[1] { typeof(float) }, new object[1] { amount }).GetValue(); } public static bool IsStaggering(Character c) { return Traverse.Create((object)c).Method("IsStaggering", Array.Empty()).GetValue(); } public static bool InAttack(Character c) { return Traverse.Create((object)c).Method("InAttack", Array.Empty()).GetValue(); } public static bool CanMove(Character c) { return Traverse.Create((object)c).Method("CanMove", Array.Empty()).GetValue(); } } [HarmonyPatch(typeof(Humanoid), "StartAttack")] public static class PPRStartAttackPatch { [HarmonyPrefix] public static void Prefix(Humanoid __instance, bool secondaryAttack) { try { if (!PPRConfig.Enable.Value || !secondaryAttack) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && PPRState.IsParryWindowActive(val)) { ItemData currentWeapon = PPRAccess.GetCurrentWeapon(__instance); if (currentWeapon == null || !PPRAccess.HaveSecondaryAttack(currentWeapon)) { Plugin.Log.LogWarning((object)"[PPR] 카운터 실패 - 현재 무기에 특수 공격 없음"); PPRState.ConsumeParry(val); } else { PPRState.ConsumeParry(val); PPRState.ActivateCounterSpeed(val); Plugin.Log.LogInfo((object)$"[PPR] 카운터 발동! InAttack={PPRAccess.InAttack((Character)(object)__instance)}, CanMove={PPRAccess.CanMove((Character)(object)__instance)}"); } } } catch (Exception ex) { Plugin.Log.LogError((object)("[PPR] StartAttackPatch 오류: " + ex.Message)); } } } [HarmonyPatch(typeof(Player), "Update")] public static class PPRTickPatch { [HarmonyPostfix] public static void Postfix(Player __instance) { try { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { PPRState.Tick(__instance, Time.deltaTime); PPRState.TickCounterSpeed(__instance, PPRAccess.InAttack((Character)(object)__instance)); } } catch (Exception ex) { Plugin.Log.LogError((object)("[PPR] TickPatch 오류: " + ex.Message)); } } } [HarmonyPatch(typeof(Humanoid), "BlockAttack")] public static class PPRBlockPatch { [HarmonyPostfix] public static void Postfix(Humanoid __instance, HitData hit, Character attacker, bool __result, float ___m_blockTimer) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Invalid comparison between Unknown and I4 //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Invalid comparison between Unknown and I4 //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Invalid comparison between Unknown and I4 try { if (!__result || !PPRConfig.Enable.Value) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return; } ItemData currentWeapon = PPRAccess.GetCurrentWeapon(__instance); if (currentWeapon == null) { return; } ItemType itemType = currentWeapon.m_shared.m_itemType; if (((int)itemType == 3 || (int)itemType == 14 || (int)itemType == 22) && PPRAccess.HaveSecondaryAttack(currentWeapon)) { ItemData currentBlocker = PPRAccess.GetCurrentBlocker(__instance); if (currentBlocker != null && !(currentBlocker.m_shared.m_timedBlockBonus <= 1f) && PPRAccess.HaveStamina((Character)(object)__instance, 0f) && !PPRAccess.IsStaggering((Character)(object)__instance) && !(___m_blockTimer < 0f) && !(___m_blockTimer >= PPRConfig.ParryWindow.Value)) { PPRState.ActivateParry(val); PPRState.SpawnParryVFX(val); Plugin.Log.LogInfo((object)$"[PPR] 패링 감지 (blockTimer={___m_blockTimer:F3}s)"); } } } catch (Exception ex) { Plugin.Log.LogError((object)("[PPR] BlockPatch 오류: " + ex.Message)); } } } public static class PPRConfig { private const string SEC = "Perfect Parry"; private const string SEC_DMG = "Perfect Parry - Damage Reduction"; private const string SEC_VFX = "Perfect Parry - Effect"; public static ConfigEntry Enable; public static ConfigEntry ParryWindow; public static ConfigEntry CounterWindow; public static ConfigEntry CounterSpeedMultiplier; public static ConfigEntry DamageReductionEnabled; public static ConfigEntry DamageReductionPercent; public static ConfigEntry VFXEnabled; public static ConfigEntry VFXPrefab; public static ConfigEntry VFXOffsetX; public static ConfigEntry VFXOffsetY; public static ConfigEntry VFXOffsetZ; public static void Init(ConfigFile cfg) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Expected O, but got Unknown //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Expected O, but got Unknown //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Expected O, but got Unknown //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Expected O, but got Unknown Enable = cfg.Bind("Perfect Parry", "(Enable) Perfect Parry", true, "Enable or disable the Perfect Parry feature."); ParryWindow = cfg.Bind("Perfect Parry", "(Parry) Detection Window (sec)", 0.1f, new ConfigDescription("Time window to register a perfect parry. Must be shorter than the base perfect block window (0.25s).", (AcceptableValueBase)(object)new AcceptableValueRange(0.01f, 0.24f), Array.Empty())); CounterWindow = cfg.Bind("Perfect Parry", "(Counter) Input Window (sec)", 0.6f, new ConfigDescription("Time after a successful parry during which you can press the secondary attack key to trigger a counter.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 2f), Array.Empty())); CounterSpeedMultiplier = cfg.Bind("Perfect Parry", "(Counter) Animation Speed Multiplier", 2.5f, new ConfigDescription("Animation speed multiplier for the counter special attack. 2.5 = 2.5x faster.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 5f), Array.Empty())); DamageReductionEnabled = cfg.Bind("Perfect Parry - Damage Reduction", "(Enable) Damage Reduction", true, "Reduce incoming damage during the counter window after a successful parry."); DamageReductionPercent = cfg.Bind("Perfect Parry - Damage Reduction", "(Damage Reduction) Reduction Rate (%)", 50f, new ConfigDescription("Damage reduction percentage during the counter window. 50 = 50% less damage.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); VFXEnabled = cfg.Bind("Perfect Parry - Effect", "(Enable) Head Effect", true, "Show a VFX above the player's head on successful parry."); VFXPrefab = cfg.Bind("Perfect Parry - Effect", "(Effect) VFX Name", "vfx_blocked", "Name of the VFX prefab to display. Must be a valid ZNetScene prefab name."); VFXOffsetX = cfg.Bind("Perfect Parry - Effect", "(Effect) Offset X", 0f, new ConfigDescription("Horizontal offset (left/right) relative to the player.", (AcceptableValueBase)(object)new AcceptableValueRange(-5f, 5f), Array.Empty())); VFXOffsetY = cfg.Bind("Perfect Parry - Effect", "(Effect) Offset Y", 2.5f, new ConfigDescription("Vertical offset (height) relative to the player. Default 2.5 = above head.", (AcceptableValueBase)(object)new AcceptableValueRange(-2f, 8f), Array.Empty())); VFXOffsetZ = cfg.Bind("Perfect Parry - Effect", "(Effect) Offset Z", 0f, new ConfigDescription("Depth offset (forward/back) relative to the player.", (AcceptableValueBase)(object)new AcceptableValueRange(-5f, 5f), Array.Empty())); } } [HarmonyPatch(typeof(Character), "ApplyDamage")] public static class PPRDamagePatch { [HarmonyPrefix] public static void Prefix(Character __instance, HitData hit) { try { if (PPRConfig.Enable.Value && PPRConfig.DamageReductionEnabled.Value) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && PPRState.IsParryWindowActive(val) && hit != null) { float num = 1f - PPRConfig.DamageReductionPercent.Value / 100f; ((DamageTypes)(ref hit.m_damage)).Modify(num); Plugin.Log.LogInfo((object)$"[PPR] 데미지 감소 적용 ({PPRConfig.DamageReductionPercent.Value}%)"); } } } catch (Exception ex) { Plugin.Log.LogError((object)("[PPR] DamagePatch 오류: " + ex.Message)); } } } [HarmonyPatch(typeof(Game), "Awake")] public static class PPRSpeedRegistrar { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static Handler <>9__1_0; internal double b__1_0(Character character, double speed) { try { if (!PPRConfig.Enable.Value) { return speed; } Player val = (Player)(object)((character is Player) ? character : null); if (val == null) { return speed; } if (!PPRState.IsCounterSpeedActive(val)) { return speed; } if (!PPRAccess.InAttack((Character)(object)val)) { return speed; } return speed * (double)PPRConfig.CounterSpeedMultiplier.Value; } catch (Exception ex) { Plugin.Log.LogError((object)("[PPR] SpeedCallback 오류: " + ex.Message)); return speed; } } } private static bool _registered; [HarmonyPostfix] public static void Postfix() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown if (_registered) { return; } _registered = true; object obj = <>c.<>9__1_0; if (obj == null) { Handler val = delegate(Character character, double speed) { try { if (!PPRConfig.Enable.Value) { return speed; } Player val2 = (Player)(object)((character is Player) ? character : null); if (val2 == null) { return speed; } if (!PPRState.IsCounterSpeedActive(val2)) { return speed; } if (!PPRAccess.InAttack((Character)(object)val2)) { return speed; } return speed * (double)PPRConfig.CounterSpeedMultiplier.Value; } catch (Exception ex) { Plugin.Log.LogError((object)("[PPR] SpeedCallback 오류: " + ex.Message)); return speed; } }; <>c.<>9__1_0 = val; obj = (object)val; } AnimationSpeedManager.Add((Handler)obj, 400); Plugin.Log.LogInfo((object)"[PPR] AnimationSpeedManager 콜백 등록 완료."); } } public static class PPRState { private static readonly Dictionary _counterWindow = new Dictionary(); private static readonly HashSet _speedActive = new HashSet(); private static readonly Dictionary _prevInAttack = new Dictionary(); private static readonly Dictionary _parryVFX = new Dictionary(); public static void ActivateParry(Player player) { _counterWindow[player] = PPRConfig.CounterWindow.Value; Plugin.Log.LogInfo((object)"[PPR] 퍼펙트 패링 성공!"); } public static bool IsParryWindowActive(Player player) { float value; return _counterWindow.TryGetValue(player, out value) && value > 0f; } public static void ConsumeParry(Player player) { _counterWindow[player] = 0f; DestroyParryVFX(player); } public static void Tick(Player player, float dt) { if (_counterWindow.TryGetValue(player, out var value) && !(value <= 0f)) { float num = Mathf.Max(0f, value - dt); _counterWindow[player] = num; if (num <= 0f) { DestroyParryVFX(player); } } } public static void ActivateCounterSpeed(Player player) { _speedActive.Add(player); _prevInAttack[player] = false; } public static bool IsCounterSpeedActive(Player player) { return _speedActive.Contains(player); } public static void TickCounterSpeed(Player player, bool isInAttack) { bool value; if (!_speedActive.Contains(player)) { _prevInAttack.Remove(player); } else if (_prevInAttack.TryGetValue(player, out value) && value && !isInAttack) { DeactivateCounterSpeed(player); } else { _prevInAttack[player] = isInAttack; } } public static void DeactivateCounterSpeed(Player player) { _speedActive.Remove(player); _prevInAttack.Remove(player); Plugin.Log.LogInfo((object)"[PPR] 카운터 속도 해제"); } public static void SpawnParryVFX(Player player) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) DestroyParryVFX(player); if (!PPRConfig.VFXEnabled.Value) { return; } string value = PPRConfig.VFXPrefab.Value; if (string.IsNullOrEmpty(value) || (Object)(object)ZNetScene.instance == (Object)null) { return; } GameObject prefab = ZNetScene.instance.GetPrefab(value); if ((Object)(object)prefab == (Object)null) { Plugin.Log.LogWarning((object)("[PPR] VFX 프리팹 없음: " + value)); return; } Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(PPRConfig.VFXOffsetX.Value, PPRConfig.VFXOffsetY.Value, PPRConfig.VFXOffsetZ.Value); GameObject val2 = Object.Instantiate(prefab, ((Component)player).transform.position + val, Quaternion.identity); val2.transform.SetParent(((Component)player).transform, false); val2.transform.localPosition = val; ZNetView[] componentsInChildren = val2.GetComponentsInChildren(true); foreach (ZNetView val3 in componentsInChildren) { Object.Destroy((Object)(object)val3); } ZSyncTransform[] componentsInChildren2 = val2.GetComponentsInChildren(true); foreach (ZSyncTransform val4 in componentsInChildren2) { Object.Destroy((Object)(object)val4); } _parryVFX[player] = val2; Plugin.Log.LogInfo((object)("[PPR] VFX 스폰: " + value)); } public static void DestroyParryVFX(Player player) { if (_parryVFX.TryGetValue(player, out var value) && (Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } _parryVFX.Remove(player); } } [BepInPlugin("com.KorCaptain.PPR", "PPR", "1.0.0")] public class Plugin : BaseUnityPlugin { public const string PluginGUID = "com.KorCaptain.PPR"; public const string PluginName = "PPR"; public const string PluginVersion = "1.0.0"; internal static ManualLogSource Log; private readonly Harmony _harmony = new Harmony("com.KorCaptain.PPR"); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; PPRConfig.Init(((BaseUnityPlugin)this).Config); _harmony.PatchAll(); Log.LogInfo((object)"PPR v1.0.0 loaded."); } } }