ef5689055e
Merge from develop
1826 lines
63 KiB
C#
1826 lines
63 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using BrewMonster.Scripts;
|
|
using CSNetwork.GPDataType;
|
|
using UnityEngine;
|
|
using ModelViewer.Common;
|
|
|
|
using CombinedActMap = System.Collections.Generic.Dictionary<string, object>;
|
|
using CoGfxMap = System.Collections.Generic.Dictionary<string, object>;
|
|
using ConvexHullDataArray = System.Collections.Generic.List<object>;
|
|
using ECModelHookMap = System.Collections.Generic.Dictionary<string, object>;
|
|
using ActQueue = System.Collections.Generic.List<A3DCombActDynData>;
|
|
using BrewMonster;
|
|
using BrewMonster.Scripts.ECModel;
|
|
using Cysharp.Threading.Tasks;
|
|
using Unity.VisualScripting.FullSerializer;
|
|
using Unity.VisualScripting;
|
|
using Animancer;
|
|
public enum ECMScript
|
|
{
|
|
enumECMScriptStartAction = 0,
|
|
enumECMScriptEndActioin,
|
|
enumECMScriptInit,
|
|
enumECMScriptRelease,
|
|
enumECMScriptModelLoaded,
|
|
enumECMScriptChangeEquip,
|
|
enumECMScriptChangeEquipTableInit,
|
|
enumECMScriptPhysBreak,
|
|
enumECMScriptCount
|
|
}
|
|
|
|
public enum ECMScriptVar
|
|
{
|
|
enumECMVarModel = 0,
|
|
enumECMVarActName,
|
|
enumECMVarActChannel,
|
|
enumECMVarEquipId,
|
|
enumECMVarEquipFlag,
|
|
enumECMVarFashionMode,
|
|
enumECMVarPathId,
|
|
enumECMVarId,
|
|
enumECMVarEquipIndex,
|
|
enumECMVarBreakOffsetX,
|
|
enumECMVarBreakOffsetY,
|
|
enumECMVarBreakOffsetZ,
|
|
enumECMVarCasterId,
|
|
enumECMVarCastTargetId,
|
|
enumECMVarSelf,
|
|
enumECMVarCount
|
|
}
|
|
public enum ActionChannel
|
|
{
|
|
ACTCHA_DEFAULT = 0,
|
|
ACTCHA_WOUND = 1,
|
|
ACTCHA_MAX = 16, // Keep this value same with A3DSkinModelActionCore::ACTCHA_MAX
|
|
};
|
|
public class FX_BINDING_BASE
|
|
{
|
|
protected EVENT_INFO m_pInfo;
|
|
protected A3DCombActDynData m_pDynData;
|
|
public FX_BINDING_BASE(A3DCombActDynData pDynData)
|
|
{
|
|
m_pInfo = null;
|
|
m_pDynData = pDynData;
|
|
}
|
|
public virtual bool IsStop() { return false; }
|
|
public virtual bool IsStart() {return false;}
|
|
public virtual void Stop() {}
|
|
public virtual void Render() {}
|
|
public virtual void UpdateParam(CECModel pECModel, int nDeltaTime) {}
|
|
public void SetInfo(EVENT_INFO pInfo) { m_pInfo = pInfo; }
|
|
public EVENT_INFO GetInfo() { return m_pInfo; }
|
|
public A3DCombActDynData GetDynData() { return m_pDynData; }
|
|
};
|
|
|
|
public class GFX_BINDING : FX_BINDING_BASE
|
|
{
|
|
public GameObject m_pGfx;
|
|
public bool m_bNeedSetTM;
|
|
public bool m_bInitTMUpdated; // is the first time a delay update gfx to be set to place
|
|
public int m_dwTimeSpan;
|
|
public bool isStartPlaying = false;
|
|
private ParticleSystem _ps;
|
|
bool _destroyScheduled;
|
|
/// <summary>Prefab baseline local rotation (before event euler). Keeps correct orientation after reparent to hook. / 预制体基准本地旋转(事件欧拉之前),重挂到挂点后仍保持正确朝向</summary>
|
|
Quaternion _prefabLocalRot = Quaternion.identity;
|
|
Vector3 _prefabPosition = Vector3.zero;
|
|
public GFX_BINDING(A3DCombActDynData pDynData) : base(pDynData)
|
|
{
|
|
|
|
}
|
|
|
|
public override void Stop()
|
|
{
|
|
if (m_pGfx == null || _destroyScheduled)
|
|
return;
|
|
_destroyScheduled = true;
|
|
DestroyAfterParticlesFinish().Forget();
|
|
}
|
|
|
|
async UniTaskVoid DestroyAfterParticlesFinish()
|
|
{
|
|
GameObject go = m_pGfx;
|
|
CancellationToken ct = go != null ? go.transform.GetCancellationTokenOnDestroy() : default;
|
|
try
|
|
{
|
|
if (go == null)
|
|
return;
|
|
if (_ps != null)
|
|
{
|
|
var main = _ps.main;
|
|
var delayType = main.useUnscaledTime ? DelayType.Realtime : DelayType.DeltaTime;
|
|
if (main.loop)
|
|
{
|
|
// Let the effect run one full loop cycle, then stop emitting. / 再播完一整圈循环后停发粒子
|
|
// Longest main.duration under this GFX (loop period per particle system). / 取挂点下所有粒子的 duration 最大值作为一整圈时长
|
|
float oneLoop = 0f;
|
|
await UniTask.Delay(TimeSpan.FromSeconds(oneLoop), delayType, cancellationToken: ct);
|
|
if (_ps != null && _ps.IsAlive(true))
|
|
_ps.Stop(true, ParticleSystemStopBehavior.StopEmitting);
|
|
}
|
|
else
|
|
{
|
|
if (_ps.IsAlive(true))
|
|
_ps.Stop(true, ParticleSystemStopBehavior.StopEmitting);
|
|
}
|
|
//await UniTask.WaitUntil(() => _ps == null || !_ps.IsAlive(true), cancellationToken: ct);
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
Debug.Log("GFX_BINDING DestroyAfterParticlesFinish: OperationCanceledException");
|
|
// go was destroyed elsewhere; skip / 对象在其他地方被销毁
|
|
}
|
|
finally
|
|
{
|
|
if (m_pGfx != null)
|
|
GameObject.Destroy(m_pGfx);
|
|
m_pGfx = null;
|
|
_ps = null;
|
|
_destroyScheduled = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>True when this GFX has finished and may be removed from <see cref="A3DCombActDynData.m_ActFxArray"/>. / 特效已结束,可从绑定列表移除时为 true</summary>
|
|
public override bool IsStop()
|
|
{
|
|
if (_destroyScheduled)
|
|
return true;
|
|
if (!isStartPlaying)
|
|
return false;
|
|
return _ps == null || !_ps.isPlaying;
|
|
}
|
|
public override bool IsStart()
|
|
{
|
|
return isStartPlaying;
|
|
}
|
|
public override void Render() {
|
|
if (m_pGfx == null)
|
|
return;
|
|
|
|
CECModel host = GetDynData()?.GetHostModel();
|
|
if (m_pInfo is FX_BASE_INFO fx && host != null)
|
|
{
|
|
Transform hookT = null;
|
|
if (!string.IsNullOrEmpty(fx.m_strHookName))
|
|
{
|
|
hookT = host.GetHook(fx.m_strHookName, true);
|
|
}
|
|
|
|
Quaternion eventLocalRot = _prefabLocalRot *
|
|
Quaternion.Euler(
|
|
RadianToFloat(fx.m_fPitch),
|
|
RadianToFloat(fx.m_fYaw),
|
|
RadianToFloat(fx.m_fRot)
|
|
);
|
|
Vector3 hookLocalPos = _prefabPosition + EC_Utility.ToVector3(fx.m_vOffset);
|
|
|
|
if (hookT != null)
|
|
{
|
|
if (fx.m_bBindParent)
|
|
m_pGfx.transform.SetParent(hookT, false);
|
|
else
|
|
{
|
|
m_pGfx.transform.SetPositionAndRotation(
|
|
hookT.TransformPoint(hookLocalPos),
|
|
hookT.rotation * eventLocalRot);
|
|
}
|
|
}
|
|
|
|
if (hookT == null || fx.m_bBindParent)
|
|
{
|
|
m_pGfx.transform.localPosition = hookLocalPos;
|
|
m_pGfx.transform.localRotation = eventLocalRot;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pGfx.transform.localPosition = Vector3.zero;
|
|
m_pGfx.transform.localRotation = _prefabLocalRot;
|
|
}
|
|
|
|
m_pGfx.transform.localScale = Vector3.one;
|
|
m_pGfx.SetActive(true);
|
|
_ps = m_pGfx.GetComponent<ParticleSystem>();
|
|
if (_ps != null)
|
|
_ps.Play();
|
|
isStartPlaying = true;
|
|
}
|
|
public float RadianToFloat(float f)
|
|
{
|
|
return f * 180 / Mathf.PI;
|
|
}
|
|
public override void UpdateParam(CECModel pECModel, int nDeltaTime)
|
|
{
|
|
if(m_pGfx == null)
|
|
return;
|
|
if (m_pInfo != null && m_pInfo.m_dwStartTime <= nDeltaTime && !isStartPlaying)
|
|
{
|
|
Render();
|
|
|
|
var PSes = m_pGfx.GetComponentsInChildren<ParticleSystem>();
|
|
float span = -1;
|
|
foreach(var PS in PSes)
|
|
{
|
|
var duration = PS.main.duration;
|
|
//remove default duration and get max duration
|
|
if( duration!= 5 && duration > span && span !=100000)
|
|
{
|
|
span = duration;
|
|
}
|
|
}
|
|
m_dwTimeSpan = (int)span *1000;
|
|
m_pInfo.m_dwTimeSpan = (int)m_pInfo.m_dwStartTime + (int)span *1000;
|
|
|
|
}
|
|
}
|
|
public void SetGfx(GameObject gfx, Quaternion prefabRotation, Vector3 prefabPosition) {
|
|
m_pGfx = gfx;
|
|
_prefabPosition = prefabPosition;
|
|
_prefabLocalRot = prefabRotation;
|
|
m_pGfx.transform.localRotation = prefabRotation;
|
|
}
|
|
|
|
//A3DMATRIX4 CalcGfxParentTM(A3DSkeletonHook* pHook, CECModel* pECModel);
|
|
//void UpdateGfxParentTM(A3DSkeletonHook* pHook, CECModel* pECModel, int iDeltaTime);
|
|
};
|
|
public class A3DCombActDynData
|
|
{
|
|
protected A3DCombinedAction m_pAct;
|
|
protected CECModel m_pECModel;
|
|
protected int m_nCurLoop;
|
|
protected int m_nCurActLoop;
|
|
protected int m_dwTimeSpan;
|
|
protected float m_fWeight;
|
|
protected int m_nTransTime;
|
|
protected int m_dwUserData;
|
|
protected bool m_bAbsTrack;
|
|
protected bool m_bNoFx;
|
|
//ALISTPOSITION m_CurEventPos;
|
|
//const ChildActInfo* m_pParentInfo;
|
|
//CECModel* m_pParentModel;
|
|
protected int m_dwDeltaTime;
|
|
protected bool m_bStopPrevAct;
|
|
//important
|
|
//FxBindBaseList m_ActFxArray;
|
|
protected int m_nChannel;
|
|
|
|
protected int m_dwEventMask;
|
|
public List<ACTIONDYN_DATA> m_arrActLoopNum;
|
|
protected int m_nCurActIndex;
|
|
protected int m_dwDynComActSpan;
|
|
protected bool m_bIsInfiniteComAct;
|
|
|
|
//important
|
|
//EventInfoMap m_mapDynAddedInfo;
|
|
|
|
/// <summary>Attack event tied to this combined action, if any. / 与此组合动作绑定的攻击事件(若有)
|
|
/// this is alternative for m_IdCaster to m_IsAttackAct in c++
|
|
/// </summary>
|
|
public CECAttackEvent ActiveAttackEvent;
|
|
|
|
public bool m_bResetPSWhenActionStop;
|
|
public bool m_bSetSpeedWhenActStart;
|
|
public float m_fModelScale;
|
|
|
|
/// <summary>Elapsed time (ms) along the combined action timeline; drives FX event triggers. / 组合动作时间轴已播放毫秒数,用于触发特效事件</summary>
|
|
public int GetComActElapsedMs() { return m_dwTimeSpan; }
|
|
|
|
/// <summary>Host <see cref="CECModel"/> for hook lookup and FX attach. / 用于挂点查询与特效挂接的宿主模型</summary>
|
|
public CECModel GetHostModel() => m_pECModel;
|
|
|
|
|
|
public List<string> m_ActionNames = new List<string>();
|
|
public List<string> m_SFXNames = new List<string>();
|
|
public List<FX_BINDING_BASE> m_ActFxArray = new List<FX_BINDING_BASE>();
|
|
/// <summary>True when there is no attack or damage has been applied. / 无攻击事件或已造成伤害则为 true</summary>
|
|
public A3DCombActDynData(A3DCombinedAction pAct, CECModel pECModel, CECAttackEvent attackEvent)
|
|
{
|
|
m_pAct = pAct;
|
|
m_pECModel = pECModel;
|
|
m_nCurLoop = 0;
|
|
m_nCurActLoop = 0;
|
|
m_dwTimeSpan = 0;
|
|
m_fWeight = 1.0f;
|
|
m_nTransTime = 200;
|
|
m_dwUserData = 0;
|
|
m_bAbsTrack = false;
|
|
m_bNoFx = false;
|
|
// m_CurEventPos = 0;
|
|
// m_pParentInfo = 0;
|
|
// m_pParentModel = 0;
|
|
m_bStopPrevAct = false;
|
|
m_dwDeltaTime = 0;
|
|
m_nChannel = 0;
|
|
m_dwEventMask = -1;
|
|
//use active attack event instead of idCaster to m_IsAttackAct
|
|
ActiveAttackEvent = attackEvent;
|
|
// m_idCaster = 0;
|
|
// m_idCastTarget = 0;
|
|
// m_ptFixed = 0;
|
|
// m_SerialId = 0;
|
|
// m_IsAttackAct = false;
|
|
m_nCurActIndex = -1;
|
|
m_dwDynComActSpan = 0;
|
|
m_bIsInfiniteComAct = false;
|
|
m_bResetPSWhenActionStop = false;
|
|
m_bSetSpeedWhenActStart = false;
|
|
m_fModelScale = 1.0f;
|
|
m_arrActLoopNum = new List<ACTIONDYN_DATA>();
|
|
var actInfoList = pAct.m_ActLst;
|
|
foreach(var actInfo in actInfoList)
|
|
{
|
|
int nDynLoopNum = actInfo.CalcLoopNum();
|
|
if (!m_bIsInfiniteComAct && nDynLoopNum < 0) m_bIsInfiniteComAct = true;
|
|
m_arrActLoopNum.Add(new ACTIONDYN_DATA(nDynLoopNum, actInfo));
|
|
}
|
|
//Calculate the span of the combined action
|
|
RefreshCombinedActionSpansFromClips();
|
|
}
|
|
|
|
/// <param name="triggerVisualAndFx">
|
|
/// When true, publishes <see cref="PlayActionEvent"/> for Animancer, tracks <see cref="m_EventNames"/>,
|
|
/// and plays combined-action sound FX from data (unless <paramref name="bNoFx"/> suppresses audio).
|
|
/// / 为 true 时发布 PlayActionEvent、维护 m_EventNames,并按数据播放组合动作音效(bNoFx 时跳过音效)。
|
|
/// </param>
|
|
public void Play(int nChannel, float fWeight, int nTransTime, int dwEventMask, bool bRestart, bool bAbsTrack, bool bNoFx,
|
|
bool bForceStopPrevious = false, byte channelRank = 0)
|
|
{
|
|
m_nChannel = nChannel;
|
|
m_fWeight = fWeight;
|
|
m_nTransTime = nTransTime;
|
|
m_dwEventMask = dwEventMask;
|
|
m_bAbsTrack = bAbsTrack;
|
|
m_bNoFx = bNoFx;
|
|
|
|
// if (bRestart)
|
|
// m_nCurLoop = 0;
|
|
|
|
m_fModelScale = 1; //Vm_pECModel.GetAllRootBonesScale();
|
|
|
|
RefreshCombinedActionSpansFromClips();
|
|
|
|
// PublishVisual(bForceStopPrevious, channelRank);
|
|
// if (!m_bNoFx)
|
|
// TriggerSFxFromEventList();
|
|
Resume();
|
|
UpdateAct(0, m_pECModel.GetPlaySpeed());
|
|
if(true /*!m_bNoFx*/)
|
|
{
|
|
LoadFXFromEventList();
|
|
UpdateEvent(0, 0);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sound events embedded in combined action; skip gfx paths. / 组合动作内嵌音效事件;跳过 gfx 路径。
|
|
/// </summary>
|
|
async void LoadFXFromEventList()
|
|
{
|
|
if (m_bNoFx || m_pAct == null)
|
|
return;
|
|
|
|
var eventInfoList = m_pAct.m_EventInfoLst;
|
|
if (eventInfoList == null || eventInfoList.Count == 0)
|
|
return;
|
|
|
|
foreach (var eventInfo in eventInfoList)
|
|
{
|
|
if (eventInfo is FX_BASE_INFO sfx)
|
|
{
|
|
if (sfx.m_strFilePaths != null && sfx.m_strFilePaths.Count > 0)
|
|
{
|
|
string path = AFile.NormalizePath(sfx.m_strFilePaths[0], true);
|
|
if (path.Contains("gfx"))
|
|
goto GFX;
|
|
|
|
path = path.ToLower();
|
|
m_SFXNames.Add(path);
|
|
try
|
|
{
|
|
SFXManager.Instance
|
|
.PlaySkillSfxAtPointAsync(path, Vector3.zero).Forget();
|
|
}
|
|
finally
|
|
{
|
|
// remove AFTER sound finished
|
|
m_SFXNames.Remove(path);
|
|
}
|
|
goto END;
|
|
GFX:
|
|
{
|
|
path = "gfx/" + path;
|
|
Debug.Log("[GFX_BINDING] LoadFXFromEventList: " + path);
|
|
GameObject prefab = await AddressableManager.Instance.LoadPrefabAsync(path);
|
|
GFX_BINDING fx = new GFX_BINDING(this);
|
|
if(prefab != null && m_pECModel != null)
|
|
{
|
|
var gfx = GameObject.Instantiate(prefab, m_pECModel.transform);
|
|
gfx.SetActive(false);
|
|
Quaternion prefabRotation = prefab.transform.rotation;
|
|
Vector3 prefabPosition = prefab.transform.position;
|
|
fx.SetGfx(gfx,prefabRotation,prefabPosition);
|
|
}
|
|
else if (prefab == null)
|
|
{
|
|
Debug.LogWarning("Missing gfx prefab, using placeholder: " + path);
|
|
string path2 = "gfx/人物/技能/妖兽/NullDefault.gfx";
|
|
GameObject prefab2 = await AddressableManager.Instance.LoadPrefabAsync(path2);
|
|
var gfx = GameObject.Instantiate(prefab2, m_pECModel.transform);
|
|
gfx.SetActive(false);
|
|
Quaternion prefabRotation = prefab2.transform.rotation;
|
|
Vector3 prefabPosition = prefab2.transform.position;
|
|
fx.SetGfx(gfx,prefabRotation,prefabPosition);
|
|
|
|
}
|
|
if(fx != null)
|
|
{
|
|
m_ActFxArray.Add(fx);
|
|
fx.SetInfo(eventInfo);
|
|
}
|
|
// var gfxLogPath = Path.Combine(Application.dataPath, "PerfectWorld", "Scripts", "NPC", "GFXLOG.txt");
|
|
// var lines = new List<string>();
|
|
// if (File.Exists(gfxLogPath))
|
|
// {
|
|
// foreach (var line in File.ReadAllLines(gfxLogPath))
|
|
// {
|
|
// if (string.Equals(line.Trim(), path, StringComparison.OrdinalIgnoreCase))
|
|
// continue;
|
|
// lines.Add(line);
|
|
// }
|
|
// }
|
|
// lines.Add(path);
|
|
// File.WriteAllLines(gfxLogPath, lines);
|
|
}
|
|
END:
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set each <see cref="ACTION_INFO"/> span from loaded <see cref="AnimationClip"/> duration (milliseconds), then refresh <see cref="m_dwDynComActSpan"/>.
|
|
/// / 根据已加载 AnimationClip 时长(毫秒)设置各段 span,并更新组合总时长 m_dwDynComActSpan。
|
|
/// </summary>
|
|
void RefreshCombinedActionSpansFromClips()
|
|
{
|
|
if (m_pECModel != null && m_pAct?.m_ActLst != null)
|
|
{
|
|
foreach (var actInfo in m_pAct.m_ActLst)
|
|
{
|
|
if (actInfo == null || string.IsNullOrEmpty(actInfo.m_strName))
|
|
continue;
|
|
AnimationClip clip = m_pECModel.GetAnimationClip(actInfo.m_strName);
|
|
if (clip == null)
|
|
continue;
|
|
uint spanMs = (uint)Mathf.Max(1, Mathf.RoundToInt(clip.length * 1000f));
|
|
actInfo.SetTimeSpan(spanMs);
|
|
}
|
|
}
|
|
|
|
if (m_arrActLoopNum != null)
|
|
CalcDynComActSpan();
|
|
}
|
|
|
|
public void CalcDynComActSpan()
|
|
{
|
|
m_dwDynComActSpan = 0;
|
|
int nIndex, nNum = m_arrActLoopNum.Count;
|
|
for (nIndex = 0 ; nIndex < nNum ; ++nIndex) {
|
|
if (m_arrActLoopNum[nIndex].GetLoopNum() > 0)
|
|
m_dwDynComActSpan += m_arrActLoopNum[nIndex].GetTotalTime();
|
|
else
|
|
m_dwDynComActSpan += m_arrActLoopNum[nIndex].GetTimeSpan();
|
|
}
|
|
}
|
|
public void Resume()
|
|
{
|
|
m_dwTimeSpan = 0;
|
|
m_nCurActIndex = -1;
|
|
m_nCurActLoop = 0;
|
|
RemoveAllActiveFx();
|
|
}
|
|
public void Stop(bool bStopAct, bool bForceStopFx = false )
|
|
{
|
|
Debug.Log("[DefaultPolicy]Stop: " + m_pAct?.m_strName + " bStopAct: " + bStopAct + " bForceStopFx: " + bForceStopFx);
|
|
RemoveAllActiveFx();
|
|
if (ActiveAttackEvent != null)
|
|
{
|
|
ActiveAttackEvent.m_bSignaled = true;
|
|
}
|
|
|
|
|
|
// FlushDamageInfo();
|
|
|
|
// ClearParentInfo();
|
|
// m_nCurLoop = 0;
|
|
// m_nCurActLoop = 0;
|
|
|
|
// RemoveAllActiveFx(bForceStopFx);
|
|
// A3DSkinModel* pSkinModel = m_pECModel->GetA3DSkinModel();
|
|
|
|
// m_pECModel->GetStaticData()->OnScriptEndAction(m_pECModel, m_nChannel, m_pAct->GetName());
|
|
|
|
// if (IsResetPSWhenActStop())
|
|
// {
|
|
// m_pECModel->RemoveReplaceShader();
|
|
// SetResetPSWhenActStop(false);
|
|
// }
|
|
|
|
// if (m_pAct->GetResetMaterialScale())
|
|
// m_pECModel->ResetMaterialScale();
|
|
|
|
// if (m_pAct->GetStopChildrenAct())
|
|
// m_pECModel->StopChildrenAct();
|
|
|
|
// if (pSkinModel == NULL || !bStopAct)
|
|
// return;
|
|
|
|
// A3DSMActionChannel* pChannel = pSkinModel->GetActionChannel(m_nChannel);
|
|
|
|
// if (pChannel)
|
|
// pChannel->StopAction(m_pAct->GetRank(m_nChannel));
|
|
}
|
|
public void UpdateAct(uint dwDeltaTime, float fPlaySpeed = 1f)
|
|
{
|
|
m_dwTimeSpan += (int)dwDeltaTime;
|
|
|
|
if(true/*!m_bNoFx*/)
|
|
UpdateEvent((uint)m_dwTimeSpan, 0);
|
|
ACTION_INFO pCur = null, pNext = null;
|
|
if (m_arrActLoopNum.Count == 0)
|
|
return;
|
|
if (m_nCurActIndex == -1) // first
|
|
pNext = m_arrActLoopNum[0].GetActInfo();
|
|
else if (m_nCurActIndex == m_arrActLoopNum.Count - 1) // last
|
|
pCur = m_arrActLoopNum[m_nCurActIndex].GetActInfo();
|
|
else // in middle
|
|
{
|
|
pCur = m_arrActLoopNum[m_nCurActIndex].GetActInfo();
|
|
pNext = m_arrActLoopNum[m_nCurActIndex + 1].GetActInfo();
|
|
}
|
|
|
|
if (pCur != null)
|
|
{
|
|
if (m_dwTimeSpan >= pCur.m_dwEndTime) // µ±Ç°¶¯×÷³¬Ê±
|
|
{
|
|
m_nCurActLoop++;
|
|
|
|
if (!IsCurActFinished(pCur)) //ûÓнáÊøÔòÖØÐÂÑ»·
|
|
{
|
|
LoopCurAct(pCur, dwDeltaTime);
|
|
return;
|
|
}
|
|
else if (pNext == null) // ûÓÐÏÂÒ»¸ö¶¯×÷
|
|
{
|
|
LoopNext(dwDeltaTime);
|
|
return;
|
|
}
|
|
// else ÏÂÒ»¸ö¶¯×÷¿ªÊ¼
|
|
}
|
|
else // ²¥·Åµ±Ç°¶¯×÷
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (pNext != null)
|
|
{
|
|
if (m_dwTimeSpan >= pNext.m_dwStartTime) // ÏÂÒ»¸ö¶¯×÷ʱ¼äµ½´ï
|
|
{
|
|
m_nCurActLoop = 0;
|
|
m_nCurActIndex++;
|
|
EventBus.PublishChannel(m_pECModel.GetId(), new PlayActionEvent( pNext.m_strName, m_nTransTime, false, null, pNext.CalcLoopNum()==-1 && pNext.GetTimeSpan() > 10, fPlaySpeed));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void UpdateEvent(uint dwActTime, uint dwEventTime)
|
|
{
|
|
if (m_bNoFx)
|
|
return;
|
|
ExpireActFx();
|
|
foreach(var fx in m_ActFxArray)
|
|
{
|
|
fx.UpdateParam(m_pECModel, (int)dwActTime);
|
|
}
|
|
}
|
|
public void ExpireActFx()
|
|
{
|
|
for(int i = 0; i < m_ActFxArray.Count; i++)
|
|
{
|
|
FX_BINDING_BASE pFx = m_ActFxArray[i];
|
|
|
|
if (IsFxEnd(pFx, m_dwTimeSpan))
|
|
{
|
|
m_ActFxArray.RemoveAt(i);
|
|
pFx.Stop();
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool IsFxEnd(FX_BINDING_BASE pFx, int dwTimeSpan)
|
|
{
|
|
int dwMax = -1;
|
|
if (pFx.IsStop())
|
|
return true;
|
|
if (!pFx.IsStart())
|
|
return false;
|
|
if (pFx.GetInfo().m_dwTimeSpan > 10000 || pFx.GetInfo().m_dwTimeSpan == dwMax)
|
|
return false;
|
|
return false;
|
|
// Debug.Log( $"Span: {dwTimeSpan} TickTill{pFx.GetInfo().m_dwStartTime + pFx.GetInfo().m_dwTimeSpan}");
|
|
// return dwTimeSpan >= pFx.GetInfo().m_dwStartTime + pFx.GetInfo().m_dwTimeSpan;
|
|
}
|
|
|
|
bool IsCurActFinished(ACTION_INFO pCur)
|
|
{
|
|
return !pCur.IsInfinite() || m_nCurActLoop >= m_arrActLoopNum[m_nCurActIndex].GetLoopNum();
|
|
}
|
|
bool IsAllEventFinished() { return m_ActFxArray.Count == 0 ; }
|
|
public bool IsActionStopped()
|
|
{
|
|
return m_pAct.m_ActLst.Count == 0 || (m_pAct.m_nLoops != -1 && m_nCurLoop >= m_pAct.m_nLoops);
|
|
}
|
|
public bool IsAllFinished()
|
|
{
|
|
return IsActionStopped() && IsAllEventFinished();
|
|
}
|
|
bool IsActionFinished()
|
|
{
|
|
return m_dwTimeSpan >= m_dwDynComActSpan;
|
|
}
|
|
|
|
void LoopCurAct(ACTION_INFO pCur, uint dwDeltaTime)
|
|
{
|
|
m_dwTimeSpan = (int)(pCur.m_dwStartTime + m_dwTimeSpan - pCur.m_dwEndTime); // °Ñʱ¼äÉè»ØÈ¥
|
|
}
|
|
void LoopNext(uint dwDeltaTime)
|
|
{
|
|
m_nCurLoop++;
|
|
if (!IsActionStopped())
|
|
{
|
|
RemoveAllActiveFx();
|
|
m_nCurActIndex = -1;
|
|
Play(m_nChannel, m_fWeight, m_nTransTime, m_dwEventMask, false, m_bAbsTrack, m_bNoFx);
|
|
}
|
|
}
|
|
public void RemoveAllActiveFx()
|
|
{
|
|
foreach(var fx in m_ActFxArray)
|
|
{
|
|
if(fx != null)
|
|
{
|
|
Debug.Log("RemoveAllActiveFx: " + fx.GetInfo()?.m_pAct?.m_strName);
|
|
fx.Stop();
|
|
}
|
|
}
|
|
m_ActFxArray.Clear();
|
|
}
|
|
public void SetUserData(int dwUserData) { m_dwUserData = dwUserData; }
|
|
public void SetTransTime(int nTransTime) { m_nTransTime = nTransTime; }
|
|
public void SetStopPrevAct(bool bStopPrevAct) { m_bStopPrevAct = bStopPrevAct; }
|
|
public void SetNoFxFlag(bool bNoFx) { m_bNoFx = bNoFx; }
|
|
public void SetSpeedWhenActStart(bool bResetSpeed) { m_bSetSpeedWhenActStart = bResetSpeed; }
|
|
public bool GetStopPrevAct() { return m_bStopPrevAct; }
|
|
public int GetUserData() { return m_dwUserData; }
|
|
public int GetTransTime() { return m_nTransTime; }
|
|
public bool GetNoFxFlag() { return m_bNoFx; }
|
|
public bool IsSetSpeedWhenActStart() { return m_bSetSpeedWhenActStart; }
|
|
public A3DCombinedAction GetComAct() { return m_pAct; }
|
|
}
|
|
public class ChannelActNode
|
|
{
|
|
public byte m_Rank;
|
|
public A3DCombActDynData m_pActive;
|
|
public bool m_pActFlag;
|
|
public int m_dwFlagMode;
|
|
public ActQueue m_QueuedActs;
|
|
public ChannelActNode()
|
|
{
|
|
m_Rank = 0;
|
|
m_pActive = null;
|
|
m_pActFlag = false;
|
|
m_dwFlagMode = 0;
|
|
if(m_QueuedActs == null)
|
|
{
|
|
m_QueuedActs = new ActQueue();
|
|
}
|
|
m_QueuedActs.Clear();
|
|
}
|
|
public void RemoveQueuedActs()
|
|
{
|
|
m_pActive.RemoveAllActiveFx();
|
|
m_QueuedActs.Clear();
|
|
}
|
|
}
|
|
public class ChannelAct
|
|
{
|
|
public List<ChannelActNode> m_RankNodes = new List<ChannelActNode>();
|
|
|
|
public ChannelAct()
|
|
{
|
|
Release();
|
|
}
|
|
|
|
public void Release()
|
|
{
|
|
if(m_RankNodes == null)
|
|
{
|
|
m_RankNodes = new List<ChannelActNode>();
|
|
}
|
|
else
|
|
{
|
|
foreach(var node in m_RankNodes)
|
|
{
|
|
if(node != null)
|
|
{
|
|
node.RemoveQueuedActs();
|
|
}
|
|
}
|
|
}
|
|
m_RankNodes.Clear();
|
|
}
|
|
|
|
public ChannelActNode GetNodeByRank(byte rank)
|
|
{
|
|
if(m_RankNodes == null || m_RankNodes.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
foreach(var node in m_RankNodes)
|
|
{
|
|
if(node.m_Rank == rank)
|
|
{
|
|
return node;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public ChannelActNode GetHighestRankNode()
|
|
{
|
|
if(m_RankNodes == null || m_RankNodes.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
ChannelActNode highestNode = m_RankNodes[0];
|
|
foreach(var node in m_RankNodes)
|
|
{
|
|
if(node.m_Rank > highestNode.m_Rank)
|
|
{
|
|
highestNode = node;
|
|
}
|
|
}
|
|
return highestNode;
|
|
}
|
|
|
|
public ChannelActNode RemoveNodeByRank(byte rank)
|
|
{
|
|
foreach(var node in m_RankNodes)
|
|
{
|
|
if(node.m_Rank == rank)
|
|
{
|
|
m_RankNodes.Remove(node);
|
|
return node;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
public static class CECModelConstants
|
|
{
|
|
public const int ECM_SCRIPT_MAX_VAR_COUNT = 8;
|
|
public const int OUTER_DATA_COUNT = 8;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
public struct ECM_SCRIPT_VAR
|
|
{
|
|
[MarshalAs(UnmanagedType.I4)]
|
|
public int var_count;
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CECModelConstants.ECM_SCRIPT_MAX_VAR_COUNT)]
|
|
public int[] var_index;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
public struct BoneScale
|
|
{
|
|
public int m_nIndex;
|
|
public int m_nType;
|
|
public A3DVECTOR3 m_vScale;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
public struct BoneScaleEx
|
|
{
|
|
public int m_nIndex;
|
|
public float m_fLenFactor;
|
|
public float m_fThickFactor;
|
|
public float m_fWholeFactor;
|
|
}
|
|
|
|
public class ActChannelInfo
|
|
{
|
|
public List<string> bone_names = new List<string>();
|
|
public List<int> joint_indices = new List<int>();
|
|
|
|
public ActChannelInfo Clone()
|
|
{
|
|
return new ActChannelInfo
|
|
{
|
|
bone_names = new List<string>(bone_names),
|
|
joint_indices = new List<int>(joint_indices)
|
|
};
|
|
}
|
|
}
|
|
|
|
public enum A3DBLEND
|
|
{
|
|
A3DBLEND_SRCALPHA,
|
|
A3DBLEND_INVSRCALPHA
|
|
}
|
|
|
|
public struct A3DSHADER
|
|
{
|
|
public A3DBLEND SrcBlend;
|
|
public A3DBLEND DestBlend;
|
|
}
|
|
|
|
public class CECModelStaticData
|
|
{
|
|
private string m_strFilePath = string.Empty;
|
|
private string m_strSkinModelPath = string.Empty;
|
|
private bool m_bAutoUpdate = true;
|
|
private bool m_bActMapped;
|
|
public Dictionary<string, A3DCombinedAction> m_ActionMap = new Dictionary<string, A3DCombinedAction>();
|
|
private uint m_dwVersion;
|
|
private string m_strHook = string.Empty;
|
|
private string m_strCCName = string.Empty;
|
|
private readonly List<BoneScale> m_BoneScales = new List<BoneScale>();
|
|
private readonly List<BoneScaleEx> m_BoneScaleExArr = new List<BoneScaleEx>();
|
|
private string m_strScaleBaseBone = string.Empty;
|
|
private uint m_OrgColor = 0xFFFFFFFF;
|
|
private uint m_EmissiveColor = 0;
|
|
private float m_fDefPlaySpeed = 1.0f;
|
|
private A3DSHADER m_BlendMode = new A3DSHADER
|
|
{
|
|
SrcBlend = A3DBLEND.A3DBLEND_SRCALPHA,
|
|
DestBlend = A3DBLEND.A3DBLEND_INVSRCALPHA
|
|
};
|
|
public float[] m_OuterData = new float[CECModelConstants.OUTER_DATA_COUNT];
|
|
private bool m_bCanCastShadow = true;
|
|
private bool m_bRenderSkinModel = true;
|
|
private bool m_bRenderEdge = true;
|
|
private readonly CoGfxMap m_CoGfxMap = new CoGfxMap();
|
|
private readonly ConvexHullDataArray m_ConvexHullDataArr = new ConvexHullDataArray();
|
|
private readonly A3DAABB m_CHAABB = new A3DAABB();
|
|
private readonly Dictionary<int, ActChannelInfo> m_ChannelInfoDict = new Dictionary<int, ActChannelInfo>();
|
|
private readonly Dictionary<int, uint> m_EventMasks = new Dictionary<int, uint>();
|
|
private readonly string[] m_Scripts = new string[(int)ECMScript.enumECMScriptCount];
|
|
private readonly bool[] m_ScriptEnable = new bool[(int)ECMScript.enumECMScriptCount];
|
|
private readonly CLuaMemTbl m_ScriptMemTbl = new CLuaMemTbl();
|
|
private bool m_bInitGlobalScript;
|
|
private string m_strGlobalScriptName = string.Empty;
|
|
|
|
public class ChildInfo
|
|
{
|
|
public string m_strName = string.Empty;
|
|
public string m_strPath = string.Empty;
|
|
public string m_strHHName = string.Empty;
|
|
public string m_strCCName = string.Empty;
|
|
}
|
|
|
|
private readonly List<ChildInfo> m_ChildInfoArray = new List<ChildInfo>();
|
|
private readonly List<string> m_AdditionalSkinLst = new List<string>();
|
|
private string m_strPhysFileName = string.Empty;
|
|
private object m_pPhysSyncData;
|
|
private readonly ECModelHookMap m_ECModelHookMap = new ECModelHookMap();
|
|
private string m_strPixelShader = string.Empty;
|
|
private string m_strShaderTex = string.Empty;
|
|
private readonly List<object> m_vecPSConsts = new List<object>();
|
|
public CECModelStaticData()
|
|
{
|
|
ResetOuterData();
|
|
m_CHAABB.Clear();
|
|
}
|
|
|
|
public bool LoadData(CombinedActionSO combinedAction)
|
|
{
|
|
foreach(var action in combinedAction.CombinedActionData)
|
|
{
|
|
m_ActionMap[action.m_strName] = action;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool Save(string szFile, CECModel pModel)
|
|
{
|
|
throw new NotImplementedException("CECModelStaticData.Save requires the legacy serialization pipeline to be ported.");
|
|
}
|
|
|
|
public bool SaveBoneScaleInfo(string szFile)
|
|
{
|
|
throw new NotImplementedException("CECModelStaticData.SaveBoneScaleInfo is not yet implemented.");
|
|
}
|
|
|
|
public void Release()
|
|
{
|
|
m_BoneScales.Clear();
|
|
m_BoneScaleExArr.Clear();
|
|
m_CoGfxMap.Clear();
|
|
m_ActionMap = null;
|
|
m_AdditionalSkinLst.Clear();
|
|
m_ChildInfoArray.Clear();
|
|
m_ConvexHullDataArr.Clear();
|
|
m_ChannelInfoDict.Clear();
|
|
m_EventMasks.Clear();
|
|
Array.Clear(m_Scripts, 0, m_Scripts.Length);
|
|
Array.Clear(m_ScriptEnable, 0, m_ScriptEnable.Length);
|
|
m_ECModelHookMap.Clear();
|
|
m_vecPSConsts.Clear();
|
|
m_bInitGlobalScript = false;
|
|
m_strGlobalScriptName = string.Empty;
|
|
m_pPhysSyncData = null;
|
|
m_CHAABB.Clear();
|
|
ResetOuterData();
|
|
}
|
|
|
|
public ConvexHullDataArray GetConvexHullData() => m_ConvexHullDataArr;
|
|
|
|
public bool HasCHAABB() => m_ConvexHullDataArr.Count != 0;
|
|
|
|
public A3DAABB GetCHAABB() => m_CHAABB;
|
|
|
|
public bool UpdateScript(int index, string strScript, bool bCompileOnly)
|
|
{
|
|
if (index < 0 || index >= (int)ECMScript.enumECMScriptCount)
|
|
return false;
|
|
|
|
if (!bCompileOnly)
|
|
m_Scripts[index] = strScript ?? string.Empty;
|
|
|
|
m_ScriptEnable[index] = !string.IsNullOrEmpty(m_Scripts[index]);
|
|
return m_ScriptEnable[index];
|
|
}
|
|
|
|
public CoGfxMap GetCoGfxMap() => m_CoGfxMap;
|
|
|
|
public Dictionary<string, A3DCombinedAction> GetCombinedAction() => m_ActionMap;
|
|
|
|
public string GetPhysFileName() => m_strPhysFileName;
|
|
|
|
public void SetPhysFileName(string szFile) => m_strPhysFileName = szFile ?? string.Empty;
|
|
|
|
public object GetPhysSyncData() => m_pPhysSyncData;
|
|
|
|
public void SetPhysSyncData(object p) => m_pPhysSyncData = p;
|
|
|
|
public uint GetVersion() => m_dwVersion;
|
|
|
|
public string GetPixelShaderPath() => m_strPixelShader;
|
|
|
|
public void SetPixelShaderPath(string szPath) => m_strPixelShader = szPath ?? string.Empty;
|
|
|
|
public string GetShaderTexture() => m_strShaderTex;
|
|
|
|
public void SetShaderTexture(string szPath) => m_strShaderTex = szPath ?? string.Empty;
|
|
|
|
public IList<object> GetPSConstVec() => m_vecPSConsts;
|
|
|
|
public void SetActionEventMask(int nChannel, uint dwMask) => m_EventMasks[nChannel] = dwMask;
|
|
|
|
public uint GetActionEventMask(int nChannel)
|
|
{
|
|
return m_EventMasks.TryGetValue(nChannel, out uint mask) ? mask : uint.MaxValue;
|
|
}
|
|
|
|
public IReadOnlyDictionary<int, uint> GetActionEventMaskSnapshot() => m_EventMasks;
|
|
|
|
public void OnScriptEndAction(CECModel pModel, int nChannel, string szActName)
|
|
{
|
|
if (!IsScriptEnabled(ECMScript.enumECMScriptEndActioin))
|
|
return;
|
|
|
|
Debug.LogWarning($"CECModelStaticData.OnScriptEndAction invoked for {szActName}, scripting runtime not yet ported.");
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
m_OrgColor = 0xFFFFFFFF;
|
|
m_EmissiveColor = 0;
|
|
m_fDefPlaySpeed = 1.0f;
|
|
m_BlendMode = new A3DSHADER
|
|
{
|
|
SrcBlend = A3DBLEND.A3DBLEND_SRCALPHA,
|
|
DestBlend = A3DBLEND.A3DBLEND_INVSRCALPHA
|
|
};
|
|
|
|
ResetOuterData();
|
|
m_BoneScales.Clear();
|
|
m_BoneScaleExArr.Clear();
|
|
m_strScaleBaseBone = string.Empty;
|
|
m_ChannelInfoDict.Clear();
|
|
m_strPhysFileName = string.Empty;
|
|
}
|
|
|
|
public void AddScriptMethod(int index, string szScript)
|
|
{
|
|
if (index < 0 || index >= (int)ECMScript.enumECMScriptCount)
|
|
return;
|
|
|
|
m_ScriptEnable[index] = !string.IsNullOrEmpty(szScript);
|
|
}
|
|
|
|
public void OnScriptPlayAction(CECModel pModel, int nChannel, string szActName)
|
|
{
|
|
if (!IsScriptEnabled(ECMScript.enumECMScriptStartAction))
|
|
return;
|
|
|
|
Debug.LogWarning($"CECModelStaticData.OnScriptPlayAction triggered for {szActName}, scripting runtime not available.");
|
|
}
|
|
|
|
public void OnScriptChangeEquip(CECModel pModel, int nEquipId, int nEquipFlag, bool bFashionMode, int nPathId, int nEquipIndex)
|
|
{
|
|
if (!IsScriptEnabled(ECMScript.enumECMScriptChangeEquip))
|
|
return;
|
|
|
|
Debug.LogWarning("CECModelStaticData.OnScriptChangeEquip called, scripting runtime not available.");
|
|
}
|
|
|
|
public void OnScriptPhysBreak(CECModel pModel, float fBreakOffsetX, float fBreakOffsetY, float fBreakOffsetZ)
|
|
{
|
|
if (!IsScriptEnabled(ECMScript.enumECMScriptPhysBreak))
|
|
return;
|
|
|
|
Debug.LogWarning("CECModelStaticData.OnScriptPhysBreak called, scripting runtime not available.");
|
|
}
|
|
|
|
public void InitGlobalScript()
|
|
{
|
|
if (m_bInitGlobalScript)
|
|
return;
|
|
|
|
if (!IsScriptEnabled(ECMScript.enumECMScriptInit))
|
|
return;
|
|
|
|
m_bInitGlobalScript = true;
|
|
m_strGlobalScriptName = $"GlobalScriptFunc_{DateTime.UtcNow.Ticks}";
|
|
}
|
|
|
|
public bool LoadAdditionalSkin(object pFile, uint dwVersion, bool bLoadAdditionalSkin)
|
|
{
|
|
Debug.LogWarning("CECModelStaticData.LoadAdditionalSkin stub invoked.");
|
|
return true;
|
|
}
|
|
|
|
public IList<string> GetAdditionalSkinList() => m_AdditionalSkinLst;
|
|
|
|
public IList<ChildInfo> GetChildInfoArray() => m_ChildInfoArray;
|
|
|
|
public float[] GetOuterData() => m_OuterData;
|
|
|
|
public bool CanCastShadow => m_bCanCastShadow;
|
|
public bool CanRenderSkinModel => m_bRenderSkinModel;
|
|
public bool CanRenderEdge => m_bRenderEdge;
|
|
|
|
private bool IsScriptEnabled(ECMScript scriptId)
|
|
{
|
|
int idx = (int)scriptId;
|
|
if (idx < 0 || idx >= m_ScriptEnable.Length)
|
|
return false;
|
|
return m_ScriptEnable[idx];
|
|
}
|
|
|
|
private void ResetOuterData()
|
|
{
|
|
for (int i = 0; i < m_OuterData.Length; i++)
|
|
m_OuterData[i] = 1.0f;
|
|
}
|
|
}
|
|
|
|
public class CLuaMemTbl
|
|
{
|
|
public void Init(string tableName) { }
|
|
public bool AddMethod(string name, IList<string> args, string script) => false;
|
|
public void RemoveMethod(string name) { }
|
|
public void Release() { }
|
|
}
|
|
|
|
public class CECModel
|
|
{
|
|
|
|
private bool m_bInheritParentId = true;
|
|
private int m_nId = 0;
|
|
public GameObject m_pPlayerModel;
|
|
private const uint COMACT_FLAG_MODE_NONE = 0;
|
|
private const uint COMACT_FLAG_MODE_ONCE_IGNOREGFX = 2;
|
|
private const uint COMACT_FLAG_MODE_ONCE_MULTIIGNOREGFX = 3;
|
|
protected CECModelStaticData m_pMapModel = new CECModelStaticData();
|
|
protected NamedAnimancerComponent m_pMapAnimancer;
|
|
public SkeletonBuilder m_skeletonBuilder;
|
|
private Dictionary<string, Transform> m_hookCache = new Dictionary<string, Transform>();
|
|
private Dictionary<string, Transform> m_hangerPositionCache = new Dictionary<string, Transform>();
|
|
private Dictionary<string, CECModel> m_childModels = new Dictionary<string, CECModel>();
|
|
private Transform m_transform;
|
|
private bool m_bAbsTrack = false;
|
|
private bool m_bFirstActPlayed = false;
|
|
private float m_fPlaySpeed = 1.0f;
|
|
public bool InheritParentId() => m_bInheritParentId;
|
|
public void SetId(int nId) => m_nId = nId;
|
|
public int GetId() => m_nId;
|
|
|
|
/// <summary>Channel playback container for combined actions. / 组合动作通道容器。</summary>
|
|
public ChannelAct GetChannelAct(int nChannel)
|
|
{
|
|
if (nChannel < 0 || nChannel >= m_ChannelActs.Length)
|
|
return null;
|
|
return m_ChannelActs[nChannel];
|
|
}
|
|
|
|
//16 is
|
|
ChannelAct[] m_ChannelActs = InitChannelActs();
|
|
private static ChannelAct[] InitChannelActs()
|
|
{
|
|
var acts = new ChannelAct[(int)ActionChannel.ACTCHA_MAX];
|
|
for (int i = 0; i < acts.Length; i++)
|
|
acts[i] = new ChannelAct();
|
|
return acts;
|
|
}
|
|
public void ClearComActFlag(bool bSignalCurrent) { ClearComActFlag(0, bSignalCurrent); }
|
|
public void ClearComActFlag(int nChannel, bool bSignalCurrent)
|
|
{
|
|
/* ChannelAct & ca = m_ChannelActs[nChannel];
|
|
ChannelActNode* pNode = ca.GetHighestRankNode();
|
|
|
|
if (pNode)
|
|
{
|
|
if (pNode->m_pActFlag && bSignalCurrent)
|
|
*pNode->m_pActFlag = true;
|
|
|
|
//ca.m_dwFlagMode = COMACT_FLAG_MODE_NONE;
|
|
pNode->m_pActFlag = NULL;
|
|
}*/
|
|
}
|
|
|
|
public bool HasCHAABB() { return m_pMapModel.HasCHAABB(); }
|
|
|
|
/// <summary>
|
|
/// Set SkeletonBuilder reference for hook lookup
|
|
/// 设置用于挂点查找的SkeletonBuilder引用
|
|
/// </summary>
|
|
/// <param name="builder">SkeletonBuilder instance / SkeletonBuilder实例</param>
|
|
public void SetSkeletonBuilder(SkeletonBuilder builder)
|
|
{
|
|
m_skeletonBuilder = builder;
|
|
m_hookCache?.Clear(); // Invalidate cache on model change
|
|
}
|
|
public void SetNamedAnimancerComponent(NamedAnimancerComponent animancer)
|
|
{
|
|
m_pMapAnimancer = animancer;
|
|
}
|
|
public void SetCombinedAction(CombinedActionSO combinedAction)
|
|
{
|
|
//workaround: load data from combined action. C++ load from CMS file aka prefab. we already include those data inside the prefab
|
|
//dunno if we lack of any imformation
|
|
m_pMapModel.LoadData(combinedAction);
|
|
}
|
|
/// <summary>
|
|
/// Get the SkeletonBuilder component
|
|
/// 获取SkeletonBuilder组件
|
|
/// </summary>
|
|
/// <returns>SkeletonBuilder instance or null / SkeletonBuilder实例,未找到返回null</returns>
|
|
public SkeletonBuilder GetSkeletonBuilder()
|
|
{
|
|
return m_skeletonBuilder;
|
|
}
|
|
public NamedAnimancerComponent GetNamedAnimancerComponent()
|
|
{
|
|
return m_pMapAnimancer;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize SkeletonBuilder reference - call this after model is loaded
|
|
/// 初始化SkeletonBuilder引用 - 在模型加载后调用
|
|
/// </summary>
|
|
public void InitializeSkeletonBuilder()
|
|
{
|
|
// Try to find SkeletonBuilder in children (where it's typically attached)
|
|
// 尝试在子对象中查找SkeletonBuilder(通常附加在那里)
|
|
if (m_transform != null)
|
|
{
|
|
m_skeletonBuilder = m_transform.GetComponentInChildren<SkeletonBuilder>(true);
|
|
m_pMapAnimancer = m_transform.GetComponentInChildren<NamedAnimancerComponent>(true);
|
|
}
|
|
|
|
if (m_skeletonBuilder == null && m_pMapAnimancer == null && m_transform != null)
|
|
{
|
|
// Fallback: search in parent hierarchy (for NPCs/Players)
|
|
// 回退:在父层次结构中搜索(用于NPC/玩家)
|
|
m_skeletonBuilder = m_transform.GetComponentInParent<SkeletonBuilder>();
|
|
m_pMapAnimancer = m_transform.GetComponentInParent<NamedAnimancerComponent>();
|
|
}
|
|
|
|
if (m_skeletonBuilder == null)
|
|
{
|
|
BMLogger.LogWarning($"[CECModel] SkeletonBuilder not found for {(m_transform != null ? m_transform.name : "null")}. Hooks will not be available.");
|
|
}
|
|
else if (m_pMapAnimancer == null)
|
|
{
|
|
BMLogger.LogWarning($"[CECModel] NamedAnimancerComponent not found for {(m_transform != null ? m_transform.name : "null")}. Animancer will not be available.");
|
|
}
|
|
else
|
|
{
|
|
// Clear cache when skeleton is set
|
|
// 设置骨架时清除缓存
|
|
m_hookCache?.Clear();
|
|
BMLogger.Log($"[CECModel] SkeletonBuilder initialized for {(m_transform != null ? m_transform.name : "null")}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set Transform reference for this model
|
|
/// 设置此模型的Transform引用
|
|
/// </summary>
|
|
/// <param name="transform">Transform instance / Transform实例</param>
|
|
public void SetTransform(Transform transform)
|
|
{
|
|
m_transform = transform;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Transform reference for this model
|
|
/// 获取此模型的Transform引用
|
|
/// </summary>
|
|
public Transform transform
|
|
{
|
|
get { return m_transform; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get hook Transform by name
|
|
/// 根据名称获取挂点变换
|
|
/// </summary>
|
|
/// <param name="hookName">Hook name / 挂点名称</param>
|
|
/// <param name="recursive">Search recursively / 递归搜索</param>
|
|
/// <returns>Hook Transform or null / 挂点变换,未找到返回null</returns>
|
|
public Transform GetHook(string hookName, bool recursive = true)
|
|
{
|
|
// Auto-initialize if not set (lazy initialization)
|
|
// 如果未设置则自动初始化(延迟初始化)
|
|
if (m_skeletonBuilder == null)
|
|
{
|
|
InitializeSkeletonBuilder();
|
|
if (m_skeletonBuilder == null)
|
|
{
|
|
return null; // Still no skeleton found
|
|
}
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(hookName))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Check cache first
|
|
// 首先检查缓存
|
|
if (m_hookCache.TryGetValue(hookName, out Transform cachedHook))
|
|
{
|
|
if (cachedHook != null) // Unity "fake null" check
|
|
{
|
|
return cachedHook;
|
|
}
|
|
m_hookCache.Remove(hookName); // Remove invalid entry
|
|
}
|
|
|
|
// Lookup from skeleton
|
|
// 从骨架查找
|
|
Transform hook = m_skeletonBuilder.GetHook(hookName, recursive);
|
|
|
|
if (hook != null)
|
|
{
|
|
m_hookCache[hookName] = hook; // Cache for performance
|
|
}
|
|
return hook;
|
|
}
|
|
public AnimationClip GetAnimationClip(string animationName)
|
|
{
|
|
if (m_pMapAnimancer != null)
|
|
{
|
|
return m_pMapAnimancer.States.TryGet(animationName, out AnimancerState state) ? state.Clip : null;
|
|
}
|
|
return null;
|
|
}
|
|
public float[] GetOuterData() { return m_pMapModel.m_OuterData; }
|
|
|
|
/// <summary>
|
|
/// Invalidate hook cache (call when model reloads)
|
|
/// 使挂点缓存失效(在模型重新加载时调用)
|
|
/// </summary>
|
|
public void InvalidateHookCache()
|
|
{
|
|
m_hookCache.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a child model (weapon, pet, etc.)
|
|
/// 注册子模型(武器、宠物等)
|
|
/// </summary>
|
|
/// <param name="hangerName">Hanger name / 挂载者名称</param>
|
|
/// <param name="childModel">Child model instance / 子模型实例</param>
|
|
public void RegisterChildModel(string hangerName, CECModel childModel)
|
|
{
|
|
if (!string.IsNullOrEmpty(hangerName) && childModel != null)
|
|
{
|
|
m_childModels[hangerName] = childModel;
|
|
BMLogger.Log($"[CECModel] Registered child model '{hangerName}'");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregister a child model
|
|
/// 注销子模型
|
|
/// </summary>
|
|
/// <param name="hangerName">Hanger name / 挂载者名称</param>
|
|
public void UnregisterChildModel(string hangerName)
|
|
{
|
|
if (!string.IsNullOrEmpty(hangerName))
|
|
{
|
|
if (m_childModels.Remove(hangerName))
|
|
{
|
|
BMLogger.Log($"[CECModel] Unregistered child model '{hangerName}'");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get child model by hanger name
|
|
/// 根据挂载者名称获取子模型
|
|
/// </summary>
|
|
/// <param name="hangerName">Hanger name / 挂载者名称</param>
|
|
/// <returns>Child model or null if not found / 子模型,未找到返回null</returns>
|
|
public CECModel GetChildModel(string hangerName)
|
|
{
|
|
if (string.IsNullOrEmpty(hangerName))
|
|
return null;
|
|
|
|
return m_childModels.TryGetValue(hangerName, out CECModel child) ? child : null;
|
|
}
|
|
|
|
public bool AddChildModel(
|
|
string szHangerName,
|
|
bool bOnBone,
|
|
string szHookName,
|
|
CECModel pChild,
|
|
string szCCName)
|
|
{
|
|
if (m_childModels.TryGetValue(szHangerName, out _))
|
|
return false;
|
|
|
|
if (pChild == null)
|
|
{
|
|
return false;
|
|
}
|
|
if(m_skeletonBuilder == null )
|
|
{
|
|
return false;
|
|
}
|
|
if(pChild.m_skeletonBuilder == null)
|
|
{
|
|
return false;
|
|
}
|
|
Transform hook = m_skeletonBuilder.GetHook(szHookName, true);
|
|
Transform hangger = pChild.m_skeletonBuilder.GetHook(szCCName, true);
|
|
if (hook == null || hangger == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Align child CC/hanger to parent hook in world space, then parent with world pose preserved.
|
|
// 先在世界里对齐子模型挂点与父挂点,再挂接并保持世界坐标不变。
|
|
Vector3 worldAlign = hook.position - hangger.position;
|
|
pChild.transform.position += worldAlign;
|
|
pChild.transform.SetParent(hook.transform, true);
|
|
m_hookCache.Add(szHookName, hook);
|
|
m_childModels.Add(szHookName, pChild);
|
|
m_hangerPositionCache.Add(szHookName, hangger);
|
|
// if (m_pA3DSkinModel && pChild->m_pA3DSkinModel)
|
|
// {
|
|
// if (!m_pA3DSkinModel->AddChildModel(
|
|
// szHangerName,
|
|
// bOnBone,
|
|
// szHookName,
|
|
// pChild->m_pA3DSkinModel,
|
|
// szCCName))
|
|
// return false;
|
|
|
|
// pChild->m_pA3DSkinModel->SetAutoAABBType(A3DSkinModel::AUTOAABB_INITMESH);
|
|
// pChild->m_bLoadSkinModel = false;
|
|
// }
|
|
|
|
// pChild->m_strHookName = szHookName;
|
|
// pChild->m_strCC = szCCName;
|
|
// pChild->SetGfxUseLOD(m_bGfxUseLod, false);
|
|
// pChild->SetDisableCamShake(m_bDisableCamShake);
|
|
// pChild->SetCreatedByGfx(m_bCreatedByGfx);
|
|
|
|
// Only copy parent id when it is non-zero; mount/navigate shells often keep id 0 and must not wipe child's cid (e.g. player already SetId in clone load).
|
|
// 仅当父 id 非 0 时才覆盖子 id;骑乘/导航载体常为 0,不能抹掉子模型已设的 cid(如克隆加载时 SetId)。
|
|
if (pChild.InheritParentId() && m_nId != 0)
|
|
pChild.SetId(m_nId);
|
|
|
|
m_childModels[szHangerName] = pChild;
|
|
return true;
|
|
}
|
|
public void ResetHookPosition(string szHookName)
|
|
{
|
|
Transform hook = m_hookCache.TryGetValue(szHookName, out Transform cachedHook) ? cachedHook : null;
|
|
Transform hangger = m_hangerPositionCache.TryGetValue(szHookName, out Transform cachedHangger) ? cachedHangger : null;
|
|
GameObject childObject = m_childModels.TryGetValue(szHookName, out CECModel child) ? child.m_pPlayerModel : null;
|
|
|
|
if (hook != null && hangger != null && child != null)
|
|
{
|
|
Vector3 worldAlign = hook.position - hangger.position;
|
|
child.transform.position += worldAlign;
|
|
}
|
|
}
|
|
//ref cant not be null so seperate into two functions
|
|
public bool PlayActionByName(string szActName, float fWeight=1.0f, bool bRestart=true, int nTransTime=200, bool bForceStop=true, uint dwUserData =0, bool bNoFx=false, CECAttackEvent attackEvent = null, uint dwFlagMode = 0)
|
|
{
|
|
return PlayActionByName(0, szActName, fWeight, bRestart, nTransTime, bForceStop, dwUserData, bNoFx, attackEvent, dwFlagMode);
|
|
}
|
|
//ref cant not be null so seperate into two functions
|
|
public bool QueueAction(string szActName, int nTransTime=200, uint dwUserData=0, bool bForceStopPrevAct=false, bool bCheckTailDup = false, bool bNoFx = false, bool bResetSpeed = false, bool bResetActFlag=false, CECAttackEvent attackEvent=null, uint dwNewFlagMode=0)
|
|
{
|
|
return QueueAction(0, szActName, nTransTime, dwUserData, bForceStopPrevAct, bCheckTailDup, bNoFx, bResetSpeed ,bResetActFlag, attackEvent, dwNewFlagMode);
|
|
}
|
|
//Final trigger function
|
|
public bool PlayActionByName(int nChannel, string szActName, float fWeight, bool bRestart, int nTransTime, bool bForceStop, uint dwUserData, bool bNoFx, CECAttackEvent attackEvent, uint dwFlagMode = 0)
|
|
{
|
|
if(szActName == null || szActName == string.Empty)
|
|
{
|
|
return false;
|
|
}
|
|
A3DCombinedAction combinedAction = GetComActByName(szActName);
|
|
if(combinedAction == null)
|
|
{
|
|
return false;
|
|
}
|
|
byte rank = 0;
|
|
ChannelAct ca = m_ChannelActs[nChannel];
|
|
ChannelActNode pNode = ca.GetNodeByRank(rank);
|
|
A3DCombActDynData pActive;
|
|
|
|
if (pNode != null)
|
|
{
|
|
pActive = pNode.m_pActive;
|
|
pNode.RemoveQueuedActs();
|
|
}
|
|
else
|
|
{
|
|
pActive = null;
|
|
}
|
|
|
|
if (!bRestart && pActive != null && pActive.GetComAct().m_strName == szActName)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// if (m_pBlurInfo != null && m_pBlurInfo.m_bRoot && m_pBlurInfo.m_bStopWhenActChange)
|
|
// RemoveMotionBlurInfo();
|
|
if (pActive != null)
|
|
{
|
|
pActive.Stop(bForceStop);
|
|
pActive = null;
|
|
}
|
|
else if (!m_bFirstActPlayed)
|
|
{
|
|
m_bFirstActPlayed = true;
|
|
nTransTime = 0;
|
|
}
|
|
if (pNode == null)
|
|
{
|
|
pNode = new ChannelActNode();
|
|
pNode.m_Rank = rank;
|
|
ca.m_RankNodes.Add(pNode);
|
|
}
|
|
pNode.m_pActive = new A3DCombActDynData(combinedAction, this, attackEvent);
|
|
pNode.m_pActive.SetUserData((int)dwUserData);
|
|
pNode.m_pActive.Play(nChannel, fWeight, nTransTime, 0, bRestart, m_bAbsTrack, bNoFx, bForceStop,pNode.m_Rank);
|
|
m_bAbsTrack = false;
|
|
pNode.m_pActFlag = false;
|
|
pNode.m_dwFlagMode = (int)dwFlagMode;
|
|
|
|
return true;
|
|
}
|
|
public A3DCombinedAction GetComActByName(string szActName)
|
|
{
|
|
if(m_pMapModel == null||m_pMapModel.m_ActionMap == null)
|
|
{
|
|
return null;
|
|
}
|
|
return m_pMapModel.m_ActionMap.TryGetValue(szActName, out A3DCombinedAction combinedAction) ? combinedAction : null;
|
|
}
|
|
//Final trigger function
|
|
public int GetComActTimeSpanByName(string szActName)
|
|
{
|
|
A3DCombinedAction combinedAction = GetComActByName(szActName);
|
|
if (combinedAction == null || combinedAction.m_ActLst == null || combinedAction.m_ActLst.Count == 0)
|
|
return 0;
|
|
|
|
int totalMs = 0;
|
|
foreach (var actInfo in combinedAction.m_ActLst)
|
|
{
|
|
if (actInfo == null || string.IsNullOrEmpty(actInfo.m_strName))
|
|
continue;
|
|
AnimationClip clip = GetAnimationClip(actInfo.m_strName);
|
|
if (clip != null)
|
|
totalMs += Mathf.Max(1, Mathf.RoundToInt(clip.length * 1000f));
|
|
}
|
|
return totalMs;
|
|
}
|
|
public void SetPlaySpeed(float vScale)
|
|
{
|
|
m_fPlaySpeed = vScale;
|
|
}
|
|
public float GetPlaySpeed() => m_fPlaySpeed;
|
|
public bool QueueAction(int nChannel, string szActName, int nTransTime, uint dwUserData, bool bForceStopPrevAct, bool bCheckTailDup, bool bNoFx, bool bResetSpeed, bool bResetActFlag, CECAttackEvent attackEvent=null, uint dwNewFlagMode=0){
|
|
if(szActName == null || szActName == string.Empty)
|
|
{
|
|
return false;
|
|
}
|
|
A3DCombinedAction pComAct = GetComActByName(szActName);
|
|
if (pComAct == null)
|
|
return false;
|
|
|
|
ChannelAct ca = m_ChannelActs[nChannel];
|
|
ChannelActNode pNode = ca.GetNodeByRank(0);
|
|
|
|
A3DCombActDynData pDynData = new A3DCombActDynData(pComAct, this, attackEvent);
|
|
pDynData.SetUserData((int)dwUserData);
|
|
pDynData.SetTransTime(nTransTime);
|
|
pDynData.SetStopPrevAct(bForceStopPrevAct);
|
|
pDynData.SetNoFxFlag(bNoFx);
|
|
pDynData.SetSpeedWhenActStart(bResetSpeed);
|
|
pNode.m_QueuedActs.Add(pDynData);return true;
|
|
}
|
|
public bool ClearComActFlagAllRankNodes(bool bSignalCurrent)
|
|
{
|
|
foreach (var ca in m_ChannelActs)
|
|
ca?.Release();
|
|
EventBus.PublishChannel(m_nId, new ClearComActFlagAllRankNodesEvent(bSignalCurrent));
|
|
return false;
|
|
}
|
|
|
|
public int GetCurActionUserData(int channel = 0)
|
|
{
|
|
var returnValue = 0;
|
|
var nodeCh0 = m_ChannelActs[channel].GetHighestRankNode();
|
|
if (nodeCh0 != null)
|
|
returnValue = nodeCh0.m_pActive.GetUserData();
|
|
return returnValue;
|
|
}
|
|
/// <summary>
|
|
/// ForNPCOnly
|
|
/// </summary>
|
|
public bool QueueAction(CECNPC.INFO iNFO, string szActName, ref bool pNewActFlag, int nTransTime = 200, uint dwUserData = 0, bool bForceStopPrevAct = false, bool bCheckTailDup = false, bool bNoFx = false, bool bResetSpeed = false
|
|
/*joslian*/, bool bResetActFlag = false, uint dwNewFlagMode = COMACT_FLAG_MODE_NONE)
|
|
{
|
|
QueueAction(iNFO, 0, szActName, ref bResetActFlag, nTransTime, dwUserData, bForceStopPrevAct, bCheckTailDup, bNoFx, bResetSpeed/*joslian*/, pNewActFlag, dwNewFlagMode);
|
|
return true;
|
|
}
|
|
/// <summary>
|
|
/// ForNPCOnly
|
|
/// </summary>
|
|
public bool QueueAction(CECNPC.INFO iNFO, int nChannel, string szActName, ref bool pNewActFlag, int nTransTime, uint dwUserData/* 0 */, bool bForceStopPrevAct, bool bCheckTailDup, bool bNoFx, bool bResetSpeed
|
|
/*joslian*/, bool bResetActFlag, uint dwNewFlagMode)
|
|
{
|
|
EventBus.PublishChannel(iNFO.nid, new QueueNPCActionEvent(szActName));
|
|
return true;
|
|
}
|
|
public void StopAllActions(bool bStopFx)
|
|
{
|
|
//ResetMaterialScale();
|
|
StopChannelAction(0, true, bStopFx);
|
|
//StopChildrenAct();
|
|
}
|
|
|
|
public void StopChannelAction(int nChannel, byte rank, bool bStopAct, bool bStopFx = false)
|
|
{
|
|
ChannelAct ca = m_ChannelActs[nChannel];
|
|
ChannelActNode pNode = ca.RemoveNodeByRank(rank);
|
|
|
|
if (pNode != null)
|
|
{
|
|
pNode.m_pActive.Stop(bStopAct, bStopFx);
|
|
}
|
|
}
|
|
|
|
public void StopChannelAction(int nChannel, bool bStopAct, bool bStopFx = false)
|
|
{
|
|
ChannelAct ca = m_ChannelActs[nChannel];
|
|
foreach (var node in ca.m_RankNodes)
|
|
{
|
|
Debug.Log("[DefaultPolicy]StopChannelAction: " + node.m_pActive?.GetComAct()?.m_strName);
|
|
node.m_pActive.Stop(bStopAct, bStopFx);
|
|
}
|
|
ca.m_RankNodes.Clear();
|
|
}
|
|
public bool IsActionStopped(int nChannel =0)
|
|
{
|
|
ChannelAct ca = m_ChannelActs[nChannel];
|
|
foreach (var node in ca.m_RankNodes)
|
|
{
|
|
if (!node.m_pActive.IsActionStopped())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
public struct QueueNPCActionEvent
|
|
{
|
|
public string AnimationName;
|
|
public QueueNPCActionEvent(string animationName)
|
|
{
|
|
AnimationName = animationName;
|
|
}
|
|
}
|
|
public bool Tick(uint dwDeltaTime)
|
|
{
|
|
UpdateChannelActs((uint)(dwDeltaTime * m_fPlaySpeed));
|
|
return true;
|
|
}
|
|
void UpdateChannelActs(uint dwUpdateTime)
|
|
{
|
|
if (dwUpdateTime == 0)
|
|
return;
|
|
|
|
for (int i = 0; i < (int)ActionChannel.ACTCHA_MAX; i++)
|
|
{
|
|
ChannelAct ca = m_ChannelActs[i];
|
|
var rankNodes = ca.m_RankNodes;
|
|
for (int n = rankNodes.Count - 1; n >= 0; n--)
|
|
{
|
|
var node = rankNodes[n];
|
|
bool bActFinished = false;
|
|
if (node.m_dwFlagMode == COMACT_FLAG_MODE_ONCE_IGNOREGFX || node.m_dwFlagMode == COMACT_FLAG_MODE_ONCE_MULTIIGNOREGFX)
|
|
{
|
|
BMLogger.Log("[DefaultPolicy] Checking IsActionStopped: " + node.m_pActive?.GetComAct()?.m_strName);
|
|
bActFinished = node.m_pActive.IsActionStopped();
|
|
}
|
|
else
|
|
{
|
|
BMLogger.Log("[DefaultPolicy] Checking IsAllFinished: " + node.m_pActive?.GetComAct()?.m_strName);
|
|
bActFinished = node.m_pActive.IsAllFinished();
|
|
}
|
|
if (bActFinished)
|
|
{
|
|
if (node.m_dwFlagMode != COMACT_FLAG_MODE_NONE && node.m_pActFlag)
|
|
{
|
|
node.m_pActFlag = true;
|
|
if (node.m_dwFlagMode != COMACT_FLAG_MODE_ONCE_MULTIIGNOREGFX)
|
|
node.m_dwFlagMode = (int)COMACT_FLAG_MODE_NONE;
|
|
|
|
node.m_pActFlag = false;
|
|
}
|
|
if (node.m_QueuedActs.Count > 0)
|
|
{
|
|
A3DCombActDynData pNext = node.m_QueuedActs[0];
|
|
if (pNext.GetStopPrevAct())
|
|
{
|
|
node.m_pActive.Stop(true);
|
|
//StopChildrenAct();
|
|
}
|
|
else
|
|
{
|
|
node.m_pActive.Stop(false);
|
|
}
|
|
|
|
// script
|
|
//m_pMapModel->OnScriptPlayAction(this, i, pNext->GetComAct()->GetName());
|
|
|
|
// Èç¹ûQueueActionµÄʱºòÉèÖÃÁËResetSpeed£¬ÔòÕâÀォËÙ¶ÈÉèÖÃÖØÖã¬Ä¬ÈÏÇé¿öϸñê־Ϊfalse
|
|
//by2021 ÕâÀï¿ÉÐÞ¸Ä
|
|
if (pNext.IsSetSpeedWhenActStart())
|
|
SetPlaySpeedByChannel(i, pNext.GetComAct());
|
|
|
|
node.m_pActive = null;
|
|
node.m_pActive = pNext;
|
|
node.m_pActive.Play(i, 1f, node.m_pActive.GetTransTime(), 0/**m_EventMasks[i]*/, true, m_bAbsTrack, pNext.GetNoFxFlag());
|
|
pNext.UpdateAct(dwUpdateTime, m_fPlaySpeed);
|
|
node.m_QueuedActs.RemoveAt(0);
|
|
m_bAbsTrack = false;
|
|
}
|
|
else
|
|
{
|
|
node.m_pActive.Stop(false);
|
|
rankNodes.RemoveAt(n);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
node.m_pActive.UpdateAct(dwUpdateTime, m_fPlaySpeed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void SetPlaySpeedByChannel(int nChannel, A3DCombinedAction pComAct)
|
|
{
|
|
// if (m_nMainChannel == nChannel || 0 == nChannel)
|
|
// {
|
|
// m_fPlaySpeed = m_fDefPlaySpeed * pComAct.GetPlaySpeed();
|
|
// }
|
|
}
|
|
public void PlayGfx(string szPath, string szHook, float fScale, bool bFadeOut, A3DVECTOR3 vOffset, float fPitch, float fYaw, float fRot, bool bUseECMHook, uint dwFadeOutTime)
|
|
{
|
|
if (!bFadeOut)
|
|
dwFadeOutTime = 0;
|
|
string strKey = szPath;
|
|
strKey += szHook;
|
|
|
|
// Apply hook scale factor if using ECM hook
|
|
// 如果使用ECM挂点,应用挂点缩放因子
|
|
if (bUseECMHook && !string.IsNullOrEmpty(szHook))
|
|
{
|
|
Transform pHook = GetHook(szHook, true);
|
|
if (pHook != null)
|
|
{
|
|
// Apply hook scale factor if available
|
|
// 如果可用,应用挂点缩放因子
|
|
// TODO Phase 4: Add hook scale factor support
|
|
// TODO 第4阶段:添加挂点缩放因子支持
|
|
// fScale *= pHook->GetScaleFactor();
|
|
|
|
#if UNITY_EDITOR
|
|
BMLogger.Log($"[CECModel.PlayGfx] Using hook '{szHook}' for GFX '{szPath}', scale={fScale}");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#if UNITY_EDITOR
|
|
BMLogger.LogWarning($"[CECModel.PlayGfx] Hook '{szHook}' not found for GFX '{szPath}', using default scale");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// PGFX_INFO pInfo;
|
|
//
|
|
// CoGfxMap::iterator it = m_CoGfxMap.find(strKey);
|
|
//
|
|
// if (it != m_CoGfxMap.end())
|
|
// {
|
|
// pInfo = it->second;
|
|
// A3DGFXEx* pGfx = pInfo->GetGfx();
|
|
// TransferEcmProperties(pGfx);
|
|
// pGfx->SetScale(fScale * m_fGfxScaleFactor);
|
|
// pInfo->SetFadeOutTime(dwFadeOutTime);
|
|
// pInfo->SetModelAlpha(true);
|
|
// pInfo->SetHookName(szHook);
|
|
// pInfo->SetUseECMHook(bUseECMHook);
|
|
// pInfo->SetScale(fScale);
|
|
// pInfo->SetOffset(vOffset);
|
|
// pInfo->SetPitch(fPitch);
|
|
// pInfo->SetYaw(fYaw);
|
|
// pInfo->SetRot(fRot);
|
|
// pInfo->BuildTranMat();
|
|
// pGfx->SetSfxPriority(m_nSfxPriority);
|
|
// pGfx->Start(true);
|
|
// return;
|
|
// }
|
|
//
|
|
// pInfo = new GFX_INFO(NULL);
|
|
// pInfo->SetFilePath(szPath);
|
|
// pInfo->SetHookName(szHook);
|
|
// pInfo->SetUseECMHook(bUseECMHook);
|
|
// pInfo->SetScale(fScale);
|
|
// pInfo->Init(m_pA3DDevice);
|
|
// pInfo->SetOffset(vOffset);
|
|
// pInfo->SetPitch(fPitch);
|
|
// pInfo->SetYaw(fYaw);
|
|
// pInfo->SetRot(fRot);
|
|
// pInfo->LoadGfx();
|
|
//
|
|
// if (!pInfo->GetGfx())
|
|
// {
|
|
// delete pInfo;
|
|
// return;
|
|
// }
|
|
//
|
|
// pInfo->BuildTranMat();
|
|
// pInfo->SetFadeOutTime(dwFadeOutTime);
|
|
// pInfo->SetModelAlpha(true);
|
|
// TransferEcmProperties(pInfo->GetGfx());
|
|
// pInfo->GetGfx()->SetScale(fScale * m_fGfxScaleFactor);
|
|
// pInfo->GetGfx()->SetSfxPriority(m_nSfxPriority);
|
|
// pInfo->GetGfx()->Start(true);
|
|
//
|
|
// m_CoGfxMap[strKey] = pInfo;
|
|
}
|
|
}
|