Files
test/Assets/PerfectWorld/Scripts/AnimScenePlayerBootstrap.cs
T
2026-05-19 10:46:26 +07:00

314 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using BrewMonster;
using BrewMonster.Network;
using BrewMonster.Scripts;
using UnityEngine;
namespace PerfectWorld.Scripts
{
/// <summary>
/// One row per <c>WEAPON_SUB_TYPE.action_type</c> / <see cref="CECPlayer.GetShowingWeaponType"/> suffix index (0..14).
/// 每个武器 action_type / 动作后缀索引一行(0..14)。
/// </summary>
[System.Serializable]
public struct AnimSceneWeaponSlot
{
public GameObject rightHandModelPrefab;
public GameObject leftHandModelPrefab;
}
/// <summary>
/// Test scene only: bootstraps <see cref="CECHostPlayer"/> model load. After each successful <see cref="CECPlayer.SetPlayerModel"/>,
/// optionally applies serialized weapon prefabs via <see cref="CECPlayer.AnimSceneAttachWeaponPrefabs"/> (sets <c>m_uAttackType</c> 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 <see cref="replaceHostOnRoleSwitch"/>).
/// </summary>
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;
[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 (014). 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, 14)]
private int activeWeaponActionTypeIndex;
[SerializeField]
private bool applyWeaponAfterModelLoad = true;
[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("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);
}
/// <summary>Swap profession/gender at runtime (async load inside).</summary>
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);
}
/// <summary>profIndex: 0..<see cref="Profession.NUM_PROFESSION"/>-1; genderIndex: 0 = male, 1 = female.</summary>
public void SwitchRole(int profIndex, int genderIndex)
{
SwitchRole((Profession)profIndex, (Gender)genderIndex);
}
/// <summary>
/// Custom inspector: call before <see cref="CECPlayer.PlayAction"/> when <paramref name="host"/> is this bootstrap's <see cref="player"/>.
/// </summary>
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();
}
/// <summary>
/// Login calls <see cref="PlayerVisual.InitPlayerEventDoneHandler"/> from <c>InitCharacter</c>; animation scenes skip that — bind here.
/// </summary>
private void AnimSceneBindPlayerVisualIfEnabled()
{
if (!bindPlayerVisualAfterModelLoad || player == null)
{
return;
}
PlayerVisual pv = player.GetComponentInChildren<PlayerVisual>();
if (pv == null)
{
return;
}
GameObject modelRoot = player.m_pPlayerCECModel != null ? player.m_pPlayerCECModel.m_pPlayerModel : null;
if (modelRoot != null)
{
pv.RefreshNamedAnimancer(modelRoot);
}
pv.InitPlayerEventDoneHandler();
}
/// <summary>
/// Apply <see cref="weaponByActionType"/>[<see cref="activeWeaponActionTypeIndex"/>] on the host (Play Mode).
/// 在主机上应用当前选中的武器槽(运行模式)。
/// </summary>
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);
}
}
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<CECHostPlayer>();
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
{
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.");
if (player != null)
{
EC_Game.GetGameRun()?.RegisterAnimSceneHostPlayer(player);
}
if (applyWeaponAfterModelLoad && player != null)
{
ApplyWeaponForActiveSlot();
}
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;
}
}
}
}