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 ParryBosses v1.0.0
plugins/ParryBosses.dll
Decompiled a month agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; 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.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("ParryBosses")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("ParryBosses")] [assembly: AssemblyTitle("ParryBosses")] [assembly: AssemblyVersion("0.1.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 ParryBosses { internal static class BossSetup { public static void Apply(Character c) { if (!((Object)(object)c == (Object)null) && c.IsBoss() && BossRegistry.Peek(c) == null) { BossRegistry.Get(c); ParryBossesPlugin.LogBasic(c, "boss-awake"); if (ParryBossesPlugin.CfgForceStaggerWhenBlocked.Value && !c.m_staggerWhenBlocked) { c.m_staggerWhenBlocked = true; ParryBossesPlugin.Log.LogInfo((object)(" -> " + c.m_name + ": m_staggerWhenBlocked false -> true")); } float value = ParryBossesPlugin.CfgBossStaggerDamageFactor.Value; if (value >= 0f && Mathf.Abs(c.m_staggerDamageFactor - value) > 0.0001f) { ParryBossesPlugin.Log.LogInfo((object)$" -> {c.m_name}: m_staggerDamageFactor {c.m_staggerDamageFactor} -> {value}"); c.m_staggerDamageFactor = value; } } } } [HarmonyPatch(typeof(Character), "Awake")] internal static class Patch_Character_Awake { private static void Postfix(Character __instance) { BossSetup.Apply(__instance); } } [HarmonyPatch(typeof(Humanoid), "Awake")] internal static class Patch_Humanoid_Awake { private static void Postfix(Humanoid __instance) { BossSetup.Apply((Character)(object)__instance); } } [HarmonyPatch(typeof(Character), "Stagger", new Type[] { typeof(Vector3) })] internal static class Patch_Character_Stagger { private static void Postfix(Character __instance) { if ((Object)(object)__instance == (Object)null) { return; } if (!__instance.IsBoss()) { StaggerMeter.Begin(__instance); return; } BossRegistry.Get(__instance).Probe(__instance); switch (ParryBossesPlugin.CfgFallback.Value) { case StaggerFallbackMode.Always: ParryBossesPlugin.StartStagger(__instance, "Stagger()"); break; case StaggerFallbackMode.Auto: ParryBossesPlugin.ScheduleFallbackCheck(__instance); ParryBossesPlugin.Log.LogInfo((object)(__instance.m_name + " 被僵直:先交给原版动画(Auto 模式,0.12s 后验证)")); break; default: ParryBossesPlugin.Log.LogInfo((object)(__instance.m_name + " 被僵直:仅原版动画(Fallback=Never)")); break; } } } [HarmonyPatch(typeof(Character), "IsStaggering")] internal static class Patch_Character_IsStaggering { private static bool Prefix(Character __instance, ref bool __result) { BossState bossState = BossRegistry.Peek(__instance); if (bossState == null) { return true; } if (bossState.TimerActive) { __result = true; return false; } return true; } } [HarmonyPatch(typeof(ZSyncAnimation), "RPC_SetTrigger")] internal static class Patch_ZSyncAnimation_RPC_SetTrigger { private static bool Prefix(ZSyncAnimation __instance, string name) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Invalid comparison between Unknown and I4 if ((Object)(object)__instance == (Object)null || string.IsNullOrEmpty(name) || name != "stagger") { return true; } Character component = ((Component)__instance).GetComponent<Character>(); if ((Object)(object)component == (Object)null || !component.IsBoss()) { return true; } if (ParryBossesPlugin.HasAnimatorTrigger(component, "stagger")) { return true; } string value = ParryBossesPlugin.CfgSubstituteTrigger.Value; if (!string.IsNullOrEmpty(value)) { Animator animator = ParryBossesPlugin.GetAnimator(component); if ((Object)(object)animator != (Object)null) { AnimatorControllerParameter[] parameters = animator.parameters; foreach (AnimatorControllerParameter val in parameters) { if ((int)val.type == 9 && val.name == value) { animator.SetTrigger(value); break; } } } } return false; } } public enum StaggerFallbackMode { Always, Auto, Never } public enum FlashMode { PropertyBlock, Material } internal class BossState { public Character Character; public bool HasStaggerTrigger; public bool Probed; public float StaggeredUntil; public bool TimerActive { get { if (StaggeredUntil > 0f) { return Time.time < StaggeredUntil; } return false; } } public void Probe(Character c) { if (!Probed) { HasStaggerTrigger = ParryBossesPlugin.HasAnimatorTrigger(c, "stagger"); Probed = true; } } } internal class StaggerProbe { public Character Character; public float At; public string Label; } internal class StaggerVisual { public Character Character; public float NextEffectAt; public GameObject LightGo; public MaterialFlash Flash; } internal static class BossRegistry { private static readonly Dictionary<int, BossState> States = new Dictionary<int, BossState>(); public static BossState Get(Character c) { if ((Object)(object)c == (Object)null) { return null; } int instanceID = ((Object)c).GetInstanceID(); if (States.TryGetValue(instanceID, out var value) && (Object)(object)value.Character == (Object)(object)c) { return value; } value = new BossState { Character = c }; States[instanceID] = value; return value; } public static BossState Peek(Character c) { if ((Object)(object)c == (Object)null) { return null; } if (!States.TryGetValue(((Object)c).GetInstanceID(), out var value) || !((Object)(object)value.Character == (Object)(object)c)) { return null; } return value; } public static void Prune() { if (States.Count == 0) { return; } List<int> list = new List<int>(); foreach (KeyValuePair<int, BossState> state in States) { if ((Object)(object)state.Value.Character == (Object)null) { list.Add(state.Key); } } foreach (int item in list) { States.Remove(item); } } } [BepInPlugin("dsh.valheim.parrybosses", "Parry Bosses", "1.0.0")] public class ParryBossesPlugin : BaseUnityPlugin { public const string PluginGuid = "dsh.valheim.parrybosses"; public const string PluginName = "Parry Bosses"; public const string PluginVersion = "1.0.0"; internal static ManualLogSource Log; internal static ConfigEntry<bool> CfgForceStaggerWhenBlocked; internal static ConfigEntry<StaggerFallbackMode> CfgFallback; internal static ConfigEntry<float> CfgStaggerSeconds; internal static ConfigEntry<string> CfgSubstituteTrigger; internal static ConfigEntry<float> CfgBossStaggerDamageFactor; internal static ConfigEntry<bool> CfgHudOnStagger; internal static ConfigEntry<bool> CfgParticles; internal static ConfigEntry<float> CfgParticleInterval; internal static ConfigEntry<bool> CfgCritEffect; internal static ConfigEntry<bool> CfgFloatingText; internal static ConfigEntry<string> CfgFloatingTextString; internal static ConfigEntry<bool> CfgLight; internal static ConfigEntry<float> CfgLightRange; internal static ConfigEntry<bool> CfgMaterialFlash; internal static ConfigEntry<FlashMode> CfgFlashMode; internal static ConfigEntry<float> CfgFlashStrength; internal static ConfigEntry<bool> CfgDiagnose; internal static ConfigEntry<bool> CfgMeasure; internal static ConfigEntry<KeyboardShortcut> CfgDumpKey; internal static ConfigEntry<KeyboardShortcut> CfgForceKey; internal static ConfigEntry<KeyboardShortcut> CfgForceMobKey; private static readonly List<StaggerProbe> Probes = new List<StaggerProbe>(); private static readonly List<StaggerVisual> Visuals = new List<StaggerVisual>(); private float _pruneTimer; private void Awake() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Expected O, but got Unknown //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Expected O, but got Unknown //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Expected O, but got Unknown //IL_02e5: Unknown result type (might be due to invalid IL or missing references) //IL_0313: Unknown result type (might be due to invalid IL or missing references) //IL_0341: Unknown result type (might be due to invalid IL or missing references) //IL_035a: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; CfgForceStaggerWhenBlocked = ((BaseUnityPlugin)this).Config.Bind<bool>("1-核心", "ForceStaggerWhenBlocked", true, "把 Boss 的 m_staggerWhenBlocked 强制设为 true(原版 Eikthyr 实测本来就是 true,属保险项)。"); CfgFallback = ((BaseUnityPlugin)this).Config.Bind<StaggerFallbackMode>("1-核心", "StaggerFallback", StaggerFallbackMode.Always, "Always = 每次 Boss 被僵直都用计时器把 IsStaggering() 拉成 true(推荐);Auto = 先让原版动画试,0.12 秒后若动画状态仍未进入 stagger 就自动补计时器;Never = 只用原版。"); CfgStaggerSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("1-核心", "StaggerSeconds", 2f, new ConfigDescription("Boss 被弹反后的僵直时长(秒)。默认 2.0 —— 所有 Boss 统一 2 秒;僵直期间不能移动/攻击,并承受 2 倍伤害。(参考:原版小怪众数 1.5s,训练假人 3.0s,Fader/The Queen 自带僵直动画也是 2.0s)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>())); CfgSubstituteTrigger = ((BaseUnityPlugin)this).Config.Bind<string>("2-表现", "SubstituteTrigger", "", "Boss 没有 stagger 触发器时改播的动画触发器;留空 = 不播(Boss 通常连 hit 都没有)。"); CfgHudOnStagger = ((BaseUnityPlugin)this).Config.Bind<bool>("2-表现", "HudOnStagger", true, "僵直触发时屏幕左上角显示一行提示。"); CfgCritEffect = ((BaseUnityPlugin)this).Config.Bind<bool>("2-表现", "StaggerCritEffect", true, "僵直开始时在 Boss 身上播放暴击/重击特效(m_critHitEffects),即 2 倍伤害的那个特效。"); CfgParticles = ((BaseUnityPlugin)this).Config.Bind<bool>("2-表现", "StaggerParticles", true, "僵直期间每隔一段时间在 Boss 身上重复播放受击特效(m_hitEffects),形成持续的火花。"); CfgParticleInterval = ((BaseUnityPlugin)this).Config.Bind<float>("2-表现", "StaggerParticleInterval", 0.3f, new ConfigDescription("重复特效的间隔(秒)。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 3f), Array.Empty<object>())); CfgFloatingText = ((BaseUnityPlugin)this).Config.Bind<bool>("2-表现", "StaggerFloatingText", true, "僵直开始时在 Boss 头顶弹出漂浮文字(橙色),便于一眼看出触发了僵直。"); CfgFloatingTextString = ((BaseUnityPlugin)this).Config.Bind<string>("2-表现", "StaggerFloatingTextString", "STAGGER!", "漂浮文字内容。Boss 字体不含中文,建议用英文,避免出现方块。"); CfgLight = ((BaseUnityPlugin)this).Config.Bind<bool>("2-表现", "StaggerLight", true, "僵直期间给 Boss 挂一盏暖色点光(闪光),让僵直在视觉上非常明显。"); CfgLightRange = ((BaseUnityPlugin)this).Config.Bind<float>("2-表现", "StaggerLightRange", 8f, new ConfigDescription("点光半径。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 40f), Array.Empty<object>())); CfgMaterialFlash = ((BaseUnityPlugin)this).Config.Bind<bool>("2-表现", "StaggerMaterialFlash", true, "僵直期间给 Boss 做材质闪烁(发光 + 颜色脉冲),比点光更醒目。会实例化一份材质并在僵直结束后恢复原值、销毁副本。"); CfgFlashStrength = ((BaseUnityPlugin)this).Config.Bind<float>("2-表现", "StaggerFlashStrength", 2f, new ConfigDescription("材质发光强度(仅当材质有 _EmissionColor 时有效)。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 8f), Array.Empty<object>())); CfgFlashMode = ((BaseUnityPlugin)this).Config.Bind<FlashMode>("2-表现", "StaggerFlashMode", FlashMode.PropertyBlock, "PropertyBlock = 用 MaterialPropertyBlock 覆盖 _Color/_EmissionColor(最安全,推荐);Material = 实例化材质后改色(发光更明显;结束时会先把渲染器材质引用还原成原始共享材质,再销毁实例)。"); CfgBossStaggerDamageFactor = ((BaseUnityPlugin)this).Config.Bind<float>("3-可选", "BossStaggerDamageFactor", -1f, "小于 0 = 不改动(原版 Boss 为 0)。设为 0.3 之类可让 Boss 也吃普通伤害累积僵直。"); CfgDiagnose = ((BaseUnityPlugin)this).Config.Bind<bool>("9-诊断", "DiagnoseStagger", true, "僵直后 +0.15s / +0.6s 采样 IsStaggering 与动画状态标签写入日志。"); CfgMeasure = ((BaseUnityPlugin)this).Config.Bind<bool>("9-诊断", "MeasureStaggerDurations", true, "测量原版动画的真实僵直时长:对小怪/自己按 F9 各试 2~3 次,日志会给出每个角色的平均时长、跨角色中位数和建议的 StaggerSeconds 值。"); CfgDumpKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("9-诊断", "DumpKey", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "输出附近角色/Boss 的僵直参数(含特效内容)到日志。"); CfgForceKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("9-诊断", "ForceStaggerKey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "强制僵直最近的 Boss。"); CfgForceMobKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("9-诊断", "ForceStaggerMobKey", new KeyboardShortcut((KeyCode)290, Array.Empty<KeyCode>()), "强制僵直最近的非 Boss 敌人(对照用)。"); new Harmony("dsh.valheim.parrybosses").PatchAll(Assembly.GetExecutingAssembly()); Log.LogInfo((object)"===== Parry Bosses v1.0.0 已加载 ====="); Log.LogInfo((object)(" BepInEx 根目录 : " + Paths.BepInExRootPath)); Log.LogInfo((object)(" 插件目录 : " + Paths.PluginPath)); Log.LogInfo((object)(" 配置目录 : " + Paths.ConfigPath)); Log.LogInfo((object)$" BepInEx 版本 : {typeof(BaseUnityPlugin).Assembly.GetName().Version}"); Log.LogInfo((object)$" Harmony 版本 : {typeof(Harmony).Assembly.GetName().Version}"); VerifyTargets(); Log.LogInfo((object)($" 生效配置: ForceStaggerWhenBlocked={CfgForceStaggerWhenBlocked.Value} " + $"Fallback={CfgFallback.Value} StaggerSeconds={CfgStaggerSeconds.Value} | " + $"暴击特效={CfgCritEffect.Value} 持续特效={CfgParticles.Value}/{CfgParticleInterval.Value}s " + $"漂浮文字={CfgFloatingText.Value}(\"{CfgFloatingTextString.Value}\") 点光={CfgLight.Value} " + $"材质闪烁={CfgMaterialFlash.Value}(强度{CfgFlashStrength.Value}) 时长测量={CfgMeasure.Value}")); if (CfgFallback.Value != StaggerFallbackMode.Always) { Log.LogWarning((object)($"注意:StaggerFallback={CfgFallback.Value}。实测 Boss 动画器没有 tag=stagger 的状态," + "只有 Always(或 Auto 的自愈)才会真正产生僵直。可在 " + Paths.ConfigPath + " 里改成 Always。")); } } private static void VerifyTargets() { (Type, string, Type[])[] array = new(Type, string, Type[])[5] { (typeof(Character), "Awake", Type.EmptyTypes), (typeof(Humanoid), "Awake", Type.EmptyTypes), (typeof(Character), "Stagger", new Type[1] { typeof(Vector3) }), (typeof(Character), "IsStaggering", Type.EmptyTypes), (typeof(ZSyncAnimation), "RPC_SetTrigger", new Type[2] { typeof(long), typeof(string) }) }; for (int i = 0; i < array.Length; i++) { (Type, string, Type[]) tuple = array[i]; MethodBase methodBase = AccessTools.Method(tuple.Item1, tuple.Item2, tuple.Item3, (Type[])null); ManualLogSource log = Log; string[] obj = new string[8] { " 目标 ", tuple.Item1.Name, ".", tuple.Item2, "(", null, null, null }; object[] item = tuple.Item3; obj[5] = string.Join(",", item); obj[6] = "): "; obj[7] = ((methodBase != null) ? "OK" : "★未找到★"); log.LogInfo((object)string.Concat(obj)); } } private void Update() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) _pruneTimer += Time.deltaTime; if (_pruneTimer > 5f) { _pruneTimer = 0f; BossRegistry.Prune(); } RunProbes(); RunVisuals(); StaggerMeter.Update(); KeyboardShortcut value = CfgDumpKey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { DumpNearby(); } value = CfgForceKey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { ForceStagger(onlyBoss: true); } value = CfgForceMobKey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { ForceStagger(onlyBoss: false); } } internal static void StartStagger(Character c, string reason) { if (!((Object)(object)c == (Object)null)) { BossState bossState = BossRegistry.Get(c); bossState.Probe(c); float value = CfgStaggerSeconds.Value; bossState.StaggeredUntil = Time.time + value; Log.LogInfo((object)($"{c.m_name} 进入僵直 {value:0.0}s(来源={reason},Fallback={CfgFallback.Value}," + $"动画有 stagger 触发器={bossState.HasStaggerTrigger})")); if (CfgCritEffect.Value) { PlayEffect(c, c.m_critHitEffects, "暴击"); } StaggerVisual staggerVisual = Visuals.Find((StaggerVisual x) => (Object)(object)x.Character == (Object)(object)c); if (staggerVisual == null) { staggerVisual = new StaggerVisual { Character = c }; Visuals.Add(staggerVisual); } staggerVisual.NextEffectAt = Time.time + CfgParticleInterval.Value; if (CfgLight.Value) { EnsureLight(c); } if (CfgMaterialFlash.Value) { MaterialFlashHelper.Begin(c, staggerVisual); } if (CfgFloatingText.Value && !string.IsNullOrEmpty(CfgFloatingTextString.Value)) { ShowFloatingText(c, CfgFloatingTextString.Value); } if (CfgHudOnStagger.Value) { Hud($"{c.m_name} 进入僵直 {value:0.0}s — 此时受 2 倍伤害", (MessageType)1); } ScheduleProbe(c, 0.15f, "+0.15s"); ScheduleProbe(c, 0.6f, "+0.60s"); } } private static void RunVisuals() { if (Visuals.Count == 0) { return; } float time = Time.time; for (int num = Visuals.Count - 1; num >= 0; num--) { StaggerVisual staggerVisual = Visuals[num]; Character character = staggerVisual.Character; BossState bossState = (((Object)(object)character == (Object)null) ? null : BossRegistry.Peek(character)); if ((Object)(object)character != (Object)null && bossState != null && bossState.TimerActive) { if (CfgMaterialFlash.Value) { MaterialFlashHelper.Tick(staggerVisual, 0.5f + 0.5f * Mathf.Sin(Time.time * 14f)); } if (CfgParticles.Value && time >= staggerVisual.NextEffectAt) { staggerVisual.NextEffectAt = time + CfgParticleInterval.Value; PlayEffect(character, character.m_hitEffects, "受击"); } } else { DestroyLight(staggerVisual); MaterialFlashHelper.End(staggerVisual); Visuals.RemoveAt(num); } } } private static void EnsureLight(Character c) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //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_0080: 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) StaggerVisual staggerVisual = Visuals.Find((StaggerVisual x) => (Object)(object)x.Character == (Object)(object)c); if (staggerVisual != null && (Object)(object)staggerVisual.LightGo != (Object)null) { return; } try { GameObject val = new GameObject("ParryBosses_StaggerLight"); val.transform.SetParent(((Component)c).transform, false); Vector3 localPosition = c.GetCenterPoint() - ((Component)c).transform.position; val.transform.localPosition = localPosition; Light obj = val.AddComponent<Light>(); obj.type = (LightType)2; obj.color = new Color(1f, 0.72f, 0.25f); obj.range = CfgLightRange.Value; obj.intensity = 2.5f; if (staggerVisual == null) { staggerVisual = new StaggerVisual { Character = c }; Visuals.Add(staggerVisual); } staggerVisual.LightGo = val; } catch (Exception ex) { Log.LogWarning((object)("创建僵直点光失败: " + ex.Message)); } } private static void DestroyLight(StaggerVisual v) { if (!((Object)(object)v?.LightGo == (Object)null)) { try { Object.Destroy((Object)(object)v.LightGo); } catch { } v.LightGo = null; } } private static void PlayEffect(Character c, EffectList list, string what) { //IL_0010: 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_0027: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)c == (Object)null || list == null) { return; } try { list.Create(c.GetCenterPoint(), Quaternion.identity, ((Component)c).transform, 1f, -1, c.GetZDOID()); } catch (Exception ex) { Log.LogDebug((object)("播放" + what + "特效失败: " + ex.Message)); } } private static void ShowFloatingText(Character c, string text) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)DamageText.instance != (Object)null) { DamageText.instance.ShowText((TextType)7, c.GetTopPoint(), text, false); } } catch (Exception ex) { Log.LogDebug((object)("漂浮文字失败: " + ex.Message)); } } internal static void ScheduleProbe(Character c, float delay, string label) { if (CfgDiagnose.Value && !((Object)(object)c == (Object)null)) { Probes.Add(new StaggerProbe { Character = c, At = Time.time + delay, Label = label }); } } internal static void ScheduleFallbackCheck(Character c, float delay = 0.12f) { Probes.Add(new StaggerProbe { Character = c, At = Time.time + delay, Label = "AUTO-CHECK" }); } private static void RunProbes() { if (Probes.Count == 0) { return; } float time = Time.time; for (int num = Probes.Count - 1; num >= 0; num--) { StaggerProbe staggerProbe = Probes[num]; if ((Object)(object)staggerProbe.Character == (Object)null) { Probes.RemoveAt(num); } else if (!(time < staggerProbe.At)) { Probes.RemoveAt(num); if (staggerProbe.Label == "AUTO-CHECK") { if (!RawAnimatorStaggering(staggerProbe.Character)) { Log.LogInfo((object)(staggerProbe.Character.m_name + ":Auto 模式自愈 —— 原版动画未进入 stagger 状态,改用计时器")); StartStagger(staggerProbe.Character, "Auto自愈"); } else { Log.LogInfo((object)(staggerProbe.Character.m_name + ":原版动画已进入 stagger 状态,无需计时器")); } } else { LogStaggerState(staggerProbe.Character, staggerProbe.Label); } } } } internal static bool RawAnimatorStaggering(Character c) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) int hash = ZSyncAnimation.GetHash("stagger"); try { Animator animator = GetAnimator(c); if ((Object)(object)animator == (Object)null) { return false; } AnimatorStateInfo val = animator.GetCurrentAnimatorStateInfo(0); int result; if (((AnimatorStateInfo)(ref val)).tagHash != hash) { val = animator.GetNextAnimatorStateInfo(0); result = ((((AnimatorStateInfo)(ref val)).tagHash == hash) ? 1 : 0); } else { result = 1; } return (byte)result != 0; } catch { return false; } } internal static void LogStaggerState(Character c, string label) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) BossState bossState = BossRegistry.Peek(c); bossState?.Probe(c); int hash = ZSyncAnimation.GetHash("stagger"); int num = 0; int num2 = 0; string text = null; try { Animator animator = GetAnimator(c); if ((Object)(object)animator != (Object)null) { AnimatorStateInfo val = animator.GetCurrentAnimatorStateInfo(0); num = ((AnimatorStateInfo)(ref val)).tagHash; val = animator.GetNextAnimatorStateInfo(0); num2 = ((AnimatorStateInfo)(ref val)).tagHash; } } catch (Exception ex) { text = ex.Message; } Log.LogInfo((object)($"[僵直验证 {label}] {c.m_name} | IsStaggering(经补丁)={c.IsStaggering()} | 计时器生效={bossState?.TimerActive ?? false} | " + $"动画标签 current={num}(=stagger?{num == hash}) next={num2}(=stagger?{num2 == hash})" + ((text != null) ? (" | 读动画异常:" + text) : ""))); } internal static void Hud(string msg, MessageType type = (MessageType)2) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) Log.LogInfo((object)("[屏幕提示] " + msg)); try { if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage(type, msg, 0, (Sprite)null, false, true); } } catch (Exception ex) { Log.LogWarning((object)("屏幕提示失败: " + ex.Message)); } } private static void DumpNearby() { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) List<Character> list = new List<Character>(Character.GetAllCharacters()); Player localPlayer = Player.m_localPlayer; int num = 0; int num2 = 0; Log.LogInfo((object)$"===== ParryBosses 诊断:共 {list.Count} 个 Character ====="); foreach (Character item in list) { if (!((Object)(object)item == (Object)null)) { bool flag = item.IsBoss(); if (flag) { num++; } bool flag2 = (Object)(object)localPlayer != (Object)null && Vector3.Distance(((Component)item).transform.position, ((Component)localPlayer).transform.position) < 40f; if (flag || flag2) { LogCharacter(item, flag ? "boss" : "nearby"); num2++; } } } Log.LogInfo((object)$"===== 诊断结束(输出 {num2} 个,其中 Boss {num} 个)====="); StaggerMeter.DumpStats(); Hud($"ParryBosses 诊断已写入日志:Boss {num} 个 / 附近角色 {num2} 个", (MessageType)2); } private static void ForceStagger(bool onlyBoss) { //IL_00e4: 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_0078: Unknown result type (might be due to invalid IL or missing references) List<Character> list = new List<Character>(Character.GetAllCharacters()); Player localPlayer = Player.m_localPlayer; Character val = null; float num = 60f; foreach (Character item in list) { if (!((Object)(object)item == (Object)null) && !item.IsPlayer() && !item.IsDead() && (!onlyBoss || item.IsBoss()) && (onlyBoss || !item.IsBoss())) { float num2 = (((Object)(object)localPlayer == (Object)null) ? 0f : Vector3.Distance(((Component)item).transform.position, ((Component)localPlayer).transform.position)); if (num2 < num) { num = num2; val = item; } } } if ((Object)(object)val == (Object)null) { Hud(onlyBoss ? "ParryBosses:60 米内没找到 Boss(先 spawn 一只,比如 spawn gd_king)" : "ParryBosses:60 米内没找到非 Boss 敌人", (MessageType)2); return; } LogCharacter(val, onlyBoss ? "force-stagger-boss" : "force-stagger-mob"); val.Stagger(Vector3.zero); Hud($"ParryBosses:已对 {val.m_name} 施加僵直({CfgStaggerSeconds.Value}s)", (MessageType)2); } internal static void LogBasic(Character c, string tag) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)c == (Object)null)) { Log.LogInfo((object)($"[{tag}] {c.m_name} | boss={c.IsBoss()} hp={c.GetHealth():0.#}/{c.GetMaxHealth():0.#} | " + $"m_staggerWhenBlocked={c.m_staggerWhenBlocked} m_staggerDamageFactor={c.m_staggerDamageFactor} | " + $"faction={c.m_faction}")); } } internal static void LogCharacter(Character c, string tag) { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)c == (Object)null)) { Log.LogInfo((object)($"[{tag}] {c.m_name} | boss={c.IsBoss()} hp={c.GetHealth():0.#}/{c.GetMaxHealth():0.#} | " + $"m_staggerWhenBlocked={c.m_staggerWhenBlocked} m_staggerDamageFactor={c.m_staggerDamageFactor} | " + $"IsStaggering={c.IsStaggering()} | 原版动画僵直={RawAnimatorStaggering(c)} | " + string.Format("animator(stagger触发器={0}) | faction={1}", HasAnimatorTrigger(c, "stagger"), c.m_faction))); if (c.IsBoss()) { DumpEffectLists(c); } } } private static void DumpEffectLists(Character c) { Log.LogInfo((object)(" 特效内容 hit=[" + Names(c.m_hitEffects) + "] crit=[" + Names(c.m_critHitEffects) + "]")); } private static string Names(EffectList list) { if (list == null || list.m_effectPrefabs == null) { return ""; } List<string> list2 = new List<string>(); EffectData[] effectPrefabs = list.m_effectPrefabs; foreach (EffectData val in effectPrefabs) { if (val != null && (Object)(object)val.m_prefab != (Object)null) { list2.Add(((Object)val.m_prefab).name); } } return string.Join(", ", list2); } internal static ZSyncAnimation GetZanim(Character c) { if ((Object)(object)c == (Object)null) { return null; } try { ZSyncAnimation value = Traverse.Create((object)c).Field<ZSyncAnimation>("m_zanim").Value; if ((Object)(object)value != (Object)null) { return value; } } catch { } return ((Component)c).GetComponent<ZSyncAnimation>(); } internal static Animator GetAnimator(Character c) { ZSyncAnimation zanim = GetZanim(c); if ((Object)(object)zanim == (Object)null) { return null; } try { Animator value = Traverse.Create((object)zanim).Field<Animator>("m_animator").Value; if ((Object)(object)value != (Object)null) { return value; } } catch { } return ((Component)zanim).GetComponent<Animator>(); } internal static bool HasAnimatorTrigger(Character c, string trigger) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 try { Animator animator = GetAnimator(c); if ((Object)(object)animator == (Object)null) { return false; } AnimatorControllerParameter[] parameters = animator.parameters; foreach (AnimatorControllerParameter val in parameters) { if ((int)val.type == 9 && val.name == trigger) { return true; } } } catch (Exception ex) { Log.LogDebug((object)("HasAnimatorTrigger(" + trigger + ") 失败: " + ex.Message)); } return false; } } internal class MaterialFlash { public Character Character; public readonly List<Renderer> Renderers = new List<Renderer>(); public readonly MaterialPropertyBlock Block = new MaterialPropertyBlock(); public readonly List<Material[]> OrigShared = new List<Material[]>(); public readonly List<Material[]> Instances = new List<Material[]>(); public int TintableCount; public int EmissiveCount; public bool UseMaterialMode; } internal static class MaterialFlashHelper { public static void Begin(Character c, StaggerVisual v) { if (v.Flash != null) { return; } MaterialFlash materialFlash = new MaterialFlash { Character = c }; materialFlash.UseMaterialMode = ParryBossesPlugin.CfgFlashMode.Value == FlashMode.Material; try { Renderer[] componentsInChildren = ((Component)c).GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } Material[] sharedMaterials; try { sharedMaterials = val.sharedMaterials; } catch { continue; } bool flag = false; bool flag2 = false; bool flag3 = sharedMaterials.Length != 0; Material[] array = sharedMaterials; foreach (Material val2 in array) { if (!((Object)(object)val2 == (Object)null)) { flag3 = false; if (val2.HasProperty("_Color")) { flag = true; } if (val2.HasProperty("_EmissionColor")) { flag2 = true; } } } if (flag3) { ParryBossesPlugin.Log.LogWarning((object)(" " + c.m_name + " 的渲染器 " + ((Object)val).name + " 材质已失效(上个版本销毁材质导致),请重载世界或重启游戏恢复")); } else { if (!flag && !flag2) { continue; } if (flag) { materialFlash.TintableCount++; } if (flag2) { materialFlash.EmissiveCount++; } materialFlash.Renderers.Add(val); if (!materialFlash.UseMaterialMode) { continue; } materialFlash.OrigShared.Add(sharedMaterials); Material[] materials = val.materials; array = materials; foreach (Material val3 in array) { if ((Object)(object)val3 != (Object)null && val3.HasProperty("_EmissionColor")) { val3.EnableKeyword("_EMISSION"); } } materialFlash.Instances.Add(materials); } } } catch (Exception ex) { ParryBossesPlugin.Log.LogWarning((object)("材质闪烁初始化失败: " + ex.Message)); } v.Flash = materialFlash; ParryBossesPlugin.Log.LogInfo((object)($" 材质闪烁[{ParryBossesPlugin.CfgFlashMode.Value}]: {materialFlash.Renderers.Count} 个渲染器" + $"(可调色 {materialFlash.TintableCount} / 可发光 {materialFlash.EmissiveCount})")); if (materialFlash.Renderers.Count == 0) { ParryBossesPlugin.Log.LogWarning((object)" 该角色没有可用 _Color/_EmissionColor 的材质,闪烁不会可见"); } } public static void Tick(StaggerVisual v, float k) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) MaterialFlash flash = v.Flash; if (flash == null) { return; } float value = ParryBossesPlugin.CfgFlashStrength.Value; Color val = default(Color); ((Color)(ref val))..ctor(1f, 0.62f, 0.18f); float num = Mathf.Clamp01(0.15f + 0.55f * k); Color val2 = Color.Lerp(Color.white, val, num); Color val3 = val * (value * k * 0.5f); if (!flash.UseMaterialMode) { foreach (Renderer renderer in flash.Renderers) { if (!((Object)(object)renderer == (Object)null)) { try { flash.Block.Clear(); flash.Block.SetColor("_Color", val2); if (value > 0f) { flash.Block.SetColor("_EmissionColor", val3); } renderer.SetPropertyBlock(flash.Block); } catch { } } } return; } for (int i = 0; i < flash.Instances.Count; i++) { Material[] array = flash.Instances[i]; foreach (Material val4 in array) { if ((Object)(object)val4 == (Object)null) { continue; } try { if (val4.HasProperty("_Color")) { val4.SetColor("_Color", val2); } if (value > 0f && val4.HasProperty("_EmissionColor")) { val4.SetColor("_EmissionColor", val3); } } catch { } } } } public static void End(StaggerVisual v) { MaterialFlash flash = v.Flash; if (flash == null) { return; } for (int i = 0; i < flash.Renderers.Count; i++) { Renderer val = flash.Renderers[i]; if ((Object)(object)val != (Object)null) { try { if (flash.UseMaterialMode) { if (i < flash.OrigShared.Count) { val.sharedMaterials = flash.OrigShared[i]; } } else { val.SetPropertyBlock((MaterialPropertyBlock)null); } } catch { } } if (!flash.UseMaterialMode || i >= flash.Instances.Count) { continue; } Material[] array = flash.Instances[i]; foreach (Material val2 in array) { if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } } } flash.Renderers.Clear(); flash.OrigShared.Clear(); flash.Instances.Clear(); v.Flash = null; } } internal class StaggerMeasurement { public Character Character; public float TriggeredAt; public float StartedAt = -1f; public bool SeenStaggering; } internal static class StaggerMeter { private static readonly List<StaggerMeasurement> Pending = new List<StaggerMeasurement>(); private static readonly Dictionary<string, List<float>> Samples = new Dictionary<string, List<float>>(); public static void Begin(Character c) { if (ParryBossesPlugin.CfgMeasure.Value && !((Object)(object)c == (Object)null) && !c.IsBoss()) { Pending.RemoveAll((StaggerMeasurement p) => (Object)(object)p.Character == (Object)(object)c); Pending.Add(new StaggerMeasurement { Character = c, TriggeredAt = Time.time }); } } public static void Update() { if (Pending.Count == 0) { return; } float time = Time.time; for (int num = Pending.Count - 1; num >= 0; num--) { StaggerMeasurement staggerMeasurement = Pending[num]; Character character = staggerMeasurement.Character; if ((Object)(object)character == (Object)null) { Pending.RemoveAt(num); } else if (ParryBossesPlugin.RawAnimatorStaggering(character)) { staggerMeasurement.SeenStaggering = true; if (staggerMeasurement.StartedAt < 0f) { staggerMeasurement.StartedAt = time; } } else if (staggerMeasurement.SeenStaggering) { float seconds = time - staggerMeasurement.StartedAt; Report(character.m_name, seconds, "动画僵直"); Pending.RemoveAt(num); } else if (time - staggerMeasurement.TriggeredAt > 2f) { ParryBossesPlugin.Log.LogInfo((object)("[僵直时长] " + character.m_name + ":2 秒内动画未进入 stagger 状态(该角色没有 tag=stagger 的动画状态)")); Pending.RemoveAt(num); } } } private static void Report(string name, float seconds, string kind) { if (!Samples.TryGetValue(name, out var value)) { value = new List<float>(); Samples[name] = value; } value.Add(seconds); float num = 0f; foreach (float item in value) { num += item; } num /= (float)value.Count; ParryBossesPlugin.Log.LogInfo((object)($"[僵直时长] {name}:本次 {seconds:0.00}s({kind}),该角色已测 {value.Count} 次,平均 {num:0.00}s" + ((value.Count >= 3) ? ("," + Suggest()) : ""))); } private static string Suggest() { List<float> list = new List<float>(); foreach (KeyValuePair<string, List<float>> sample in Samples) { if (sample.Value.Count < 1) { continue; } float num = 0f; foreach (float item in sample.Value) { num += item; } list.Add(num / (float)sample.Value.Count); } if (list.Count == 0) { return ""; } list.Sort(); float num2 = list[list.Count / 2]; return $"跨角色中位数 {num2:0.00}s → 建议 StaggerSeconds = {num2:0.0}"; } public static void DumpStats() { if (Samples.Count == 0) { ParryBossesPlugin.Log.LogInfo((object)"[僵直时长统计] 还没有样本(用 F9 对几种小怪各试 2~3 次)"); return; } ParryBossesPlugin.Log.LogInfo((object)"[僵直时长统计]"); foreach (KeyValuePair<string, List<float>> sample in Samples) { float num = 0f; float num2 = float.MaxValue; float num3 = float.MinValue; foreach (float item in sample.Value) { num += item; if (item < num2) { num2 = item; } if (item > num3) { num3 = item; } } ParryBossesPlugin.Log.LogInfo((object)$" {sample.Key}: 平均 {num / (float)sample.Value.Count:0.00}s({sample.Value.Count} 次,{num2:0.00}~{num3:0.00}s)"); } ParryBossesPlugin.Log.LogInfo((object)(" " + Suggest())); } } }