add hook system
This commit is contained in:
@@ -10,7 +10,6 @@ namespace BrewMonster.Scripts
|
||||
public Transform rootBone;
|
||||
public Dictionary<string, Transform> hooks = new Dictionary<string, Transform>();
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This function retrieves an array of transforms for the specified bone names.
|
||||
@@ -67,12 +66,21 @@ namespace BrewMonster.Scripts
|
||||
if (hooks.TryGetValue(hookName, out Transform hook))
|
||||
return hook;
|
||||
|
||||
// Recursive search in child models (Phase 2)
|
||||
// 在子模型中递归搜索(阶段2)
|
||||
// Recursive search in child models (if recursive flag is true)
|
||||
// 在子模型中递归搜索(如果递归标志为true)
|
||||
if (recursive)
|
||||
{
|
||||
// TODO Phase 2: Search child models
|
||||
// TODO 阶段2:搜索子模型
|
||||
// Search in child SkeletonBuilders (weapons, pets, etc.)
|
||||
// 在子SkeletonBuilder中搜索(武器、宠物等)
|
||||
SkeletonBuilder[] childBuilders = GetComponentsInChildren<SkeletonBuilder>(true);
|
||||
foreach (SkeletonBuilder childBuilder in childBuilders)
|
||||
{
|
||||
if (childBuilder == this) continue; // Skip self
|
||||
|
||||
Transform childHook = childBuilder.GetHook(hookName, false); // Don't recurse again
|
||||
if (childHook != null)
|
||||
return childHook;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -365,7 +365,8 @@ namespace BrewMonster
|
||||
// 这与C++逻辑匹配:当投射物击中地面(无目标)时使用m_szHitGrndGfx
|
||||
bool bTargetExists = m_bTargetExist && m_nTargetID != 0;
|
||||
GameObject prefab = bTargetExists ? m_pComposer.GetHitGFX() : m_pComposer.GetHitGrdGFX();
|
||||
|
||||
BMLogger.LogError("HitGfx : " + m_pComposer.hitGfxName);
|
||||
|
||||
if (prefab == null)
|
||||
{
|
||||
// Fallback: if ground hit GFX is null but target doesn't exist, try regular hit GFX
|
||||
@@ -465,11 +466,66 @@ namespace BrewMonster
|
||||
{
|
||||
if (bIsGoblinSkill)
|
||||
{
|
||||
// TODO: Handle goblin skill position
|
||||
// if (pPlayer->GetGoblinModel())
|
||||
// vPos = pPlayer->GetGoblinModel()->GetModel()->GetModelAABB().Center;
|
||||
// else
|
||||
// return false;
|
||||
// Get goblin model from player
|
||||
// 从玩家获取小精灵模型
|
||||
// Note: GetGoblin() method may need to be implemented in CECPlayer
|
||||
// 注意:GetGoblin()方法可能需要在CECPlayer中实现
|
||||
// For now, we'll try to get it via a common pattern
|
||||
// 目前,我们将尝试通过通用模式获取它
|
||||
|
||||
// Try to find goblin model - this is a placeholder until GetGoblin() is implemented
|
||||
// 尝试查找小精灵模型 - 这是占位符,直到实现GetGoblin()
|
||||
// TODO: Implement GetGoblin() in CECPlayer when goblin system is available
|
||||
// TODO: 当小精灵系统可用时,在CECPlayer中实现GetGoblin()
|
||||
|
||||
// For Phase 3, we'll search for a child model named "goblin" or similar
|
||||
// 对于第3阶段,我们将搜索名为"goblin"或类似的子模型
|
||||
CECModel pModel = pPlayer.GetPlayerModel();
|
||||
if (pModel != null)
|
||||
{
|
||||
// Try common goblin hanger names
|
||||
// 尝试常见的小精灵挂载者名称
|
||||
CECModel goblinModel = pModel.GetChildModel("goblin") ??
|
||||
pModel.GetChildModel("pet") ??
|
||||
pModel.GetChildModel("_goblin");
|
||||
|
||||
if (goblinModel != null)
|
||||
{
|
||||
// Use hook if specified, otherwise use model center
|
||||
// 如果指定了挂点则使用挂点,否则使用模型中心
|
||||
if (!string.IsNullOrEmpty(szHook))
|
||||
{
|
||||
Transform pHook = goblinModel.GetHook(szHook, true);
|
||||
if (pHook != null)
|
||||
{
|
||||
Transform modelTransform = goblinModel.transform;
|
||||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.Log($"[HOOK_DEBUG] Found goblin hook '{szHook}' for player ID {nID}, position={vPos}");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to goblin position
|
||||
// 回退到小精灵位置
|
||||
if (goblinModel.transform != null)
|
||||
{
|
||||
vPos = goblinModel.transform.position;
|
||||
vPos.y += 0.5f;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.Log($"[HOOK_DEBUG] Using goblin center position for player ID {nID}, position={vPos}");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.LogWarning($"[HOOK_DEBUG] Goblin model not found for player ID {nID}");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@@ -481,33 +537,55 @@ namespace BrewMonster
|
||||
if (string.IsNullOrEmpty(szHook))
|
||||
break;
|
||||
|
||||
// TODO: Get player model and hook position
|
||||
/*CECModel pModel = pPlayer->GetPlayerModel();
|
||||
if (!pModel)
|
||||
break;
|
||||
|
||||
if (szHanger && bChildHook)
|
||||
pModel = pModel->GetChildModel(szHanger);
|
||||
|
||||
if (!pModel)
|
||||
break;
|
||||
|
||||
A3DSkinModel* pSkin = pModel->GetA3DSkinModel();
|
||||
A3DSkeletonHook* pHook = pSkin->GetSkeletonHook(szHook, true);
|
||||
|
||||
if (!pHook)
|
||||
break;
|
||||
|
||||
if (bRelHook)
|
||||
vPos = pHook->GetAbsoluteTM() * pOffset;
|
||||
else
|
||||
// Get player model and hook position
|
||||
// 获取玩家模型和挂点位置
|
||||
CECModel pModel = pPlayer.GetPlayerModel();
|
||||
if (pModel == null)
|
||||
{
|
||||
vPos = pSkin->GetAbsoluteTM() * pOffset;
|
||||
vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.LogWarning($"[HOOK_DEBUG] Player model not found for ID {nID}, hook '{szHook}'");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
return true;*/
|
||||
break;
|
||||
// Handle child model (hanger) if specified
|
||||
// 如果指定了子模型(挂载者),则处理
|
||||
if (!string.IsNullOrEmpty(szHanger) && bChildHook)
|
||||
{
|
||||
pModel = pModel.GetChildModel(szHanger);
|
||||
if (pModel == null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.LogWarning($"[HOOK_DEBUG] Child model '{szHanger}' not found for player ID {nID}, hook '{szHook}'");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get hook Transform (non-recursive search as per C++: GetSkeletonHook(szHook, true))
|
||||
// 获取挂点变换(非递归搜索,对应C++:GetSkeletonHook(szHook, true))
|
||||
Transform pHook = pModel.GetHook(szHook, false);
|
||||
if (pHook == null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.LogWarning($"[HOOK_DEBUG] Hook '{szHook}' not found for player ID {nID}, falling back to center position");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
// Get model transform for absolute offset calculation
|
||||
// 获取模型变换用于绝对偏移计算
|
||||
Transform modelTransform = pModel.transform;
|
||||
|
||||
// Calculate position based on relative/absolute offset
|
||||
// 根据相对/绝对偏移计算位置
|
||||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.Log($"[HOOK_DEBUG] Found hook '{szHook}' for player ID {nID}, position={vPos}, relative={bRelHook}, offset={pOffset}");
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (HitPos == GfxHitPos.enumHitBottom)
|
||||
@@ -539,20 +617,58 @@ namespace BrewMonster
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// TODO: Get NPC hook position
|
||||
/*A3DSkeletonHook* pHook = pNPC->GetSgcHook(szHanger, bChildHook, szHook);
|
||||
if (!pHook)
|
||||
if (string.IsNullOrEmpty(szHook))
|
||||
break;
|
||||
A3DSkinModel *pSkin = pNPC->GetSgcSkinModel(szHanger, bChildHook, szHook);
|
||||
if (bRelHook)
|
||||
vPos = pHook->GetAbsoluteTM() * pOffset;
|
||||
else
|
||||
|
||||
// Get NPC model
|
||||
// 获取NPC模型
|
||||
CECModel pModel = pNPC.GetModel();
|
||||
if (pModel == null)
|
||||
{
|
||||
vPos = pSkin->GetAbsoluteTM() * pOffset;
|
||||
vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.LogWarning($"[HOOK_DEBUG] NPC model not found for ID {nID}, hook '{szHook}'");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
return true;*/
|
||||
break;
|
||||
|
||||
// Handle child model (hanger) if specified
|
||||
// 如果指定了子模型(挂载者),则处理
|
||||
if (!string.IsNullOrEmpty(szHanger) && bChildHook)
|
||||
{
|
||||
pModel = pModel.GetChildModel(szHanger);
|
||||
if (pModel == null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.LogWarning($"[HOOK_DEBUG] Child model '{szHanger}' not found for NPC ID {nID}, hook '{szHook}'");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get hook Transform (non-recursive search as per C++: GetSkeletonHook(szHook, true))
|
||||
// 获取挂点变换(非递归搜索,对应C++:GetSkeletonHook(szHook, true))
|
||||
Transform pHook = pModel.GetHook(szHook, false);
|
||||
if (pHook == null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.LogWarning($"[HOOK_DEBUG] Hook '{szHook}' not found for NPC ID {nID}, falling back to center position");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
// Get model transform for absolute offset calculation
|
||||
// 获取模型变换用于绝对偏移计算
|
||||
Transform modelTransform = pModel.transform;
|
||||
|
||||
// Calculate position based on relative/absolute offset
|
||||
// 根据相对/绝对偏移计算位置
|
||||
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
BMLogger.Log($"[HOOK_DEBUG] Found hook '{szHook}' for NPC ID {nID}, position={vPos}, relative={bRelHook}, offset={pOffset}");
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (HitPos == GfxHitPos.enumHitBottom)
|
||||
|
||||
@@ -367,6 +367,7 @@ public class CECModel
|
||||
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)
|
||||
@@ -532,6 +533,51 @@ public class CECModel
|
||||
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)
|
||||
@@ -539,16 +585,34 @@ public class CECModel
|
||||
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 (bUseECMHook) {
|
||||
// if (CECModelHook* pHook = GetECMHook(szHook))
|
||||
// {
|
||||
// fScale *= pHook->GetScaleFactor();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (it != m_CoGfxMap.end())
|
||||
// {
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
# Hook System Flow - C++ Analysis
|
||||
|
||||
**Date Created:** 2026-02-24
|
||||
**Purpose:** Complete flow analysis of how player skill casting spawns GFX and uses hook system to attach GFX to correct bone positions
|
||||
|
||||
---
|
||||
|
||||
## Complete Flow Diagram
|
||||
|
||||
```
|
||||
1. Player Casts Skill
|
||||
↓
|
||||
2. CECAttacksMan::AddSkillAttack() [EC_ManAttacks.cpp:657]
|
||||
↓
|
||||
3. CECAttackEvent Created (with timing: m_timeToBeFired, m_timeToDoDamage)
|
||||
↓
|
||||
4. CECAttackEvent::Tick() [EC_ManAttacks.cpp:458]
|
||||
- Updates m_timeLived
|
||||
- When m_timeToBeFired <= 0 → calls DoFire()
|
||||
↓
|
||||
5. CECAttackEvent::DoFire() [EC_ManAttacks.cpp:111]
|
||||
- For Player skills (m_idSkill != 0):
|
||||
↓
|
||||
6. A3DSkillGfxComposerMan::Play() [EC_ManAttacks.cpp:137-139]
|
||||
- Finds composer by skill ID
|
||||
↓
|
||||
7. A3DSkillGfxComposer::Play() [A3DSkillGfxComposer2.cpp:453]
|
||||
- Iterates through all targets
|
||||
- For each target → calls AddOneTarget()
|
||||
↓
|
||||
8. A3DSkillGfxComposer::AddOneTarget() [A3DSkillGfxComposer2.cpp:388]
|
||||
- Determines host/target based on TargetMode
|
||||
- Calculates scale
|
||||
- Calls AddSkillGfxEvent()
|
||||
↓
|
||||
9. A3DSkillGfxMan::AddSkillGfxEvent() [A3DSkillGfxEvent2.cpp:678]
|
||||
- Creates multiple GFX events (clustering support)
|
||||
- For each event → calls AddOneSkillGfxEvent()
|
||||
↓
|
||||
10. A3DSkillGfxMan::AddOneSkillGfxEvent() [A3DSkillGfxEvent2.cpp:603]
|
||||
- Creates A3DSkillGfxEvent object
|
||||
- Loads fly GFX and hit GFX
|
||||
- Pushes event to update list
|
||||
↓
|
||||
11. CECSkillGfxEvent::Tick() [EC_ManSkillGfx.cpp:307]
|
||||
- **THIS IS WHERE HOOK SYSTEM IS USED**
|
||||
- Updates host and target positions using hooks
|
||||
↓
|
||||
12. _get_pos_by_id() [EC_ManSkillGfx.cpp:10]
|
||||
- **HOOK LOOKUP AND POSITION CALCULATION**
|
||||
- Gets hook from skeleton
|
||||
- Calculates world position using hook transform
|
||||
↓
|
||||
13. A3DSkillGfxEvent::Tick() [A3DSkillGfxEvent2.cpp:487]
|
||||
- State machine: Wait → Flying → Hit → Finished
|
||||
- Updates fly GFX position/rotation each frame
|
||||
- When hit → spawns hit GFX
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Detailed Hook System Usage
|
||||
|
||||
### Step 11: CECSkillGfxEvent::Tick() - Hook Position Updates
|
||||
|
||||
**File:** `EC_ManSkillGfx.cpp:307-373`
|
||||
|
||||
```cpp
|
||||
void CECSkillGfxEvent::Tick(DWORD dwDeltaTime)
|
||||
{
|
||||
if (A3DSkillGfxComposer* pComposer = GetComposer())
|
||||
{
|
||||
const SGC_POS_INFO *pHostPos, *pTargetPos;
|
||||
|
||||
// Determine which position info to use (reverse mode support)
|
||||
if (m_pMoveMethod->IsReverse())
|
||||
{
|
||||
pHostPos = &m_pComposer->m_FlyEndPos; // Target becomes host
|
||||
pTargetPos = &m_pComposer->m_FlyPos; // Host becomes target
|
||||
}
|
||||
else
|
||||
{
|
||||
pHostPos = &m_pComposer->m_FlyPos; // Normal: host is caster
|
||||
pTargetPos = &m_pComposer->m_FlyEndPos; // Normal: target is receiver
|
||||
}
|
||||
|
||||
// UPDATE HOST POSITION USING HOOK
|
||||
m_bHostExist = _get_pos_by_id(
|
||||
m_pPlayerMan,
|
||||
m_pNPCMan,
|
||||
m_nHostID,
|
||||
m_vHostPos, // Output: world position
|
||||
pHostPos->HitPos, // HitPos enum (enumHitCenter, enumHitBottom)
|
||||
m_bIsGoblinSkill,
|
||||
pHostPos->szHook, // Hook name (e.g., "hand_r", "weapon")
|
||||
pHostPos->bRelHook, // Relative offset flag
|
||||
&pHostPos->vOffset, // Offset vector
|
||||
pHostPos->szHanger, // Child model name (weapon, pet)
|
||||
pHostPos->bChildHook); // Use child model flag
|
||||
|
||||
// UPDATE TARGET POSITION USING HOOK
|
||||
m_bTargetExist = _get_pos_by_id(
|
||||
m_pPlayerMan,
|
||||
m_pNPCMan,
|
||||
m_nTargetID,
|
||||
m_vTargetPos, // Output: world position
|
||||
pTargetPos->HitPos,
|
||||
false,
|
||||
pTargetPos->szHook, // Hook name for target
|
||||
pTargetPos->bRelHook,
|
||||
&pTargetPos->vOffset,
|
||||
pTargetPos->szHanger,
|
||||
pTargetPos->bChildHook);
|
||||
|
||||
// Get target direction/up for orientation
|
||||
m_bTargetDirAndUpExist = _get_dir_and_up_by_id(
|
||||
m_pPlayerMan, m_pNPCMan, m_nTargetID,
|
||||
m_vTargetDir, m_vTargetUp);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: no composer, use default positions
|
||||
m_bHostExist = _get_pos_by_id(..., m_pMoveMethod->GetHitPos(), ...);
|
||||
m_bTargetExist = _get_pos_by_id(..., m_pMoveMethod->GetHitPos());
|
||||
}
|
||||
|
||||
// Call base class tick (handles movement and GFX updates)
|
||||
A3DSkillGfxEvent::Tick(dwDeltaTime);
|
||||
}
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Hook information comes from `SGC_POS_INFO` structure in composer
|
||||
- `SGC_POS_INFO` contains: `szHook`, `vOffset`, `bRelHook`, `szHanger`, `bChildHook`, `HitPos`
|
||||
- Position is updated **every frame** during GFX event lifetime
|
||||
|
||||
---
|
||||
|
||||
### Step 12: _get_pos_by_id() - Hook Lookup and Position Calculation
|
||||
|
||||
**File:** `EC_ManSkillGfx.cpp:10-122`
|
||||
|
||||
#### Player Branch (Lines 24-87)
|
||||
|
||||
```cpp
|
||||
if (ISPLAYERID(nID))
|
||||
{
|
||||
CECPlayer* pPlayer = pPlayerMan->GetPlayer(nID);
|
||||
if (pPlayer)
|
||||
{
|
||||
// Hook lookup loop (only if szHook is provided)
|
||||
while (1)
|
||||
{
|
||||
if (!szHook)
|
||||
break; // No hook specified, use default position
|
||||
|
||||
// 1. Get player model
|
||||
CECModel* pModel = pPlayer->GetPlayerModel();
|
||||
if (!pModel)
|
||||
break;
|
||||
|
||||
// 2. Handle child model (weapon/pet) if specified
|
||||
if (szHanger && bChildHook)
|
||||
pModel = pModel->GetChildModel(szHanger);
|
||||
|
||||
if (!pModel)
|
||||
break;
|
||||
|
||||
// 3. Get skeleton from model
|
||||
A3DSkinModel* pSkin = pModel->GetA3DSkinModel();
|
||||
|
||||
// 4. LOOKUP HOOK BY NAME (non-recursive)
|
||||
A3DSkeletonHook* pHook = pSkin->GetSkeletonHook(szHook, true); // true = non-recursive
|
||||
|
||||
if (!pHook)
|
||||
break;
|
||||
|
||||
// 5. CALCULATE POSITION USING HOOK TRANSFORM
|
||||
if (bRelHook)
|
||||
{
|
||||
// Relative offset: transform offset in hook's local space
|
||||
// C++: pHook->GetAbsoluteTM() * (*pOffset)
|
||||
vPos = pHook->GetAbsoluteTM() * (*pOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Absolute offset: transform offset in model space, then translate to hook
|
||||
// C++: vPos = pSkin->GetAbsoluteTM() * (*pOffset);
|
||||
// vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
|
||||
vPos = pSkin->GetAbsoluteTM() * (*pOffset);
|
||||
vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
|
||||
}
|
||||
|
||||
return true; // Success: position calculated from hook
|
||||
}
|
||||
|
||||
// Fallback: No hook or hook lookup failed, use default position
|
||||
if (HitPos == enumHitBottom)
|
||||
vPos = pPlayer->GetPos();
|
||||
else
|
||||
{
|
||||
const A3DAABB& aabb = pPlayer->GetPlayerAABB();
|
||||
vPos = aabb.Center;
|
||||
vPos.y += aabb.Extents.y * .5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### NPC Branch (Lines 89-118)
|
||||
|
||||
```cpp
|
||||
else if (ISNPCID(nID))
|
||||
{
|
||||
CECNPC* pNPC = pNPCMan->GetNPCFromAll(nID);
|
||||
if (pNPC)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// NPC hook lookup (handles child models internally)
|
||||
A3DSkeletonHook* pHook = pNPC->GetSgcHook(szHanger, bChildHook, szHook);
|
||||
if (!pHook)
|
||||
break;
|
||||
|
||||
A3DSkinModel* pSkin = pNPC->GetSgcSkinModel(szHanger, bChildHook, szHook);
|
||||
|
||||
// Same position calculation as player
|
||||
if (bRelHook)
|
||||
vPos = pHook->GetAbsoluteTM() * (*pOffset);
|
||||
else
|
||||
{
|
||||
vPos = pSkin->GetAbsoluteTM() * (*pOffset);
|
||||
vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback: default position
|
||||
if (HitPos == enumHitBottom)
|
||||
vPos = pNPC->GetPos();
|
||||
else
|
||||
{
|
||||
const A3DAABB& aabb = pNPC->GetPickAABB();
|
||||
vPos = aabb.Center;
|
||||
vPos.y += aabb.Extents.y * .5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Hook lookup is **optional** - if `szHook` is NULL or lookup fails, uses default position
|
||||
- `bRelHook = true`: Offset is in hook's local space (rotates with hook)
|
||||
- `bRelHook = false`: Offset is in model space, then translated to hook position
|
||||
- `bChildHook = true`: Searches in child model (weapon/pet) instead of main model
|
||||
- `szHanger`: Name of child model to search (e.g., "weapon", "pet")
|
||||
|
||||
---
|
||||
|
||||
## Hook Data Source: SGC_POS_INFO
|
||||
|
||||
**File:** `A3DSkillGfxComposer2.cpp:43-74`
|
||||
|
||||
The hook information is loaded from skill GFX composer files (`.sgc` files):
|
||||
|
||||
```cpp
|
||||
struct SGC_POS_INFO
|
||||
{
|
||||
char szHook[64]; // Hook name (e.g., "hand_r", "weapon", "head")
|
||||
A3DVECTOR3 vOffset; // Offset from hook position
|
||||
bool bRelHook; // Relative offset flag
|
||||
char szHanger[64]; // Child model name (weapon, pet)
|
||||
bool bChildHook; // Use child model flag
|
||||
GfxHitPos HitPos; // Fallback hit position enum
|
||||
};
|
||||
|
||||
// Loaded from .sgc file:
|
||||
_load_sgc_pos(dwVersion, pFile, szLine, m_FlyPos); // Host position
|
||||
_load_sgc_pos(dwVersion, pFile, szLine, m_FlyEndPos); // Target position
|
||||
_load_sgc_pos(dwVersion, pFile, szLine, m_HitPos); // Hit position
|
||||
```
|
||||
|
||||
**Example .sgc file content:**
|
||||
```
|
||||
Hook: hand_r # Hook name for fly GFX spawn position
|
||||
Pos: 0.0, 0.0, 0.0 # Offset from hook
|
||||
RelHook: 1 # Use relative offset
|
||||
Hanger: weapon # Child model name (optional)
|
||||
ChildHook: 1 # Search in child model
|
||||
HitPos: 0 # Fallback hit position enum
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GFX Position Updates During Flight
|
||||
|
||||
**File:** `A3DSkillGfxEvent2.cpp:487-568`
|
||||
|
||||
After hook positions are calculated, the GFX event updates the fly GFX transform:
|
||||
|
||||
```cpp
|
||||
void A3DSkillGfxEvent::Tick(DWORD dwDeltaTime)
|
||||
{
|
||||
// ... state machine logic ...
|
||||
|
||||
if (m_enumState == enumFlying)
|
||||
{
|
||||
// Update movement
|
||||
if (m_pMoveMethod->TickMove(dwDeltaTime, m_vHostPos, m_vTargetPos))
|
||||
HitTarget(GetTargetCenter()); // Target hit
|
||||
else if (m_pFlyGfx)
|
||||
{
|
||||
// UPDATE FLY GFX TRANSFORM
|
||||
A3DVECTOR3 vDir, vUp;
|
||||
|
||||
if (m_pMoveMethod->GetMode() == enumOnTarget &&
|
||||
m_pMoveMethod->IsReverse() &&
|
||||
GetTargetDirAndUp(vDir, vUp))
|
||||
{
|
||||
// Use target's direction/up for orientation
|
||||
m_pFlyGfx->SetParentTM(a3d_TransformMatrix(vDir, vUp, m_pMoveMethod->GetPos()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use movement direction for orientation
|
||||
m_pFlyGfx->SetParentTM(_build_matrix(m_pMoveMethod->GetMoveDir(), m_pMoveMethod->GetPos()));
|
||||
}
|
||||
|
||||
m_pMoveMethod->UpdateGfxParam(m_pFlyGfx, m_vHostPos, m_vTargetPos);
|
||||
m_pFlyGfx->TickAnimation(dwDeltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- `m_vHostPos` and `m_vTargetPos` are updated **every frame** via `CECSkillGfxEvent::Tick()`
|
||||
- Fly GFX position is calculated by movement method (`m_pMoveMethod->GetPos()`)
|
||||
- Fly GFX rotation uses movement direction or target's direction/up
|
||||
- Host position (`m_vHostPos`) comes from hook lookup (if hook specified)
|
||||
|
||||
---
|
||||
|
||||
## Summary: Hook System Integration Points
|
||||
|
||||
1. **Configuration:** Hook info stored in `.sgc` files (composer files)
|
||||
- `FlyPos`: Hook for fly GFX spawn position (host)
|
||||
- `FlyEndPos`: Hook for fly GFX target position (target)
|
||||
- `HitPos`: Hook for hit GFX spawn position
|
||||
|
||||
2. **Runtime Lookup:** `_get_pos_by_id()` called every frame in `CECSkillGfxEvent::Tick()`
|
||||
- Looks up hook by name from skeleton
|
||||
- Supports child models (weapons, pets)
|
||||
- Calculates world position using hook transform matrix
|
||||
|
||||
3. **Position Calculation:**
|
||||
- **Relative (`bRelHook = true`)**: `hookWorldMatrix * offset` → offset rotates with hook
|
||||
- **Absolute (`bRelHook = false`)**: `modelWorldMatrix * offset` then translate to hook position
|
||||
|
||||
4. **Fallback:** If hook lookup fails, uses default position (AABB center or bottom)
|
||||
|
||||
5. **GFX Attachment:**
|
||||
- Fly GFX spawns at `m_vHostPos` (from hook if specified)
|
||||
- Fly GFX moves toward `m_vTargetPos` (from hook if specified)
|
||||
- Hit GFX spawns at `GetTargetCenter()` (from hook if specified)
|
||||
|
||||
---
|
||||
|
||||
## C++ to C# Conversion Notes
|
||||
|
||||
### Key Functions to Convert:
|
||||
|
||||
1. **`_get_pos_by_id()`** → `HookUtils.GetHookWorldPosition()`
|
||||
- Already planned in conversion documents
|
||||
- Needs model transform parameter for absolute offset
|
||||
|
||||
2. **`CECSkillGfxEvent::Tick()`** → `CECSkillGfxEvent.Tick()`
|
||||
- Update positions every frame using hook system
|
||||
- Pass composer's `SGC_POS_INFO` to hook lookup
|
||||
|
||||
3. **`A3DSkeletonHook::GetAbsoluteTM()`** → `Transform` (Unity)
|
||||
- C++ returns matrix, Unity uses Transform component
|
||||
- Hook transforms are already stored in `SkeletonBuilder`
|
||||
|
||||
### Data Structures:
|
||||
|
||||
- `SGC_POS_INFO` → `SgcPosInfo` (C# struct)
|
||||
- Hook names come from `.sgc` file parsing
|
||||
- Hook lookup uses `SkeletonBuilder.GetHook(name, recursive)`
|
||||
|
||||
---
|
||||
|
||||
**End of Document**
|
||||
Reference in New Issue
Block a user