# C++ Skill GFX → SFX Play Flow (Perfect World client) This document describes the **C++ client-side processing flow** that results in **sound effects (SFX)** playing when a **skill’s GFX** is spawned/started. The key idea is: - **Skills don’t usually “play sound” directly.** - Instead, the skill system triggers **GFX** playback (fly/hit effects). - The **`.gfx` resource** can contain an **embedded Sound element** (`ID_ELE_TYPE_SOUND`). - When the GFX starts ticking, the **sound element** loads and plays **SFX\\** (or an **audio event path** in newer audio mode). --- ## High-level flow (skill → gfx → sound) ```mermaid flowchart TD A[Skill attack fired] --> B[Skill GFX composer plays] B --> C[A3DSkillGfxMan creates skill gfx events] C --> D[Event loads fly/hit GFX resources] D --> E[GFX starts: Start(true)] E --> F[GFX elements tick each frame] F --> G{Has Sound element?} G -->|Yes| H[A3DGFXSound TickAnimation -> TickSound] H --> I[GFXSOUNDIMP / GFXSOUNDIMP22 loads & plays] G -->|No| J[No SFX from this GFX] ``` --- ## Step-by-step: where sound comes from ### 1) Skill triggers the Skill GFX system (not sound) When a skill attack is ready to “fire”, the attacks system calls into the **Skill GFX composer manager** (single-section or multi-section). You can see this flow summarized in your existing analysis doc: - `Documentation/HOOK_SYSTEM_FLOW_C++_ANALYSIS.md` That doc traces: - `CECAttackEvent::DoFire()` → `A3DSkillGfxComposerMan::Play()` → `A3DSkillGfxComposer::Play()` → `A3DSkillGfxMan::AddSkillGfxEvent()` → `AddOneSkillGfxEvent()` → `A3DSkillGfxEvent::Tick()` At this stage, the system is concerned with **spawning/starting GFX**, not explicitly choosing SFX. --- ### 2) Skill GFX events load GFX resources (fly/hit) When a skill GFX event is created, it loads (or prepares) the referenced GFX resources (fly/hit). Once the event transitions into an active state, it calls **`Start(true)`** on the GFX instance. Important: in this engine, “GFX” is a container of **elements** (particles, decals, models, **sound**, etc.). Sound is *just another element type*. --- ### 3) `.gfx` files can contain a Sound element When a `.gfx` resource is loaded, its elements are created by element type. The sound element type is `ID_ELE_TYPE_SOUND`, which constructs an `A3DGFXSound` element. **Source:** `CElement/GfxCommon/A3DGFXElement.cpp` ```648:655:e:\Projects\perfect-world-source\perfect-world-source\CElement\GfxCommon\A3DGFXElement.cpp case ID_ELE_TYPE_ECMODEL: pEle = new A3DGFXECModel(pGfx); break; case ID_ELE_TYPE_SOUND: pEle = new A3DGFXSound(pGfx); break; case ID_ELE_TYPE_GFX_CONTAINER: pEle = new A3DGFXContainer(pGfx); break; ``` So the “mapping” is usually: - **GFX path** → contains **Sound element** → references **SFX file(s)** or **Audio event path** --- ### 4) `A3DGFXSound` drives playback during animation ticking `A3DGFXSound` is a `A3DGFXElement` that ticks sound as part of its animation tick. **Source:** `CElement/GfxCommon/A3DGFXSound.cpp` ```89:106:e:\Projects\perfect-world-source\perfect-world-source\CElement\GfxCommon\A3DGFXSound.cpp bool A3DGFXSound::TickAnimation(DWORD dwTickTime) { if (!A3DGFXElement::TickAnimation(dwTickTime)) { if (IsFinished() && IsSoundValid()) StopSound(); return false; } TickSound(dwTickTime); return true; } ``` So once the parent GFX is started and ticking, sound playback “comes along for free” if a sound element exists in that GFX. --- ## How the sound element selects what to play There are two implementations selected at runtime: - **`GFXSOUNDIMP`**: classic “file-based SFX” that loads `Sfx\\` - **`GFXSOUNDIMP22`**: “audio event” mode (newer sound system) that plays an **event path** `A3DGFXSound::SetupActiveImp()` chooses which one is active: **Source:** `CElement/GfxCommon/A3DGFXSound.cpp` ```8:14:e:\Projects\perfect-world-source\perfect-world-source\CElement\GfxCommon\A3DGFXSound.cpp void A3DGFXSound::SetupActiveImp() { if (GFX_IsUseAudioEvent()) m_pActiveImp = &m_SoundImp22; else m_pActiveImp = &m_SoundImp; } ``` --- ### A) Classic mode: SFX files under `Sfx\\` #### A.1) The `.gfx` sound element stores 1+ file paths The sound element loader reads either: - a single `Path: ` for old versions, or - `PathNum: N` + repeated `Path: ` lines for newer versions. **Source:** `CElement/GfxCommon/A3DGFXSoundImp.cpp` ```108:161:e:\Projects\perfect-world-source\perfect-world-source\CElement\GfxCommon\A3DGFXSoundImp.cpp bool GFXSOUNDIMP::Load(AFile* pFile, DWORD dwVersion) { ... // dwVersion < 88: one path // dwVersion >= 88: multiple paths (PathNum + Path lines) ... return m_ParamInfo.LoadSoundParamInfo(pFile); } ``` #### A.2) When ticking, it loads & plays `Sfx\\` When conditions are met (distance checks, etc.), `GFXSOUNDIMP` loads either: - looped sound: `AfxLoadLoopSound("Sfx\\" + file)` - non-looped sound: `AfxLoadNonLoopSound("Sfx\\" + file, priority)` **Source:** `CElement/GfxCommon/A3DGFXSoundImp.cpp` ```331:355:e:\Projects\perfect-world-source\perfect-world-source\CElement\GfxCommon\A3DGFXSoundImp.cpp static const AString strPath = "Sfx\\"; if (pSound->IsInfinite()) { AM3DSoundBuffer* pSfx = AfxLoadLoopSound(strPath + strFile); ... m_pBuf = pSfx; } else { AM3DSoundBuffer* pSfx = AfxLoadNonLoopSound(strPath + strFile, pSound->GetParentGfx()->GetSfxPriority()); ... m_pBuf = pSfx; } ``` This is the core “GFX → SFX” connection: the GFX’s sound element points at SFX asset names. --- ### B) Audio-event mode (newer): plays an event path If audio-event mode is enabled, `GFXSOUNDIMP22` loads an “event path” and uses an audio engine event instance. **Source:** `CElement/GfxCommon/A3DGFXSoundImp.cpp` - It loads `Path: ` plus optional custom distances (for `dwVersion >= 96`). - It creates an event instance via `CreateAudioEventInstance(m_strEventPath)`. - During tick, it calls `Start()` and updates 3D attributes. This means some `.gfx` files won’t reference `Sfx\\*.wav`-style filenames; they reference an engine-specific event path instead. --- ## What this means for “SFX corresponding to each GFX” There often isn’t a single global “table” mapping. Instead, the mapping is usually **data-driven inside the `.gfx` resource**: - **If the `.gfx` has an `ID_ELE_TYPE_SOUND` element**, it will play the sound(s) configured in that element. - If it doesn’t, spawning that GFX will not automatically produce a sound. --- ## How to trace a specific skill’s sound (practical checklist) 1. Find the skill’s **fly/hit GFX path** (from the composer/skill gfx config that the skill uses). 2. Locate the corresponding `.gfx` resource file. 3. Inspect the `.gfx` element list for a **Sound element** and extract: - **file path list** (classic mode) → SFX asset name(s) under `Sfx\\` - or **audio event path** (audio-event mode) 4. Confirm behavior in code paths above: - element creation: `A3DGFXElement::CreateEmptyElement()` creates `A3DGFXSound` - ticking: `A3DGFXSound::TickAnimation()` calls `TickSound()` - playback: `GFXSOUNDIMP::ChangeSoundFile()` loads `Sfx\\...` --- ## Key files (C++ client) - **Skill → GFX event flow** - `CElement/CElementClient/EC_ManAttacks.cpp` (attack firing) - `CElement/CCommon/Gfx/A3DSkillGfxEvent2.cpp` (skill gfx event state machine) - `CElement/CElementClient/EC_ManSkillGfx.cpp` (hook positions + per-frame updates) - `CElement/CCommon/Gfx/A3DSkillGfxComposer2.*` (composer data, Play/AddOneTarget) - **GFX sound element** - `CElement/GfxCommon/A3DGFXElement.cpp` (creates `A3DGFXSound` for `ID_ELE_TYPE_SOUND`) - `CElement/GfxCommon/A3DGFXSound.cpp` (ticks sound as part of element animation) - `CElement/GfxCommon/A3DGFXSoundImp.cpp` / `.h` (file-based SFX + audio-event implementation) --- ## Notes / caveats - **Distance & 2D/3D rules**: classic mode checks listener distance and may stop/release audio if too far (`CheckDist()`). - **Multiple sound paths**: `PathNum` allows random choice among multiple SFX files. - **Loop vs non-loop**: loop selection is tied to whether the sound element is considered “infinite” and to its parameters.