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