using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using BrewMonster.Scripts; using CSNetwork.GPDataType; using UnityEngine; using ModelViewer.Common; using CombinedActMap = System.Collections.Generic.Dictionary; using CoGfxMap = System.Collections.Generic.Dictionary; using ConvexHullDataArray = System.Collections.Generic.List; using ECModelHookMap = System.Collections.Generic.Dictionary; using ActQueue = System.Collections.Generic.List; using BrewMonster; using BrewMonster.Scripts.ECModel; using Cysharp.Threading.Tasks; using Unity.VisualScripting.FullSerializer; using Unity.VisualScripting; 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 A3DCombActDynData { protected A3DCombinedAction m_pAct; protected CECModel m_pECModel; 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; //ActDynArray m_arrActLoopNum; protected int m_nCurActIndex; protected int m_dwDynComActSpan; protected bool m_bIsInfiniteComAct; //important //EventInfoMap m_mapDynAddedInfo; /// Attack event tied to this combined action, if any. / 与此组合动作绑定的攻击事件(若有) /// this is alternative for m_IdCaster to m_IsAttackAct in c++ /// public CECAttackEvent ActiveAttackEvent; public bool m_bResetPSWhenActionStop; public bool m_bSetSpeedWhenActStart; public float m_fModelScale; public List m_ActionNames = new List(); public List m_SFXNames = new List(); public List m_GFXObjects = new List(); public bool IsActionStopped() { return m_ActionNames.Count == 0; } /// True when there is no attack or damage has been applied. / 无攻击事件或已造成伤害则为 true public bool IsAllEventFinished() { return (ActiveAttackEvent == null || ActiveAttackEvent.m_bDoDamaged); } /// Animations finished and attack-event phase finished. / 动画与攻击事件阶段均结束 public bool IsAllFinished() { if(IsActionStopped() && IsAllEventFinished()) { return true; } return false; } 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; var actInfoList = pAct.m_ActLst; foreach(var actInfo in actInfoList) { int nDynLoopNum = actInfo.CalcLoopNum(); if (!m_bIsInfiniteComAct && nDynLoopNum < 0) m_bIsInfiniteComAct = true; } } /// /// When true, publishes for Animancer, tracks , /// and plays combined-action sound FX from data (unless suppresses audio). /// / 为 true 时发布 PlayActionEvent、维护 m_EventNames,并按数据播放组合动作音效(bNoFx 时跳过音效)。 /// 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(); PublishVisual(bForceStopPrevious, channelRank); if (!m_bNoFx) TriggerSFxFromEventList(); // Resume(); // UpdateAct(0); // #ifdef _ANGELICA21 // UpdateEvent(0, 0); // #endif } /// /// Notify visual layer and fire combined-action audio using data on this instance. /// / 通知表现层并根据本实例数据触发组合动作音效。 /// void PublishVisual(bool bForceStopPrevious, byte channelRank) { if (m_pECModel == null || m_pAct == null) return; var actionInfos = m_pAct.m_ActLst; if (actionInfos != null && actionInfos.Count > 0 && !string.IsNullOrEmpty(actionInfos[0].m_strName)) { ChannelAct channelAct = m_pECModel.GetChannelAct(m_nChannel); if (channelAct != null) { bool isLoop = m_bIsInfiniteComAct; int ownerId = m_pECModel.GetId(); EventBus.PublishChannel(ownerId, new PlayActionEvent(ref channelAct, actionInfos[0].m_strName, m_nTransTime, bForceStopPrevious, ActiveAttackEvent, isLoop, channelRank)); m_ActionNames.Add(actionInfos[0].m_strName); for(int i = 1; i < actionInfos.Count; i++) { isLoop = m_bIsInfiniteComAct; EventBus.PublishChannelClass(ownerId, new QueueActionEvent(ref channelAct, actionInfos[i].m_strName, null, false, null, m_nTransTime, false, isLoop, channelRank)); m_ActionNames.Add(actionInfos[i].m_strName); } } } } /// /// Sound events embedded in combined action; skip gfx paths. / 组合动作内嵌音效事件;跳过 gfx 路径。 /// async void TriggerSFxFromEventList() { 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; GameObject prefab = await AddressableManager.Instance.LoadPrefabAsync(path); GameObject fx = null; if(prefab != null && m_pECModel != null) { // LoadPrefabAsync returns the prefab asset; must Instantiate before modifying transform. // LoadPrefabAsync 返回的是预制体资源,必须先 Instantiate 再改 Transform。 fx = GameObject.Instantiate(prefab, m_pECModel.transform); fx.SetActive(true); fx.transform.localPosition = Vector3.zero; fx.transform.localRotation = Quaternion.identity; fx.transform.localScale = Vector3.one; ParticleSystem ps = fx.GetComponent(); if (ps != null) ps.Play(); } else if (prefab == null) { GameObject prefab2 = Resources.Load("GFX/" + "PlaceHolder"); if(prefab2 != null && m_pECModel != null) { fx = GameObject.Instantiate(prefab2, m_pECModel.transform); fx.SetActive(true); fx.transform.localPosition = Vector3.zero; fx.transform.localRotation = Quaternion.identity; fx.transform.localScale = Vector3.one; ParticleSystem ps2 = fx.GetComponent(); if (ps2 != null) ps2.Play(); } } if(fx != null) { m_GFXObjects.Add(fx); } // var gfxLogPath = Path.Combine(Application.dataPath, "PerfectWorld", "Scripts", "NPC", "GFXLOG.txt"); // var lines = new List(); // 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; } } } } public void Stop(bool bStopAct, bool bForceStopFx = false ) { RemoveAllActiveFx(); // 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 dwUpdateTime) { if(IsActionStopped()) { RemoveAllActiveFx(); } } public void RemoveAllActiveFx() { foreach(var fx in m_GFXObjects) { if(fx != null) { GameObject.Destroy(fx); } } m_GFXObjects.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 m_RankNodes = new List(); public ChannelAct() { Release(); } public void Release() { if(m_RankNodes == null) { m_RankNodes = new List(); } 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 bone_names = new List(); public List joint_indices = new List(); public ActChannelInfo Clone() { return new ActChannelInfo { bone_names = new List(bone_names), joint_indices = new List(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 m_ActionMap = new Dictionary(); private uint m_dwVersion; private string m_strHook = string.Empty; private string m_strCCName = string.Empty; private readonly List m_BoneScales = new List(); private readonly List m_BoneScaleExArr = new List(); 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 m_ChannelInfoDict = new Dictionary(); private readonly Dictionary m_EventMasks = new Dictionary(); 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 m_ChildInfoArray = new List(); private readonly List m_AdditionalSkinLst = new List(); 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 m_vecPSConsts = new List(); 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 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 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 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 GetAdditionalSkinList() => m_AdditionalSkinLst; public IList 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 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(); public SkeletonBuilder m_skeletonBuilder; private Dictionary m_hookCache = new Dictionary(); private Dictionary m_hangerPositionCache = new Dictionary(); private Dictionary m_childModels = new Dictionary(); private Transform m_transform; private bool m_bAbsTrack = false; private bool m_bFirstActPlayed = false; public bool InheritParentId() => m_bInheritParentId; public void SetId(int nId) => m_nId = nId; public int GetId() => m_nId; /// Channel playback container for combined actions. / 组合动作通道容器。 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(); } /// /// Set SkeletonBuilder reference for hook lookup /// 设置用于挂点查找的SkeletonBuilder引用 /// /// SkeletonBuilder instance / SkeletonBuilder实例 public void SetSkeletonBuilder(SkeletonBuilder builder) { m_skeletonBuilder = builder; m_hookCache?.Clear(); // Invalidate cache on model change } 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); } /// /// Get the SkeletonBuilder component /// 获取SkeletonBuilder组件 /// /// SkeletonBuilder instance or null / SkeletonBuilder实例,未找到返回null public SkeletonBuilder GetSkeletonBuilder() { return m_skeletonBuilder; } /// /// Initialize SkeletonBuilder reference - call this after model is loaded /// 初始化SkeletonBuilder引用 - 在模型加载后调用 /// public void InitializeSkeletonBuilder() { // Try to find SkeletonBuilder in children (where it's typically attached) // 尝试在子对象中查找SkeletonBuilder(通常附加在那里) if (m_transform != null) { m_skeletonBuilder = m_transform.GetComponentInChildren(true); } if (m_skeletonBuilder == null && m_transform != null) { // Fallback: search in parent hierarchy (for NPCs/Players) // 回退:在父层次结构中搜索(用于NPC/玩家) m_skeletonBuilder = m_transform.GetComponentInParent(); } if (m_skeletonBuilder == null) { BMLogger.LogWarning($"[CECModel] SkeletonBuilder not found for {(m_transform != null ? m_transform.name : "null")}. Hooks 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")}"); } } /// /// Set Transform reference for this model /// 设置此模型的Transform引用 /// /// Transform instance / Transform实例 public void SetTransform(Transform transform) { m_transform = transform; } /// /// Get Transform reference for this model /// 获取此模型的Transform引用 /// public Transform transform { get { return m_transform; } } /// /// Get hook Transform by name /// 根据名称获取挂点变换 /// /// Hook name / 挂点名称 /// Search recursively / 递归搜索 /// Hook Transform or null / 挂点变换,未找到返回null 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 float[] GetOuterData() { return m_pMapModel.m_OuterData; } /// /// Invalidate hook cache (call when model reloads) /// 使挂点缓存失效(在模型重新加载时调用) /// public void InvalidateHookCache() { m_hookCache.Clear(); } /// /// Register a child model (weapon, pet, etc.) /// 注册子模型(武器、宠物等) /// /// Hanger name / 挂载者名称 /// Child model instance / 子模型实例 public void RegisterChildModel(string hangerName, CECModel childModel) { if (!string.IsNullOrEmpty(hangerName) && childModel != null) { m_childModels[hangerName] = childModel; BMLogger.Log($"[CECModel] Registered child model '{hangerName}'"); } } /// /// Unregister a child model /// 注销子模型 /// /// Hanger name / 挂载者名称 public void UnregisterChildModel(string hangerName) { if (!string.IsNullOrEmpty(hangerName)) { if (m_childModels.Remove(hangerName)) { BMLogger.Log($"[CECModel] Unregistered child model '{hangerName}'"); } } } /// /// Get child model by hanger name /// 根据挂载者名称获取子模型 /// /// Hanger name / 挂载者名称 /// Child model or null if not found / 子模型,未找到返回null 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; } 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); if (pChild.InheritParentId()) 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 = 2;//COMACT_FLAG_MODE_ONCE_IGNOREGFX; 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 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); // EventBus.PublishChannelClass(m_nId, new QueueActionEvent(ref m_ChannelActs[nChannel], actionInfos[0].m_strName, null, false, attackEvent, nTransTime, bForceStopPrevAct, isLoop, node.m_Rank)); // for(int i = 1; i < actionInfos.Count; i++) // { // EventBus.PublishChannelClass(m_nId, new QueueActionEvent(ref m_ChannelActs[nChannel], actionInfos[i].m_strName, null, false, attackEvent, nTransTime, false, isLoop, node.m_Rank)); // actData.m_EventNames.Add(actionInfos[i].m_strName); // } //todo: should add sfx and gfx logic to channel act. currently we dont have logic for queue action. only available for play action. 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 = -1; var nodeCh0 = m_ChannelActs[channel].GetHighestRankNode(); if (nodeCh0 != null) returnValue = nodeCh0.m_pActive.GetUserData(); return returnValue; } 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; } 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 StopChannelAction(int nChannel, bool bStopAct, bool bStopFx = false) { //TODO: Implement StopChannelAction } public struct QueueNPCActionEvent { public string AnimationName; public QueueNPCActionEvent(string animationName) { AnimationName = animationName; } } public bool Tick(uint dwDeltaTime) { UpdateChannelActs(dwDeltaTime); 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) bActFinished = node.m_pActive.IsActionStopped(); else 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); node.m_QueuedActs.RemoveAt(0); m_bAbsTrack = false; } else { node.m_pActive.Stop(false); rankNodes.RemoveAt(n); } } else { node.m_pActive.UpdateAct(dwUpdateTime); } } } } 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; } }