Files
test/Documentation/CONVERTER_GFX_TO_SFX.md
T
2026-04-10 16:35:07 +07:00

103 lines
4.9 KiB
Markdown

# Converter: GFX paths → SFX paths in skill stubs
This document describes how **skill `.cs` files** get **`m_szFlySfxPath` / `m_szHitGrndSfxPath` / `m_szHitSfxPath`** from the **same client `.gfx` assets** that supply **`m_sz*GfxPath`**, and how to extend or re-run the pipeline.
## Goal
For each non-empty GFX path emitted from `.sgc` (`m_szFlyGfxPath`, `m_szHitGrndGfxPath`, `m_szHitGfxPath`), find the matching **`.gfx` file on disk** under the **GFX root** (`--gfx`, e.g. `C:\Users\BrewPC\Downloads\gfx`) and read the **classic file-based sound** path from the **Sound element** inside that `.gfx`. Emit the corresponding **`m_sz*SfxPath`** field on `SkillStub`.
Example (skill 1):
- `m_szHitGfxPath = "策划联入/人物技能/击中/虎击击中.gfx"`
- On disk: `…\gfx\策划联入\人物技能\击中\虎击击中.gfx`
- Inside `.gfx`: Sound element (`GFXELEMENTID: 170`) → `Path:` / `PathNum:` + `Path:` lines (see C++ `GFXSOUNDIMP::Load`)
- Result: `m_szHitSfxPath = "Skill\\01Fighter\\虎击_C"` (exact string depends on asset data)
## Reference: client behavior
See [C++_SkillGfx_Sfx_Play_Flow.md](C++_SkillGfx_Sfx_Play_Flow.md): the client plays **`Sfx\` + filename** from the Sound element in the GFX resource.
## Unity data model
In [skill.cs](../Assets/PerfectWorld/Scripts/Skills/skill.cs), `SkillStub` defines:
| Field | Source |
|--------|--------|
| `m_szFlyGfxPath` | `.sgc` line 1 `Path` |
| `m_szHitGrndGfxPath` | `.sgc` line 2 `Path` |
| `m_szHitGfxPath` | `.sgc` 3rd `Path` (after version/scales per format) |
| `m_szFlySfxPath` | First classic SFX path parsed from **fly** `.gfx` |
| `m_szHitGrndSfxPath` | Same for **hit-ground** `.gfx` |
| `m_szHitSfxPath` | Same for **hit** `.gfx` |
If a GFX path is empty, or the `.gfx` file is missing, or there is no parseable Sound element, the matching SFX field is **`string.Empty`**.
## Tool: `convert_skills_fixed.py`
### Inputs
- `--cpp``CElementSkill` (skill `*.h`)
- `--cs` — Unity `Scripts/Skills` (or subfolder layout you use)
- `--gfx`**GFX root** (folder that contains `sgc\` and the same tree as in-game paths, e.g. `策划联入\...`)
### Resolution rule (GFX file on disk)
Game path uses **`/`**. On Windows, join under `gfx_root`:
```text
gfx_root + path.replace("/", os.sep)
```
Example: `策划联入/人物技能/击中/虎击击中.gfx``gfx_root\策划联入\人物技能\击中\虎击击中.gfx`
### Text `.gfx` detection
Text format starts with a line matching **`Version: <int>`** (Angelica `A3DGFXEx::Load`). If the file does not match (likely **binary** `.gfx`), SFX extraction is **skipped** (empty string).
### Parsing Sound element (classic mode)
1. Split file into blocks after each line `GFXELEMENTID: <id>`.
2. For blocks with **id = 170** (`ID_ELE_TYPE_SOUND` in client headers):
- Take the substring **before** the first **`SoundVer:`** line (start of `GfxSoundParamInfo`).
- If **`PathNum: N`**: read the next **N** lines **`Path: …`**; use the **first** path as the stub value (runtime may randomize among N).
- Else: use the **last** **`Path: …`** line in that prefix (legacy single path).
3. Normalize:
- `"\\"``"/"` for logical path (same separator style as `m_sz*GfxPath`)
- Strip trailing **`.wav` / `.ogg` / `.mp3`** if present
4. Escape for C# with the same helper used for other strings in the generator.
### Limitations (v1)
| Topic | Behavior |
|--------|-----------|
| Binary `.gfx` | No SFX extracted |
| `GFXSOUNDIMP22` audio-event `Path:` after `SoundVer:` | Not used as classic SFX in v1 |
| Multiple `GFXELEMENTID: 170` | First non-empty classic path wins |
| No `--gfx` / missing file | All three `m_sz*SfxPath` = `string.Empty` |
| Skills **without** `.sgc` data | No `gfx_path_code` block; stub defaults apply |
## Regenerating stubs
From repo root (adjust paths):
```bash
python convert_skills_fixed.py --stubs "E:\Projects\perfect-world-source\perfect-world-source\CElement\CElementSkill\stubs1.cpp" --gfx "C:\Users\BrewPC\Downloads\gfx"
```
Or `--ids 1` for a single skill smoke test.
## Future work (checklist)
- [ ] **Binary `.gfx`**: port minimal binary reader for `GFXELEMENTID` + Sound chunk, or shell out to a small C++/CLI tool.
- [ ] **Audio-event mode**: map `GFXSOUNDIMP22` `Path:` (after `SoundVer:`) to a separate field or naming convention if Unity uses FMOD/Wwise events.
- [ ] **Multiple sounds**: emit multiple candidates or document “first only” vs random.
- [ ] **Runtime**: wire `m_sz*SfxPath` into actual playback (load under `Sfx\` or Addressables) where `m_sz*GfxPath` is consumed today.
## Files touched by this feature
| File | Role |
|------|------|
| [convert_skills_fixed.py](../../convert_skills_fixed.py) | `_resolve_gfx_path_on_disk`, text `.gfx` parse, `m_sz*SfxPath` emission |
| [skill.cs](../Assets/PerfectWorld/Scripts/Skills/skill.cs) | `SkillStub` fields `m_szFlySfxPath`, `m_szHitGrndSfxPath`, `m_szHitSfxPath` |
| Generated `skillN.cs` | Constructor assignments next to GFX paths |