103 lines
4.9 KiB
Markdown
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 |
|