# Hook System C++ Reference & Conversion Verification This document shows the actual C++ implementation of the Hook System and verifies the conversion plan matches it correctly. **Date Created:** 2026-02-24 --- ## 1. C++ Hook System Architecture ### 1.1 Core Classes #### A3DSkeletonHook (Angelica2/Angelica3D/Source/A3DSkeleton.cpp) ```cpp class A3DSkeletonHook { A3DSkeleton* m_pA3DSkeleton; // Parent skeleton int m_iBone; // Bone index (-1 = root) A3DMATRIX4 m_matHookTM; // Local hook transform (relative to bone) A3DMATRIX4 m_matScaledHookTM; // Scaled hook transform A3DMATRIX4 m_matAbs; // Cached absolute world matrix A3DMATRIX4 m_matNoScaleAbs; // Cached absolute matrix (no scale) bool m_bFixDir; // Fix direction to skeleton DWORD m_dwUpdateCnt; // Update counter // Get world transform matrix const A3DMATRIX4& GetAbsoluteTM() { Update(false); // Update if needed return m_matAbs; } // Update hook transform (called automatically) void Update(bool bForce) { if (m_iBone < 0) { // Root bone hook m_matAbs = m_matScaledHookTM * m_pA3DSkeleton->GetAbsoluteTM(); } else { // Bone-attached hook A3DBone* pBone = m_pA3DSkeleton->GetBone(m_iBone); m_matAbs = matWholeScale * (m_matScaledHookTM * pBone->GetNoScaleAbsTM()); } } }; ``` **Key Points:** - Hook has local transform (`m_matHookTM`) relative to bone - `GetAbsoluteTM()` returns world-space transform matrix - Automatically updates when skeleton animates - Supports bone scaling #### A3DSkinModel::GetSkeletonHook() (A3DSkinModel.cpp:2357) ```cpp A3DSkeletonHook* A3DSkinModel::GetSkeletonHook(const char* szName, bool bNoChild) { A3DSkeletonHook* pHook = NULL; // Search in main skeleton if (m_pA3DSkeleton) { if ((pHook = m_pA3DSkeleton->GetHook(szName, NULL))) return pHook; } // Search in child models (if bNoChild == false, recursive) if (!bNoChild) { for (int i = 0; i < m_aChildModels.GetSize(); i++) { A3DSkinModel* pChild = m_aChildModels[i]; if ((pHook = pChild->GetSkeletonHook(szName, false))) return pHook; } } return NULL; } ``` **Key Points:** - `bNoChild = false` means **recursive search** (searches child models) - `bNoChild = true` means **non-recursive** (only main skeleton) - Searches child models (weapons, pets) if recursive --- ## 2. C++ Hook Usage in Skill GFX ### 2.1 Player Hook Lookup (CECSkillGfxEvent::get_pos_by_id) **C++ Code (from commented C# code):** ```cpp // Player branch CECModel* pModel = pPlayer->GetPlayerModel(); if (!pModel) break; // Handle child model (hanger) if specified if (szHanger && bChildHook) pModel = pModel->GetChildModel(szHanger); if (!pModel) break; A3DSkinModel* pSkin = pModel->GetA3DSkinModel(); A3DSkeletonHook* pHook = pSkin->GetSkeletonHook(szHook, true); // true = non-recursive if (!pHook) break; // Calculate position if (bRelHook) { // Relative offset: transform offset in hook's local space vPos = pHook->GetAbsoluteTM() * pOffset; // Matrix * Vector3 } else { // Absolute offset: offset in model space, then translate to hook position vPos = pSkin->GetAbsoluteTM() * pOffset; vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3); } return true; ``` **Logic Breakdown:** 1. Get player model 2. If `szHanger` and `bChildHook` are set, get child model (weapon/pet) 3. Get skeleton hook by name (non-recursive search) 4. Calculate position: - **Relative (`bRelHook = true`)**: `hookWorldMatrix * offset` → offset in hook's local space - **Absolute (`bRelHook = false`)**: `modelWorldMatrix * offset` then translate to hook position ### 2.2 NPC Hook Lookup (CECNPC::GetSgcHook) **C++ Code (EC_NPCModel.cpp:505):** ```cpp A3DSkeletonHook* CECNPCModelDefaultPolicy::GetSgcHook( const char* szHanger, bool bChildHook, const char* szHook) { if (A3DSkinModel* pSkinModel = GetSgcSkinModel(szHanger, bChildHook, szHook)) { return pSkinModel->GetSkeletonHook(szHook, true); // true = non-recursive } return NULL; } ``` **Usage in get_pos_by_id (NPC branch):** ```cpp // NPC branch A3DSkeletonHook* pHook = pNPC->GetSgcHook(szHanger, bChildHook, szHook); if (!pHook) break; A3DSkinModel* pSkin = pNPC->GetSgcSkinModel(szHanger, bChildHook, szHook); if (bRelHook) { vPos = pHook->GetAbsoluteTM() * pOffset; } else { vPos = pSkin->GetAbsoluteTM() * pOffset; vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3); } return true; ``` --- ## 3. C++ to C# Conversion Mapping ### 3.1 Transform Matrix Operations | C++ Operation | C# Equivalent | Notes | |---------------|---------------|-------| | `pHook->GetAbsoluteTM()` | `hookTransform` (Unity Transform) | World transform | | `pHook->GetAbsoluteTM() * pOffset` | `hookTransform.TransformPoint(offset)` | Relative offset | | `pSkin->GetAbsoluteTM() * pOffset` | `skinTransform.TransformPoint(offset)` | Model-space offset | | `pHook->GetAbsoluteTM().GetRow(3)` | `hookTransform.position` | Hook world position | | `pSkin->GetAbsoluteTM().GetRow(3)` | `skinTransform.position` | Model world position | ### 3.2 Hook Lookup | C++ | C# (Plan) | Status | |-----|-----------|--------| | `pSkin->GetSkeletonHook(szHook, true)` | `skeletonBuilder.GetHook(szHook, false)` | ⚠️ **INVERTED FLAG** | | `pModel->GetChildModel(szHanger)` | `pModel.GetChildModel(szHanger)` | ✅ Matches | | `pNPC->GetSgcHook(...)` | `pNPC.GetHook(...)` | ✅ Simplified | **⚠️ IMPORTANT: Flag Inversion** C++ `GetSkeletonHook(szHook, true)` means **non-recursive** (don't search children). C# plan uses `GetHook(szHook, false)` which means **non-recursive** (don't recurse). **The conversion plan is CORRECT** - Unity's `recursive = false` matches C++'s `bNoChild = true`. ### 3.3 Position Calculation **C++ Relative Offset:** ```cpp vPos = pHook->GetAbsoluteTM() * pOffset; ``` **C# Equivalent (from plan):** ```csharp vPos = hookTransform.TransformPoint(offset); // ✅ CORRECT ``` **C++ Absolute Offset:** ```cpp vPos = pSkin->GetAbsoluteTM() * pOffset; vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3); ``` **C# Equivalent (from plan):** ```csharp Vector3 hookWorldPos = hookTransform.position; Vector3 offsetWorld = hookTransform.TransformDirection(offset); vPos = hookWorldPos + offsetWorld; // ⚠️ NEEDS VERIFICATION ``` **Verification Needed:** The C++ absolute offset logic: 1. Transforms offset in **model's world space** (`pSkin->GetAbsoluteTM() * pOffset`) 2. Subtracts model position 3. Adds hook position This is equivalent to: **offset in model's local space, then translate to hook position**. **C# Correct Implementation:** ```csharp // Get model transform (equivalent to pSkin) Transform modelTransform = pModel.transform; // Or get from SkeletonBuilder root // Transform offset in model's world space Vector3 offsetWorld = modelTransform.TransformPoint(offset); // Translate to hook position vPos = offsetWorld - modelTransform.position + hookTransform.position; ``` **OR simpler (if offset is in model local space):** ```csharp // Transform offset from model local to world Vector3 offsetWorld = modelTransform.TransformDirection(offset); // Add hook position vPos = hookTransform.position + offsetWorld; ``` --- ## 4. Conversion Plan Verification ### 4.1 ✅ Correct Aspects 1. **Hook Storage**: Plan correctly stores hooks in `Dictionary` ✅ 2. **Hook Lookup**: Plan correctly implements `GetHook()` with recursive flag ✅ 3. **Relative Offset**: Plan correctly uses `TransformPoint()` ✅ 4. **Child Model Support**: Plan correctly implements `GetChildModel()` ✅ 5. **Caching**: Plan correctly adds hook cache for performance ✅ ### 4.2 ⚠️ Needs Correction 1. **Absolute Offset Calculation** (HookUtils.cs:400-409) **Current Plan:** ```csharp Vector3 hookWorldPos = hookTransform.position; Vector3 offsetWorld = hookTransform.TransformDirection(offset); return hookWorldPos + offsetWorld; ``` **Should Be:** ```csharp // Need model transform reference // C++: pSkin->GetAbsoluteTM() * pOffset Vector3 offsetWorld = modelTransform.TransformPoint(offset); // C++: vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3) return offsetWorld - modelTransform.position + hookTransform.position; ``` **Problem:** `HookUtils.GetHookWorldPosition()` doesn't have access to model transform. **Solution:** Pass model transform as parameter, OR get it from hook's root parent. **Updated HookUtils:** ```csharp public static Vector3 GetHookWorldPosition( Transform hookTransform, Vector3 offset, bool bRelative, Transform modelTransform = null) // Add model transform parameter { if (hookTransform == null) return Vector3.zero; if (bRelative) { return hookTransform.TransformPoint(offset); } else { // Absolute offset: transform in model space, then translate to hook if (modelTransform == null) { // Fallback: use hook's root (skeleton root) modelTransform = hookTransform.root; } Vector3 offsetWorld = modelTransform.TransformPoint(offset); return offsetWorld - modelTransform.position + hookTransform.position; } } ``` 2. **GetSkeletonHook Flag Usage** **C++:** `GetSkeletonHook(szHook, true)` = non-recursive **Plan:** `GetHook(szHook, true)` = recursive (inverted!) **Correction:** The plan's `recursive` parameter should match C++'s `bNoChild`: - C++ `bNoChild = true` → C# `recursive = false` - C++ `bNoChild = false` → C# `recursive = true` **Current plan uses `recursive = true` in calls, which is CORRECT** (matches C++ `bNoChild = false` for recursive search). **Verification:** - C++: `GetSkeletonHook(szHook, true)` = non-recursive (only main skeleton) - Plan: `GetHook(szHook, false)` = non-recursive ✅ **CORRECT** --- ## 5. Updated Conversion Plan Corrections ### 5.1 Fix HookUtils.GetHookWorldPosition() **File:** `Assets/PerfectWorld/Scripts/Utility/HookUtils.cs` **Update absolute offset calculation:** ```csharp public static Vector3 GetHookWorldPosition( Transform hookTransform, Vector3 offset, bool bRelative, Transform modelTransform = null) // Add optional model transform { if (hookTransform == null) return Vector3.zero; if (bRelative) { // Relative offset: transform offset from hook's local space to world space // C++: pHook->GetAbsoluteTM() * pOffset return hookTransform.TransformPoint(offset); } else { // Absolute offset: transform offset in model space, then translate to hook position // C++: vPos = pSkin->GetAbsoluteTM() * pOffset; // vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3); // Get model transform (skeleton root or provided) if (modelTransform == null) { // Fallback: use hook's root bone (skeleton root) // Find SkeletonBuilder component in hierarchy SkeletonBuilder skeleton = hookTransform.GetComponentInParent(); modelTransform = skeleton != null ? skeleton.rootBone : hookTransform.root; } // Transform offset in model's world space Vector3 offsetWorld = modelTransform.TransformPoint(offset); // Translate to hook position return offsetWorld - modelTransform.position + hookTransform.position; } } ``` ### 5.2 Update get_pos_by_id() Calls **File:** `Assets/PerfectWorld/Scripts/Managers/CECSkillGfxMan.cs` **Pass model transform for absolute offset:** ```csharp // In player branch: CECModel pModel = pPlayer.GetPlayerModel(); Transform modelTransform = pModel.transform; // Or get from SkeletonBuilder Transform pHook = pModel.GetHook(szHook, true); vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform); ``` --- ## 6. Summary ### ✅ Conversion Plan is Mostly Correct The conversion plan correctly: - Maps C++ hook lookup to Unity Transform system - Implements recursive search correctly - Handles relative offset correctly - Supports child models ### ⚠️ One Correction Needed **Absolute Offset Calculation:** - Current plan uses `TransformDirection()` which is incorrect - Should use `TransformPoint()` on model transform, then translate - Need to pass model transform to `HookUtils.GetHookWorldPosition()` ### 📝 Implementation Notes 1. **Hook Lookup Flag**: C++ `bNoChild = true` → C# `recursive = false` ✅ 2. **Relative Offset**: `TransformPoint()` is correct ✅ 3. **Absolute Offset**: Needs model transform reference ⚠️ 4. **Child Models**: Plan correctly implements `GetChildModel()` ✅ --- **End of Document**