From d3650ef53a4a17f5ac1bf506b146a0c69efa69f3 Mon Sep 17 00:00:00 2001 From: vuong dinh hoang Date: Fri, 29 May 2026 11:05:10 +0700 Subject: [PATCH] update show skill tool --- Assets/PerfectWorld/Scene/AnimationTest.unity | 2 +- .../AnimTestScene/AnimScenePlayerBootstrap.cs | 2 + .../AnimTestScene/LogPanelAnimeScene.cs | 87 ++++++++++++ .../SkillVisibleStateResolver.cs | 128 ++++++++++++++++++ .../SkillVisibleStateResolver.cs.meta | 2 + .../Scripts/Debug/SkillTriggerPanel.cs | 3 + .../Scripts/Managers/CECAttacksMan.cs | 25 ++++ .../Scripts/Move/CECPlayer.AnimSceneWeapon.cs | 8 ++ Assets/PerfectWorld/Scripts/Move/CECPlayer.cs | 14 ++ 9 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 Assets/PerfectWorld/Scripts/AnimTestScene/SkillVisibleStateResolver.cs create mode 100644 Assets/PerfectWorld/Scripts/AnimTestScene/SkillVisibleStateResolver.cs.meta diff --git a/Assets/PerfectWorld/Scene/AnimationTest.unity b/Assets/PerfectWorld/Scene/AnimationTest.unity index 88ef4c7a9d..9bb21abaee 100644 --- a/Assets/PerfectWorld/Scene/AnimationTest.unity +++ b/Assets/PerfectWorld/Scene/AnimationTest.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9327b85e3d879a676a6928066162e7270af3c40f439dac47630f1325d3f925d5 +oid sha256:aa95dfb364ee6af6d4dac2c026057bfbba33638c76305b26a1611c3aeb134847 size 150615 diff --git a/Assets/PerfectWorld/Scripts/AnimTestScene/AnimScenePlayerBootstrap.cs b/Assets/PerfectWorld/Scripts/AnimTestScene/AnimScenePlayerBootstrap.cs index 459c6549f5..70efea53e7 100644 --- a/Assets/PerfectWorld/Scripts/AnimTestScene/AnimScenePlayerBootstrap.cs +++ b/Assets/PerfectWorld/Scripts/AnimTestScene/AnimScenePlayerBootstrap.cs @@ -361,6 +361,8 @@ namespace PerfectWorld.Scripts await player.SetPlayerModel(prof, gen); Debug.Log("[AnimSceneBootstrap] SetPlayerModel pipeline finished."); + player.AnimSceneMarkResourcesReady(); + ApplyWeaponForActiveSlot(); AnimSceneInitSkillModelAndRefreshPanel(prof, gen); diff --git a/Assets/PerfectWorld/Scripts/AnimTestScene/LogPanelAnimeScene.cs b/Assets/PerfectWorld/Scripts/AnimTestScene/LogPanelAnimeScene.cs index 9b6f4f67f2..c5af32d623 100644 --- a/Assets/PerfectWorld/Scripts/AnimTestScene/LogPanelAnimeScene.cs +++ b/Assets/PerfectWorld/Scripts/AnimTestScene/LogPanelAnimeScene.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; using BrewMonster.Scripts; using BrewMonster.Scripts.Skills; +using BrewMonster.Config; using ModelViewer.Common; @@ -147,6 +149,75 @@ namespace BrewMonster LogSkillStubFlyHitGfx(ev.m_idSkill, ev.m_nSkillSection); } + /// + /// Logs visible-state aura GFX and state-reaction ComAct GFX (skill_state_action + name fallback for local anim-test). + /// + public void LogSkillStateGfx(CECHostPlayer player, int skillId) + { + List stateIds = SkillVisibleStateResolver.ResolveVisibleStateIds(skillId); + bool hasConfigRows = SkillVisibleStateResolver.TryGetConfigRows(skillId, out IReadOnlyList rows); + + // #region agent log + AgentDebugLog("LogSkillStateGfx:entry", "resolved visible states", "H3", + $"{{\"skillId\":{skillId},\"stateIds\":\"{string.Join(",", stateIds)}\",\"hasConfigRows\":{hasConfigRows.ToString().ToLower()},\"configRowCount\":{(hasConfigRows ? rows.Count : 0)}}}"); + // #endregion + + if (stateIds.Count == 0 && !hasConfigRows) + return; + + const string stateGfxBasePath = "gfx/策划联入/状态效果/"; + var seenStateGfx = new HashSet(StringComparer.OrdinalIgnoreCase); + var seenBeHitActions = new HashSet(StringComparer.Ordinal); + var seenStayActions = new HashSet(StringComparer.Ordinal); + + CECModel playerModel = player?.GetPlayerCECModel(); + int profession = player != null ? player.GetProfession() : 0; + + foreach (int stateId in stateIds) + { + VisibleState visibleState = VisibleState.Query(profession, stateId); + string effect = visibleState?.GetEffect(); + // #region agent log + AgentDebugLog("LogSkillStateGfx:query", "VisibleState resolved", "H2", + $"{{\"skillId\":{skillId},\"stateId\":{stateId},\"profession\":{profession},\"effect\":\"{effect ?? ""}\",\"name\":\"{visibleState?.GetName() ?? ""}\"}}"); + // #endregion + + if (string.IsNullOrWhiteSpace(effect)) + continue; + + string rawPath = stateGfxBasePath + effect; + string displayText = GfxBasename(rawPath); + if (string.IsNullOrEmpty(displayText) || !seenStateGfx.Add(displayText)) + continue; + + string stateLabel = visibleState.GetName(); + if (string.IsNullOrEmpty(stateLabel)) + stateLabel = $"state {stateId}"; + else + stateLabel += $" (id {stateId})"; + + AddCopyTextButton("State", displayText, GfxAddressableExists(rawPath), stateLabel); + } + + if (!hasConfigRows) + return; + + foreach (SkillStateActionRow row in rows) + { + if (playerModel != null && !string.IsNullOrWhiteSpace(row.beHitAction) && + seenBeHitActions.Add(row.beHitAction)) + { + AddGfxEventsFromComAct(playerModel.GetComActByName(row.beHitAction), "StateBeHit"); + } + + if (playerModel != null && !string.IsNullOrWhiteSpace(row.stayDownAction) && + seenStayActions.Add(row.stayDownAction)) + { + AddGfxEventsFromComAct(playerModel.GetComActByName(row.stayDownAction), "StateStay"); + } + } + } + public void AddGfxEventsFromComAct(A3DCombinedAction comAct, string label) { if (string.IsNullOrEmpty(label) || comAct?.m_EventInfoLst == null) @@ -221,5 +292,21 @@ namespace BrewMonster AddGfxPathRow("Hit", hit); AddGfxPathRow("HitGrnd", hitGrnd); } + + // #region agent log + public static void AgentDebugLog(string location, string message, string hypothesisId, string dataJson) + { + try + { + string path = Path.GetFullPath(Path.Combine(Application.dataPath, "..", "..", "debug-1197c4.log")); + long ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + string line = "{\"sessionId\":\"1197c4\",\"location\":\"" + location + "\",\"message\":\"" + message + "\",\"hypothesisId\":\"" + hypothesisId + "\",\"timestamp\":" + ts + ",\"data\":" + dataJson + "}"; + File.AppendAllText(path, line + Environment.NewLine); + } + catch + { + } + } + // #endregion } } diff --git a/Assets/PerfectWorld/Scripts/AnimTestScene/SkillVisibleStateResolver.cs b/Assets/PerfectWorld/Scripts/AnimTestScene/SkillVisibleStateResolver.cs new file mode 100644 index 0000000000..f70ce1cc0e --- /dev/null +++ b/Assets/PerfectWorld/Scripts/AnimTestScene/SkillVisibleStateResolver.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using BrewMonster.Config; +using BrewMonster.Scripts; +using BrewMonster.Scripts.Skills; + +namespace BrewMonster +{ + /// + /// Resolves visible-state ids for anim-test (skill_state_action + name fallback) and applies ext-state bits locally (no server). + /// + public static class SkillVisibleStateResolver + { + const int ExtStateCount = 6; + const int BitSize = 32; + + public static List ResolveVisibleStateIds(int skillId) + { + var ids = new HashSet(); + + if (CECAttacksMan.Instance != null && + CECAttacksMan.Instance.TryGetSkillStateActions(skillId, out IReadOnlyList rows)) + { + for (int i = 0; i < rows.Count; i++) + ids.Add(rows[i].state); + } + + SkillStub stub = SkillStub.GetStub((uint)skillId); + if (stub != null && !string.IsNullOrEmpty(stub.name)) + { + foreach (KeyValuePair kv in GNET.VisibleState) + { + string visibleName = kv.Value?.GetName(); + if (string.IsNullOrEmpty(visibleName)) + continue; + + if (string.Equals(visibleName, stub.name, StringComparison.Ordinal) || + string.Equals(visibleName, stub.nativename, StringComparison.Ordinal)) + ids.Add(kv.Key); + } + } + + return new List(ids); + } + + public static bool TryGetConfigRows(int skillId, out IReadOnlyList rows) + { + rows = null; + return CECAttacksMan.Instance != null && + CECAttacksMan.Instance.TryGetSkillStateActions(skillId, out rows); + } + + /// + /// Simulates server UPDATE_EXT_STATE for AnimationTest: sets bits and calls ShowExtendStates via SetNewExtendStates. + /// + public static void TryApplyLocalExtStates(int skillId, CECHostPlayer hostPlayer, CECMonsterTest targetMarker) + { + List stateIds = ResolveVisibleStateIds(skillId); + // #region agent log + LogPanelAnimeScene.AgentDebugLog("TryApplyLocalExtStates", "resolved state ids", "H4", + $"{{\"skillId\":{skillId},\"stateIds\":\"{string.Join(",", stateIds)}\",\"targetType\":{GetSkillTargetType((uint)skillId)}}}"); + // #endregion + + if (stateIds.Count == 0 || hostPlayer == null) + return; + + if (!hostPlayer.IsAllResReady() && hostPlayer.IsPlayerModelReady) + hostPlayer.AnimSceneMarkResourcesReady(); + + uint[] nextStates = BuildExtStates(hostPlayer.m_aExtStates, stateIds); + int targetType = GetSkillTargetType((uint)skillId); + + // #region agent log + LogPanelAnimeScene.AgentDebugLog("TryApplyLocalExtStates:apply", "calling SetNewExtendStates", "H5", + $"{{\"skillId\":{skillId},\"targetType\":{targetType},\"isAllResReady\":{hostPlayer.IsAllResReady().ToString().ToLower()},\"isModelReady\":{hostPlayer.IsPlayerModelReady.ToString().ToLower()},\"stateBits\":\"{string.Join(",", nextStates)}\"}}"); + // #endregion + + if (targetType == 0) + { + hostPlayer.SetNewExtendStates(0, nextStates, ExtStateCount); + return; + } + + if (targetMarker != null) + targetMarker.SetNewExtendStates(0, nextStates, ExtStateCount); + } + + static uint[] BuildExtStates(uint[] current, IEnumerable stateIds) + { + var states = new uint[ExtStateCount]; + if (current != null && current.Length >= ExtStateCount) + Array.Copy(current, states, ExtStateCount); + + foreach (int stateId in stateIds) + { + if (stateId < 0) + continue; + + int dwordIndex = stateId / BitSize; + int bit = stateId % BitSize; + if (dwordIndex < ExtStateCount) + states[dwordIndex] |= 1u << bit; + } + + return states; + } + + static int GetSkillTargetType(uint skillId) + { + SkillStub stub = SkillStub.GetStub(skillId); + if (stub == null) + return 1; + + if (stub.restrict_corpse == 1) + return 2; + if (stub.restrict_corpse == 2) + return 3; + if (stub.type == (int)skill_type.TYPE_ATTACK || stub.type == (int)skill_type.TYPE_CURSE) + return 1; + if (stub.type == (int)skill_type.TYPE_BLESSPET) + return 4; + if (stub.GetRange().NoTarget()) + return 0; + + return 1; + } + } +} diff --git a/Assets/PerfectWorld/Scripts/AnimTestScene/SkillVisibleStateResolver.cs.meta b/Assets/PerfectWorld/Scripts/AnimTestScene/SkillVisibleStateResolver.cs.meta new file mode 100644 index 0000000000..9bfef36da2 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/AnimTestScene/SkillVisibleStateResolver.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1ba8d537bc3de3c49845d22a1f9a69ce \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Debug/SkillTriggerPanel.cs b/Assets/PerfectWorld/Scripts/Debug/SkillTriggerPanel.cs index 05a9573489..8be846bbb5 100644 --- a/Assets/PerfectWorld/Scripts/Debug/SkillTriggerPanel.cs +++ b/Assets/PerfectWorld/Scripts/Debug/SkillTriggerPanel.cs @@ -614,6 +614,7 @@ namespace BrewMonster logPanelAnimeScene.LogSkillCastGfx(player, skillId); logPanelAnimeScene.LogPlayAttackEffectGfx(player, skillId, 0); + logPanelAnimeScene.LogSkillStateGfx(player, skillId); // Self-cast skills target the host; all others target the draggable marker. // 自身施法技能以主角为目标;其余技能以可拖动标记为目标。 @@ -805,6 +806,8 @@ namespace BrewMonster if(!replayAttackAnim) ScheduleFadeAllGfxWhenAttackStopped(attackTime); + + SkillVisibleStateResolver.TryApplyLocalExtStates(skillId, hostPlayer, targetMarker); } } } diff --git a/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs b/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs index 539dd0f1ac..f410a75352 100644 --- a/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs +++ b/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs @@ -223,6 +223,31 @@ namespace BrewMonster return false; } + /// + /// Returns all skill_state_action rows for a skill (anim-test / debug lookup). + /// + public bool TryGetSkillStateActions(int skillId, out IReadOnlyList rows) + { + rows = null; + SkillStateActionConfig cfg = skillStateActionConfig; + if (cfg?.Entries == null || cfg.Entries.Count == 0) + return false; + + var matches = new List(); + for (int i = 0; i < cfg.Entries.Count; i++) + { + SkillStateActionRow row = cfg.Entries[i]; + if (row.skill == skillId) + matches.Add(row); + } + + if (matches.Count == 0) + return false; + + rows = matches; + return true; + } + private void Update() { uint dwDeltaTime = (uint)(Time.deltaTime * 1000); diff --git a/Assets/PerfectWorld/Scripts/Move/CECPlayer.AnimSceneWeapon.cs b/Assets/PerfectWorld/Scripts/Move/CECPlayer.AnimSceneWeapon.cs index 1de1790a42..e6d23837e8 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECPlayer.AnimSceneWeapon.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECPlayer.AnimSceneWeapon.cs @@ -111,6 +111,14 @@ namespace BrewMonster m_iFashionWeaponType = -1; } + /// + /// Animation test scene skips server LoadResources; mark all resource flags so ShowExtendStates can play state GFX. + /// + public void AnimSceneMarkResourcesReady() + { + m_dwResFlags = (uint)PlayerResourcesReadyFlag.RESFG_ALL; + } + /// /// Inspector/debug: non-null string means will no-op or return false before Animancer runs. /// 非空表示 PlayAction 会在播放前失败或直接被拒。 diff --git a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs index 735f367cc6..fc16307e4c 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs @@ -915,7 +915,13 @@ namespace BrewMonster return; } if (!IsAllResReady() || !GetMajorModel()) + { + // #region agent log + LogPanelAnimeScene.AgentDebugLog("ShowExtendStates:blocked", "resource gate", "H5", + $"{{\"isAllResReady\":{IsAllResReady().ToString().ToLower()},\"hasMajorModel\":{(GetMajorModel() != null).ToString().ToLower()},\"isModelReady\":{IsPlayerModelReady.ToString().ToLower()}}}"); + // #endregion return; + } //TODO: Implement optimization // if (!bIgnoreOptimize && // !CECOptimize::Instance().GetGFX().CanShowState(GetCharacterID(), GetClassID())) @@ -3086,6 +3092,10 @@ namespace BrewMonster if (prefab == null) { BMLogger.LogWarning($"[StateGFX] Failed to load prefab: {path}"); + // #region agent log + LogPanelAnimeScene.AgentDebugLog("PlayStateGfxAsync:fail", "prefab load failed", "H6", + $"{{\"path\":\"{path}\",\"hook\":\"{hook ?? ""}\"}}"); + // #endregion return; } // [中文] 查找挂点骨骼,未找到则回退到玩家根 transform @@ -3098,6 +3108,10 @@ namespace BrewMonster vfx.transform.localPosition = Vector3.zero; _stateGfxObjects[key] = vfx; + // #region agent log + LogPanelAnimeScene.AgentDebugLog("PlayStateGfxAsync:spawned", "state gfx instantiated", "H6", + $"{{\"path\":\"{path}\",\"hook\":\"{hook ?? ""}\",\"parent\":\"{parent.name}\"}}"); + // #endregion } // [中文] 在武器 CECModel 上移除状态效果 GFX(武器挂点逻辑未接入,暂存桩)