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..2d16aa6a66 --- /dev/null +++ b/Assets/PerfectWorld/Scene/AnimationTest.unity @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2daf9dffc5bed73510959aebf1ee1b4b2fe326380d6417e1a262c919b651095e +size 70182 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