314 lines
12 KiB
C#
314 lines
12 KiB
C#
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 (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, 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;
|
||
}
|
||
}
|
||
}
|
||
}
|