feat(editor): add WVOX (Voiceover Audio) — 114th open format

Novel replacement for the implicit per-NPC voice dialog
system vanilla WoW encoded across CreatureTextSounds
(server-side aggro/death barks), npc_text (gossip audio
cross-references), and per-quest dialog blobs. Each
entry binds one NPC to one voice clip for one
triggering event with metadata covering audio path,
duration, volume, gender hint for randomized casts,
variant index for multiple lines per event, and a
transcript field for accessibility (TTS engines + chat-
bubble subtitles).

Nine eventKind values cover the full NPC dialog
surface: Greeting / Aggro / Death / QuestStart /
QuestProgress / QuestComplete / Goodbye / Special /
Phase. The Phase kind specifically supports boss-fight
percentage milestones (75%/50%/25% transitions) where
multiple Phase entries with distinct variantIndex
disambiguate the boss-encounter scripting.

Three preset emitters: makeQuestgiver (5-clip canonical
quest dialog flow), makeBoss (6-clip Lich King fight
with phase milestones at 75/50/25%, special mechanic
call at +5dB for raid audibility, death line),
makeVendor (4-clip vendor interaction).

Validator's most novel check is per-(npcId, eventKind,
variantIndex) triple uniqueness — two clips with all
three matching would be ambiguous when the trigger
handler picks one randomly. The vendor preset
originally bound both Buy and Sell to (Special, 0)
which the validator caught and flagged before commit;
fix uses variantIndex 0 for Buy and 1 for Sell so the
trigger handler can distinguish.

Validator also warns on durationMs=0 with non-empty
audioPath (subtitle sync impossible), volumeDb outside
[-20,+6] (clip risk), and empty transcript (TTS +
chat-bubble subtitle would be blank).

Format count 113 -> 114. CLI flag count 1220 -> 1225.
This commit is contained in:
Kelsi 2026-05-10 02:25:34 -07:00
parent a5a16cae52
commit 78555a79b0
10 changed files with 820 additions and 0 deletions

View file

@ -2349,6 +2349,16 @@ void printUsage(const char* argv0) {
std::printf(" Export binary .wspv to a human-editable JSON sidecar (defaults to <base>.wspv.json; emits conditionKind as both int AND name string; conditionValue stays as raw uint32 — its semantics depend on conditionKind so no general-purpose pretty-printing)\n");
std::printf(" --import-wspv-json <json-path> [out-base]\n");
std::printf(" Import a .wspv.json sidecar back into binary .wspv (conditionKind int OR \"stance\"/\"form\"/\"talent\"/\"race\"/\"equippedweapon\"/\"auraactive\")\n");
std::printf(" --gen-vox <wvox-base> [name]\n");
std::printf(" Emit .wvox 5 questgiver voice clips (Greeting / QuestStart / QuestProgress / QuestComplete / Goodbye)\n");
std::printf(" --gen-vox-boss <wvox-base> [name]\n");
std::printf(" Emit .wvox 6 boss voice clips with phase milestones (Aggro / 75%% Phase / 50%% Phase / 25%% Phase / Special Mechanic / Death) — Lich King example\n");
std::printf(" --gen-vox-vendor <wvox-base> [name]\n");
std::printf(" Emit .wvox 4 vendor voice clips (Greeting / Buy / Sell / Goodbye)\n");
std::printf(" --info-wvox <wvox-base> [--json]\n");
std::printf(" Print WVOX entries (id / npcId / eventKind / gender / variant / duration ms / volume dB / name) plus per-entry transcript\n");
std::printf(" --validate-wvox <wvox-base> [--json]\n");
std::printf(" Static checks: id+name+npcId required, eventKind 0..8, genderHint 0..2, audioPath non-empty, no duplicate voiceIds, no two clips at same (npcId, eventKind, variantIndex) triple (random pick at trigger time would be ambiguous); warns on durationMs=0 (subtitle sync impossible), volumeDb outside [-20,+6] (clip risk), empty transcript (TTS+chat-bubble blank)\n");
std::printf(" --catalog-pluck <wXXX-file> <id> [--json]\n");
std::printf(" Extract one entry by id from any registered catalog format. Auto-detects magic, dispatches to the per-format --info-* handler internally, then prints just the matching entry. Primary-key field is auto-detected (first *Id field, or first numeric)\n");
std::printf(" --catalog-find <directory> <id> [--magic <WXXX>] [--json]\n");