Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PPR v1.2.5
BepInEx/plugins/KorCaptain-PPR/PPR.dll
Decompiled 3 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("PPR")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("PPR")] [assembly: AssemblyTitle("PPR")] [assembly: AssemblyVersion("1.0.0.0")] namespace PPR; [BepInPlugin("com.KorCaptain.PPR", "PPR", "1.2.5")] public class Plugin : BaseUnityPlugin { public const string PluginGUID = "com.KorCaptain.PPR"; public const string PluginName = "PPR"; public const string PluginVersion = "1.2.5"; internal static ManualLogSource Log; internal static Plugin Instance; private readonly Harmony _harmony = new Harmony("com.KorCaptain.PPR"); private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; PPRConfig.Init(((BaseUnityPlugin)this).Config); _harmony.PatchAll(); Log.LogInfo((object)"PPR v1.2.5 loaded."); } } internal static class PPRAccess { public static ItemData GetCurrentWeapon(Humanoid h) { return Traverse.Create((object)h).Method("GetCurrentWeapon", Array.Empty<object>()).GetValue<ItemData>(); } public static ItemData GetCurrentBlocker(Humanoid h) { return Traverse.Create((object)h).Method("GetCurrentBlocker", Array.Empty<object>()).GetValue<ItemData>(); } public static bool HaveSecondaryAttack(ItemData item) { return Traverse.Create((object)item).Method("HaveSecondaryAttack", Array.Empty<object>()).GetValue<bool>(); } 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<bool>(); } public static bool IsStaggering(Character c) { return Traverse.Create((object)c).Method("IsStaggering", Array.Empty<object>()).GetValue<bool>(); } public static bool InAttack(Character c) { return Traverse.Create((object)c).Method("InAttack", Array.Empty<object>()).GetValue<bool>(); } public static bool CanMove(Character c) { return Traverse.Create((object)c).Method("CanMove", Array.Empty<object>()).GetValue<bool>(); } } [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_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Invalid comparison between Unknown and I4 //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Invalid comparison between Unknown and I4 //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Invalid comparison between Unknown and I4 //IL_0122: Unknown result type (might be due to invalid IL or missing references) try { if (!__result) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return; } bool flag = ___m_blockTimer >= 0f; ItemData currentBlocker = PPRAccess.GetCurrentBlocker(__instance); bool flag2 = currentBlocker != null && currentBlocker.m_shared.m_timedBlockBonus > 1f; if (PPRConfig.Enable.Value && flag && ___m_blockTimer < PPRConfig.ParryWindow.Value && flag2 && PPRAccess.HaveStamina((Character)(object)__instance, 0f) && !PPRAccess.IsStaggering((Character)(object)__instance)) { ItemData currentWeapon = PPRAccess.GetCurrentWeapon(__instance); bool flag3 = false; string text = "무기 없음"; if (currentWeapon != null) { ItemType itemType = currentWeapon.m_shared.m_itemType; bool flag4 = (int)itemType == 3 || (int)itemType == 14 || (int)itemType == 22; bool flag5 = PPRAccess.HaveSecondaryAttack(currentWeapon); if (flag4 && flag5) { PPRState.ActivateParry(val); PPRState.SpawnParryVFX(val); flag3 = true; } else { text = $"itemType={itemType}(eligible={flag4}), hasSecondaryAttack={flag5}"; } } Plugin.Log.LogInfo((object)string.Format("[PPR] 퍼펙트 패링 (blockTimer={0:F3}s, 카운터윈도우={1})", ___m_blockTimer, flag3 ? "활성(데미지감소 가능)" : ("비활성(" + text + ")"))); if (hit.m_ranged && PPRConfig.ProjectileReflect.Value) { PPRProjectileReflectPatch.TryReflect(__instance, hit, attacker, isPerfect: true); } } else if (flag && hit.m_ranged) { if (___m_blockTimer < 0.25f && PPRConfig.NormalParryReflect.Value) { Plugin.Log.LogInfo((object)$"[PPR] 패링 성공(반사) (blockTimer={___m_blockTimer:F3}s)"); PPRProjectileReflectPatch.TryReflect(__instance, hit, attacker, isPerfect: false); } else { 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"; private const string SEC_RANGED = "Perfect Parry - Ranged"; public static ConfigEntry<bool> Enable; public static ConfigEntry<float> ParryWindow; public static ConfigEntry<float> CounterWindow; public static ConfigEntry<float> CounterSpeedMultiplier; public static ConfigEntry<bool> DamageReductionEnabled; public static ConfigEntry<float> DamageReductionPercent; public static ConfigEntry<bool> VFXEnabled; public static ConfigEntry<string> VFXPrefab; public static ConfigEntry<float> VFXOffsetX; public static ConfigEntry<float> VFXOffsetY; public static ConfigEntry<float> VFXOffsetZ; public static ConfigEntry<bool> VFXColorEnabled; public static ConfigEntry<string> VFXColorHex; public static ConfigEntry<bool> RangedNoStagger; public static ConfigEntry<bool> ProjectileReflect; public static ConfigEntry<float> ProjectileReflectDamageMult; public static ConfigEntry<bool> NormalParryReflect; public static ConfigEntry<float> NormalParryReflectDamageMult; public static void Init(ConfigFile cfg) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected O, but got Unknown //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Expected O, but got Unknown //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Expected O, but got Unknown //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Expected O, but got Unknown //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Expected O, but got Unknown //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Expected O, but got Unknown //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Expected O, but got Unknown //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Expected O, but got Unknown //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Expected O, but got Unknown //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Expected O, but got Unknown //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Expected O, but got Unknown //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Expected O, but got Unknown //IL_0312: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Expected O, but got Unknown Enable = cfg.Bind<bool>("Perfect Parry", "(Enable) Perfect Parry", true, "Enable or disable the Perfect Parry feature."); ParryWindow = cfg.Bind<float>("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)new AcceptableValueRange<float>(0.01f, 0.24f), Array.Empty<object>())); CounterWindow = cfg.Bind<float>("Perfect Parry", "(Counter) Input Window (sec)", 1f, new ConfigDescription("Time after a successful parry during which you can press the secondary attack key to trigger a counter.", (AcceptableValueBase)new AcceptableValueRange<float>(0.1f, 2f), Array.Empty<object>())); CounterSpeedMultiplier = cfg.Bind<float>("Perfect Parry", "(Counter) Animation Speed Multiplier", 2f, new ConfigDescription("Animation speed multiplier for the counter special attack. 2.5 = 2.5x faster.", (AcceptableValueBase)new AcceptableValueRange<float>(1f, 5f), Array.Empty<object>())); DamageReductionEnabled = cfg.Bind<bool>("Perfect Parry - Damage Reduction", "(Enable) Damage Reduction", true, "Reduce incoming damage during the counter window after a successful parry."); DamageReductionPercent = cfg.Bind<float>("Perfect Parry - Damage Reduction", "(Damage Reduction) Reduction Rate (%)", 50f, new ConfigDescription("Damage reduction percentage during the counter window. 50 = 50% less damage.", (AcceptableValueBase)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); VFXEnabled = cfg.Bind<bool>("Perfect Parry - Effect", "(Enable) Head Effect", true, "Show a VFX above the player's head on successful parry."); VFXPrefab = cfg.Bind<string>("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<float>("Perfect Parry - Effect", "(Effect) Offset X", 0f, new ConfigDescription("Horizontal offset (left/right) relative to the player.", (AcceptableValueBase)new AcceptableValueRange<float>(-5f, 5f), Array.Empty<object>())); VFXOffsetY = cfg.Bind<float>("Perfect Parry - Effect", "(Effect) Offset Y", 1.25f, new ConfigDescription("Vertical offset (height) relative to the player. Default 2.5 = above head.", (AcceptableValueBase)new AcceptableValueRange<float>(-2f, 8f), Array.Empty<object>())); VFXOffsetZ = cfg.Bind<float>("Perfect Parry - Effect", "(Effect) Offset Z", 0f, new ConfigDescription("Depth offset (forward/back) relative to the player.", (AcceptableValueBase)new AcceptableValueRange<float>(-5f, 5f), Array.Empty<object>())); VFXColorEnabled = cfg.Bind<bool>("Perfect Parry - Effect", "(Effect) Custom Color Enabled", true, "Apply a custom color to the perfect parry VFX. Only affects the PPR-spawned effect, not the vanilla block VFX."); VFXColorHex = cfg.Bind<string>("Perfect Parry - Effect", "(Effect) Custom Color (Hex)", "#4DB8FF", "Hex color code for the perfect parry VFX. Examples: #FF4444 (red), #44FF88 (green), #4DB8FF (blue), #FFD700 (gold)."); RangedNoStagger = cfg.Bind<bool>("Perfect Parry - Ranged", "(Enable) Ranged No Stagger", true, "Ranged attackers are not staggered when their projectile is blocked or parried."); ProjectileReflect = cfg.Bind<bool>("Perfect Parry - Ranged", "(Enable) Projectile Reflect", true, "Reflect projectiles back on perfect parry."); ProjectileReflectDamageMult = cfg.Bind<float>("Perfect Parry - Ranged", "(Reflect) Perfect Parry Damage Multiplier", 3f, new ConfigDescription("Damage multiplier for reflected projectiles on perfect parry.", (AcceptableValueBase)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); NormalParryReflect = cfg.Bind<bool>("Perfect Parry - Ranged", "(Enable) Normal Parry Reflect", true, "Reflect projectiles on normal parry (vanilla timed block window, outside perfect parry window)."); NormalParryReflectDamageMult = cfg.Bind<float>("Perfect Parry - Ranged", "(Reflect) Normal Parry Damage Multiplier", 2f, new ConfigDescription("Damage multiplier for reflected projectiles on normal parry.", (AcceptableValueBase)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); } } [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 (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && PPRState.IsParryWindowActive(val) && hit != null) { float totalDamage = ((DamageTypes)(ref hit.m_damage)).GetTotalDamage(); float num = 1f - PPRConfig.DamageReductionPercent.Value / 100f; ((DamageTypes)(ref hit.m_damage)).Modify(num); float totalDamage2 = ((DamageTypes)(ref hit.m_damage)).GetTotalDamage(); Plugin.Log.LogInfo((object)("[PPR] 데미지 감소 적용 (카운터윈도우, 퍼펙트패링) - " + $"배율={num:F2}x({PPRConfig.DamageReductionPercent.Value}%), 적용전={totalDamage:F1} → 적용후={totalDamage2:F1}")); } } } catch (Exception ex) { Plugin.Log.LogError((object)("[PPR] DamagePatch 오류: " + ex.Message)); } } } [HarmonyPatch] public static class PPRProjectileReflectPatch { private static Projectile _currentProjectile = null; private static Aoe _currentAoe = null; private static DamageTypes _originalDamage; private static readonly FieldInfo _fVel = AccessTools.Field(typeof(Projectile), "m_vel"); private static readonly FieldInfo _fOwner = AccessTools.Field(typeof(Projectile), "m_owner"); private static readonly FieldInfo _fOrigHit = AccessTools.Field(typeof(Projectile), "m_originalHitData"); private static readonly FieldInfo _fDamage = AccessTools.Field(typeof(Projectile), "m_damage"); private static readonly FieldInfo _fBlock = AccessTools.Field(typeof(Projectile), "m_blockable"); private static readonly FieldInfo _fDodge = AccessTools.Field(typeof(Projectile), "m_dodgeable"); private static readonly FieldInfo _fForce = AccessTools.Field(typeof(Projectile), "m_attackForce"); private static readonly FieldInfo _fBackstab = AccessTools.Field(typeof(Projectile), "m_backstabBonus"); private static readonly FieldInfo _fHealth = AccessTools.Field(typeof(Projectile), "m_healthReturn"); private static readonly FieldInfo _fSEHash = AccessTools.Field(typeof(Projectile), "m_statusEffectHash"); [HarmonyPrefix] [HarmonyPatch(typeof(Projectile), "OnHit")] public static void OnHit_Prefix(Projectile __instance) { _currentProjectile = __instance; } [HarmonyPostfix] [HarmonyPatch(typeof(Projectile), "OnHit")] public static void OnHit_Postfix() { _currentProjectile = null; } [HarmonyPrefix] [HarmonyPatch(typeof(Aoe), "OnHit", new Type[] { typeof(Collider), typeof(Vector3) })] public static bool AoeOnHit_Prefix(Aoe __instance) { _currentAoe = __instance; return true; } [HarmonyPostfix] [HarmonyPatch(typeof(Aoe), "OnHit", new Type[] { typeof(Collider), typeof(Vector3) })] public static void AoeOnHit_Postfix() { _currentAoe = null; } [HarmonyPrefix] [HarmonyPatch(typeof(Humanoid), "BlockAttack")] public static void BlockAttack_Prefix(HitData hit) { //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) if ((Object)(object)_currentProjectile != (Object)null || (Object)(object)_currentAoe != (Object)null) { _originalDamage = ((DamageTypes)(ref hit.m_damage)).Clone(); } } public static void TryReflect(Humanoid blocker, HitData hit, Character attacker, bool isPerfect) { //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: 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_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0183: 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_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Expected O, but got Unknown //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Unknown result type (might be due to invalid IL or missing references) Component val = (Component)(((object)_currentProjectile) ?? ((object)_currentAoe)); if ((Object)(object)val == (Object)null || (Object)(object)attacker == (Object)null) { return; } try { string text = ((Object)val.gameObject).name.Replace("(Clone)", "").Trim(); ZNetScene instance = ZNetScene.instance; GameObject val2 = ((instance != null) ? instance.GetPrefab(text) : null); if ((Object)(object)val2 == (Object)null) { Plugin.Log.LogWarning((object)("[PPR] 투사체 반사: prefab 없음 (" + text + ")")); return; } float num = 10f; if ((Object)(object)_currentProjectile != (Object)null && _fVel != null) { Vector3 val3 = (Vector3)_fVel.GetValue(_currentProjectile); num = Mathf.Max(((Vector3)(ref val3)).magnitude, 10f); } else if ((Object)(object)_currentAoe != (Object)null) { Rigidbody component = ((Component)_currentAoe).GetComponent<Rigidbody>(); if ((Object)(object)component != (Object)null) { Vector3 linearVelocity = component.linearVelocity; num = Mathf.Max(((Vector3)(ref linearVelocity)).magnitude, 10f); } } Vector3 val4 = ((Character)blocker).GetLookDir(); if (((Vector3)(ref val4)).sqrMagnitude < 0.001f) { val4 = ((Component)blocker).transform.forward; } Vector3 val5 = ((Component)blocker).transform.position + Vector3.up * 1.5f; GameObject val6 = Object.Instantiate<GameObject>(val2, val5, Quaternion.LookRotation(val4)); float num2 = (isPerfect ? PPRConfig.ProjectileReflectDamageMult.Value : PPRConfig.NormalParryReflectDamageMult.Value); HitData val7 = new HitData(); val7.m_damage = ((DamageTypes)(ref _originalDamage)).Clone(); ((DamageTypes)(ref val7.m_damage)).Modify(num2); val7.m_pushForce = hit.m_pushForce; val7.m_toolTier = hit.m_toolTier; val7.m_ranged = true; val7.SetAttacker((Character)(object)blocker); Projectile component2 = val6.GetComponent<Projectile>(); if ((Object)(object)component2 != (Object)null) { SetupProjectileReflection(component2, (Character)(object)blocker, val4 * num, val7); } else { IProjectile val8 = val6.GetComponent<IProjectile>() ?? val6.GetComponentInChildren<IProjectile>(); if (val8 == null) { ZNetScene instance2 = ZNetScene.instance; if (instance2 != null) { instance2.Destroy(val6); } return; } val8.Setup((Character)(object)blocker, val4 * num, -1f, val7, (ItemData)null, (ItemData)null); } string text2 = (isPerfect ? "퍼펙트" : "일반"); Plugin.Log.LogInfo((object)("[PPR] " + text2 + " 투사체 반사: " + text + " " + $"(speed={num:F1}, dmg={((DamageTypes)(ref val7.m_damage)).GetTotalDamage():F1}x{num2})")); } catch (Exception arg) { Plugin.Log.LogError((object)$"[PPR] 투사체 반사 오류: {arg}"); } } private static void SetupProjectileReflection(Projectile proj, Character owner, Vector3 vel, HitData hitData) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) _fOwner?.SetValue(proj, owner); _fVel?.SetValue(proj, vel); _fOrigHit?.SetValue(proj, hitData); _fDamage?.SetValue(proj, hitData.m_damage); _fBlock?.SetValue(proj, hitData.m_blockable); _fDodge?.SetValue(proj, hitData.m_dodgeable); _fForce?.SetValue(proj, hitData.m_pushForce); _fBackstab?.SetValue(proj, hitData.m_backstabBonus); _fHealth?.SetValue(proj, hitData.m_healthReturn); _fSEHash?.SetValue(proj, hitData.m_statusEffectHash); } } [HarmonyPatch] public static class PPRRangedNoStaggerPatch { private static Character _patchedAttacker; private static bool _savedStaggerWhenBlocked; [HarmonyPrefix] [HarmonyPatch(typeof(Humanoid), "BlockAttack")] public static void BlockAttack_Prefix(HitData hit, Character attacker) { _patchedAttacker = null; if (PPRConfig.RangedNoStagger.Value && !((Object)(object)attacker == (Object)null)) { bool flag = hit?.m_ranged ?? false; bool flag2 = IsRangedWeaponEquipped(attacker); if (flag || flag2) { _patchedAttacker = attacker; _savedStaggerWhenBlocked = attacker.m_staggerWhenBlocked; attacker.m_staggerWhenBlocked = false; Plugin.Log.LogInfo((object)$"[PPR] 원거리 BlockAttack: m_staggerWhenBlocked=false (rangedHit={flag}, rangedWeapon={flag2}, attacker={((Object)attacker).name})"); } } } [HarmonyPostfix] [HarmonyPatch(typeof(Humanoid), "BlockAttack")] public static void BlockAttack_Postfix() { if ((Object)(object)_patchedAttacker != (Object)null) { _patchedAttacker.m_staggerWhenBlocked = _savedStaggerWhenBlocked; _patchedAttacker = null; } } private static bool IsRangedWeaponEquipped(Character c) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected I4, but got Unknown try { if ((Object)(object)c == (Object)null) { return false; } Humanoid val = (Humanoid)(object)((c is Humanoid) ? c : null); if ((Object)(object)val == (Object)null) { return false; } ItemData currentWeapon = val.GetCurrentWeapon(); if (currentWeapon == null) { return false; } int num = (int)currentWeapon.m_shared.m_itemType; return num == 5 || num == 19; } catch { return false; } } } [HarmonyPatch(typeof(ZNetScene), "RemoveObjects")] public static class PPRRemoveObjectsDiagnosticsPatch { private const int DetailedLogLimit = 5; private const int SampledLogInterval = 100; private static int _exceptionCount = 0; private static readonly HashSet<string> BossaddKnownVfx = new HashSet<string>(StringComparer.Ordinal) { "lightningAOE", "vfx_GodExplosion", "fx_eikthyr_stomp", "vfx_spawn_small" }; [HarmonyFinalizer] public static Exception Finalizer(Exception __exception) { if (__exception == null) { return null; } int num = ++_exceptionCount; if (num <= 5) { LogGhostEntries(num); Plugin.Log.LogError((object)$"[RO-DIAG] RemoveObjects 예외 #{num}:\n{__exception}"); } else if (num % 100 == 0) { Plugin.Log.LogError((object)$"[RO-DIAG] RemoveObjects 예외 누적 {num}회 ({100}회마다 샘플 로그)"); } return __exception; } private static void LogGhostEntries(int occurrence) { try { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return; } StringBuilder stringBuilder = new StringBuilder(); int num = 0; foreach (KeyValuePair<ZDO, ZNetView> instance2 in instance.m_instances) { if (instance2.Value != null && (Object)(object)instance2.Value == (Object)null) { num++; string text = ResolvePrefabName(instance, instance2.Key); string arg = ClassifyOwner(text); stringBuilder.AppendLine($" - zdo={instance2.Key} prefab='{text}' owner={arg}"); } } if (num > 0) { Plugin.Log.LogError((object)$"[VFX-DIAG] RemoveObjects 예외 #{occurrence} — 유령 ZNetView {num}개 발견:\n{stringBuilder}"); } else { Plugin.Log.LogWarning((object)$"[VFX-DIAG] RemoveObjects 예외 #{occurrence} — 유령 ZNetView 스캔에서 못 찾음 (다른 원인 가능성, 스택트레이스 참고)."); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[VFX-DIAG] 유령 항목 스캔 중 추가 예외(무시): " + ex.Message)); } } private static string ResolvePrefabName(ZNetScene scene, ZDO zdo) { try { int prefab = zdo.GetPrefab(); GameObject prefab2 = scene.GetPrefab(prefab); return ((Object)(object)prefab2 != (Object)null) ? ((Object)prefab2).name : $"(unknown hash:{prefab})"; } catch { return "(zdo 접근 실패)"; } } private static string ClassifyOwner(string prefabName) { if (string.IsNullOrEmpty(prefabName)) { return "UNKNOWN"; } string text = PPRConfig.VFXPrefab?.Value; if (!string.IsNullOrEmpty(text) && string.Equals(prefabName, text, StringComparison.Ordinal)) { return "PPR"; } if (BossaddKnownVfx.Contains(prefabName)) { return "bossadd"; } return "UNKNOWN(직접 확인 필요)"; } } [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 <Postfix>b__1_0(Character character, double speed) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown try { if (!PPRConfig.Enable.Value) { return speed; } Player val = (Player)((character is Player) ? character : null); if ((Object)(object)val == (Object)null) { return speed; } if (!PPRState.IsCounterSpeedActive(val)) { return speed; } if (!PPRAccess.InAttack((Character)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) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown try { if (!PPRConfig.Enable.Value) { return speed; } Player val2 = (Player)((character is Player) ? character : null); if ((Object)(object)val2 == (Object)null) { return speed; } if (!PPRState.IsCounterSpeedActive(val2)) { return speed; } if (!PPRAccess.InAttack((Character)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 콜백 등록 완료."); } } [HarmonyPatch(typeof(Humanoid), "StartAttack")] public static class PPRStartAttackPatch { private static readonly FieldInfo _timeSinceLastAttackField = AccessTools.Field(typeof(Humanoid), "m_timeSinceLastAttack"); private static readonly FieldInfo _currentAttackIsSecondaryField = AccessTools.Field(typeof(Humanoid), "m_currentAttackIsSecondary"); private static bool _counterHandled = false; private static float _savedTimeSinceLastAttack = 0f; [HarmonyPrefix] public static void Prefix(Humanoid __instance, bool secondaryAttack) { _counterHandled = false; try { if (!PPRConfig.Enable.Value || !secondaryAttack) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)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); return; } _savedTimeSinceLastAttack = ((_timeSinceLastAttackField != null) ? ((float)_timeSinceLastAttackField.GetValue(__instance)) : 0f); _counterHandled = true; 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)); } } [HarmonyPostfix] public static void Postfix(Humanoid __instance, bool secondaryAttack) { if (!_counterHandled) { return; } _counterHandled = false; try { _timeSinceLastAttackField?.SetValue(__instance, _savedTimeSinceLastAttack); _currentAttackIsSecondaryField?.SetValue(__instance, false); } catch (Exception ex) { Plugin.Log.LogError((object)("[PPR] StartAttackPatch Postfix 오류: " + ex.Message)); } } } public static class PPRState { private static readonly Dictionary<Player, float> _counterWindow = new Dictionary<Player, float>(); private static readonly HashSet<Player> _speedActive = new HashSet<Player>(); private static readonly Dictionary<Player, bool> _prevInAttack = new Dictionary<Player, bool>(); private static readonly Dictionary<Player, GameObject> _parryVFX = new Dictionary<Player, GameObject>(); private static readonly Dictionary<Player, Coroutine> _pendingVfxSpawn = new Dictionary<Player, Coroutine>(); 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_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: 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)) { return; } Vector3 offset = new Vector3(PPRConfig.VFXOffsetX.Value, PPRConfig.VFXOffsetY.Value, PPRConfig.VFXOffsetZ.Value); Vector3 position = ((Component)player).transform.position + offset; Coroutine val = PPRVFXHelper.SpawnLocalVFXDeferred((MonoBehaviour)(object)Plugin.Instance, value, position, Quaternion.identity, delegate(GameObject vfxObj) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) _pendingVfxSpawn.Remove(player); if (!((Object)(object)vfxObj == (Object)null)) { if ((Object)(object)player == (Object)null) { Object.Destroy((Object)(object)vfxObj); } else { vfxObj.transform.SetParent(((Component)player).transform, false); vfxObj.transform.localPosition = offset; ApplyVFXColor(vfxObj); vfxObj.SetActive(true); _parryVFX[player] = vfxObj; Plugin.Log.LogInfo((object)("[PPR] VFX 스폰: " + value)); } } }); if (val != null) { _pendingVfxSpawn[player] = val; } } private static void ApplyVFXColor(GameObject vfxObj) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: 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_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: 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) //IL_00d2: 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_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Expected O, but got Unknown //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_014e: 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_0167: Unknown result type (might be due to invalid IL or missing references) //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02e6: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_02f9: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_020b: 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_032d: Unknown result type (might be due to invalid IL or missing references) if (!PPRConfig.VFXColorEnabled.Value) { return; } string text = PPRConfig.VFXColorHex.Value?.Trim() ?? "#4DB8FF"; if (!text.StartsWith("#")) { text = "#" + text; } Color val = default(Color); if (!ColorUtility.TryParseHtmlString(text, ref val)) { Plugin.Log.LogWarning((object)("[PPR] VFX 색상 파싱 실패: " + text)); return; } ParticleSystem[] componentsInChildren = vfxObj.GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem val2 in componentsInChildren) { MainModule main = val2.main; MinMaxGradient val3 = ((MainModule)(ref main)).startColor; Color color = ((MinMaxGradient)(ref val3)).color; ((MainModule)(ref main)).startColor = new MinMaxGradient(new Color(val.r, val.g, val.b, color.a)); ColorOverLifetimeModule colorOverLifetime = val2.colorOverLifetime; if (!((ColorOverLifetimeModule)(ref colorOverLifetime)).enabled) { continue; } val3 = ((ColorOverLifetimeModule)(ref colorOverLifetime)).color; Gradient gradient = ((MinMaxGradient)(ref val3)).gradient; Gradient val4 = new Gradient(); GradientColorKey[] array = (GradientColorKey[])(object)new GradientColorKey[(gradient != null) ? gradient.colorKeys.Length : 2]; if (gradient != null && gradient.colorKeys.Length != 0) { for (int j = 0; j < gradient.colorKeys.Length; j++) { array[j] = new GradientColorKey(val, gradient.colorKeys[j].time); } } else { array[0] = new GradientColorKey(val, 0f); array[1] = new GradientColorKey(val, 1f); } GradientAlphaKey[] array2 = (GradientAlphaKey[])((gradient != null && gradient.alphaKeys.Length != 0) ? ((Array)gradient.alphaKeys) : ((Array)new GradientAlphaKey[2] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(0f, 1f) })); val4.SetKeys(array, array2); ((ColorOverLifetimeModule)(ref colorOverLifetime)).color = new MinMaxGradient(val4); } Light[] componentsInChildren2 = vfxObj.GetComponentsInChildren<Light>(true); foreach (Light val5 in componentsInChildren2) { val5.color = val; } Renderer[] componentsInChildren3 = vfxObj.GetComponentsInChildren<Renderer>(true); foreach (Renderer val6 in componentsInChildren3) { Material[] materials = val6.materials; foreach (Material val7 in materials) { if (val7.HasProperty("_Color")) { val7.color = new Color(val.r, val.g, val.b, val7.color.a); } if (val7.HasProperty("_TintColor")) { val7.SetColor("_TintColor", new Color(val.r, val.g, val.b, val7.GetColor("_TintColor").a)); } if (val7.HasProperty("_EmissionColor")) { val7.SetColor("_EmissionColor", val * 2f); } } } Plugin.Log.LogInfo((object)("[PPR] VFX 색상 적용: " + text)); } public static void DestroyParryVFX(Player player) { if (_pendingVfxSpawn.TryGetValue(player, out var value) && value != null && (Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StopCoroutine(value); } _pendingVfxSpawn.Remove(player); if (_parryVFX.TryGetValue(player, out var value2) && (Object)(object)value2 != (Object)null) { Object.Destroy((Object)(object)value2); } _parryVFX.Remove(player); } } [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)); } } } public static class PPRVFXHelper { private const int StackTraceLogLimit = 3; private static int _spawnCallCount; public static Coroutine SpawnLocalVFXDeferred(MonoBehaviour host, string prefabName, Vector3 position, Quaternion rotation, Action<GameObject> onSpawned) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)host == (Object)null) { onSpawned?.Invoke(SpawnLocalVFXImmediate(prefabName, position, rotation)); return null; } return host.StartCoroutine(SpawnLocalVFXRoutine(prefabName, position, rotation, onSpawned)); } private static IEnumerator SpawnLocalVFXRoutine(string prefabName, Vector3 position, Quaternion rotation, Action<GameObject> onSpawned) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) yield return null; onSpawned?.Invoke(SpawnLocalVFXImmediate(prefabName, position, rotation)); } private static GameObject SpawnLocalVFXImmediate(string prefabName, Vector3 position, Quaternion rotation) { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(prefabName) || (Object)(object)ZNetScene.instance == (Object)null) { return null; } GameObject prefab = ZNetScene.instance.GetPrefab(prefabName); if ((Object)(object)prefab == (Object)null) { Plugin.Log.LogWarning((object)("[PPR] VFX 프리팹 없음: " + prefabName)); return null; } int num = ++_spawnCallCount; if (num <= 3) { Plugin.Log.LogInfo((object)$"[VFX-DIAG] SpawnLocalVFX 실행#{num} 호출 스택:\n{new StackTrace(0, fNeedFileInfo: true)}"); } bool activeSelf = prefab.activeSelf; prefab.SetActive(false); GameObject val = Object.Instantiate<GameObject>(prefab, position, rotation); prefab.SetActive(activeSelf); DestroyImmediateAndVerify<ZNetView>(val, "ZNetView", num); DestroyImmediateAndVerify<ZSyncTransform>(val, "ZSyncTransform", num); DestroyImmediateAndVerify<ZSyncAnimation>(val, "ZSyncAnimation", num); DestroyImmediateAndVerify<Aoe>(val, "Aoe", num); return val; } private static void DestroyImmediateAndVerify<T>(GameObject obj, string label, int callIndex) where T : Component { T[] componentsInChildren = obj.GetComponentsInChildren<T>(true); foreach (T val in componentsInChildren) { Object.DestroyImmediate((Object)(object)val); } T[] componentsInChildren2 = obj.GetComponentsInChildren<T>(true); if (componentsInChildren2.Length != 0) { Plugin.Log.LogError((object)$"[VFX-DIAG] SpawnLocalVFX 실행#{callIndex} — DestroyImmediate 거부 감지: {label} {componentsInChildren2.Length}개가 제거 후에도 여전히 존재함 (물리/애니메이션 콜백 컨텍스트 의심)"); } } } [HarmonyPatch(typeof(Humanoid), "BlockAttack")] public static class PPRVFXSuppressPatch { private static EffectList _targetList; private static EffectData[] _savedEffects; [HarmonyPrefix] public static void Prefix(Humanoid __instance, float ___m_blockTimer) { _targetList = null; _savedEffects = null; if (!PPRConfig.Enable.Value || !PPRConfig.VFXEnabled.Value || !PPRConfig.VFXColorEnabled.Value) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer)) { ItemData currentBlocker = PPRAccess.GetCurrentBlocker(__instance); if (currentBlocker != null && !(currentBlocker.m_shared.m_timedBlockBonus <= 1f) && ___m_blockTimer >= 0f && ___m_blockTimer < PPRConfig.ParryWindow.Value) { _targetList = currentBlocker.m_shared.m_blockEffect; _savedEffects = _targetList.m_effectPrefabs; _targetList.m_effectPrefabs = Array.Empty<EffectData>(); } } } [HarmonyPostfix] public static void Postfix() { if (_targetList != null && _savedEffects != null) { _targetList.m_effectPrefabs = _savedEffects; _targetList = null; _savedEffects = null; } } }