Files
test/Assets/PerfectWorld/Scripts/NPC/CECModel.cs
T
2026-03-03 18:06:20 +07:00

671 lines
22 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using BrewMonster.Scripts;
using CSNetwork.GPDataType;
using UnityEngine;
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 BrewMonster;
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 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;
private readonly CombinedActMap m_ActionMap = new CombinedActMap();
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
};
private readonly 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(string szModelFile, bool bLoadAdditionalSkin)
{
throw new NotImplementedException("CECModelStaticData.LoadData requires the legacy AFile system to be ported.");
}
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.Clear();
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 CombinedActMap GetCombinedActMap() => 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 const uint COMACT_FLAG_MODE_NONE = 0;
protected CECModelStaticData m_pMapModel;
private SkeletonBuilder m_skeletonBuilder;
private Dictionary<string, Transform> m_hookCache = new Dictionary<string, Transform>();
private Dictionary<string, CECModel> m_childModels = new Dictionary<string, CECModel>();
private Transform m_transform;
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 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 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
}
/// <summary>
/// Get the SkeletonBuilder component
/// 获取SkeletonBuilder组件
/// </summary>
/// <returns>SkeletonBuilder instance or null / SkeletonBuilder实例,未找到返回null</returns>
public SkeletonBuilder GetSkeletonBuilder()
{
return m_skeletonBuilder;
}
/// <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);
}
if (m_skeletonBuilder == null && m_transform != null)
{
// Fallback: search in parent hierarchy (for NPCs/Players)
// 回退:在父层次结构中搜索(用于NPC/玩家)
m_skeletonBuilder = m_transform.GetComponentInParent<SkeletonBuilder>();
}
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")}");
}
}
/// <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;
}
/// <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 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;
}
}
// Action channel
public enum ActionChannel
{
ACTCHA_WOUND = 1,
};