From e9522193c6443b34e243c891e87ade5df94f6bd8 Mon Sep 17 00:00:00 2001 From: VDH Date: Tue, 24 Feb 2026 09:50:08 +0700 Subject: [PATCH] plan --- .../Network/CSNetwork/CSNetwork.csproj.meta | 7 - .../Scripts/Skills/SkillStubs1/SkillStubs1.cs | 5 + .../Scripts/Skills/SkillStubs1/skill1.cs | 3 - Assets/PerfectWorld/Scripts/Skills/skill1.cs | 193 -------- Assets/Scripts/CECGameRun.cs | 1 + SKILL_GFX_CONVERSION_PLAN.md | 362 +++++++++------ SKILL_GFX_QUICK_START.md | 319 ++++++++----- agent-skills/01-naming-conventions.md | 191 ++++++++ agent-skills/02-type-mappings.md | 286 ++++++++++++ agent-skills/03-code-patterns.md | 418 ++++++++++++++++++ agent-skills/04-architecture-understanding.md | 250 +++++++++++ agent-skills/05-skill-gfx-deep-dive.md | 333 ++++++++++++++ agent-skills/README.md | 45 ++ 13 files changed, 1961 insertions(+), 452 deletions(-) delete mode 100644 Assets/PerfectWorld/Scripts/Network/CSNetwork/CSNetwork.csproj.meta delete mode 100644 Assets/PerfectWorld/Scripts/Skills/skill1.cs create mode 100644 agent-skills/01-naming-conventions.md create mode 100644 agent-skills/02-type-mappings.md create mode 100644 agent-skills/03-code-patterns.md create mode 100644 agent-skills/04-architecture-understanding.md create mode 100644 agent-skills/05-skill-gfx-deep-dive.md create mode 100644 agent-skills/README.md diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/CSNetwork.csproj.meta b/Assets/PerfectWorld/Scripts/Network/CSNetwork/CSNetwork.csproj.meta deleted file mode 100644 index 333a9b1482..0000000000 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/CSNetwork.csproj.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: d0c06c588e2a6442488a3542551fb243 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/PerfectWorld/Scripts/Skills/SkillStubs1/SkillStubs1.cs b/Assets/PerfectWorld/Scripts/Skills/SkillStubs1/SkillStubs1.cs index 9293eeba60..b0d6bff62f 100644 --- a/Assets/PerfectWorld/Scripts/Skills/SkillStubs1/SkillStubs1.cs +++ b/Assets/PerfectWorld/Scripts/Skills/SkillStubs1/SkillStubs1.cs @@ -5,6 +5,11 @@ namespace BrewMonster { public static partial class SkillStubs { + public static void Init() + { + int i = 0; + } + // Skill stub declarations public static Skill1Stub __stub_Skill1Stub = new Skill1Stub(); public static Skill2Stub __stub_Skill2Stub = new Skill2Stub(); diff --git a/Assets/PerfectWorld/Scripts/Skills/SkillStubs1/skill1.cs b/Assets/PerfectWorld/Scripts/Skills/SkillStubs1/skill1.cs index 6b6fdd5f06..27a925a456 100644 --- a/Assets/PerfectWorld/Scripts/Skills/SkillStubs1/skill1.cs +++ b/Assets/PerfectWorld/Scripts/Skills/SkillStubs1/skill1.cs @@ -166,9 +166,6 @@ namespace BrewMonster public float GetAttackdistance(Skill skill) => 0f; public float GetAngle(Skill skill) => (float)(1 - 0.0111111 * 0); public override float GetPraydistance(Skill skill) => (float)(skill.GetPlayer().GetRange()); - public override int GetRequiredLevel(Skill skill) => RequiredLevelArray[skill.GetLevel() - 1]; - public override int GetRequiredSp(Skill skill) => RequiredSpArray[skill.GetLevel() - 1]; - public override int GetRequiredMoney(Skill skill) => RequiredMoneyArray[skill.GetLevel() - 1]; #if SKILL_CLIENT public override int GetIntroduction(Skill skill, StringBuilder buffer, string format) diff --git a/Assets/PerfectWorld/Scripts/Skills/skill1.cs b/Assets/PerfectWorld/Scripts/Skills/skill1.cs deleted file mode 100644 index 6b6fdd5f06..0000000000 --- a/Assets/PerfectWorld/Scripts/Skills/skill1.cs +++ /dev/null @@ -1,193 +0,0 @@ -#define SKILL_CLIENT -using BrewMonster.Scripts.Skills; -using CSNetwork.GPDataType; -using System.Collections.Generic; -using System.Text; -using UnityEngine; -using static BrewMonster.PET_EVOLVE_CONFIG; - -namespace BrewMonster -{ - -#if SKILL_SERVER - public class Skill1 : Skill - { - public const int SKILL_ID = 1; - - public Skill1() : base(SKILL_ID) - { - } - } -#endif - - public class Skill1Stub : SkillStub - { - private static readonly int[] RequiredLevelArray = { 0, 5, 10, 15, 20, 25, 30, 35, 40, 45 }; - private static readonly int[] RequiredSpArray = { 0, 300, 1200, 2800, 5200, 8400, 12800, 18600, 26300, 36500 }; - private static readonly int[] RequiredMoneyArray = { 0, 30, 130, 280, 480, 730, 1180, 1630, 2080, 2580 }; - - -#if SKILL_SERVER - public class State1 : SkillStub.State - { - public int GetTime(Skill skill) => 400; - public bool Quit(Skill skill) => false; - public bool Loop(Skill skill) => false; - public bool Bypass(Skill skill) => false; - public void Calculate(Skill skill) - { - skill.GetPlayer().SetDecmp(0.2f *(-5 + 7 * skill.GetLevel())); - skill.GetPlayer().SetPray(1); - } - public bool Interrupt(Skill skill) => false; - public bool Cancel(Skill skill) => true; - public bool Skip(Skill skill) => false; - } -#endif - -#if SKILL_SERVER - public class State2 : SkillStub.State - { - public int GetTime(Skill skill) => 700; - public bool Quit(Skill skill) => false; - public bool Loop(Skill skill) => false; - public bool Bypass(Skill skill) => false; - public void Calculate(Skill skill) - { - skill.GetPlayer().SetDecmp(0.8f *(-5 + 7 * skill.GetLevel())); - skill.SetPlus(1.9f * skill.GetLevel() * skill.GetLevel() + 64 * skill.GetLevel() + 36.7f); - skill.SetRatio(0); - skill.SetDamage(skill.GetAttack()); - skill.GetPlayer().SetPerform(1); - } - public bool Interrupt(Skill skill) => false; - public bool Cancel(Skill skill) => false; - public bool Skip(Skill skill) => false; - } -#endif - -#if SKILL_SERVER - public class State3 : SkillStub.State - { - public int GetTime(Skill skill) => 0; - public bool Quit(Skill skill) => false; - public bool Loop(Skill skill) => false; - public bool Bypass(Skill skill) => false; - public void Calculate(Skill skill) - { - } - public bool Interrupt(Skill skill) => false; - public bool Cancel(Skill skill) => false; - public bool Skip(Skill skill) => false; - } -#endif - - public Skill1Stub() : base(1) - { - cls = 0; - name = "虎击"; - nativename = "虎击"; - icon = "虎击"; - max_level = 10; - type = 1; - apcost = 0; - arrowcost = 0; - apgain = 10; - attr = 1; - rank = 0; - eventflag = 0; - posdouble = 0; - clslimit = 0; - time_type = 0; - showorder = 1101; - allow_land = true; - allow_air = true; - allow_water = true; - allow_ride = false; - auto_attack = true; - long_range = 0; - restrict_corpse = 0; - allow_forms = 1; - effect = "虎击"; - doenchant = 0; - dobless = 0; - commoncooldown = 0; - commoncooldowntime = 0; - m_szFlyGfxPath = string.Empty; - m_szHitGrndGfxPath = string.Empty; - m_szHitGfxPath = "策划联入/人物技能/击中/虎击击中.gfx"; - - // GFX Movement and Behavior Parameters / GFX移动和行为参数 - m_MoveMode = (GfxMoveMode)0; - m_TargetMode = (GfxTargetMode)0; - m_AttFlyMode = (GfxAttackMode)0; - m_AttHitMode = (GfxAttackMode)0; - m_dwFlyTime = 0; - m_bTraceTarget = false; - m_FlyClusterCount = 1; - m_FlyClusterInterval = 0; - m_HitClusterCount = 1; - m_HitClusterInterval = 0; - m_bOneHit = true; - m_bFadeOut = false; - m_bRelScl = false; - m_fDefTarScl = 1.8f; - - // Area parameters (commented out) / 区域参数(已注释) - // m_bArea = false; - // m_Shape = (EmitShape)0; - // m_vSize = new Vector3(0.0f, 0.0f, 0.0f); - - // Param (commented out) / 参数(已注释) - // m_paramType = (GfxSkillValType)1; - // m_param = new GFX_SKILL_PARAM(); - // m_param.nVal = 0; - restrict_weapons.Add(0); - restrict_weapons.Add(1); - restrict_weapons.Add(182); - restrict_weapons.Add(5); - restrict_weapons.Add(292); - restrict_weapons.Add(9); - range = new Range(); - range.type = 0; -#if SKILL_SERVER - statestub.Add(new State1()); - statestub.Add(new State2()); - statestub.Add(new State3()); -#endif - } - - ~Skill1Stub() { } - - public override float GetMpcost(Skill skill) => (float)(-5 + 7 * skill.GetLevel()); - public override int GetExecutetime(Skill skill) => 700; - public override int GetCoolingtime(Skill skill) => 3000; - public float GetRadius(Skill skill) => 0f; - public float GetAttackdistance(Skill skill) => 0f; - public float GetAngle(Skill skill) => (float)(1 - 0.0111111 * 0); - public override float GetPraydistance(Skill skill) => (float)(skill.GetPlayer().GetRange()); - public override int GetRequiredLevel(Skill skill) => RequiredLevelArray[skill.GetLevel() - 1]; - public override int GetRequiredSp(Skill skill) => RequiredSpArray[skill.GetLevel() - 1]; - public override int GetRequiredMoney(Skill skill) => RequiredMoneyArray[skill.GetLevel() - 1]; - -#if SKILL_CLIENT - public override int GetIntroduction(Skill skill, StringBuilder buffer, string format) - { - buffer.Append(GPDataTypeHelper.ReplacePercentD(format, - skill.GetLevel(), - -5 + 7 * skill.GetLevel(), - 1.9 * skill.GetLevel() * skill.GetLevel() + 64 * skill.GetLevel() + 36.7)); - return buffer.Length; - } -#endif - -#if SKILL_SERVER - public int GetEnmity(Skill skill) => 0; - public bool TakeEffect(Skill skill) => true; - public float GetEffectdistance(Skill skill) => 13.3f; - public int GetAttackspeed(Skill skill) => 3; - public float GetHitrate(Skill skill) => 1.2f + 0.05f * skill.GetLevel(); -#endif - } -} - diff --git a/Assets/Scripts/CECGameRun.cs b/Assets/Scripts/CECGameRun.cs index 5c511f6dcb..0ac9dcca29 100644 --- a/Assets/Scripts/CECGameRun.cs +++ b/Assets/Scripts/CECGameRun.cs @@ -116,6 +116,7 @@ public partial class CECGameRun private void LoadPrefabs() { + BMLogger.LogError("CECGameRun::LoadPrefabs, Loading prefabs from Resources. Consider using Addressables for better performance and memory management."); _playerPrefab = Resources.Load(AddressResourceConfig.PlayerPrefab); _monsterPrefab = Resources.Load(AddressResourceConfig.MonsterPrefab); _npcServerPrefab = Resources.Load(AddressResourceConfig.NpcServerPrefab); diff --git a/SKILL_GFX_CONVERSION_PLAN.md b/SKILL_GFX_CONVERSION_PLAN.md index 804542b059..d438200bfb 100644 --- a/SKILL_GFX_CONVERSION_PLAN.md +++ b/SKILL_GFX_CONVERSION_PLAN.md @@ -52,7 +52,7 @@ A3DSkillGfxMan.AddSkillGfxEvent() - Creates GFX events with clustering ✅ ↓ A3DSkillGfxEvent.Tick() - State machine (Wait → Flying → Hit → Finished) ⚠️ COMMENTED OUT ↓ -CGfxMoveBase / IGfxMovement - Movement calculation ❌ NOT CREATED +CGfxMoveBase - Movement calculation ❌ NOT CREATED ↓ GFX Spawning (fly/hit) - Instantiate prefabs ❌ NOT IMPLEMENTED ``` @@ -169,7 +169,7 @@ GFX Spawning (fly/hit) - Instantiate prefabs ❌ NOT IMPLEMENTED ### 2.3 What's Completely Missing ❌ 1. **Movement System (CGfxMoveBase)** - - No `IGfxMovement` interface + - No `CGfxMoveBase` abstract class - No movement implementations (Linear, Parabolic, Missile, etc.) - `m_pMoveMethod` referenced in comments but never created - All movement-related calls are commented out @@ -278,140 +278,226 @@ event.Tick() → mostly no-op event.Tick() → full state machine: **New Files:** -**`Assets/PerfectWorld/Scripts/Vfx/IGfxMovement.cs`** +**`Assets/PerfectWorld/Scripts/Vfx/CGfxMoveBase.cs`** (same name as C++) ```csharp - public interface IGfxMovement + // Mirrors C++ CGfxMoveBase exactly: + // - StartMove / TickMove are pure virtual (= 0) → abstract + // - SetParam / SetIsCluster / SetReverse / UpdateGfxParam are virtual with default impl → virtual + // - GetMode / GetPos / GetMoveDir / IsReverse / SetMaxFlyTime are non-virtual → regular methods + public abstract class CGfxMoveBase { - void SetMaxFlyTime(uint dwMaxFlyTime); - void StartMove(Vector3 vHost, Vector3 vTarget); - bool TickMove(float deltaTimeMs, Vector3 vHostPos, Vector3 vTargetPos); - Vector3 GetPos(); - Vector3 GetMoveDir(); - GfxMoveMode GetMode(); - GfxHitPos GetHitPos(); - bool IsReverse(); - void SetReverse(bool bReverse); - void SetParam(GFX_SKILL_PARAM param); - void SetIsCluster(bool bCluster); + // Fields (same as C++) + protected GfxMoveMode m_Mode; + protected GfxHitPos m_HitPos = GfxHitPos.enumHitCenter; + protected Vector3 m_vPos; + protected Vector3 m_vMoveDir; + protected bool m_bOneOfCluser; // C++ spelling kept + protected uint m_dwMaxFlyTime; + protected bool m_bReverse; + protected bool m_bArea; + protected EmitShape m_Shape; + protected Vector3 m_vSize; + protected Vector3 m_vXRange; + protected Vector3 m_vYRange; + protected Vector3 m_vZRange; + protected float m_fSquare; + protected float m_fSquareH; + + protected CGfxMoveBase(GfxMoveMode mode) + { + m_Mode = mode; + m_HitPos = GfxHitPos.enumHitCenter; + } + + // Pure virtual (= 0) → abstract — subclasses MUST override + public abstract void StartMove(Vector3 vHost, Vector3 vTarget); + public abstract bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos); + + // Protected helpers (same as C++ CGfxMoveBase) + protected static float Normalize(ref Vector3 v) + { + float mag = v.magnitude; + if (mag < 1e-6f) { v = Vector3.zero; return 0f; } + v /= mag; + return mag; + } + + protected void CalcRange(Vector3 vDir) + { + m_vYRange = Vector3.up; + m_vZRange = new Vector3(vDir.x, 0, vDir.z); + if (Normalize(ref m_vZRange) < 0.01f) m_vZRange = Vector3.forward; + m_vXRange = Vector3.Cross(m_vYRange, m_vZRange); + m_vXRange *= m_vSize.x; + m_vYRange *= m_vSize.y; + m_vZRange *= m_vSize.z; + m_fSquare = m_vSize.sqrMagnitude; + m_fSquareH = m_vSize.x * m_vSize.x + m_vSize.z * m_vSize.z; + } + + protected Vector3 GetRandOff() + { + // Mirrors C++ GetRandOff() with enumBox/enumSphere/enumCylinder + float x, y, z; + if (m_Shape == EmitShape.enumBox) + return SymRandom() * m_vXRange + SymRandom() * m_vYRange + SymRandom() * m_vZRange; + else if (m_Shape == EmitShape.enumSphere) + { + do { x = SymRandom(); y = SymRandom(); z = SymRandom(); } + while (x*x + y*y + z*z > 1f); + return x * m_vXRange + y * m_vYRange + z * m_vZRange; + } + else if (m_Shape == EmitShape.enumCylinder) + { + y = SymRandom(); + do { x = SymRandom(); z = SymRandom(); } + while (x*x + z*z > 1f); + return x * m_vXRange + y * m_vYRange + z * m_vZRange; + } + return Vector3.zero; + } + + private static float SymRandom() { return UnityEngine.Random.value * 2f - 1f; } + + // Virtual with default implementation — subclasses CAN override, but don't have to + public virtual void SetParam(GFX_SKILL_PARAM param) + { + m_bArea = param.m_bArea; + m_Shape = param.m_Shape; + m_vSize = new Vector3(param.m_vSize.x, param.m_vSize.y, param.m_vSize.z); + } + public virtual void SetIsCluster(bool bCluster) { m_bOneOfCluser = bCluster; } + public virtual void SetReverse(bool bReverse) { m_bReverse = bReverse; } + public virtual void UpdateGfxParam(Vector3 vHost, Vector3 vTarget) { } + + // Non-virtual — same as C++ (no override possible) + public GfxMoveMode GetMode() { return m_Mode; } + public GfxHitPos GetHitPos() { return m_HitPos; } + public Vector3 GetPos() { return m_vPos; } + public Vector3 GetMoveDir() { return m_vMoveDir; } + public bool IsReverse() { return m_bReverse; } + public void SetMaxFlyTime(uint dwTime) { m_dwMaxFlyTime = dwTime; if (m_dwMaxFlyTime == 0) m_dwMaxFlyTime = 1; } + public void SetIsArea(bool bArea) { m_bArea = bArea; } + public void SetShape(EmitShape shape) { m_Shape = shape; } + public void SetRange(Vector3 vSize) { m_vSize = vSize; } + + // Factory method (same as C++ CGfxMoveBase::CreateMoveMethod) + public static CGfxMoveBase CreateMoveMethod(GfxMoveMode mode) + { + switch (mode) + { + case GfxMoveMode.enumLinearMove: return new CGfxLinearMove(mode); + case GfxMoveMode.enumOnTarget: return new CGfxOnTargetMove(mode); + // Phase 2: add more modes + default: return new CGfxOnTargetMove(mode); + } + } } ``` -**`Assets/PerfectWorld/Scripts/Vfx/GfxLinearMove.cs`** (C++ ref: A3DSkillGfxEvent2.cpp:22-52) +**`Assets/PerfectWorld/Scripts/Vfx/CGfxLinearMove.cs`** (C++ ref: A3DSkillGfxEvent2.cpp:22-52) ```csharp -public class GfxLinearMove : IGfxMovement - { - private Vector3 m_vPos; - private Vector3 m_vMoveDir; - private float m_fSpeed; - private uint m_dwMaxFlyTime; - private bool m_bReverse; - - private const float _fly_speed = 20.0f; // units/sec - - public void SetMaxFlyTime(uint dwMaxFlyTime) { m_dwMaxFlyTime = dwMaxFlyTime; } - - public void StartMove(Vector3 vHost, Vector3 vTarget) - { - m_vPos = vHost; - m_vMoveDir = vTarget - m_vPos; - float fDist = m_vMoveDir.magnitude; - if (fDist < 1e-4f) { m_vMoveDir = Vector3.forward; m_fSpeed = _fly_speed; return; } - m_vMoveDir /= fDist; - - float fMax = _fly_speed / 1000f * m_dwMaxFlyTime; - m_fSpeed = fMax >= fDist ? _fly_speed / 1000f : fDist / m_dwMaxFlyTime; - } - - public bool TickMove(float deltaTimeMs, Vector3 vHostPos, Vector3 vTargetPos) - { - Vector3 vFlyDir = vTargetPos - m_vPos; - float fDist = vFlyDir.magnitude; - if (fDist < 1e-4f) return true; - vFlyDir /= fDist; - - float fFlyDist = m_fSpeed * deltaTimeMs; - if (fFlyDist >= fDist) return true; - - m_vPos += vFlyDir * fFlyDist; - m_vMoveDir = vFlyDir; - return false; - } - - public Vector3 GetPos() => m_vPos; - public Vector3 GetMoveDir() => m_vMoveDir; - public GfxMoveMode GetMode() => GfxMoveMode.enumLinearMove; - public GfxHitPos GetHitPos() => GfxHitPos.enumHitCenter; - public bool IsReverse() => m_bReverse; - public void SetReverse(bool b) { m_bReverse = b; } - public void SetParam(GFX_SKILL_PARAM param) { } - public void SetIsCluster(bool b) { } -} -``` - -**`Assets/PerfectWorld/Scripts/Vfx/GfxOnTargetMove.cs`** (for instant-hit skills like 虎击) - ```csharp -public class GfxOnTargetMove : IGfxMovement - { - private Vector3 m_vPos; - private Vector3 m_vMoveDir; - private bool m_bReverse; - - public void SetMaxFlyTime(uint dwMaxFlyTime) { } - - public void StartMove(Vector3 vHost, Vector3 vTarget) - { - m_vPos = vTarget; // Instantly at target - m_vMoveDir = (vTarget - vHost); - if (m_vMoveDir.magnitude > 1e-4f) m_vMoveDir.Normalize(); - else m_vMoveDir = Vector3.forward; - } - - public bool TickMove(float deltaTimeMs, Vector3 vHostPos, Vector3 vTargetPos) - { - m_vPos = vTargetPos; - return true; // Instantly hits - } - - public Vector3 GetPos() => m_vPos; - public Vector3 GetMoveDir() => m_vMoveDir; - public GfxMoveMode GetMode() => GfxMoveMode.enumOnTarget; - public GfxHitPos GetHitPos() => GfxHitPos.enumHitCenter; - public bool IsReverse() => m_bReverse; - public void SetReverse(bool b) { m_bReverse = b; } - public void SetParam(GFX_SKILL_PARAM param) { } - public void SetIsCluster(bool b) { } -} -``` - -**`Assets/PerfectWorld/Scripts/Vfx/GfxMoveFactory.cs`** - ```csharp -public static class GfxMoveFactory +// Mirrors C++ CGfxLinearMove exactly (A3DSkillGfxEvent2.cpp:22-52) +public class CGfxLinearMove : CGfxMoveBase { - public static IGfxMovement Create(GfxMoveMode mode) + protected float m_fSpeed; + private const float _fly_speed = 20.0f / 1000.0f; // same as C++ + + public CGfxLinearMove(GfxMoveMode mode) : base(mode) { } + + public override void StartMove(Vector3 vHost, Vector3 vTarget) { - switch (mode) + if (m_bArea) { - case GfxMoveMode.enumLinearMove: return new GfxLinearMove(); - case GfxMoveMode.enumOnTarget: return new GfxOnTargetMove(); - // Phase 2: add more modes - default: return new GfxLinearMove(); + CalcRange((vTarget - vHost).normalized); + m_vPos = vHost + GetRandOff(); } + else + m_vPos = vHost; + + m_vMoveDir = vTarget - m_vPos; + float fDist = Normalize(ref m_vMoveDir); + float fMax = _fly_speed * m_dwMaxFlyTime; + + if (fMax >= fDist) + m_fSpeed = _fly_speed; + else + m_fSpeed = fDist / m_dwMaxFlyTime; + } + + public override bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos) + { + Vector3 vFlyDir = vTargetPos - m_vPos; + float fDist = Normalize(ref vFlyDir); + float fFlyDist = m_fSpeed * dwDeltaTime; + + if (fFlyDist >= fDist) return true; // target hit + m_vPos += vFlyDir * fFlyDist; + m_vMoveDir = vFlyDir; + return false; } } ``` +**`Assets/PerfectWorld/Scripts/Vfx/CGfxOnTargetMove.cs`** (for instant-hit skills like 虎击) + ```csharp +// Mirrors C++ CGfxOnTargetMove exactly (A3DSkillGfxEvent2.cpp:297-322) +public class CGfxOnTargetMove : CGfxMoveBase +{ + protected float m_fRadius; + protected Vector3 m_vOffset; + + public CGfxOnTargetMove(GfxMoveMode mode) : base(mode) { m_HitPos = GfxHitPos.enumHitBottom; m_fRadius = 0; } + + public override void StartMove(Vector3 vHost, Vector3 vTarget) + { + m_vPos = vTarget; + m_vMoveDir = vTarget - vHost; + m_vMoveDir.y = 0; // C++: zero out Y before normalize + if (Normalize(ref m_vMoveDir) == 0) + m_vMoveDir = Vector3.forward; // _unit_z + + if (m_bOneOfCluser) + { + float fRandAng = UnityEngine.Random.value * Mathf.PI * 2f; + float fRadius = UnityEngine.Random.value * m_fRadius; + m_vOffset.x = Mathf.Cos(fRandAng) * fRadius; + m_vOffset.z = Mathf.Sin(fRandAng) * fRadius; + m_vOffset.y = 0; + m_vPos += m_vOffset; + } + else + m_vOffset = Vector3.zero; + } + + public override bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos) + { + m_vPos = vTargetPos + m_vOffset; + return false; // C++ returns false — hit is triggered by fly time timeout, NOT by TickMove + } + + public override void SetParam(GFX_SKILL_PARAM param) + { + base.SetParam(param); + m_fRadius = param.value.fVal; // C# union access: param.value.fVal + } +} +``` + +*(No separate factory file needed — `CGfxMoveBase.CreateMoveMethod()` handles creation, same as C++)* + #### Task 1.2: Un-comment & Wire Up Event State Machine (1-2 days) **Changes to `A3DSkillGfxMan.cs` — `A3DSkillGfxEvent` class:** 1. Add `m_pMoveMethod` field (un-comment): ```csharp - protected IGfxMovement m_pMoveMethod; + protected CGfxMoveBase m_pMoveMethod; ``` 2. In constructor, create movement: ```csharp - m_pMoveMethod = GfxMoveFactory.Create(mode); + m_pMoveMethod = CGfxMoveBase.CreateMoveMethod(mode); ``` 3. Un-comment the state machine in `Tick()`: @@ -491,18 +577,18 @@ public static class GfxMoveFactory | Mode | Class | C++ Ref | Complexity | Priority | |------|-------|---------|------------|----------| -| Linear | `GfxLinearMove` | lines 22-52 | ✅ Done in Phase 1 | — | -| OnTarget | `GfxOnTargetMove` | — | ✅ Done in Phase 1 | — | -| Parabolic | `GfxParabolicMove` | lines 54-92 | Medium | High | -| Missile | `GfxMissileMove` | lines 94-142 | High | High | -| Meteoric | `GfxMeteoricMove` | lines 144-188 | Low | Medium | -| Accelerated | `GfxAccMove` | lines 319-370 | Low | Medium | -| Helix | `GfxHelixMove` | lines 190-256 | Medium | Low | -| Curved | `GfxCurvedMove` | lines 258-317 | High | Low | -| Link | `GfxLinkMove` | lines 372-420 | Medium | Low | -| Random | `GfxRandMove` | lines 422-458 | Medium | Low | +| Linear | `CGfxLinearMove` | lines 22-52 | ✅ Done in Phase 1 | — | +| OnTarget | `CGfxOnTargetMove` | — | ✅ Done in Phase 1 | — | +| Parabolic | `CGfxParabolicMove` | lines 54-92 | Medium | High | +| Missile | `CGfxMissileMove` | lines 94-142 | High | High | +| Meteoric | `CGfxMeteoricMove` | lines 144-188 | Low | Medium | +| Accelerated | `CGfxAccMove` | lines 319-370 | Low | Medium | +| Helix | `CGfxHelixMove` | lines 190-256 | Medium | Low | +| Curved | `CGfxCurvedMove` | lines 258-317 | High | Low | +| Link | `CGfxLinkMove` | lines 372-420 | Medium | Low | +| Random | `CGfxRandMove` | lines 422-458 | Medium | Low | -**Pattern:** Each implements `IGfxMovement`, added to `GfxMoveFactory.Create()` +**Pattern:** Each extends `CGfxMoveBase`, added to `CGfxMoveBase.CreateMoveMethod()` #### Task 2.2: GFX Lifecycle Management (2-3 days) @@ -676,7 +762,7 @@ Complete the NPC melee branch in `DoFire()`: | File | Lines | What Needs Changing | |------|-------|---------------------| -| `A3DSkillGfxMan.cs` | 516 | Un-comment movement + state machine in Tick(), add IGfxMovement field, remove SpawnGFX call | +| `A3DSkillGfxMan.cs` | 516 | Un-comment movement + state machine in Tick(), add CGfxMoveBase field, remove SpawnGFX call | | `CECSkillGfxMan.cs` | 878 | Un-comment Tick() position updates, implement GFX spawning, un-comment HitTarget() | | `CECAttacksMan.cs` | 1354 | Fix Play() GFX strings, add scale fields, un-comment NPC skill branch, remove SpawnGFX hack | @@ -684,18 +770,18 @@ Complete the NPC melee branch in `DoFire()`: | File | Phase | Purpose | |------|-------|---------| -| `IGfxMovement.cs` | Phase 1 | Movement interface | -| `GfxLinearMove.cs` | Phase 1 | Straight-line projectile | -| `GfxOnTargetMove.cs` | Phase 1 | Instant-hit (no flight) | -| `GfxMoveFactory.cs` | Phase 1 | Factory for movement modes | -| `GfxParabolicMove.cs` | Phase 2 | Arc trajectory | -| `GfxMissileMove.cs` | Phase 2 | Homing missile | -| `GfxMeteoricMove.cs` | Phase 2 | Falls from sky | -| `GfxAccMove.cs` | Phase 2 | Accelerated flight | -| `GfxHelixMove.cs` | Phase 2 | Spiral path | -| `GfxCurvedMove.cs` | Phase 2 | Bezier curve | -| `GfxLinkMove.cs` | Phase 2 | Lightning chain | -| `GfxRandMove.cs` | Phase 2 | Random walk | +| `CGfxMoveBase.cs` | Phase 1 | Movement abstract base class (same as C++) | +| `CGfxLinearMove.cs` | Phase 1 | Straight-line projectile | +| `CGfxOnTargetMove.cs` | Phase 1 | Instant-hit (no flight) | +| *(no separate factory file)* | — | *Factory is `CGfxMoveBase.CreateMoveMethod()`, same as C++* | +| `CGfxParabolicMove.cs` | Phase 2 | Arc trajectory | +| `CGfxMissileMove.cs` | Phase 2 | Homing missile | +| `CGfxMeteoricMove.cs` | Phase 2 | Falls from sky | +| `CGfxAccMove.cs` | Phase 2 | Accelerated flight | +| `CGfxHelixMove.cs` | Phase 2 | Spiral path | +| `CGfxCurvedMove.cs` | Phase 2 | Bezier curve | +| `CGfxLinkMove.cs` | Phase 2 | Lightning chain | +| `CGfxRandMove.cs` | Phase 2 | Random walk | | `GfxPool.cs` | Phase 2 | Object pooling | --- @@ -822,8 +908,8 @@ BMLogger.Log($"[GFX_FLOW] HitTarget at {vTarget}"); | Line(s) | What | Action | |---------|------|--------| -| 205-206 | `m_pMoveMethod` field | Un-comment, use IGfxMovement | -| 251 | `m_pMoveMethod = CGfxMoveBase.CreateMoveMethod(mode)` | Replace with `GfxMoveFactory.Create(mode)` | +| 205-206 | `m_pMoveMethod` field | Un-comment, use CGfxMoveBase | +| 251 | `m_pMoveMethod = CGfxMoveBase.CreateMoveMethod(mode)` | Un-comment (same name as C++) | | 336-337 | `GetMoveMethod()`, `GetMode()`, `GetHitPos()` | Un-comment | | 343 | `SetReverse()` | Un-comment | | 344-345 | `SetParam()`, `SetIsCluster()` | Un-comment | diff --git a/SKILL_GFX_QUICK_START.md b/SKILL_GFX_QUICK_START.md index 4d2151bfab..18cfa80a29 100644 --- a/SKILL_GFX_QUICK_START.md +++ b/SKILL_GFX_QUICK_START.md @@ -9,7 +9,7 @@ - ⚠️ **State machine commented out** — Tick() logic exists but is commented out - ⚠️ **GFX strings empty** — Play() passes `""` instead of actual GFX names - ⚠️ **SpawnGFX temp hack** — Instantiates flyGFX at target pos (wrong, needs removal) -- ❌ **Movement system missing** — No IGfxMovement, no GfxLinearMove, etc. +- ❌ **Movement system missing** — No CGfxMoveBase, no CGfxLinearMove, etc. - ❌ **GFX spawning missing** — No fly/hit GFX instantiation in event Tick() ### What's the Skill GFX System? @@ -37,7 +37,7 @@ SpawnGFX() temp hack ⚠️ WRONG - needs removal ↓ A3DSkillGfxEvent.Tick() ⚠️ State machine COMMENTED OUT ↓ -IGfxMovement.TickMove() ❌ NOT CREATED +CGfxMoveBase.TickMove() ❌ NOT CREATED ↓ Spawn/update fly GFX ❌ NOT IMPLEMENTED ↓ @@ -52,142 +52,239 @@ CECAttackEvent.DoDamage() ✅ Working ### Step 1: Create Movement System (1 day) -Create 4 new files in `Assets/PerfectWorld/Scripts/Vfx/`: +Create 3 new files in `Assets/PerfectWorld/Scripts/Vfx/`: -**`IGfxMovement.cs`** — Movement interface -```csharp -namespace BrewMonster -{ - public interface IGfxMovement - { - void SetMaxFlyTime(uint dwMaxFlyTime); - void StartMove(UnityEngine.Vector3 vHost, UnityEngine.Vector3 vTarget); - bool TickMove(float deltaTimeMs, UnityEngine.Vector3 vHostPos, UnityEngine.Vector3 vTargetPos); - UnityEngine.Vector3 GetPos(); - UnityEngine.Vector3 GetMoveDir(); - GfxMoveMode GetMode(); - GfxHitPos GetHitPos(); - bool IsReverse(); - void SetReverse(bool bReverse); - void SetParam(GFX_SKILL_PARAM param); - void SetIsCluster(bool bCluster); - } -} -``` - -**`GfxLinearMove.cs`** — Straight-line projectile +**`CGfxMoveBase.cs`** — Movement base class (same name and pattern as C++) ```csharp using UnityEngine; namespace BrewMonster { - public class GfxLinearMove : IGfxMovement + // Mirrors C++ CGfxMoveBase exactly: + // - StartMove / TickMove → pure virtual (= 0) → abstract + // - SetParam / SetIsCluster / SetReverse → virtual with default impl → virtual + // - GetMode / GetPos / GetMoveDir / IsReverse / SetMaxFlyTime → non-virtual → regular + public abstract class CGfxMoveBase { - private Vector3 m_vPos, m_vMoveDir; - private float m_fSpeed; - private uint m_dwMaxFlyTime; - private bool m_bReverse; - private const float _fly_speed = 20.0f; // units/sec → 0.02 units/ms + protected GfxMoveMode m_Mode; + protected GfxHitPos m_HitPos = GfxHitPos.enumHitCenter; + protected Vector3 m_vPos; + protected Vector3 m_vMoveDir; + protected bool m_bOneOfCluser; // C++ spelling kept + protected uint m_dwMaxFlyTime; + protected bool m_bReverse; + protected bool m_bArea; + protected EmitShape m_Shape; + protected Vector3 m_vSize; + protected Vector3 m_vXRange; + protected Vector3 m_vYRange; + protected Vector3 m_vZRange; + protected float m_fSquare; + protected float m_fSquareH; - public void SetMaxFlyTime(uint t) { m_dwMaxFlyTime = t; } - - public void StartMove(Vector3 vHost, Vector3 vTarget) + protected CGfxMoveBase(GfxMoveMode mode) { - m_vPos = vHost; - m_vMoveDir = vTarget - vHost; - float fDist = m_vMoveDir.magnitude; - if (fDist < 1e-4f) { m_vMoveDir = Vector3.forward; m_fSpeed = _fly_speed / 1000f; return; } - m_vMoveDir /= fDist; - float fMax = (_fly_speed / 1000f) * m_dwMaxFlyTime; - m_fSpeed = fMax >= fDist ? _fly_speed / 1000f : fDist / m_dwMaxFlyTime; + m_Mode = mode; + m_HitPos = GfxHitPos.enumHitCenter; } - public bool TickMove(float dtMs, Vector3 vHostPos, Vector3 vTargetPos) + // Pure virtual (= 0) → abstract — subclasses MUST override + public abstract void StartMove(Vector3 vHost, Vector3 vTarget); + public abstract bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos); + + // Protected helpers (same as C++ CGfxMoveBase) + protected static float Normalize(ref Vector3 v) { - Vector3 dir = vTargetPos - m_vPos; - float dist = dir.magnitude; - if (dist < 1e-4f) return true; - dir /= dist; - float fly = m_fSpeed * dtMs; - if (fly >= dist) return true; - m_vPos += dir * fly; - m_vMoveDir = dir; - return false; + float mag = v.magnitude; + if (mag < 1e-6f) { v = Vector3.zero; return 0f; } + v /= mag; + return mag; } - public Vector3 GetPos() => m_vPos; - public Vector3 GetMoveDir() => m_vMoveDir; - public GfxMoveMode GetMode() => GfxMoveMode.enumLinearMove; - public GfxHitPos GetHitPos() => GfxHitPos.enumHitCenter; - public bool IsReverse() => m_bReverse; - public void SetReverse(bool b) { m_bReverse = b; } - public void SetParam(GFX_SKILL_PARAM p) { } - public void SetIsCluster(bool b) { } - } -} -``` - -**`GfxOnTargetMove.cs`** — Instant hit (no flight) -```csharp -using UnityEngine; -namespace BrewMonster -{ - public class GfxOnTargetMove : IGfxMovement - { - private Vector3 m_vPos, m_vMoveDir; - private bool m_bReverse; - - public void SetMaxFlyTime(uint t) { } - public void StartMove(Vector3 vHost, Vector3 vTarget) + protected void CalcRange(Vector3 vDir) { - m_vPos = vTarget; - m_vMoveDir = (vTarget - vHost); - if (m_vMoveDir.magnitude > 1e-4f) m_vMoveDir.Normalize(); - else m_vMoveDir = Vector3.forward; + m_vYRange = Vector3.up; + m_vZRange = new Vector3(vDir.x, 0, vDir.z); + if (Normalize(ref m_vZRange) < 0.01f) m_vZRange = Vector3.forward; + m_vXRange = Vector3.Cross(m_vYRange, m_vZRange); + m_vXRange *= m_vSize.x; + m_vYRange *= m_vSize.y; + m_vZRange *= m_vSize.z; + m_fSquare = m_vSize.sqrMagnitude; + m_fSquareH = m_vSize.x * m_vSize.x + m_vSize.z * m_vSize.z; } - public bool TickMove(float dtMs, Vector3 h, Vector3 t) { m_vPos = t; return true; } - public Vector3 GetPos() => m_vPos; - public Vector3 GetMoveDir() => m_vMoveDir; - public GfxMoveMode GetMode() => GfxMoveMode.enumOnTarget; - public GfxHitPos GetHitPos() => GfxHitPos.enumHitCenter; - public bool IsReverse() => m_bReverse; - public void SetReverse(bool b) { m_bReverse = b; } - public void SetParam(GFX_SKILL_PARAM p) { } - public void SetIsCluster(bool b) { } - } -} -``` -**`GfxMoveFactory.cs`** — Factory -```csharp -namespace BrewMonster -{ - public static class GfxMoveFactory - { - public static IGfxMovement Create(GfxMoveMode mode) + protected Vector3 GetRandOff() + { + float x, y, z; + if (m_Shape == EmitShape.enumBox) + return SymRandom() * m_vXRange + SymRandom() * m_vYRange + SymRandom() * m_vZRange; + else if (m_Shape == EmitShape.enumSphere) + { + do { x = SymRandom(); y = SymRandom(); z = SymRandom(); } + while (x*x + y*y + z*z > 1f); + return x * m_vXRange + y * m_vYRange + z * m_vZRange; + } + else if (m_Shape == EmitShape.enumCylinder) + { + y = SymRandom(); + do { x = SymRandom(); z = SymRandom(); } + while (x*x + z*z > 1f); + return x * m_vXRange + y * m_vYRange + z * m_vZRange; + } + return Vector3.zero; + } + + private static float SymRandom() { return UnityEngine.Random.value * 2f - 1f; } + + // Virtual with default implementation — subclasses CAN override + public virtual void SetParam(GFX_SKILL_PARAM param) + { + m_bArea = param.m_bArea; + m_Shape = param.m_Shape; + m_vSize = new Vector3(param.m_vSize.x, param.m_vSize.y, param.m_vSize.z); + } + public virtual void SetIsCluster(bool bCluster) { m_bOneOfCluser = bCluster; } + public virtual void SetReverse(bool bReverse) { m_bReverse = bReverse; } + public virtual void UpdateGfxParam(Vector3 vHost, Vector3 vTarget) { } + + // Non-virtual — same as C++ + public GfxMoveMode GetMode() { return m_Mode; } + public GfxHitPos GetHitPos() { return m_HitPos; } + public Vector3 GetPos() { return m_vPos; } + public Vector3 GetMoveDir() { return m_vMoveDir; } + public bool IsReverse() { return m_bReverse; } + public void SetMaxFlyTime(uint dwTime) + { + m_dwMaxFlyTime = dwTime; + if (m_dwMaxFlyTime == 0) m_dwMaxFlyTime = 1; + } + public void SetIsArea(bool bArea) { m_bArea = bArea; } + public void SetShape(EmitShape shape) { m_Shape = shape; } + public void SetRange(Vector3 vSize) { m_vSize = vSize; } + + // Factory method (same as C++ CGfxMoveBase::CreateMoveMethod) + public static CGfxMoveBase CreateMoveMethod(GfxMoveMode mode) { switch (mode) { - case GfxMoveMode.enumOnTarget: return new GfxOnTargetMove(); - case GfxMoveMode.enumLinearMove: return new GfxLinearMove(); - default: return new GfxLinearMove(); // fallback + case GfxMoveMode.enumOnTarget: return new CGfxOnTargetMove(mode); + case GfxMoveMode.enumLinearMove: return new CGfxLinearMove(mode); + default: return new CGfxLinearMove(mode); } } } } ``` +**`CGfxLinearMove.cs`** — Straight-line projectile +```csharp +using UnityEngine; +namespace BrewMonster +{ + // Mirrors C++ CGfxLinearMove exactly (A3DSkillGfxEvent2.cpp:22-52) + public class CGfxLinearMove : CGfxMoveBase + { + protected float m_fSpeed; + private const float _fly_speed = 20.0f / 1000.0f; // same as C++ + + public CGfxLinearMove(GfxMoveMode mode) : base(mode) { } + + public override void StartMove(Vector3 vHost, Vector3 vTarget) + { + if (m_bArea) + { + CalcRange((vTarget - vHost).normalized); + m_vPos = vHost + GetRandOff(); + } + else + m_vPos = vHost; + + m_vMoveDir = vTarget - m_vPos; + float fDist = Normalize(ref m_vMoveDir); + float fMax = _fly_speed * m_dwMaxFlyTime; + + if (fMax >= fDist) + m_fSpeed = _fly_speed; + else + m_fSpeed = fDist / m_dwMaxFlyTime; + } + + public override bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos) + { + Vector3 vFlyDir = vTargetPos - m_vPos; + float fDist = Normalize(ref vFlyDir); + float fFlyDist = m_fSpeed * dwDeltaTime; + + if (fFlyDist >= fDist) return true; // target hit + m_vPos += vFlyDir * fFlyDist; + m_vMoveDir = vFlyDir; + return false; + } + } +} +``` + +**`CGfxOnTargetMove.cs`** — Instant hit (no flight) +```csharp +using UnityEngine; +namespace BrewMonster +{ + // Mirrors C++ CGfxOnTargetMove exactly (A3DSkillGfxEvent2.cpp:297-322) + public class CGfxOnTargetMove : CGfxMoveBase + { + protected float m_fRadius; + protected Vector3 m_vOffset; + + public CGfxOnTargetMove(GfxMoveMode mode) : base(mode) { m_HitPos = GfxHitPos.enumHitBottom; m_fRadius = 0; } + + public override void StartMove(Vector3 vHost, Vector3 vTarget) + { + m_vPos = vTarget; + m_vMoveDir = vTarget - vHost; + m_vMoveDir.y = 0; // C++: zero out Y before normalize + if (Normalize(ref m_vMoveDir) == 0) + m_vMoveDir = Vector3.forward; // _unit_z + + if (m_bOneOfCluser) + { + float fRandAng = UnityEngine.Random.value * Mathf.PI * 2f; + float fRadius = UnityEngine.Random.value * m_fRadius; + m_vOffset.x = Mathf.Cos(fRandAng) * fRadius; + m_vOffset.z = Mathf.Sin(fRandAng) * fRadius; + m_vOffset.y = 0; + m_vPos += m_vOffset; + } + else + m_vOffset = Vector3.zero; + } + + public override bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos) + { + m_vPos = vTargetPos + m_vOffset; + return false; // C++ returns false — hit triggered by fly time timeout + } + + public override void SetParam(GFX_SKILL_PARAM param) + { + base.SetParam(param); + m_fRadius = param.value.fVal; // C# union access: param.value.fVal + } + } +} +``` + ### Step 2: Wire Up A3DSkillGfxEvent State Machine (1-2 days) **In `A3DSkillGfxMan.cs`:** 1. **Add field** (un-comment line ~205): ```csharp - protected IGfxMovement m_pMoveMethod; + protected CGfxMoveBase m_pMoveMethod; ``` 2. **In constructor** (replace line ~251): ```csharp - m_pMoveMethod = GfxMoveFactory.Create(mode); + m_pMoveMethod = CGfxMoveBase.CreateMoveMethod(mode); ``` 3. **Un-comment in Tick():** @@ -195,15 +292,16 @@ namespace BrewMonster - Line 434-435: `m_pMoveMethod.SetMaxFlyTime()` + `StartMove()` - Line 454: `m_pMoveMethod.TickMove()` → `HitTarget(GetTargetCenter())` -4. **Un-comment setters** (lines 336-345): +4. **Un-comment delegating setters** (lines 336-345) — these delegate to `m_pMoveMethod` which has the correct default behavior: ```csharp - public IGfxMovement GetMoveMethod() { return m_pMoveMethod; } + public CGfxMoveBase GetMoveMethod() { return m_pMoveMethod; } public GfxMoveMode GetMode() { return m_pMoveMethod.GetMode(); } public GfxHitPos GetHitPos() { return m_pMoveMethod.GetHitPos(); } public void SetReverse(bool bReverse) { m_pMoveMethod.SetReverse(bReverse); } public void SetParam(GFX_SKILL_PARAM param) { m_pMoveMethod.SetParam(param); } public void SetIsCluster(bool bCluster) { m_pMoveMethod.SetIsCluster(bCluster); } ``` + These work correctly because `CGfxMoveBase.SetIsCluster()` / `SetReverse()` / `SetParam()` have default implementations that store the values. Subclasses inherit the defaults unless they need to do extra work (like `CGfxMeteoricMove.SetParam()` which also stores a radius). 5. **In AddOneSkillGfxEvent():** - Un-comment line 150: `pEvent.SetReverse(bReverse)` @@ -352,7 +450,7 @@ if (composerMan != null) **Expected behavior:** 1. Player attacks → DoFire() 2. Composer loads: `m_MoveMode = enumOnTarget`, `m_dwFlyTime = 0` -3. Movement: `GfxOnTargetMove.TickMove()` → returns `true` immediately +3. Movement: `CGfxOnTargetMove.TickMove()` → returns `false` (stays at target + offset), hit triggered by fly time timeout 4. `HitTarget()` → spawns hit GFX at target 5. `m_timeToDoDamage = 1` → damage immediately @@ -426,10 +524,9 @@ BMLogger.Log($"[GFX] Hit target at {vTarget}, hitGFX={m_pComposer.GetHitGFX()?.n | File | What to Change | Phase | |------|----------------|-------| -| `IGfxMovement.cs` | **CREATE** — movement interface | 1 | -| `GfxLinearMove.cs` | **CREATE** — linear projectile | 1 | -| `GfxOnTargetMove.cs` | **CREATE** — instant hit | 1 | -| `GfxMoveFactory.cs` | **CREATE** — factory | 1 | +| `CGfxMoveBase.cs` | **CREATE** — movement base class + factory (same as C++) | 1 | +| `CGfxLinearMove.cs` | **CREATE** — linear projectile | 1 | +| `CGfxOnTargetMove.cs` | **CREATE** — instant hit | 1 | | `A3DSkillGfxMan.cs` | **EDIT** — un-comment state machine, add movement field | 1 | | `CECSkillGfxMan.cs` | **EDIT** — un-comment Tick, add GFX spawning | 1 | | `CECAttacksMan.cs` | **EDIT** — fix GFX strings, remove SpawnGFX, add scale | 1 | @@ -438,7 +535,7 @@ BMLogger.Log($"[GFX] Hit target at {vTarget}, hitGFX={m_pComposer.GetHitGFX()?.n ## Estimated Time: 3-5 days for Phase 1 -- Day 1: Create movement system (4 files) +- Day 1: Create movement system (3 files) - Day 2: Un-comment + wire up state machine in A3DSkillGfxMan + CECSkillGfxMan - Day 3: Fix composer Play() GFX refs, remove SpawnGFX hack, add accessor methods - Day 4: Testing, debugging, logging diff --git a/agent-skills/01-naming-conventions.md b/agent-skills/01-naming-conventions.md new file mode 100644 index 0000000000..686654a994 --- /dev/null +++ b/agent-skills/01-naming-conventions.md @@ -0,0 +1,191 @@ +# Naming Conventions - CRITICAL RULES + +## ⚠️ CRITICAL: Preserve C++ Names Exactly + +**Never change C++ class/field/method names unless explicitly requested by the user.** + +## Class Names + +### Movement System +| C++ Name | C# Name | ✅/❌ | +|----------|---------|------| +| `CGfxMoveBase` | `CGfxMoveBase` | ✅ Correct | +| `CGfxLinearMove` | `CGfxLinearMove` | ✅ Correct | +| `CGfxOnTargetMove` | `CGfxOnTargetMove` | ✅ Correct | +| `CGfxParabolicMove` | `CGfxParabolicMove` | ✅ Correct | +| `CGfxMissileMove` | `CGfxMissileMove` | ✅ Correct | +| `CGfxMeteoricMove` | `CGfxMeteoricMove` | ✅ Correct | +| `CGfxHelixMove` | `CGfxHelixMove` | ✅ Correct | +| `CGfxCurvedMove` | `CGfxCurvedMove` | ✅ Correct | +| `CGfxAccMove` | `CGfxAccMove` | ✅ Correct | +| `CGfxLinkMove` | `CGfxLinkMove` | ✅ Correct | +| `CGfxRandMove` | `CGfxRandMove` | ✅ Correct | + +**❌ WRONG:** `IGfxMovement`, `GfxLinearMove`, `LinearMovement` + +### Event System +| C++ Name | C# Name | ✅/❌ | +|----------|---------|------| +| `A3DSkillGfxEvent` | `A3DSkillGfxEvent` | ✅ Correct | +| `A3DSkillGfxMan` | `A3DSkillGfxMan` | ✅ Correct | +| `A3DSkillGfxComposer` | `A3DSkillGfxComposer` | ✅ Correct | +| `A3DSkillGfxComposerMan` | `A3DSkillGfxComposerMan` | ✅ Correct | +| `CECSkillGfxEvent` | `CECSkillGfxEvent` | ✅ Correct | +| `CECSkillGfxMan` | `CECSkillGfxMan` | ✅ Correct | + +## Field Names + +### Movement Base Fields +| C++ Name | C# Name | Notes | +|----------|---------|-------| +| `m_Mode` | `m_Mode` | GfxMoveMode enum | +| `m_HitPos` | `m_HitPos` | GfxHitPos enum | +| `m_vPos` | `m_vPos` | Position vector | +| `m_vMoveDir` | `m_vMoveDir` | Movement direction | +| `m_bOneOfCluser` | `m_bOneOfCluser` | ⚠️ **TYPO PRESERVED** - C++ has this spelling | +| `m_dwMaxFlyTime` | `m_dwMaxFlyTime` | Max flight time (uint) | +| `m_bReverse` | `m_bReverse` | Reverse direction flag | +| `m_bArea` | `m_bArea` | Area emission flag | +| `m_Shape` | `m_Shape` | EmitShape enum | +| `m_vSize` | `m_vSize` | Size vector | +| `m_vXRange` | `m_vXRange` | X range for area emission | +| `m_vYRange` | `m_vYRange` | Y range for area emission | +| `m_vZRange` | `m_vZRange` | Z range for area emission | +| `m_fSquare` | `m_fSquare` | Square magnitude | +| `m_fSquareH` | `m_fSquareH` | Horizontal square magnitude | + +**❌ WRONG:** `m_bOneOfCluster` (correct spelling) - C++ has the typo, we must preserve it + +### Event Fields +| C++ Name | C# Name | Notes | +|----------|---------|-------| +| `m_pComposer` | `m_pComposer` | A3DSkillGfxComposer reference | +| `m_pMoveMethod` | `m_pMoveMethod` | CGfxMoveBase reference | +| `m_pFlyGfx` | `m_pFlyGfx` | Fly GFX instance | +| `m_pHitGfx` | `m_pHitGfx` | Hit GFX instance | +| `m_dwFlyTimeSpan` | `m_dwFlyTimeSpan` | Flight time span (uint) | +| `m_dwCurSpan` | `m_dwCurSpan` | Current span (uint) | +| `m_dwDelayTime` | `m_dwDelayTime` | Delay time (uint) | +| `m_bTraceTarget` | `m_bTraceTarget` | Trace target flag | +| `m_bFadeOut` | `m_bFadeOut` | Fade out flag | +| `m_nHostID` | `m_nHostID` | Host ID (long/clientid_t) | +| `m_nTargetID` | `m_nTargetID` | Target ID (long/clientid_t) | +| `m_dwModifier` | `m_dwModifier` | Modifier (uint) | +| `m_bIsGoblinSkill` | `m_bIsGoblinSkill` | Goblin skill flag | +| `m_vHostPos` | `m_vHostPos` | Host position | +| `m_vTargetPos` | `m_vTargetPos` | Target position | +| `m_vTargetDir` | `m_vTargetDir` | Target direction | +| `m_vTargetUp` | `m_vTargetUp` | Target up vector | +| `m_bHostExist` | `m_bHostExist` | Host exists flag | +| `m_bTargetExist` | `m_bTargetExist` | Target exists flag | +| `m_bHitGfxInfinite` | `m_bHitGfxInfinite` | Hit GFX infinite flag | +| `m_bTargetDirAndUpExist` | `m_bTargetDirAndUpExist` | Target dir/up exist flag | +| `m_enumState` | `m_enumState` | GfxSkillEventState enum | + +## Method Names + +### Movement Methods +| C++ Name | C# Name | Notes | +|----------|---------|-------| +| `StartMove()` | `StartMove()` | Initialize movement | +| `TickMove()` | `TickMove()` | Update movement (returns bool) | +| `SetParam()` | `SetParam()` | Set GFX_SKILL_PARAM | +| `SetIsCluster()` | `SetIsCluster()` | Set cluster flag | +| `SetReverse()` | `SetReverse()` | Set reverse flag | +| `UpdateGfxParam()` | `UpdateGfxParam()` | Update GFX parameters | +| `GetMode()` | `GetMode()` | Get movement mode | +| `GetHitPos()` | `GetHitPos()` | Get hit position | +| `GetPos()` | `GetPos()` | Get current position | +| `GetMoveDir()` | `GetMoveDir()` | Get movement direction | +| `IsReverse()` | `IsReverse()` | Check reverse flag | +| `SetMaxFlyTime()` | `SetMaxFlyTime()` | Set max flight time | +| `CreateMoveMethod()` | `CreateMoveMethod()` | Static factory method | + +### Event Methods +| C++ Name | C# Name | Notes | +|----------|---------|-------| +| `Tick()` | `Tick()` | Update event | +| `HitTarget()` | `HitTarget()` | Handle target hit | +| `GetTargetCenter()` | `GetTargetCenter()` | Get target center (abstract) | +| `GetTargetDirAndUp()` | `GetTargetDirAndUp()` | Get target direction/up | +| `SetComposer()` | `SetComposer()` | Set composer | +| `GetMoveMethod()` | `GetMoveMethod()` | Get movement method | +| `Resume()` | `Resume()` | Resume event | + +## Enum Names + +| C++ Name | C# Name | Notes | +|----------|---------|-------| +| `GfxMoveMode` | `GfxMoveMode` | Movement mode enum | +| `GfxHitPos` | `GfxHitPos` | Hit position enum | +| `EmitShape` | `EmitShape` | Emission shape enum | +| `GfxSkillValType` | `GfxSkillValType` | Value type enum | +| `GfxSkillEventState` | `GfxSkillEventState` | Event state enum | + +## Enum Values + +### GfxMoveMode +- `enumLinearMove` (0) +- `enumParabolicMove` (1) +- `enumMissileMove` (2) +- `enumMeteoricMove` (3) +- `enumHelixMove` (4) +- `enumCurvedMove` (5) +- `enumAccMove` (6) +- `enumOnTarget` (7) +- `enumLink` (8) +- `enumRandMove` (9) +- `enumMoveModeNum` (10) + +### GfxHitPos +- `enumHitCenter` (0) +- `enumHitBottom` (1) + +### EmitShape +- `enumBox` (0) +- `enumSphere` (1) +- `enumCylinder` (2) +- `enumShapeNum` (3) + +### GfxSkillEventState +- `enumWait` (0) +- `enumFlying` (1) +- `enumHit` (2) +- `enumFinished` (3) + +## File Names + +| C++ File | C# File | Location | +|----------|---------|----------| +| `A3DSkillGfxEvent2.h` | `CGfxMoveBase.cs` | `Assets/PerfectWorld/Scripts/Vfx/` | +| `A3DSkillGfxEvent2.cpp` | `CGfxLinearMove.cs` | `Assets/PerfectWorld/Scripts/Vfx/` | +| `A3DSkillGfxEvent2.cpp` | `CGfxOnTargetMove.cs` | `Assets/PerfectWorld/Scripts/Vfx/` | +| `A3DSkillGfxMan.h` | `A3DSkillGfxMan.cs` | `Assets/PerfectWorld/Scripts/Managers/` | +| `CECSkillGfxMan.h` | `CECSkillGfxMan.cs` | `Assets/PerfectWorld/Scripts/Managers/` | + +## Naming Patterns + +### Hungarian Notation (Preserved from C++) +- `m_` prefix = member variable +- `p` prefix = pointer/reference (C#: reference) +- `n` prefix = number/ID +- `dw` prefix = DWORD (uint) +- `b` prefix = bool +- `f` prefix = float +- `v` prefix = vector +- `sz` prefix = string (C#: string) + +### Examples +- `m_pMoveMethod` = member pointer/reference to MoveMethod +- `m_dwFlyTimeSpan` = member DWORD (uint) for fly time span +- `m_bTraceTarget` = member bool for trace target +- `m_vHostPos` = member vector for host position +- `szFlyGfx` = string for fly GFX path + +## Rules Summary + +1. **Preserve ALL C++ names exactly** - including typos (`m_bOneOfCluser`) +2. **Keep Hungarian notation** - maintain `m_`, `p`, `dw`, `b`, `f`, `v` prefixes +3. **Match enum values** - exact same names and order +4. **Preserve file structure** - similar organization to C++ +5. **Never "improve" names** - unless user explicitly requests it diff --git a/agent-skills/02-type-mappings.md b/agent-skills/02-type-mappings.md new file mode 100644 index 0000000000..24569cd3a6 --- /dev/null +++ b/agent-skills/02-type-mappings.md @@ -0,0 +1,286 @@ +# Type Mappings - C++ to C# Conversions + +## Primitive Types + +| C++ Type | C# Type | Notes | +|----------|---------|-------| +| `DWORD` | `uint` | 32-bit unsigned integer | +| `bool` | `bool` | Boolean | +| `int` | `int` | 32-bit signed integer | +| `float` | `float` | 32-bit floating point | +| `double` | `double` | 64-bit floating point | +| `char*` | `string` | String (C# uses managed strings) | +| `const char*` | `string` | Constant string | +| `size_t` | `uint` or `ulong` | Size type (usually uint) | +| `clientid_t` | `long` | Client ID type | + +## Vector Types + +| C++ Type | C# Type | Conversion Notes | +|----------|---------|------------------| +| `A3DVECTOR3` | `Vector3` | Unity's Vector3 | +| `A3DVECTOR3&` | `Vector3` (ref) | Pass by reference | +| `const A3DVECTOR3&` | `Vector3` (ref) | Const reference | + +### A3DVECTOR3 to Vector3 Conversion + +**C++:** +```cpp +A3DVECTOR3 vPos; +vPos.x = 1.0f; +vPos.y = 2.0f; +vPos.z = 3.0f; +``` + +**C#:** +```csharp +Vector3 vPos = new Vector3(1.0f, 2.0f, 3.0f); +// Or +Vector3 vPos; +vPos.x = 1.0f; +vPos.y = 2.0f; +vPos.z = 3.0f; +``` + +**When accessing from A3DVECTOR3 struct:** +```csharp +// If param.m_vSize is A3DVECTOR3 +m_vSize = new Vector3(param.m_vSize.x, param.m_vSize.y, param.m_vSize.z); +``` + +## Pointer Types + +| C++ Type | C# Type | Notes | +|----------|---------|-------| +| `T*` | `T` (reference) | C# uses references, not pointers | +| `T*&` | `ref T` | Reference to pointer → ref reference | +| `const T*` | `T` (readonly) | Const pointer → readonly reference | +| `T**` | `ref T` or `T[]` | Pointer to pointer → ref or array | + +### Examples + +**C++:** +```cpp +A3DSkillGfxComposer* m_pComposer; +CGfxMoveBase* m_pMoveMethod; +void SetComposer(A3DSkillGfxComposer* pComposer); +``` + +**C#:** +```csharp +A3DSkillGfxComposer m_pComposer; +CGfxMoveBase m_pMoveMethod; +void SetComposer(A3DSkillGfxComposer pComposer); +``` + +## Struct Types + +### GFX_SKILL_PARAM + +**C++:** +```cpp +struct GFX_SKILL_PARAM +{ + union + { + bool bVal; + int nVal; + float fVal; + }; + bool m_bArea; + EmitShape m_Shape; + A3DVECTOR3 m_vSize; +}; +``` + +**C#:** +```csharp +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct GFX_SKILL_PARAM +{ + public ValueUnion value; // Union wrapped in struct + + public bool m_bArea; + public EmitShape m_Shape; + public A3DVECTOR3 m_vSize; // Note: Uses A3DVECTOR3, not Vector3 + + [StructLayout(LayoutKind.Explicit)] + public struct ValueUnion + { + [FieldOffset(0)] + public bool bVal; + + [FieldOffset(0)] + public int nVal; + + [FieldOffset(0)] + public float fVal; + } +} +``` + +**Access Pattern:** +```csharp +// C++: param.fVal +// C#: param.value.fVal +m_fRadius = param.value.fVal; +``` + +## Enum Types + +| C++ Enum | C# Enum | Notes | +|----------|---------|-------| +| `enum GfxMoveMode { ... }` | `public enum GfxMoveMode { ... }` | Same values | +| `enum GfxHitPos { ... }` | `public enum GfxHitPos { ... }` | Same values | +| `enum EmitShape { ... }` | `public enum EmitShape { ... }` | Same values | + +**Preserve exact enum value names and order!** + +## Function Signatures + +### Method Parameters + +**C++:** +```cpp +bool TickMove(DWORD dwDeltaTime, const A3DVECTOR3& vHostPos, const A3DVECTOR3& vTargetPos); +void SetParam(const GFX_SKILL_PARAM* param); +``` + +**C#:** +```csharp +bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos); +void SetParam(GFX_SKILL_PARAM param); // Struct passed by value (or ref if needed) +``` + +### Return Types + +**C++:** +```cpp +CGfxMoveBase* CreateMoveMethod(GfxMoveMode mode); +const A3DVECTOR3& GetPos() const; +``` + +**C#:** +```csharp +static CGfxMoveBase CreateMoveMethod(GfxMoveMode mode); +Vector3 GetPos(); // No const in C# +``` + +## Const Correctness + +| C++ | C# Equivalent | +|-----|---------------| +| `const T&` | `T` (value type) or `readonly T` (reference) | +| `const T*` | `T` (reference, readonly) | +| `const` method | No equivalent (use `readonly` for fields) | + +**C++:** +```cpp +const A3DVECTOR3& GetPos() const; +bool IsReverse() const; +``` + +**C#:** +```csharp +Vector3 GetPos(); // No const modifier needed +bool IsReverse(); // No const modifier needed +``` + +## Memory Management + +| C++ | C# | +|-----|-----| +| `new T()` | `new T()` | +| `delete ptr` | Automatic GC (or `Dispose()` for IDisposable) | +| `delete[] arr` | Automatic GC | +| `malloc/free` | Automatic GC | + +**C++:** +```cpp +CGfxMoveBase* pMove = new CGfxLinearMove(mode); +delete pMove; +``` + +**C#:** +```csharp +CGfxMoveBase pMove = new CGfxLinearMove(mode); +// Automatic garbage collection, or implement IDisposable if needed +``` + +## Static Constants + +**C++:** +```cpp +static const float _fly_speed = 20.0f / 1000.0f; +static const float _gravity = 9.8e-6f; +``` + +**C#:** +```csharp +private const float _fly_speed = 20.0f / 1000.0f; +private const float _gravity = 9.8e-6f; +``` + +## Helper Functions + +### Normalize + +**C++:** +```cpp +float Normalize(A3DVECTOR3& v) +{ + float mag = v.Magnitude(); + if (mag < 1e-6f) { v = _unit_zero; return 0f; } + v /= mag; + return mag; +} +``` + +**C#:** +```csharp +protected static float Normalize(ref Vector3 v) +{ + float mag = v.magnitude; + if (mag < 1e-6f) { v = Vector3.zero; return 0f; } + v /= mag; + return mag; +} +``` + +## Common Patterns + +### Passing by Reference + +**C++:** +```cpp +void CalcRange(const A3DVECTOR3& vDir); +bool GetTargetDirAndUp(A3DVECTOR3& vDir, A3DVECTOR3& vUp); +``` + +**C#:** +```csharp +void CalcRange(Vector3 vDir); // Value type, passed by value +bool GetTargetDirAndUp(out Vector3 vDir, out Vector3 vUp); // Use out/ref +``` + +### Array/Container Types + +| C++ | C# | +|-----|-----| +| `std::vector` | `List` | +| `std::list` | `List` or `LinkedList` | +| `std::map` | `Dictionary` | +| `T[]` | `T[]` | + +## Type Conversion Checklist + +When converting a method: +- [ ] `DWORD` → `uint` +- [ ] `A3DVECTOR3` → `Vector3` +- [ ] `T*` → `T` (remove pointer) +- [ ] `const T&` → `T` (remove const and reference) +- [ ] `const T*` → `T` (remove const and pointer) +- [ ] `char*` → `string` +- [ ] `clientid_t` → `long` +- [ ] Union access: `param.fVal` → `param.value.fVal` +- [ ] `A3DVECTOR3` struct fields → `Vector3` constructor diff --git a/agent-skills/03-code-patterns.md b/agent-skills/03-code-patterns.md new file mode 100644 index 0000000000..6badaed4a3 --- /dev/null +++ b/agent-skills/03-code-patterns.md @@ -0,0 +1,418 @@ +# Code Patterns - C++ to C# Conversions + +## Abstract Base Classes + +### C++ Pattern +```cpp +class CGfxMoveBase +{ +protected: + CGfxMoveBase(GfxMoveMode mode) : m_Mode(mode) {} + +public: + virtual ~CGfxMoveBase() {} + virtual void StartMove(const A3DVECTOR3& vHost, const A3DVECTOR3& vTarget) = 0; + virtual bool TickMove(DWORD dwDeltaTime, ...) = 0; + virtual void SetParam(const GFX_SKILL_PARAM* param) { /* default impl */ } + static CGfxMoveBase* CreateMoveMethod(GfxMoveMode mode); +}; +``` + +### C# Pattern +```csharp +public abstract class CGfxMoveBase +{ + protected CGfxMoveBase(GfxMoveMode mode) + { + m_Mode = mode; + } + + // Pure virtual (= 0) → abstract + public abstract void StartMove(Vector3 vHost, Vector3 vTarget); + public abstract bool TickMove(uint dwDeltaTime, ...); + + // Virtual with default → virtual + public virtual void SetParam(GFX_SKILL_PARAM param) + { + // default implementation + } + + // Static factory method + public static CGfxMoveBase CreateMoveMethod(GfxMoveMode mode) + { + switch (mode) + { + case GfxMoveMode.enumLinearMove: return new CGfxLinearMove(mode); + default: return new CGfxLinearMove(mode); + } + } +} +``` + +## Inheritance Patterns + +### C++ Pattern +```cpp +class CGfxLinearMove : public CGfxMoveBase +{ +protected: + float m_fSpeed; + +public: + CGfxLinearMove(GfxMoveMode mode) : CGfxMoveBase(mode) {} + virtual void StartMove(const A3DVECTOR3& vHost, const A3DVECTOR3& vTarget); + virtual bool TickMove(DWORD dwDeltaTime, ...); +}; +``` + +### C# Pattern +```csharp +public class CGfxLinearMove : CGfxMoveBase +{ + protected float m_fSpeed; + + public CGfxLinearMove(GfxMoveMode mode) : base(mode) { } + + public override void StartMove(Vector3 vHost, Vector3 vTarget) + { + // implementation + } + + public override bool TickMove(uint dwDeltaTime, ...) + { + // implementation + } +} +``` + +## Static Factory Methods + +### C++ Pattern +```cpp +inline CGfxMoveBase* CGfxMoveBase::CreateMoveMethod(GfxMoveMode mode) +{ + switch(mode) + { + case enumLinearMove: + return new CGfxLinearMove(mode); + case enumOnTarget: + return new CGfxOnTargetMove(mode); + default: + assert(false); + return NULL; + } +} +``` + +### C# Pattern +```csharp +public static CGfxMoveBase CreateMoveMethod(GfxMoveMode mode) +{ + switch (mode) + { + case GfxMoveMode.enumLinearMove: + return new CGfxLinearMove(mode); + case GfxMoveMode.enumOnTarget: + return new CGfxOnTargetMove(mode); + default: + return new CGfxLinearMove(mode); // fallback + } +} +``` + +## Vector Operations + +### Normalize Pattern + +**C++:** +```cpp +A3DVECTOR3 vDir = vTarget - m_vPos; +float fDist = vDir.Normalize(); // In-place normalize, returns magnitude +if (fDist < 1e-4f) return true; +``` + +**C#:** +```csharp +Vector3 vDir = vTarget - m_vPos; +float fDist = Normalize(ref vDir); // Helper method +if (fDist < 1e-4f) return true; + +// Helper implementation: +protected static float Normalize(ref Vector3 v) +{ + float mag = v.magnitude; + if (mag < 1e-6f) { v = Vector3.zero; return 0f; } + v /= mag; + return mag; +} +``` + +### Vector Math + +**C++:** +```cpp +m_vPos += vFlyDir * fFlyDist; +m_vMoveDir = vFlyDir; +``` + +**C#:** +```csharp +m_vPos += vFlyDir * fFlyDist; +m_vMoveDir = vFlyDir; +``` + +## Protected Helper Methods + +### C++ Pattern +```cpp +protected: + void CalcRange(const A3DVECTOR3& vDir) + { + m_vYRange = _unit_y; + m_vZRange.Set(vDir.x, 0, vDir.z); + if (m_vZRange.Normalize() < .01f) m_vZRange = _unit_z; + m_vXRange = CrossProduct(m_vYRange, m_vZRange); + // ... + } + + A3DVECTOR3 GetRandOff() const + { + if (m_Shape == enumBox) + { + // ... + } + // ... + } +``` + +### C# Pattern +```csharp +protected void CalcRange(Vector3 vDir) +{ + m_vYRange = Vector3.up; + m_vZRange = new Vector3(vDir.x, 0, vDir.z); + if (Normalize(ref m_vZRange) < 0.01f) m_vZRange = Vector3.forward; + m_vXRange = Vector3.Cross(m_vYRange, m_vZRange); + // ... +} + +protected Vector3 GetRandOff() +{ + if (m_Shape == EmitShape.enumBox) + { + // ... + } + // ... +} +``` + +## State Machine Pattern + +### C++ Pattern +```cpp +enum +{ + enumWait, + enumFlying, + enumHit, + enumFinished +} m_enumState; + +void Tick(DWORD dwDeltaTime) +{ + if (m_enumState == enumFinished) return; + else if (m_enumState == enumHit) + { + // handle hit state + } + else if (m_enumState == enumWait) + { + if (m_dwCurSpan < m_dwDelayTime) return; + m_enumState = enumFlying; + } + else // enumFlying + { + if (m_pMoveMethod.TickMove(dwDeltaTime, ...)) + HitTarget(GetTargetCenter()); + } +} +``` + +### C# Pattern +```csharp +protected GfxSkillEventState m_enumState; + +public virtual void Tick(uint dwDeltaTime) +{ + if (m_enumState == GfxSkillEventState.enumFinished) return; + else if (m_enumState == GfxSkillEventState.enumHit) + { + // handle hit state + } + else if (m_enumState == GfxSkillEventState.enumWait) + { + if (m_dwCurSpan < m_dwDelayTime) return; + m_enumState = GfxSkillEventState.enumFlying; + } + else // enumFlying + { + if (m_pMoveMethod.TickMove(dwDeltaTime, ...)) + HitTarget(GetTargetCenter()); + } +} +``` + +## Const Correctness → Readonly + +### C++ Pattern +```cpp +const A3DVECTOR3& GetPos() const { return m_vPos; } +GfxMoveMode GetMode() const { return m_Mode; } +``` + +### C# Pattern +```csharp +public Vector3 GetPos() { return m_vPos; } // No const needed +public GfxMoveMode GetMode() { return m_Mode; } // No const needed +``` + +## Delegate Pattern (Virtual Methods) + +### C++ Pattern +```cpp +class A3DSkillGfxEvent +{ + CGfxMoveBase* m_pMoveMethod; + +public: + GfxMoveMode GetMode() const { return m_pMoveMethod->GetMode(); } + void SetReverse(bool bReverse) const { m_pMoveMethod->SetReverse(bReverse); } + void SetParam(const GFX_SKILL_PARAM* param) { m_pMoveMethod->SetParam(param); } +}; +``` + +### C# Pattern +```csharp +public class A3DSkillGfxEvent +{ + protected CGfxMoveBase m_pMoveMethod; + + public GfxMoveMode GetMode() { return m_pMoveMethod.GetMode(); } + public void SetReverse(bool bReverse) { m_pMoveMethod.SetReverse(bReverse); } + public void SetParam(GFX_SKILL_PARAM param) { m_pMoveMethod.SetParam(param); } +} +``` + +## Random Number Generation + +### C++ Pattern +```cpp +float _SymmetricRandom() { return (rand() / (float)RAND_MAX) * 2.0f - 1.0f; } +float _UnitRandom() { return rand() / (float)RAND_MAX; } + +A3DVECTOR3 GetRandOff() const +{ + if (m_Shape == enumBox) + { + A3DVECTOR3 xOff, yOff, zOff; + xOff = _SymmetricRandom() * m_vXRange; + yOff = _SymmetricRandom() * m_vYRange; + zOff = _SymmetricRandom() * m_vZRange; + return xOff + yOff + zOff; + } +} +``` + +### C# Pattern +```csharp +private static float SymRandom() +{ + return UnityEngine.Random.value * 2f - 1f; +} + +private static float UnitRandom() +{ + return UnityEngine.Random.value; +} + +protected Vector3 GetRandOff() +{ + if (m_Shape == EmitShape.enumBox) + { + Vector3 xOff = SymRandom() * m_vXRange; + Vector3 yOff = SymRandom() * m_vYRange; + Vector3 zOff = SymRandom() * m_vZRange; + return xOff + yOff + zOff; + } +} +``` + +## Conditional Compilation → #if UNITY_EDITOR + +### C++ Pattern +```cpp +#ifdef _DEBUG + assert(false); +#endif +``` + +### C# Pattern +```csharp +#if UNITY_EDITOR + UnityEngine.Debug.Assert(false); +#endif +``` + +## Memory Management Patterns + +### C++ Pattern +```cpp +void ReleaseFlyGfx() +{ + if (m_pFlyGfx) + { + if (m_bFadeOut) + AfxGetGFXExMan()->QueueFadeOutGfx(m_pFlyGfx, 1000); + else + { + m_pFlyGfx->Release(); + delete m_pFlyGfx; + } + m_pFlyGfx = NULL; + } +} +``` + +### C# Pattern +```csharp +protected void ReleaseFlyGfx() +{ + if (m_pFlyGfx != null) + { + if (m_bFadeOut) + AfxGetGFXExMan().QueueFadeOutGfx(m_pFlyGfx, 1000); + else + { + m_pFlyGfx.Release(); + // C#: Automatic garbage collection, no delete needed + } + m_pFlyGfx = null; + } +} +``` + +## Common Conversion Checklist + +When converting a class: +- [ ] Abstract base → `abstract class` +- [ ] Pure virtual methods → `abstract` methods +- [ ] Virtual with default → `virtual` methods +- [ ] Non-virtual → regular methods +- [ ] Static factory → `static` method +- [ ] Const methods → regular methods (no const) +- [ ] Pointers → references (remove `*`) +- [ ] References → value types or `ref`/`out` +- [ ] `DWORD` → `uint` +- [ ] `A3DVECTOR3` → `Vector3` +- [ ] Union access → `param.value.fVal` +- [ ] Hungarian notation → preserved diff --git a/agent-skills/04-architecture-understanding.md b/agent-skills/04-architecture-understanding.md new file mode 100644 index 0000000000..6b4edaadfd --- /dev/null +++ b/agent-skills/04-architecture-understanding.md @@ -0,0 +1,250 @@ +# Architecture Understanding + +## System Overview + +The Skill GFX system manages visual effects when skills are cast in Perfect World. It handles projectile flight, hit effects, and various movement patterns. + +## High-Level Flow + +``` +Player Casts Skill + ↓ +CECAttacksMan.AddSkillAttack() + ↓ +CECAttackEvent.Tick() - Timing system + ↓ +CECAttackEvent.DoFire() - Triggers when skill fires + ↓ +A3DSkillGfxComposerMan.Play() - Finds composer by skill ID + ↓ +A3DSkillGfxComposer.Play() - Iterates targets, calls AddOneTarget + ↓ +A3DSkillGfxMan.AddSkillGfxEvent() - Creates GFX event + ↓ +A3DSkillGfxEvent.Tick() - State machine (Wait → Flying → Hit → Finished) + ↓ +CGfxMoveBase.TickMove() - Updates projectile position + ↓ +Unity VFX System - Renders effects +``` + +## Core Components + +### 1. Attack Event System +- **CECAttacksMan**: Manages all attack events +- **CECAttackEvent**: Individual attack event, handles timing +- **Purpose**: Determines WHEN to trigger GFX (based on skill timing) + +### 2. GFX Composer System +- **A3DSkillGfxComposerMan**: Manages composers, loads from SkillStub +- **A3DSkillGfxComposer**: Contains GFX paths and parameters for a skill +- **Purpose**: Maps skill ID → GFX configuration + +### 3. GFX Event System +- **A3DSkillGfxMan**: Base manager (abstract) +- **A3DSkillGfxEvent**: Base event class (abstract) +- **CECSkillGfxMan**: Unity-specific manager (inherits A3DSkillGfxMan) +- **CECSkillGfxEvent**: Unity-specific event (inherits A3DSkillGfxEvent) +- **Purpose**: Manages active GFX instances and state machine + +### 4. Movement System +- **CGfxMoveBase**: Abstract base class for movement patterns +- **CGfxLinearMove**: Straight-line projectile +- **CGfxOnTargetMove**: Instant hit (no flight) +- **CGfxParabolicMove**: Arc trajectory +- **CGfxMissileMove**: Homing missile +- **Purpose**: Calculates projectile position over time + +## Class Hierarchy + +### Movement Classes +``` +CGfxMoveBase (abstract) +├── CGfxLinearMove +├── CGfxOnTargetMove +├── CGfxParabolicMove +├── CGfxMissileMove +├── CGfxMeteoricMove +├── CGfxHelixMove +├── CGfxCurvedMove +├── CGfxAccMove +├── CGfxLinkMove +└── CGfxRandMove +``` + +### Event Classes +``` +A3DSkillGfxMan (abstract) +└── CECSkillGfxMan (Unity implementation) + +A3DSkillGfxEvent (abstract) +└── CECSkillGfxEvent (Unity implementation) +``` + +### Composer Classes +``` +A3DSkillGfxComposerMan +└── A3DSkillGfxComposer +``` + +## State Machine + +### A3DSkillGfxEvent States + +1. **enumWait**: Waiting for delay time + - Increments `m_dwCurSpan` + - Transitions to `enumFlying` when delay expires + +2. **enumFlying**: Projectile in flight + - Calls `m_pMoveMethod.TickMove()` each frame + - Updates GFX position + - Transitions to `enumHit` when `TickMove()` returns `true` OR timeout + +3. **enumHit**: Hit effect playing + - Plays hit GFX at target position + - Transitions to `enumFinished` when hit GFX completes + +4. **enumFinished**: Event complete + - Event can be recycled/pooled + +## Data Flow + +### Skill Configuration +``` +SkillStub (C# data structure) + ↓ +A3DSkillGfxComposer.Load(SkillStub) + ↓ +Populates: +- Fly GFX path +- Hit GFX path +- Movement mode +- Fly time +- Scales +- Parameters +``` + +### Event Creation +``` +A3DSkillGfxComposer.Play() + ↓ +For each target: + A3DSkillGfxMan.AddSkillGfxEvent( + composer, + hostID, + targetID, + flyGfxPath, + hitGfxPath, + flyTimeSpan, + moveMode, + ... + ) + ↓ +Creates A3DSkillGfxEvent: + - Sets composer + - Creates movement method (CGfxMoveBase.CreateMoveMethod) + - Sets parameters + - Initializes state to enumWait +``` + +### Runtime Update +``` +A3DSkillGfxMan.Tick() (called each frame) + ↓ +For each active event: + A3DSkillGfxEvent.Tick(deltaTime) + ↓ + State machine: + - enumWait: Check delay + - enumFlying: + - m_pMoveMethod.TickMove() + - Update GFX position + - Check timeout + - enumHit: Check hit GFX completion + - enumFinished: Mark for cleanup +``` + +## Key Relationships + +### A3DSkillGfxEvent → CGfxMoveBase +- **Relationship**: Composition +- **Field**: `m_pMoveMethod : CGfxMoveBase` +- **Usage**: Delegates movement calculation to movement method +- **Creation**: `CGfxMoveBase.CreateMoveMethod(mode)` + +### A3DSkillGfxEvent → A3DSkillGfxComposer +- **Relationship**: Reference +- **Field**: `m_pComposer : A3DSkillGfxComposer` +- **Usage**: Accesses GFX paths and parameters +- **Set**: `SetComposer(composer)` + +### A3DSkillGfxMan → A3DSkillGfxEvent +- **Relationship**: Container/Manager +- **Storage**: List/array of active events +- **Management**: Creates, updates, destroys events +- **Pooling**: Reuses finished events + +### CECSkillGfxEvent → Unity VFX +- **Relationship**: Unity integration +- **Fields**: `m_flyGfxInstance : GameObject`, `m_hitGfxInstance : GameObject` +- **Usage**: Instantiates Unity particle systems/prefabs +- **Loading**: Uses Addressables for async loading + +## Memory Management + +### C++ Pattern +- Manual `new`/`delete` +- Object pooling for events +- GFX caching system + +### C# Pattern +- Automatic garbage collection +- Object pooling still recommended for performance +- Unity Addressables for asset loading + +## Threading Model + +- **C++**: Single-threaded game loop +- **C#**: Unity main thread (MonoBehaviour.Update) +- **Async**: Addressables loading can be async, but GFX updates on main thread + +## Performance Considerations + +1. **Object Pooling**: Reuse `A3DSkillGfxEvent` instances +2. **GFX Caching**: Cache loaded GFX prefabs +3. **Batch Updates**: Update all events in single loop +4. **Early Exit**: Skip finished events quickly +5. **LOD System**: Use `m_bGfxUseLod` for distance-based quality + +## Integration Points + +### With Attack System +- **Trigger**: `CECAttackEvent.DoFire()` calls `A3DSkillGfxComposerMan.Play()` +- **Timing**: GFX starts when skill "fires" (not when cast begins) + +### With Character System +- **Position**: `GetTargetCenter()` gets character position +- **Tracking**: `m_bTraceTarget` enables position updates during flight +- **Hooks**: Future feature for bone attachment + +### With Unity VFX +- **Loading**: Addressables async loading +- **Instantiation**: Unity GameObject/Prefab system +- **Rendering**: Unity Particle System or custom VFX + +## Extension Points + +### New Movement Modes +1. Create new class inheriting `CGfxMoveBase` +2. Implement `StartMove()` and `TickMove()` +3. Add case to `CreateMoveMethod()` + +### New Event Types +1. Create new class inheriting `A3DSkillGfxEvent` or `CECSkillGfxEvent` +2. Override `GetTargetCenter()` and other virtual methods +3. Implement Unity-specific rendering + +### Custom GFX Systems +1. Override `LoadFlyGfx()` / `LoadHitGfx()` +2. Implement custom VFX loading/instantiation +3. Use Unity Addressables or custom asset system diff --git a/agent-skills/05-skill-gfx-deep-dive.md b/agent-skills/05-skill-gfx-deep-dive.md new file mode 100644 index 0000000000..31cd5bcfd3 --- /dev/null +++ b/agent-skills/05-skill-gfx-deep-dive.md @@ -0,0 +1,333 @@ +# Skill GFX System Deep Dive + +## Movement System Architecture + +### CGfxMoveBase Design + +**Purpose**: Abstract base for all projectile movement patterns. + +**Key Responsibilities**: +- Calculate position over time +- Handle area emission (random offsets) +- Support clustering (multiple projectiles) +- Provide position/direction queries + +**Core Methods**: +```csharp +public abstract void StartMove(Vector3 vHost, Vector3 vTarget); +public abstract bool TickMove(uint dwDeltaTime, Vector3 vHostPos, Vector3 vTargetPos); +``` + +**Return Value Semantics**: +- `StartMove()`: Initialize movement, set initial position/direction +- `TickMove()`: + - Returns `true` = target reached/hit + - Returns `false` = still in flight + - Updates `m_vPos` and `m_vMoveDir` + +### Movement Mode Details + +#### CGfxLinearMove +- **Pattern**: Straight line from host to target +- **Speed**: `_fly_speed = 20.0f / 1000.0f` (units per millisecond) +- **Behavior**: + - If max fly time allows, uses constant speed + - Otherwise, calculates speed to reach target in max time + - Updates direction each frame to track moving target + +#### CGfxOnTargetMove +- **Pattern**: Instant hit (no flight) +- **Behavior**: + - `StartMove()`: Sets position to target immediately + - `TickMove()`: Returns `false` (stays at target) + - Hit triggered by fly time timeout, NOT by `TickMove()` return + - Supports cluster offset (random radius around target) + +#### CGfxParabolicMove +- **Pattern**: Arc trajectory with gravity +- **Physics**: + - Horizontal velocity constant + - Vertical velocity affected by gravity + - Calculates initial vertical velocity to reach target + +#### CGfxMissileMove +- **Pattern**: Homing missile +- **Behavior**: + - Accelerates toward target + - Rotates to face target + - Constant acceleration until hit + +#### CGfxMeteoricMove +- **Pattern**: Falls from sky +- **Behavior**: + - Starts above target + - Falls straight down + - Configurable radius + +#### CGfxHelixMove +- **Pattern**: Spiral path +- **Behavior**: + - Spiral around center axis + - Radius can shrink over time + - Configurable radius parameter + +#### CGfxCurvedMove +- **Pattern**: Bezier-like curve +- **Behavior**: + - Curves from host to target + - Can curve left or right + - Configurable lateral speed + +#### CGfxAccMove +- **Pattern**: Accelerated flight +- **Behavior**: + - Constant acceleration + - Configurable acceleration value + +#### CGfxLinkMove +- **Pattern**: Lightning chain +- **Behavior**: + - Links between host and target + - Updates GFX parameters dynamically + - No movement (stays linked) + +#### CGfxRandMove +- **Pattern**: Random walk +- **Behavior**: + - Random direction changes + - Configurable step size and speed + - Uses control points for smoothness + +## Area Emission System + +### Purpose +Emit multiple projectiles in a random area around the start position. + +### Configuration +- **m_bArea**: Enable area emission +- **m_Shape**: Emission shape (Box, Sphere, Cylinder) +- **m_vSize**: Size of emission area + +### Implementation + +**CalcRange()**: Calculates X/Y/Z range vectors based on movement direction. +```csharp +protected void CalcRange(Vector3 vDir) +{ + m_vYRange = Vector3.up; + m_vZRange = new Vector3(vDir.x, 0, vDir.z); // Project to horizontal + if (Normalize(ref m_vZRange) < 0.01f) m_vZRange = Vector3.forward; + m_vXRange = Vector3.Cross(m_vYRange, m_vZRange); // Perpendicular + // Scale by m_vSize +} +``` + +**GetRandOff()**: Generates random offset based on shape. +- **Box**: Uniform distribution in box +- **Sphere**: Uniform distribution in sphere (rejection sampling) +- **Cylinder**: Uniform in horizontal circle, uniform in Y + +### Usage +```csharp +if (m_bArea) +{ + CalcRange((vTarget - vHost).normalized); + m_vPos = vHost + GetRandOff(); +} +``` + +## Clustering System + +### Purpose +Emit multiple projectiles with time intervals (e.g., machine gun effect). + +### Configuration +- **m_bOneOfCluser**: This projectile is part of a cluster +- **Cluster params**: Count and interval (handled by manager) + +### OnTarget Clustering +For `CGfxOnTargetMove`, clustering adds random offset: +```csharp +if (m_bOneOfCluser) +{ + float fRandAng = Random.value * 2π; + float fRadius = Random.value * m_fRadius; + m_vOffset = new Vector3(cos(ang) * radius, 0, sin(ang) * radius); + m_vPos += m_vOffset; +} +``` + +## State Machine Details + +### enumWait State +- **Entry**: Event created +- **Behavior**: + - Increment `m_dwCurSpan` + - Wait for `m_dwDelayTime` +- **Exit**: When `m_dwCurSpan >= m_dwDelayTime` +- **Transition**: → `enumFlying` + +### enumFlying State +- **Entry**: Delay expired +- **Behavior**: + - Call `m_pMoveMethod.SetMaxFlyTime(m_dwFlyTimeSpan)` + - Call `m_pMoveMethod.StartMove(m_vHostPos, m_vTargetPos)` + - Each frame: + - Update host/target positions if `m_bTraceTarget` + - Call `m_pMoveMethod.TickMove(dwDeltaTime, ...)` + - Update GFX position + - Check timeout: `m_dwCurSpan > m_dwFlyTimeSpan` +- **Exit Conditions**: + - `TickMove()` returns `true` (target hit) + - `m_dwCurSpan > m_dwFlyTimeSpan` (timeout) +- **Transition**: → `enumHit` + +### enumHit State +- **Entry**: Target hit or timeout +- **Behavior**: + - Release fly GFX (fade out or immediate) + - Spawn hit GFX at target position + - Update hit GFX position if `m_bTraceTarget` + - Check if hit GFX is infinite (`m_bHitGfxInfinite`) +- **Exit Conditions**: + - Hit GFX completes (not infinite) + - Hit GFX timeout (5 seconds for infinite) + - Target disappears +- **Transition**: → `enumFinished` + +### enumFinished State +- **Entry**: Hit effect complete +- **Behavior**: + - Release all GFX + - Mark event for cleanup/pooling +- **Exit**: Event removed from active list + +## Position Tracking + +### Host Position +- **Source**: Character position from `m_nHostID` +- **Update**: Each frame if `m_bHostExist` +- **Usage**: + - Initial position for `StartMove()` + - Dynamic updates for `TickMove()` (if target moves) + +### Target Position +- **Source**: Character position from `m_nTargetID` +- **Update**: Each frame if `m_bTargetExist` and `m_bTraceTarget` +- **Usage**: + - Target position for `StartMove()` + - Dynamic updates for `TickMove()` (tracking) + +### GetTargetCenter() +- **Abstract method**: Must be implemented by derived class +- **Purpose**: Get current target center position +- **Implementation**: + - `CECSkillGfxEvent`: Gets character position from ID + - Can use hooks/bones in future + +## GFX Loading & Instantiation + +### C++ Pattern +```cpp +A3DGFXEx* LoadFlyGfx(A3DDevice* pDev, const char* szPath); +void SetFlyGfx(A3DGFXEx* pFlyGfx); +``` + +### C# Pattern (Unity) +```csharp +// Async loading with Addressables +async Task LoadFlyGfxAsync(string path) +{ + var handle = Addressables.LoadAssetAsync(path); + await handle.Task; + return handle.Result; +} + +// Instantiation +GameObject instance = Instantiate(prefab, position, rotation); +``` + +### Lifecycle +1. **Load**: Async load prefab (Addressables) +2. **Instantiate**: Create instance at position +3. **Update**: Update position each frame +4. **Release**: Destroy instance, release prefab + +## Parameter System + +### GFX_SKILL_PARAM Structure +```csharp +public struct GFX_SKILL_PARAM +{ + public ValueUnion value; // Union: bVal, nVal, or fVal + public bool m_bArea; + public EmitShape m_Shape; + public A3DVECTOR3 m_vSize; +} +``` + +### Usage by Movement Mode +- **CGfxLinearMove**: Uses `m_bArea`, `m_Shape`, `m_vSize` (default) +- **CGfxOnTargetMove**: Also uses `value.fVal` for radius +- **CGfxMeteoricMove**: Uses `value.fVal` for fall radius +- **CGfxHelixMove**: Uses `value.fVal` for spiral radius +- **CGfxCurvedMove**: Uses `value.bVal` for curve direction +- **CGfxAccMove**: Uses `value.fVal` for acceleration +- **CGfxRandMove**: Uses `value.fVal` for speed + +### Setting Parameters +```csharp +GFX_SKILL_PARAM param = new GFX_SKILL_PARAM(); +param.value.fVal = 5.0f; // Radius for OnTarget +param.m_bArea = true; +param.m_Shape = EmitShape.enumSphere; +param.m_vSize = new A3DVECTOR3(2, 2, 2); + +m_pMoveMethod.SetParam(param); +``` + +## Performance Optimizations + +### Object Pooling +- **Purpose**: Reuse event instances +- **Implementation**: `m_FreeLst` in `CECSkillGfxMan` +- **Usage**: + - Get from pool when creating event + - Return to pool when finished + +### GFX Caching +- **Purpose**: Avoid reloading same prefabs +- **Implementation**: Cache loaded Addressable handles +- **Key**: Use path as key + +### Batch Updates +- **Purpose**: Update all events efficiently +- **Implementation**: Single loop through active events +- **Optimization**: Early exit for finished events + +### LOD System +- **Purpose**: Reduce quality for distant effects +- **Field**: `m_bGfxUseLod` +- **Usage**: Disable expensive effects when far away + +## Debugging Tips + +### Common Issues +1. **Projectile doesn't move**: Check `StartMove()` called, `m_fSpeed` set +2. **Projectile goes wrong direction**: Check `m_vMoveDir` calculation +3. **Hit doesn't trigger**: Check `TickMove()` return value, timeout +4. **GFX doesn't appear**: Check loading, instantiation, position +5. **Performance issues**: Check pooling, caching, LOD + +### Debug Fields +- `m_vPos`: Current position +- `m_vMoveDir`: Current direction +- `m_enumState`: Current state +- `m_dwCurSpan`: Current time span +- `m_dwFlyTimeSpan`: Max fly time + +### Visualization +- Draw gizmos for projectile position +- Draw line from host to target +- Show state in inspector +- Log state transitions diff --git a/agent-skills/README.md b/agent-skills/README.md new file mode 100644 index 0000000000..f893d1296f --- /dev/null +++ b/agent-skills/README.md @@ -0,0 +1,45 @@ +# Agent Skills for Perfect World Unity C++ to C# Conversion + +This folder contains comprehensive skills and guidelines for AI agents working on the Perfect World Unity C++ to C# conversion project. + +## Skills Index + +### Core Conversion Skills +1. **[Naming Conventions](./01-naming-conventions.md)** - Maintain C++ naming exactly (CGfxMoveBase, m_bOneOfCluser, etc.) +2. **[Type Mappings](./02-type-mappings.md)** - C++ to C# type conversions (DWORD→uint, A3DVECTOR3→Vector3, etc.) +3. **[Code Patterns](./03-code-patterns.md)** - Common C++ patterns and their C# equivalents + +### Architecture & Understanding +4. **[Architecture Understanding](./04-architecture-understanding.md)** - System structure, flow, and relationships +5. **[Skill GFX System Deep Dive](./05-skill-gfx-deep-dive.md)** - Detailed GFX system architecture + +### Quality & Validation +6. **[Testing & Validation](./06-testing-validation.md)** - How to verify conversions are correct +7. **[Common Pitfalls](./07-common-pitfalls.md)** - Mistakes to avoid during conversion +8. **[Best Practices](./08-best-practices.md)** - Recommended approaches and patterns + +### Reference +9. **[C++ Source Reference](./09-cpp-source-reference.md)** - Key C++ files and their locations +10. **[Unity C# Reference](./10-unity-csharp-reference.md)** - Key Unity C# files and their locations + +## Quick Start + +When starting a conversion task: +1. Read [Naming Conventions](./01-naming-conventions.md) - **CRITICAL** - Never change C++ names unnecessarily +2. Check [Type Mappings](./02-type-mappings.md) for type conversions +3. Review [Common Pitfalls](./07-common-pitfalls.md) before coding +4. Use [Testing & Validation](./06-testing-validation.md) to verify your work + +## Priority Rules + +1. **Preserve C++ naming** - If C++ has `CGfxMoveBase`, use `CGfxMoveBase` (not `IGfxMovement`) +2. **Match C++ behavior exactly** - Don't "improve" logic unless explicitly requested +3. **Maintain field names** - Even typos like `m_bOneOfCluser` must be preserved +4. **Verify against C++ source** - Always check the actual C++ implementation + +## Project Context + +- **Source:** `perfect-world-source/` - Original C++ codebase +- **Target:** `perfect-world-unity/Assets/` - Unity C# project +- **Main Plan:** `SKILL_GFX_CONVERSION_PLAN.md` - Overall conversion strategy +- **Quick Guide:** `SKILL_GFX_QUICK_START.md` - Fast implementation guide