diff --git a/Assets/ModelRenderer/Art/Gfx/gfx/人物/技能/武侠/霸王断岳施放.prefab b/Assets/ModelRenderer/Art/Gfx/gfx/人物/技能/武侠/霸王断岳施放.prefab
index ae8b1f99fb..9451c24919 100644
--- a/Assets/ModelRenderer/Art/Gfx/gfx/人物/技能/武侠/霸王断岳施放.prefab
+++ b/Assets/ModelRenderer/Art/Gfx/gfx/人物/技能/武侠/霸王断岳施放.prefab
@@ -4869,7 +4869,7 @@ Transform:
m_GameObject: {fileID: 4197944621747409826}
serializedVersion: 2
m_LocalRotation: {x: -0.6878776, y: -0.16378161, z: 0.16378164, w: 0.6878776}
- m_LocalPosition: {x: 0.024, y: 0.554, z: -0.33}
+ m_LocalPosition: {x: 0.26, y: 1.51, z: -0.52}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
@@ -5595,7 +5595,7 @@ ParticleSystem:
sphericalDirectionAmount: 0
randomPositionAmount: 0
radius:
- value: 0.71
+ value: 2.13
mode: 0
spread: 0
speed:
@@ -19445,8 +19445,8 @@ Transform:
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
+ m_LocalScale: {x: 3, y: 3, z: 3}
+ m_ConstrainProportionsScale: 1
m_Children: []
m_Father: {fileID: 1604809390761698253}
m_LocalEulerAnglesHint: {x: -90, y: 0, z: 0}
diff --git a/Assets/ModelRenderer/Art/Models/models/weapons/人物/唐刀/细刀16品/细刀16品.prefab b/Assets/ModelRenderer/Art/Models/models/weapons/人物/唐刀/细刀16品/细刀16品.prefab
index c67796562d..0f324b9fff 100644
--- a/Assets/ModelRenderer/Art/Models/models/weapons/人物/唐刀/细刀16品/细刀16品.prefab
+++ b/Assets/ModelRenderer/Art/Models/models/weapons/人物/唐刀/细刀16品/细刀16品.prefab
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4274349585449340130}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalRotation: {x: 0, y: 0.7071068, z: 0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 90, z: 0}
--- !u!33 &7795735887883509743
MeshFilter:
m_ObjectHideFlags: 0
diff --git a/Assets/ModelRenderer/Art/Models/models/weapons/人物/弓弩/弓/18品弓/18品弓.prefab b/Assets/ModelRenderer/Art/Models/models/weapons/人物/弓弩/弓/18品弓/18品弓.prefab
index 4c52cbfd74..d24f975593 100644
--- a/Assets/ModelRenderer/Art/Models/models/weapons/人物/弓弩/弓/18品弓/18品弓.prefab
+++ b/Assets/ModelRenderer/Art/Models/models/weapons/人物/弓弩/弓/18品弓/18品弓.prefab
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4135001540345507112}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068}
+ m_LocalPosition: {x: 0, y: -0.1, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &5782369747155273987
MeshFilter:
m_ObjectHideFlags: 0
diff --git a/Assets/ModelRenderer/Art/Models/models/weapons/人物/弓弩/弩/18品弩/18品弩.prefab b/Assets/ModelRenderer/Art/Models/models/weapons/人物/弓弩/弩/18品弩/18品弩.prefab
index 0cdcd056f2..241a14a756 100644
--- a/Assets/ModelRenderer/Art/Models/models/weapons/人物/弓弩/弩/18品弩/18品弩.prefab
+++ b/Assets/ModelRenderer/Art/Models/models/weapons/人物/弓弩/弩/18品弩/18品弩.prefab
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2368932797394218666}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalRotation: {x: 0.35355344, y: 0.6123724, z: 0.6123724, w: 0.35355344}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_LocalEulerAnglesHint: {x: -30, y: 90, z: 90}
--- !u!33 &6354786712980373978
MeshFilter:
m_ObjectHideFlags: 0
diff --git a/Assets/ModelRenderer/Art/Models/models/weapons/人物/弓弩/弹弓/19品弹弓/19品弹弓.prefab b/Assets/ModelRenderer/Art/Models/models/weapons/人物/弓弩/弹弓/19品弹弓/19品弹弓.prefab
index b2a2315223..13960c7c33 100644
--- a/Assets/ModelRenderer/Art/Models/models/weapons/人物/弓弩/弹弓/19品弹弓/19品弹弓.prefab
+++ b/Assets/ModelRenderer/Art/Models/models/weapons/人物/弓弩/弹弓/19品弹弓/19品弹弓.prefab
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 486393464438265562}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalRotation: {x: 0, y: 1, z: 0, w: 0}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 180, z: 0}
--- !u!33 &2795371212256588815
MeshFilter:
m_ObjectHideFlags: 0
diff --git a/Assets/ModelRenderer/Art/Models/models/weapons/人物/拳套/短刃/19品短刃/19品短刃_右.prefab b/Assets/ModelRenderer/Art/Models/models/weapons/人物/拳套/短刃/19品短刃/19品短刃_右.prefab
index 648af18f1b..defbe42bff 100644
--- a/Assets/ModelRenderer/Art/Models/models/weapons/人物/拳套/短刃/19品短刃/19品短刃_右.prefab
+++ b/Assets/ModelRenderer/Art/Models/models/weapons/人物/拳套/短刃/19品短刃/19品短刃_右.prefab
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5042710232424246754}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalRotation: {x: 0, y: 1, z: 0, w: 0}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 180, z: 0}
--- !u!33 &7463675474240568897
MeshFilter:
m_ObjectHideFlags: 0
diff --git a/Assets/ModelRenderer/Art/Models/models/weapons/人物/斧锤/双手双斧/18品双斧/18品双斧.prefab b/Assets/ModelRenderer/Art/Models/models/weapons/人物/斧锤/双手双斧/18品双斧/18品双斧.prefab
index 7bf217925e..c8210da2ad 100644
--- a/Assets/ModelRenderer/Art/Models/models/weapons/人物/斧锤/双手双斧/18品双斧/18品双斧.prefab
+++ b/Assets/ModelRenderer/Art/Models/models/weapons/人物/斧锤/双手双斧/18品双斧/18品双斧.prefab
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 192856137541653222}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalRotation: {x: 0, y: 0.7071068, z: 0.7071068, w: 0}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_LocalEulerAnglesHint: {x: -90, y: 180, z: 0}
--- !u!33 &7385562017347778284
MeshFilter:
m_ObjectHideFlags: 0
diff --git a/Assets/ModelRenderer/Art/Models/models/weapons/人物/法器/15品法器/15品法器橙.prefab b/Assets/ModelRenderer/Art/Models/models/weapons/人物/法器/15品法器/15品法器橙.prefab
index 1cea901def..078be3d006 100644
--- a/Assets/ModelRenderer/Art/Models/models/weapons/人物/法器/15品法器/15品法器橙.prefab
+++ b/Assets/ModelRenderer/Art/Models/models/weapons/人物/法器/15品法器/15品法器橙.prefab
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5834653072341985716}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalRotation: {x: 0.3535534, y: 0.61237246, z: -0.3535534, w: 0.61237246}
+ m_LocalPosition: {x: 0, y: -0.2, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_LocalEulerAnglesHint: {x: 60, y: 90, z: 0}
--- !u!33 &5428329696048806453
MeshFilter:
m_ObjectHideFlags: 0
diff --git a/Assets/ModelRenderer/Art/Models/models/weapons/人物/长兵/重头/19品镗/19品镗.prefab b/Assets/ModelRenderer/Art/Models/models/weapons/人物/长兵/重头/19品镗/19品镗.prefab
index 8677254638..f898b8b38d 100644
--- a/Assets/ModelRenderer/Art/Models/models/weapons/人物/长兵/重头/19品镗/19品镗.prefab
+++ b/Assets/ModelRenderer/Art/Models/models/weapons/人物/长兵/重头/19品镗/19品镗.prefab
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6619641526290080649}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalRotation: {x: 0, y: 0.7071068, z: -0.7071068, w: 0}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_LocalEulerAnglesHint: {x: 90, y: 180, z: 0}
--- !u!33 &7228491550620403586
MeshFilter:
m_ObjectHideFlags: 0
diff --git a/Assets/ModelRenderer/Art/Models/models/weapons/人物/长兵/重头/紫阳槊/紫阳槊极品.prefab b/Assets/ModelRenderer/Art/Models/models/weapons/人物/长兵/重头/紫阳槊/紫阳槊极品.prefab
index 36dcf60966..9d0b8f7bc5 100644
--- a/Assets/ModelRenderer/Art/Models/models/weapons/人物/长兵/重头/紫阳槊/紫阳槊极品.prefab
+++ b/Assets/ModelRenderer/Art/Models/models/weapons/人物/长兵/重头/紫阳槊/紫阳槊极品.prefab
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1020594218048739884}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalRotation: {x: 0, y: 0.7071068, z: 0, w: 0.7071068}
+ m_LocalPosition: {x: 0.73, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 90, z: 0}
--- !u!33 &3048989088492932353
MeshFilter:
m_ObjectHideFlags: 0
diff --git a/Assets/PerfectWorld/Scene/AnimationTest.unity b/Assets/PerfectWorld/Scene/AnimationTest.unity
new file mode 100644
index 0000000000..ce2ed17328
--- /dev/null
+++ b/Assets/PerfectWorld/Scene/AnimationTest.unity
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:11f16ce7ddd16e5c3de941b56358deb495efb2d5d4783c8d59c75aab0dad1975
+size 92966
diff --git a/Assets/PerfectWorld/Scene/AnimationTest.unity.meta b/Assets/PerfectWorld/Scene/AnimationTest.unity.meta
new file mode 100644
index 0000000000..da95c5ebac
--- /dev/null
+++ b/Assets/PerfectWorld/Scene/AnimationTest.unity.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a28f72b65352d8241827c49bc1f92408
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/PerfectWorld/Scripts/AnimScenePlayerBootstrap.cs b/Assets/PerfectWorld/Scripts/AnimScenePlayerBootstrap.cs
new file mode 100644
index 0000000000..2c5107a173
--- /dev/null
+++ b/Assets/PerfectWorld/Scripts/AnimScenePlayerBootstrap.cs
@@ -0,0 +1,405 @@
+using System.Collections.Generic;
+using BrewMonster;
+using BrewMonster.Network;
+using BrewMonster.Scripts;
+using BrewMonster.Scripts.Skills;
+using UnityEngine;
+
+namespace PerfectWorld.Scripts
+{
+ ///
+ /// One row per WEAPON_SUB_TYPE.action_type / suffix index (0..14).
+ /// 每个武器 action_type / 动作后缀索引一行(0..14)。
+ ///
+ [System.Serializable]
+ public struct AnimSceneWeaponSlot
+ {
+ public GameObject rightHandModelPrefab;
+ public GameObject leftHandModelPrefab;
+ }
+
+ ///
+ /// Test scene only: bootstraps model load. After each successful ,
+ /// applies serialized weapon prefabs via (sets m_uAttackType like equip data).
+ /// After each load, destroys the previous major model instance so it does not linger in the hierarchy.
+ /// Optional: discard the whole host instance and instantiate a fresh prefab when switching role (see ).
+ ///
+ public class AnimScenePlayerBootstrap : MonoBehaviour
+ {
+ [Header("Player Reference")]
+ [Tooltip("Scene host player. Do not put this bootstrap component on the same GameObject as the host if you enable Replace host on role switch.")]
+ [SerializeField]
+ private CECHostPlayer player;
+ [SerializeField]
+ private Camera cameraObject;
+ [SerializeField]
+ private List cameraPoses;
+ [SerializeField]
+ [Range(0, 10)]
+ private int activeCameraPos;
+
+
+ [Header("Initial Role")]
+ [SerializeField]
+ private Profession profession = Profession.PROF_WARRIOR;
+
+ [SerializeField]
+ private Gender gender = Gender.GENDER_MALE;
+
+ [Header("Animation scene — weapon visuals")]
+ [Tooltip("Prefabs per weapon action_type index (0–14). Empty slot clears weapons and resets m_uAttackType when applied.")]
+ [SerializeField]
+ private AnimSceneWeaponSlot[] weaponByActionType = new AnimSceneWeaponSlot[15];
+
+ [Tooltip("Which weaponByActionType row to apply after load / when using Re-apply in the custom inspector.")]
+ [SerializeField]
+ [Range(0, 10)]
+ private int activeWeaponActionTypeIndex;
+
+ [Header("Animation scene — element data compat")]
+ [Tooltip(
+ "If true, sets m_iFashionWeaponType = -1 on the host so PlayAction / GetShowingWeaponType do not call IsFashionWeaponTypeFit with missing DT_FASHION_WEAPON_CONFIG (avoids null action_mask NRE). Disable only if you load that config row.")]
+ [SerializeField]
+ private bool animSceneAvoidFashionWeaponConfigPath = true;
+
+ [Header("Animation scene — PlayerVisual")]
+ [Tooltip(
+ "After SetPlayerModel, call RefreshNamedAnimancer on the loaded .ecm then InitPlayerEventDoneHandler. " +
+ "Without this, the animation test scene never runs InitCharacter, so PlayerVisual never subscribes to PlayActionEvent.")]
+ [SerializeField]
+ private bool bindPlayerVisualAfterModelLoad = true;
+
+ [Header("Animation scene — skill catalog")]
+ [Tooltip("Match CDlgSkillSubList god/evil path when building the debug skill grid.")]
+ [SerializeField]
+ private bool isEvilSkillPath;
+
+ [Tooltip("Default level for skills injected from CECHostSkillModel in anim scenes.")]
+ [SerializeField]
+ private int skillCatalogLevel = 1;
+
+ [Header("Test-only — cleanup & host swap")]
+ [Tooltip("After SetPlayerModel, Destroy the previous major .ecm instance (frees scene objects left by re-load).")]
+ [SerializeField]
+ private bool destroyPreviousModelAfterSwitch = true;
+
+ [Tooltip("When switching role (not on first Start), Destroy the current CECHostPlayer GameObject and Instantiate hostPlayerPrefab before SetPlayerModel. Requires a sibling bootstrap object.")]
+ [SerializeField]
+ private bool replaceHostOnRoleSwitch;
+
+ [SerializeField]
+ private CECHostPlayer hostPlayerPrefab;
+
+ private bool _initStaticResDone;
+ private bool _busy;
+
+ private async void Start()
+ {
+ if (player == null)
+ {
+ Debug.LogError(
+ "[AnimSceneBootstrap] AnimScenePlayerBootstrap: player reference is null — assign CECHostPlayer in Inspector.");
+ return;
+ }
+
+ await BootstrapAsync((byte)profession, (byte)gender, fromInitialStart: true);
+ }
+ public void ChangeCameraPos(int i)
+ {
+ if(i<0)
+ activeCameraPos = cameraPoses.Count-1;
+ if (i>= cameraPoses.Count )
+ activeCameraPos = 0;
+ else
+ activeCameraPos = i;
+ cameraObject.transform.parent = cameraPoses[activeCameraPos];
+ cameraObject.transform.localRotation = default;
+ cameraObject.transform.localPosition = default;
+ }
+ public void ChangeNextCamera()
+ {
+ ChangeCameraPos (++activeCameraPos );
+ }
+ public void ChangePreviosCamera()
+ {
+ ChangeCameraPos (--activeCameraPos );
+ }
+
+ /// Swap profession/gender at runtime (async load inside).
+ public void SwitchRole(Profession newProfession, Gender newGender)
+ {
+ if (_busy)
+ {
+ Debug.LogWarning("[AnimSceneBootstrap] SwitchRole called while busy — ignoring.");
+ return;
+ }
+
+ profession = newProfession;
+ gender = newGender;
+ _ = BootstrapAsync((byte)newProfession, (byte)newGender, fromInitialStart: false);
+ }
+
+ /// profIndex: 0..-1; genderIndex: 0 = male, 1 = female.
+ public void SwitchRole(int profIndex, int genderIndex)
+ {
+ SwitchRole((Profession)profIndex, (Gender)genderIndex);
+ }
+
+ ///
+ /// Custom inspector: call before when is this bootstrap's .
+ ///
+ public void AnimSceneEnsureFashionPathSafeBeforePlayAction(CECHostPlayer host)
+ {
+ if (!animSceneAvoidFashionWeaponConfigPath || host == null || player == null || host != player)
+ {
+ return;
+ }
+
+ host.AnimSceneSanitizeFashionWeaponStateForElementDataCompat();
+ }
+
+ private void AnimSceneApplyFashionPathCompatIfEnabled()
+ {
+ if (!animSceneAvoidFashionWeaponConfigPath || player == null)
+ {
+ return;
+ }
+
+ player.AnimSceneSanitizeFashionWeaponStateForElementDataCompat();
+ }
+
+ ///
+ /// Login calls from InitCharacter; animation scenes skip that — bind here.
+ ///
+ private void AnimSceneBindPlayerVisualIfEnabled()
+ {
+ if (!bindPlayerVisualAfterModelLoad || player == null)
+ {
+ return;
+ }
+
+ PlayerVisual pv = player.GetComponentInChildren();
+ if (pv == null)
+ {
+ return;
+ }
+
+ GameObject modelRoot = player.m_pPlayerCECModel != null ? player.m_pPlayerCECModel.m_pPlayerModel : null;
+ if (modelRoot != null)
+ {
+ pv.RefreshNamedAnimancer(modelRoot);
+ }
+
+ pv.InitPlayerEventDoneHandler();
+ }
+
+ ///
+ /// Apply [] on the host (Play Mode).
+ /// 在主机上应用当前选中的武器槽(运行模式)。
+ ///
+ public void ApplyWeaponForActiveSlot()
+ {
+ if (player == null)
+ {
+ Debug.LogWarning("[AnimSceneBootstrap] ApplyWeaponForActiveSlot: player is null.");
+ return;
+ }
+
+ if (weaponByActionType == null || weaponByActionType.Length == 0)
+ {
+ return;
+ }
+
+ int idx = Mathf.Clamp(activeWeaponActionTypeIndex, 0, weaponByActionType.Length - 1);
+ AnimSceneWeaponSlot slot = weaponByActionType[idx];
+ bool empty = slot.rightHandModelPrefab == null && slot.leftHandModelPrefab == null;
+ if (empty)
+ {
+ player.AnimSceneAttachWeaponPrefabs(null, null, CECPlayer.DEFAULT_ACTION_TYPE);
+ }
+ else
+ {
+ player.AnimSceneAttachWeaponPrefabs(
+ slot.rightHandModelPrefab,
+ slot.leftHandModelPrefab,
+ (uint)idx);
+ }
+ }
+
+ ///
+ /// Re-apply weapon, rebuild skill catalog for current role, and refresh SkillTriggerPanel.
+ /// 重新应用武器、重建技能目录并刷新技能面板。
+ ///
+ public void ResetSkillPanel(bool filterByRestrictions)
+ {
+ if (player == null)
+ {
+ Debug.LogWarning("[AnimSceneBootstrap] ResetSkillPanel: player is null.");
+ return;
+ }
+
+ ApplyWeaponForActiveSlot();
+ RefreshSkillTriggerPanel(filterByRestrictions);
+ }
+
+ /// Reload skill grid showing only skills usable with current weapon/form/env.
+ public void ResetSkillsWithRestrictions() => ResetSkillPanel(filterByRestrictions: true);
+
+ /// Reload skill grid with all catalog skills (no weapon/form/env filter).
+ public void ResetSkillsWithoutRestrictions() => ResetSkillPanel(filterByRestrictions: false);
+
+ private void ReplaceHostPlayerInstance()
+ {
+ if (hostPlayerPrefab == null)
+ {
+ Debug.LogWarning("[AnimSceneBootstrap] replaceHostOnRoleSwitch is set but hostPlayerPrefab is null — skipping host replace.");
+ return;
+ }
+
+ if (player == null)
+ {
+ return;
+ }
+
+ if (player.gameObject == gameObject)
+ {
+ Debug.LogError(
+ "[AnimSceneBootstrap] Cannot replace host: this bootstrap is on the same GameObject as CECHostPlayer. Move AnimScenePlayerBootstrap to another object (e.g. a parent or sibling).");
+ return;
+ }
+
+ Vector3 pos = player.transform.position;
+ Quaternion rot = player.transform.rotation;
+ Transform parent = player.transform.parent;
+
+ Object.Destroy(player.gameObject);
+
+ GameObject instance = Object.Instantiate(hostPlayerPrefab.gameObject, pos, rot, parent);
+ player = instance.GetComponent();
+ if (player == null)
+ {
+ Debug.LogError("[AnimSceneBootstrap] hostPlayerPrefab has no CECHostPlayer component.");
+ }
+ }
+
+ private async System.Threading.Tasks.Task BootstrapAsync(byte prof, byte gen, bool fromInitialStart)
+ {
+ _busy = true;
+ try
+ {
+ ChangeCameraPos(activeCameraPos);
+ Debug.Log("[AnimSceneBootstrap] AnimScenePlayerBootstrap — waiting for ElementDataManProvider...");
+ int waitedFrames = 0;
+ while (!ElementDataManProvider.IsDataLoaded)
+ {
+ await System.Threading.Tasks.Task.Yield();
+ waitedFrames++;
+ if (waitedFrames > 3000)
+ {
+ Debug.LogError(
+ "[AnimSceneBootstrap] ElementDataManProvider never became ready — aborting (check Addressables / load_data).");
+ return;
+ }
+ }
+
+ Debug.Log($"[AnimSceneBootstrap] ElementDataManProvider ready after ~{waitedFrames} yields.");
+
+ if (!fromInitialStart && replaceHostOnRoleSwitch)
+ {
+ ReplaceHostPlayerInstance();
+ if (player == null)
+ {
+ Debug.LogError("[AnimSceneBootstrap] No CECHostPlayer after host replace — aborting.");
+ return;
+ }
+ }
+
+ if (!_initStaticResDone)
+ {
+ CECPlayer.InitStaticRes();
+ _initStaticResDone = true;
+ }
+
+ if (player != null)
+ {
+ player.AnimSceneRebindPlayerActions();
+ AnimSceneApplyFashionPathCompatIfEnabled();
+ }
+
+ GameObject previousModel = null;
+ if (player != null && player.m_pPlayerCECModel != null)
+ {
+ previousModel = player.m_pPlayerCECModel.m_pPlayerModel;
+ }
+
+ Debug.Log($"[AnimSceneBootstrap] Loading model profession={prof} gender={gen}...");
+ await player.SetPlayerModel(prof, gen);
+ Debug.Log("[AnimSceneBootstrap] SetPlayerModel pipeline finished.");
+
+ ApplyWeaponForActiveSlot();
+
+ AnimSceneInitSkillModelAndRefreshPanel(prof, gen);
+
+ AnimSceneBindPlayerVisualIfEnabled();
+
+ if (destroyPreviousModelAfterSwitch && previousModel != null && player != null &&
+ player.m_pPlayerCECModel != null)
+ {
+ GameObject current = player.m_pPlayerCECModel.m_pPlayerModel;
+ if (previousModel != current)
+ {
+ Object.Destroy(previousModel);
+ Debug.Log($"[AnimSceneBootstrap] Destroyed previous major model: {previousModel.name}");
+ }
+ }
+ }
+ catch (System.Exception ex)
+ {
+ Debug.LogError($"[AnimSceneBootstrap] Bootstrap failed: {ex}");
+ }
+ finally
+ {
+ _busy = false;
+ }
+ }
+
+ ///
+ /// Sync profession/gender, rebuild CECHostSkillModel for the current role, and refresh SkillTriggerPanel.
+ /// SetPlayerModel loads visuals only — m_iProfession is not updated otherwise, and skill catalog init
+ /// is skipped in anim scenes (no LoadPlayerSkeleton → OnAllResourceReady).
+ ///
+ /// 同步职业/性别,重建 CECHostSkillModel,并刷新 SkillTriggerPanel。
+ ///
+ private void AnimSceneInitSkillModelAndRefreshPanel(byte prof, byte gen)
+ {
+ if (player == null)
+ {
+ return;
+ }
+
+ player.m_iProfession = prof;
+ player.m_iGender = gen;
+
+ EC_Game.GetGameRun()?.RegisterAnimSceneHostPlayer(player);
+
+ SkillStubs.Init();
+ CECHostSkillModel.Instance.Initialize(prof);
+ Debug.Log($"[AnimSceneBootstrap] CECHostSkillModel initialized for profession={prof}, " +
+ $"catalogSkills={CECHostSkillModel.Instance.CollectSkillSubListSkillIds(isEvilSkillPath).Count}.");
+
+ RefreshSkillTriggerPanel(filterByRestrictions: false);
+ }
+
+ private void RefreshSkillTriggerPanel(bool filterByRestrictions)
+ {
+ if (SkillTriggerPanel.Instance == null)
+ {
+ return;
+ }
+
+ SkillTriggerPanel.Instance.SetHostPlayer(player);
+ SkillTriggerPanel.Instance.SetEvilSkillPath(isEvilSkillPath);
+ SkillTriggerPanel.Instance.ResetSkillsFromSkillModel(filterByRestrictions, skillCatalogLevel);
+ }
+ }
+}
diff --git a/Assets/PerfectWorld/Scripts/AnimScenePlayerBootstrap.cs.meta b/Assets/PerfectWorld/Scripts/AnimScenePlayerBootstrap.cs.meta
new file mode 100644
index 0000000000..6e7cab2aae
--- /dev/null
+++ b/Assets/PerfectWorld/Scripts/AnimScenePlayerBootstrap.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 4f5be834edbd85540b34164ff311afbc
\ No newline at end of file
diff --git a/Assets/PerfectWorld/Scripts/Common/DataProcess/ElementDataManProvider.cs b/Assets/PerfectWorld/Scripts/Common/DataProcess/ElementDataManProvider.cs
index 905b313812..9ccb5ab1fd 100644
--- a/Assets/PerfectWorld/Scripts/Common/DataProcess/ElementDataManProvider.cs
+++ b/Assets/PerfectWorld/Scripts/Common/DataProcess/ElementDataManProvider.cs
@@ -11,6 +11,11 @@ namespace BrewMonster
private static ElementDataManProvider _instance;
private elementdataman _elementDataMan;
+ ///
+ /// True after succeeds. Bootstrap scripts can poll this before / .
+ ///
+ public static bool IsDataLoaded { get; private set; }
+
public static elementdataman GetElementDataMan()
{
return _instance._elementDataMan;
@@ -25,7 +30,7 @@ namespace BrewMonster
{
_elementDataMan = new();
_instance = this;
-
+ IsDataLoaded = false;
try
{
while (!AddressableManager.Instance.IsInitialized())
@@ -41,6 +46,7 @@ namespace BrewMonster
}
else
{
+ IsDataLoaded = true;
BMLogger.Log("ElementDataManProvider: Successfully loaded element data");
// Build suite equip tab now that data is loaded
// 数据加载完成后构建套装装备表
@@ -55,6 +61,7 @@ namespace BrewMonster
public void Dispose()
{
+ IsDataLoaded = false;
_elementDataMan = null;
_instance = null;
}
diff --git a/Assets/PerfectWorld/Scripts/Debug/SkillTriggerPanel.cs b/Assets/PerfectWorld/Scripts/Debug/SkillTriggerPanel.cs
new file mode 100644
index 0000000000..097aab9c2c
--- /dev/null
+++ b/Assets/PerfectWorld/Scripts/Debug/SkillTriggerPanel.cs
@@ -0,0 +1,543 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using BrewMonster.Scripts;
+using BrewMonster.Scripts.Skills;
+using BrewMonster.UI;
+using UnityEngine;
+using UnityEngine.UI;
+using BrewMonster.Managers;
+using BrewMonster.Network;
+
+namespace BrewMonster
+{
+ ///
+ /// Debug skill trigger panel.
+ /// 调试技能触发面板。
+ ///
+ /// Populates a UI GridLayoutGroup with one button per usable skill on the host player.
+ /// Clicking a button fires the skill locally: animation + VFX only, no network, no cooldown, no resource check.
+ /// 用主角可用技能填充 UI GridLayoutGroup,每个技能一个按钮。
+ /// 点击按钮本地触发技能:仅播放动画 + 特效,无网络、无冷却、无资源消耗。
+ ///
+ /// Scene setup:
+ /// 1. Assign Player (CECHostPlayer reference).
+ /// 2. Assign Target Marker (a Transform ~5 units in front of the player — used for projectile/fly-VFX skills).
+ /// 3. Assign Skill Grid Container (RectTransform with GridLayoutGroup).
+ /// 4. Assign Skill Button Prefab (GameObject with a Button root, child Image for icon, child Text for label).
+ /// 5. Enter Play Mode; the grid is populated automatically. Call Refresh() again after role/weapon changes.
+ ///
+ public class SkillTriggerPanel : MonoBehaviour
+ {
+ ///
+ /// Sentinel entity ID used to represent the draggable target marker in the GFX system.
+ /// 用于在特效系统中表示可拖动目标标记的实体 ID。
+ ///
+ /// Local id for debug NPC marker; encoded with for GFX lookups.
+ public const int DEBUG_TARGET_ID = 1200;
+
+ /// Encode a local index as a server-style NPC id (high bit set).
+ public static int EncodeDebugNpcId(int localId = DEBUG_TARGET_ID) =>
+ unchecked((int)(0x80000000u | (uint)localId));
+
+ public static SkillTriggerPanel Instance { get; private set; }
+
+ [Header("References")]
+ [Tooltip("The host player to read skills from and trigger cast on.")]
+ [SerializeField] private CECHostPlayer player;
+
+ [Tooltip("World-space Transform used as target for projectile (non-self) skills. Place a Sphere ~5 units in front of the player.")]
+ [SerializeField] public CECMonsterTest targetMarker;
+
+ [Tooltip("RectTransform that has a GridLayoutGroup component. Buttons are instantiated here.")]
+ [SerializeField] private RectTransform skillGridContainer;
+
+ [Tooltip("Prefab with: root Button, child Image (icon), child Text (skill name/ID).")]
+ [SerializeField] private GameObject skillButtonPrefab;
+
+ [Header("Filter")]
+ [Tooltip("When enabled, hides skills that fail weapon/form/move-env restrictions. Disable to show ALL positive skills regardless of restrictions.")]
+ [SerializeField] private bool filterByRestrictions = false;
+
+ [Tooltip("Match CDlgSkillSubList god/evil path: false = base+god ranks, true = base+evil ranks.")]
+ [SerializeField] private bool isEvilSkillPath;
+
+ [SerializeField] private int skillCatalogLevel = 1;
+
+ ///
+ /// World position of the draggable target marker, read by CECSkillGfxMan.get_pos_by_id.
+ /// 可拖动目标标记的世界坐标,由 CECSkillGfxMan.get_pos_by_id 读取。
+ ///
+ public static Vector3 DebugTargetPosition =>
+ Instance != null && Instance.targetMarker != null
+ ? Instance.targetMarker.transform.position
+ : Vector3.zero;
+
+ private void Awake()
+ {
+ Instance = this;
+ }
+
+ private void OnDestroy()
+ {
+ if (Instance == this)
+ Instance = null;
+ }
+
+ private void Start()
+ {
+ RegisterSceneHostWithGameRun();
+ StartCoroutine(RefreshWhenReady());
+ }
+
+ ///
+ /// Animation test scene: in-scene is not created via .
+ /// GFX get_pos_by_id resolves host through → .
+ ///
+ private void RegisterSceneHostWithGameRun()
+ {
+ if (player == null)
+ return;
+
+ var gameRun = EC_Game.GetGameRun();
+ if (gameRun == null)
+ return;
+
+ gameRun.RegisterAnimSceneHostPlayer(player);
+ }
+
+ ///
+ /// Anim scene host swap: keep panel player reference in sync with bootstrap.
+ /// 动画场景切换角色时同步面板上的主角引用。
+ ///
+ public void SetHostPlayer(CECHostPlayer host)
+ {
+ if (host == null)
+ return;
+
+ player = host;
+ RegisterSceneHostWithGameRun();
+ }
+
+ /// Target id for / GFX composer (self = host cid, else marker NPC id).
+ private int ResolveLocalCastTargetId(CECSkill skill)
+ {
+ if (skill.GetTargetType() == 0)
+ return player.GetCharacterID();
+
+ if (targetMarker != null)
+ return targetMarker.GetNPCID();
+
+ return EncodeDebugNpcId();
+ }
+
+ ///
+ /// Polls until CECGameUIMan is ready and the host has skills (server, skill model, or config fallback).
+ ///
+ /// 轮询直到 UI 图集与技能数据就绪(服务端、CECHostSkillModel 或配置回退)。
+ ///
+ private const float k_RefreshWaitTimeoutSecs = 30f;
+ private IEnumerator RefreshWhenReady()
+ {
+ var wait = new WaitForSeconds(0.5f);
+ float elapsed = 0f;
+
+ // Wait for UI atlas first — required for icons regardless of skill source.
+ // 先等待 UI 图集加载完毕,图标渲染必须。
+ while (CECUIManager.Instance?.GetInGameUIMan() == null)
+ {
+ elapsed += 0.5f;
+ yield return wait;
+ }
+
+ ResolveHostPlayerReference();
+
+ // Wait for skills: server list, CECHostSkillModel catalog (anim scene), or config fallback.
+ // 等待技能:服务端列表、CECHostSkillModel 目录(动画场景)或配置回退。
+ while (player != null && !HasSkillCatalogOrPositiveSkills())
+ {
+ if (TryInjectFromSkillModel())
+ break;
+
+ if (elapsed >= k_RefreshWaitTimeoutSecs)
+ {
+ Debug.LogWarning("[SkillTriggerPanel] No skills after " +
+ $"{k_RefreshWaitTimeoutSecs}s — falling back to SkillStub.map injection.");
+ player.InjectDebugSkillsFromConfig();
+ break;
+ }
+
+ elapsed += 0.5f;
+ yield return wait;
+ }
+
+ Refresh();
+ }
+
+ private void ResolveHostPlayerReference()
+ {
+ if (player != null)
+ return;
+
+ player = EC_Game.GetGameRun()?.GetHostPlayer();
+ }
+
+ private bool HasSkillCatalogOrPositiveSkills()
+ {
+ if (player != null && player.GetPositiveSkillNum() > 0)
+ return true;
+
+ List catalog = CECHostSkillModel.Instance?.CollectSkillSubListSkillIds(isEvilSkillPath);
+ return catalog != null && catalog.Count > 0;
+ }
+
+ private bool TryInjectFromSkillModel()
+ {
+ if (player == null)
+ return false;
+
+ List catalog = CECHostSkillModel.Instance?.CollectSkillSubListSkillIds(isEvilSkillPath);
+ if (catalog == null || catalog.Count == 0)
+ return false;
+
+ player.InjectSkillsFromSkillModel(skillCatalogLevel, isEvilSkillPath);
+ return true;
+ }
+
+ ///
+ /// Loads every skill defined in SkillStub.map (config) directly into the player's skill list,
+ /// bypassing the server skill message. Use this when testing locally without a server.
+ /// 将 SkillStub.map(配置文件)中所有技能直接注入玩家技能列表,绕过服务端消息。
+ /// 适用于无服务端的本地测试场景。
+ ///
+ [ContextMenu("Debug: Inject All Config Skills")]
+ public void InjectConfigSkills()
+ {
+ if (player == null)
+ {
+ Debug.LogWarning("[SkillTriggerPanel] player is not assigned.");
+ return;
+ }
+ player.InjectDebugSkillsFromConfig();
+ Refresh();
+ }
+
+ ///
+ /// Rebuild host positive skills from (same catalog as CDlgSkillSubList) and refresh the grid.
+ /// AnimScenePlayerBootstrap calls this after each model / role switch.
+ ///
+ /// 从 CECHostSkillModel 重建主角正向技能并刷新网格(与 CDlgSkillSubList 相同目录)。
+ ///
+ [ContextMenu("Debug: Refresh From Skill Model")]
+ public void RefreshFromSkillModel(int level = -1)
+ {
+ ResolveHostPlayerReference();
+ if (player == null)
+ {
+ Debug.LogWarning("[SkillTriggerPanel] RefreshFromSkillModel: no host player.");
+ return;
+ }
+
+ if (level > 0)
+ skillCatalogLevel = level;
+
+ player.InjectSkillsFromSkillModel(skillCatalogLevel, isEvilSkillPath);
+
+ List catalogSkills = player.BuildSkillSubListSkills(skillCatalogLevel, isEvilSkillPath);
+ PopulateSkillGrid(catalogSkills, catalogSkills.Count);
+ }
+
+ public void SetEvilSkillPath(bool isEvil)
+ {
+ isEvilSkillPath = isEvil;
+ }
+
+ ///
+ /// Reload from CECHostSkillModel and optionally hide skills that fail weapon/form/env checks.
+ /// 从技能目录重新加载;applyRestrictions 为 true 时按武器/形态/环境过滤。
+ ///
+ public void ResetSkillsFromSkillModel(bool applyRestrictions, int level = -1)
+ {
+ filterByRestrictions = applyRestrictions;
+ RefreshFromSkillModel(level);
+ }
+
+ ///
+ /// Clears and repopulates the skill grid from the host player's current positive skill list.
+ /// Call again after equipping a different weapon or changing shape/form.
+ /// Right-click this component in the Inspector to call Refresh manually.
+ /// 从主角当前正向技能列表清空并重新填充技能格。
+ /// 在更换武器或变身后再次调用;右键此组件可手动调用。
+ ///
+ [ContextMenu("Refresh")]
+ public void Refresh()
+ {
+ if (player == null)
+ {
+ Debug.LogWarning("[SkillTriggerPanel] player is not assigned.");
+ return;
+ }
+ if (skillGridContainer == null)
+ {
+ Debug.LogWarning("[SkillTriggerPanel] skillGridContainer is not assigned.");
+ return;
+ }
+ if (skillButtonPrefab == null)
+ {
+ Debug.LogWarning("[SkillTriggerPanel] skillButtonPrefab is not assigned.");
+ return;
+ }
+
+ var catalogSkills = player.BuildSkillSubListSkills(skillCatalogLevel, isEvilSkillPath);
+ if (catalogSkills.Count > 0)
+ {
+ PopulateSkillGrid(catalogSkills, catalogSkills.Count);
+ return;
+ }
+
+ int total = player.GetPositiveSkillNum();
+ if (total == 0)
+ {
+ Debug.LogWarning("[SkillTriggerPanel] GetPositiveSkillNum() = 0. " +
+ "Skills are loaded via OnMsgHstSkillData (server message). " +
+ "Make sure the host player has received skill data before calling Refresh.");
+ return;
+ }
+
+ var positiveSkills = new List(total);
+ for (int i = 0; i < total; i++)
+ {
+ CECSkill skill = player.GetPositiveSkillByIndex(i);
+ if (skill != null)
+ positiveSkills.Add(skill);
+ }
+
+ PopulateSkillGrid(positiveSkills, total);
+ }
+
+ private void PopulateSkillGrid(IReadOnlyList skills, int totalListed)
+ {
+ foreach (Transform child in skillGridContainer)
+ Destroy(child.gameObject);
+
+ CECGameUIMan uiMan = CECUIManager.Instance?.GetInGameUIMan();
+ if (uiMan == null)
+ {
+ Debug.LogWarning("[SkillTriggerPanel] CECGameUIMan not ready — icon atlas not loaded. " +
+ "Call Refresh() after the game UI finishes initializing.");
+ return;
+ }
+
+ int added = 0;
+ int filtered = 0;
+ for (int i = 0; i < skills.Count; i++)
+ {
+ CECSkill skill = skills[i];
+ if (skill == null)
+ continue;
+
+ if (filterByRestrictions && !IsSkillUsable(skill))
+ {
+ filtered++;
+ continue;
+ }
+
+ GameObject cell = Instantiate(skillButtonPrefab, skillGridContainer);
+
+ string iconName = skill.GetIconFile();
+ if (!string.IsNullOrEmpty(iconName))
+ {
+ Sprite icon = uiMan.GetIcon(iconName, EC_GAMEUI_ICONS.ICONS_SKILL);
+ Image cellImage = GetIconImage(cell);
+ if (cellImage != null && icon != null)
+ cellImage.sprite = icon;
+ }
+ else
+ {
+ Debug.LogWarning("[SkillTriggerPanel] skill.GetIconFile() is empty for skill " + skill.GetSkillID());
+ }
+
+ Text cellText = cell.GetComponentInChildren();
+ if (cellText != null)
+ {
+ string skillName = skill.GetName();
+ cellText.text = string.IsNullOrEmpty(skillName)
+ ? $"[{skill.GetSkillID()}]\nLv{skill.GetSkillLevel()}"
+ : $"{skillName}\nLv{skill.GetSkillLevel()}";
+ }
+
+ CECSkill captured = skill;
+ Button btn = cell.GetComponentInChildren