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); } } }