feat(pipeline): add WCMS (Wowee Cinematic) catalog

41st open format — replaces Movie.dbc / CinematicCamera.dbc /
CinematicSequences.dbc plus the AzerothCore cinematic_camera SQL
table. Defines pre-rendered videos, in-engine camera flythroughs,
text crawls, and still images, each with a media path, duration,
skippable flag, and a polymorphic trigger that fires on quest
events / class first-login / zone entry / dungeon clear /
achievements / level milestones.

Cross-references with prior formats — triggerTargetId resolves
by triggerKind to WQT.questId / WMS.areaId / WMS.mapId /
WCHC.classId / WACH.achievementId, and soundtrackId points at
WSND.soundId.

CLI: --gen-cinematics (3-entry starter), --gen-cinematics-intros
(4 class intros), --gen-cinematics-quests (3 quest-bound
cinematics referencing demo questIds 1/100/102), --info-wcms,
--validate-wcms with --json variants. Validator catches
id=0/duplicates, empty name/mediaPath, kind/trigger out of
range, missing target id for non-Manual/Login/LevelUp triggers,
zero-duration entries, and non-skippable pre-rendered videos.
This commit is contained in:
Kelsi 2026-05-09 18:50:43 -07:00
parent 89fcd4d4cb
commit 0be9bf86db
8 changed files with 645 additions and 0 deletions

View file

@ -628,6 +628,7 @@ set(WOWEE_SOURCES
src/pipeline/wowee_pets.cpp
src/pipeline/wowee_auction.cpp
src/pipeline/wowee_channels.cpp
src/pipeline/wowee_cinematics.cpp
src/pipeline/custom_zone_discovery.cpp
src/pipeline/dbc_layout.cpp
@ -1402,6 +1403,7 @@ add_executable(wowee_editor
tools/editor/cli_pets_catalog.cpp
tools/editor/cli_auction_catalog.cpp
tools/editor/cli_channels_catalog.cpp
tools/editor/cli_cinematics_catalog.cpp
tools/editor/cli_quest_objective.cpp
tools/editor/cli_quest_reward.cpp
tools/editor/cli_clone.cpp
@ -1508,6 +1510,7 @@ add_executable(wowee_editor
src/pipeline/wowee_pets.cpp
src/pipeline/wowee_auction.cpp
src/pipeline/wowee_channels.cpp
src/pipeline/wowee_cinematics.cpp
src/pipeline/custom_zone_discovery.cpp
src/pipeline/terrain_mesh.cpp

View file

@ -0,0 +1,117 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace pipeline {
// Wowee Open Cinematic catalog (.wcms) — novel replacement
// for Blizzard's Movie.dbc + CinematicCamera.dbc +
// CinematicSequences.dbc + the AzerothCore-style
// cinematic_camera SQL tables. The 41st open format added
// to the editor.
//
// Defines cinematics: pre-rendered videos, in-engine camera
// flythroughs, text crawls, and still images. Each
// cinematic has a media path (video file or M2 model
// reference for in-engine camera), duration, a "skippable"
// flag, and a trigger that fires the cinematic from a
// gameplay event (quest accepted, class first-login,
// dungeon cleared, achievement earned).
//
// Cross-references with previously-added formats — the
// triggerTargetId field is polymorphic by triggerKind:
// triggerKind=QuestStart/End → WQT.entry.questId
// triggerKind=ZoneEntry → WMS.area.areaId
// triggerKind=ClassStart → WCHC.class.classId
// triggerKind=DungeonClear → WMS.map.mapId
// triggerKind=AchievementGained → WACH.entry.achievementId
// WCMS.soundtrackId → WSND.entry.soundId
//
// Binary layout (little-endian):
// magic[4] = "WCMS"
// version (uint32) = current 1
// nameLen + name (catalog label)
// entryCount (uint32)
// entries (each):
// cinematicId (uint32)
// nameLen + name
// descLen + description
// mediaLen + mediaPath
// kind (uint8) / triggerKind (uint8) / skippable (uint8) / pad[1]
// durationSeconds (uint32)
// triggerTargetId (uint32)
// soundtrackId (uint32)
struct WoweeCinematic {
enum Kind : uint8_t {
PreRenderedVideo = 0, // .ogv / .bik file
CameraFlythrough = 1, // in-engine camera spline
TextCrawl = 2, // Star Wars-style scrolling text
StillImage = 3, // single image with audio
Slideshow = 4, // multi-image with timing
};
enum TriggerKind : uint8_t {
Manual = 0, // played by script call
QuestStart = 1,
QuestEnd = 2,
ClassStart = 3, // shown on first login of new char
ZoneEntry = 4,
DungeonClear = 5, // boss kill in instance
Login = 6, // every login
AchievementGained = 7,
LevelUp = 8, // milestone levels (10, 20, 60, ...)
};
struct Entry {
uint32_t cinematicId = 0;
std::string name;
std::string description;
std::string mediaPath;
uint8_t kind = PreRenderedVideo;
uint8_t triggerKind = Manual;
uint8_t skippable = 1; // 1 = ESC dismisses
uint32_t durationSeconds = 0; // 0 = unknown / video-determined
uint32_t triggerTargetId = 0;
uint32_t soundtrackId = 0; // WSND cross-ref, 0 = none
};
std::string name;
std::vector<Entry> entries;
bool isValid() const { return !entries.empty(); }
const Entry* findById(uint32_t cinematicId) const;
static const char* kindName(uint8_t k);
static const char* triggerKindName(uint8_t t);
};
class WoweeCinematicLoader {
public:
static bool save(const WoweeCinematic& cat,
const std::string& basePath);
static WoweeCinematic load(const std::string& basePath);
static bool exists(const std::string& basePath);
// Preset emitters used by --gen-cinematics* variants.
//
// makeStarter — 3 cinematics: 1 pre-rendered intro,
// 1 in-engine quest cutscene, 1 still
// image with audio (login splash).
// makeIntros — 4 class-intro cinematics for Warrior /
// Mage / Hunter / Rogue, each shown on
// first character login.
// makeQuestCinematics — 3 quest-triggered cinematics
// referencing WQT questIds 1 /
// 100 / 102 from the demo
// content stack.
static WoweeCinematic makeStarter(const std::string& catalogName);
static WoweeCinematic makeIntros(const std::string& catalogName);
static WoweeCinematic makeQuestCinematics(const std::string& catalogName);
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,254 @@
#include "pipeline/wowee_cinematics.hpp"
#include <cstdio>
#include <cstring>
#include <fstream>
namespace wowee {
namespace pipeline {
namespace {
constexpr char kMagic[4] = {'W', 'C', 'M', 'S'};
constexpr uint32_t kVersion = 1;
template <typename T>
void writePOD(std::ofstream& os, const T& v) {
os.write(reinterpret_cast<const char*>(&v), sizeof(T));
}
template <typename T>
bool readPOD(std::ifstream& is, T& v) {
is.read(reinterpret_cast<char*>(&v), sizeof(T));
return is.gcount() == static_cast<std::streamsize>(sizeof(T));
}
void writeStr(std::ofstream& os, const std::string& s) {
uint32_t n = static_cast<uint32_t>(s.size());
writePOD(os, n);
if (n > 0) os.write(s.data(), n);
}
bool readStr(std::ifstream& is, std::string& s) {
uint32_t n = 0;
if (!readPOD(is, n)) return false;
if (n > (1u << 20)) return false;
s.resize(n);
if (n > 0) {
is.read(s.data(), n);
if (is.gcount() != static_cast<std::streamsize>(n)) {
s.clear();
return false;
}
}
return true;
}
std::string normalizePath(std::string base) {
if (base.size() < 5 || base.substr(base.size() - 5) != ".wcms") {
base += ".wcms";
}
return base;
}
} // namespace
const WoweeCinematic::Entry*
WoweeCinematic::findById(uint32_t cinematicId) const {
for (const auto& e : entries) if (e.cinematicId == cinematicId) return &e;
return nullptr;
}
const char* WoweeCinematic::kindName(uint8_t k) {
switch (k) {
case PreRenderedVideo: return "video";
case CameraFlythrough: return "camera";
case TextCrawl: return "text-crawl";
case StillImage: return "image";
case Slideshow: return "slideshow";
default: return "unknown";
}
}
const char* WoweeCinematic::triggerKindName(uint8_t t) {
switch (t) {
case Manual: return "manual";
case QuestStart: return "quest-start";
case QuestEnd: return "quest-end";
case ClassStart: return "class-start";
case ZoneEntry: return "zone-entry";
case DungeonClear: return "dungeon-clear";
case Login: return "login";
case AchievementGained: return "achievement";
case LevelUp: return "level-up";
default: return "unknown";
}
}
bool WoweeCinematicLoader::save(const WoweeCinematic& cat,
const std::string& basePath) {
std::ofstream os(normalizePath(basePath), std::ios::binary);
if (!os) return false;
os.write(kMagic, 4);
writePOD(os, kVersion);
writeStr(os, cat.name);
uint32_t entryCount = static_cast<uint32_t>(cat.entries.size());
writePOD(os, entryCount);
for (const auto& e : cat.entries) {
writePOD(os, e.cinematicId);
writeStr(os, e.name);
writeStr(os, e.description);
writeStr(os, e.mediaPath);
writePOD(os, e.kind);
writePOD(os, e.triggerKind);
writePOD(os, e.skippable);
uint8_t pad = 0;
writePOD(os, pad);
writePOD(os, e.durationSeconds);
writePOD(os, e.triggerTargetId);
writePOD(os, e.soundtrackId);
}
return os.good();
}
WoweeCinematic WoweeCinematicLoader::load(const std::string& basePath) {
WoweeCinematic out;
std::ifstream is(normalizePath(basePath), std::ios::binary);
if (!is) return out;
char magic[4];
is.read(magic, 4);
if (std::memcmp(magic, kMagic, 4) != 0) return out;
uint32_t version = 0;
if (!readPOD(is, version) || version != kVersion) return out;
if (!readStr(is, out.name)) return out;
uint32_t entryCount = 0;
if (!readPOD(is, entryCount)) return out;
if (entryCount > (1u << 20)) return out;
out.entries.resize(entryCount);
for (auto& e : out.entries) {
if (!readPOD(is, e.cinematicId)) {
out.entries.clear(); return out;
}
if (!readStr(is, e.name) || !readStr(is, e.description) ||
!readStr(is, e.mediaPath)) {
out.entries.clear(); return out;
}
if (!readPOD(is, e.kind) ||
!readPOD(is, e.triggerKind) ||
!readPOD(is, e.skippable)) {
out.entries.clear(); return out;
}
uint8_t pad = 0;
if (!readPOD(is, pad)) {
out.entries.clear(); return out;
}
if (!readPOD(is, e.durationSeconds) ||
!readPOD(is, e.triggerTargetId) ||
!readPOD(is, e.soundtrackId)) {
out.entries.clear(); return out;
}
}
return out;
}
bool WoweeCinematicLoader::exists(const std::string& basePath) {
std::ifstream is(normalizePath(basePath), std::ios::binary);
return is.good();
}
WoweeCinematic WoweeCinematicLoader::makeStarter(const std::string& catalogName) {
WoweeCinematic c;
c.name = catalogName;
{
WoweeCinematic::Entry e;
e.cinematicId = 1; e.name = "Realm Intro";
e.description = "Pre-rendered intro played on character login.";
e.kind = WoweeCinematic::PreRenderedVideo;
e.triggerKind = WoweeCinematic::Login;
e.mediaPath = "Movies/Intro/realm_intro.ogv";
e.durationSeconds = 90;
e.skippable = 1;
e.soundtrackId = 2; // WSND.makeStarter music id
c.entries.push_back(e);
}
{
WoweeCinematic::Entry e;
e.cinematicId = 2; e.name = "Quest Cutscene";
e.description = "In-engine camera flythrough on quest accept.";
e.kind = WoweeCinematic::CameraFlythrough;
e.triggerKind = WoweeCinematic::QuestStart;
e.mediaPath = "Cinematics/quest_001_camera.m2";
e.durationSeconds = 30;
e.skippable = 1;
e.triggerTargetId = 1; // WQT questId 1
c.entries.push_back(e);
}
{
WoweeCinematic::Entry e;
e.cinematicId = 3; e.name = "Login Splash";
e.description = "Static splash image shown on title screen.";
e.kind = WoweeCinematic::StillImage;
e.triggerKind = WoweeCinematic::Manual;
e.mediaPath = "Splash/login_image.png";
e.durationSeconds = 5;
c.entries.push_back(e);
}
return c;
}
WoweeCinematic WoweeCinematicLoader::makeIntros(const std::string& catalogName) {
WoweeCinematic c;
c.name = catalogName;
auto add = [&](uint32_t id, const char* name, uint32_t classId,
const char* media) {
WoweeCinematic::Entry e;
e.cinematicId = id; e.name = name;
e.description = std::string("First-login intro for the ") +
name + " class.";
e.kind = WoweeCinematic::CameraFlythrough;
e.triggerKind = WoweeCinematic::ClassStart;
e.mediaPath = media;
e.durationSeconds = 45;
e.skippable = 1;
// triggerTargetId values match WCHC class IDs
// (1=Warrior, 3=Hunter, 4=Rogue, 8=Mage).
e.triggerTargetId = classId;
c.entries.push_back(e);
};
add(100, "Warrior", 1, "Cinematics/intro_warrior.m2");
add(101, "Hunter", 3, "Cinematics/intro_hunter.m2");
add(102, "Rogue", 4, "Cinematics/intro_rogue.m2");
add(103, "Mage", 8, "Cinematics/intro_mage.m2");
return c;
}
WoweeCinematic WoweeCinematicLoader::makeQuestCinematics(const std::string& catalogName) {
WoweeCinematic c;
c.name = catalogName;
auto add = [&](uint32_t id, uint8_t kind, const char* name,
uint32_t questId, uint8_t triggerKind,
uint32_t durSec) {
WoweeCinematic::Entry e;
e.cinematicId = id; e.name = name;
e.kind = WoweeCinematic::CameraFlythrough;
e.triggerKind = triggerKind;
e.mediaPath = std::string("Cinematics/quest_") +
std::to_string(questId) + ".m2";
e.durationSeconds = durSec;
e.skippable = 1;
e.triggerTargetId = questId;
(void)kind;
c.entries.push_back(e);
};
// questIds 1 / 100 / 102 match WQT.makeStarter + makeChain.
add(200, 0, "Bandit Trouble Intro", 1,
WoweeCinematic::QuestStart, 25);
add(201, 0, "Investigate Camp Briefing", 100,
WoweeCinematic::QuestStart, 30);
add(202, 0, "Bandit Trouble Resolution", 102,
WoweeCinematic::QuestEnd, 40);
return c;
}
} // namespace pipeline
} // namespace wowee

View file

@ -118,6 +118,8 @@ const char* const kArgRequired[] = {
"--export-wauc-json", "--import-wauc-json",
"--gen-channels", "--gen-channels-city", "--gen-channels-moderated",
"--info-wchn", "--validate-wchn",
"--gen-cinematics", "--gen-cinematics-intros", "--gen-cinematics-quests",
"--info-wcms", "--validate-wcms",
"--gen-weather-temperate", "--gen-weather-arctic",
"--gen-weather-desert", "--gen-weather-stormy",
"--gen-zone-atmosphere",

View file

@ -0,0 +1,246 @@
#include "cli_cinematics_catalog.hpp"
#include "cli_arg_parse.hpp"
#include "cli_box_emitter.hpp"
#include "pipeline/wowee_cinematics.hpp"
#include <nlohmann/json.hpp>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <string>
#include <vector>
namespace wowee {
namespace editor {
namespace cli {
namespace {
std::string stripWcmsExt(std::string base) {
stripExt(base, ".wcms");
return base;
}
bool saveOrError(const wowee::pipeline::WoweeCinematic& c,
const std::string& base, const char* cmd) {
if (!wowee::pipeline::WoweeCinematicLoader::save(c, base)) {
std::fprintf(stderr, "%s: failed to save %s.wcms\n",
cmd, base.c_str());
return false;
}
return true;
}
void printGenSummary(const wowee::pipeline::WoweeCinematic& c,
const std::string& base) {
std::printf("Wrote %s.wcms\n", base.c_str());
std::printf(" catalog : %s\n", c.name.c_str());
std::printf(" cinematics : %zu\n", c.entries.size());
}
int handleGenStarter(int& i, int argc, char** argv) {
std::string base = argv[++i];
std::string name = "StarterCinematics";
if (parseOptArg(i, argc, argv)) name = argv[++i];
base = stripWcmsExt(base);
auto c = wowee::pipeline::WoweeCinematicLoader::makeStarter(name);
if (!saveOrError(c, base, "gen-cinematics")) return 1;
printGenSummary(c, base);
return 0;
}
int handleGenIntros(int& i, int argc, char** argv) {
std::string base = argv[++i];
std::string name = "ClassIntros";
if (parseOptArg(i, argc, argv)) name = argv[++i];
base = stripWcmsExt(base);
auto c = wowee::pipeline::WoweeCinematicLoader::makeIntros(name);
if (!saveOrError(c, base, "gen-cinematics-intros")) return 1;
printGenSummary(c, base);
return 0;
}
int handleGenQuests(int& i, int argc, char** argv) {
std::string base = argv[++i];
std::string name = "QuestCinematics";
if (parseOptArg(i, argc, argv)) name = argv[++i];
base = stripWcmsExt(base);
auto c = wowee::pipeline::WoweeCinematicLoader::makeQuestCinematics(name);
if (!saveOrError(c, base, "gen-cinematics-quests")) return 1;
printGenSummary(c, base);
return 0;
}
int handleInfo(int& i, int argc, char** argv) {
std::string base = argv[++i];
bool jsonOut = consumeJsonFlag(i, argc, argv);
base = stripWcmsExt(base);
if (!wowee::pipeline::WoweeCinematicLoader::exists(base)) {
std::fprintf(stderr, "WCMS not found: %s.wcms\n", base.c_str());
return 1;
}
auto c = wowee::pipeline::WoweeCinematicLoader::load(base);
if (jsonOut) {
nlohmann::json j;
j["wcms"] = base + ".wcms";
j["name"] = c.name;
j["count"] = c.entries.size();
nlohmann::json arr = nlohmann::json::array();
for (const auto& e : c.entries) {
arr.push_back({
{"cinematicId", e.cinematicId},
{"name", e.name},
{"description", e.description},
{"mediaPath", e.mediaPath},
{"kind", e.kind},
{"kindName", wowee::pipeline::WoweeCinematic::kindName(e.kind)},
{"triggerKind", e.triggerKind},
{"triggerKindName", wowee::pipeline::WoweeCinematic::triggerKindName(e.triggerKind)},
{"triggerTargetId", e.triggerTargetId},
{"durationSeconds", e.durationSeconds},
{"skippable", e.skippable},
{"soundtrackId", e.soundtrackId},
});
}
j["entries"] = arr;
std::printf("%s\n", j.dump(2).c_str());
return 0;
}
std::printf("WCMS: %s.wcms\n", base.c_str());
std::printf(" catalog : %s\n", c.name.c_str());
std::printf(" cinematics : %zu\n", c.entries.size());
if (c.entries.empty()) return 0;
std::printf(" id kind trigger target dur skip snd name\n");
for (const auto& e : c.entries) {
std::printf(" %4u %-10s %-15s %5u %3us %u %3u %s\n",
e.cinematicId,
wowee::pipeline::WoweeCinematic::kindName(e.kind),
wowee::pipeline::WoweeCinematic::triggerKindName(e.triggerKind),
e.triggerTargetId,
e.durationSeconds,
e.skippable,
e.soundtrackId,
e.name.c_str());
}
return 0;
}
int handleValidate(int& i, int argc, char** argv) {
std::string base = argv[++i];
bool jsonOut = consumeJsonFlag(i, argc, argv);
base = stripWcmsExt(base);
if (!wowee::pipeline::WoweeCinematicLoader::exists(base)) {
std::fprintf(stderr,
"validate-wcms: WCMS not found: %s.wcms\n", base.c_str());
return 1;
}
auto c = wowee::pipeline::WoweeCinematicLoader::load(base);
std::vector<std::string> errors;
std::vector<std::string> warnings;
if (c.entries.empty()) {
warnings.push_back("catalog has zero entries");
}
std::vector<uint32_t> idsSeen;
for (size_t k = 0; k < c.entries.size(); ++k) {
const auto& e = c.entries[k];
std::string ctx = "entry " + std::to_string(k) +
" (id=" + std::to_string(e.cinematicId);
if (!e.name.empty()) ctx += " " + e.name;
ctx += ")";
if (e.cinematicId == 0)
errors.push_back(ctx + ": cinematicId is 0");
if (e.name.empty())
errors.push_back(ctx + ": name is empty");
if (e.mediaPath.empty())
errors.push_back(ctx + ": mediaPath is empty");
if (e.kind > wowee::pipeline::WoweeCinematic::Slideshow) {
errors.push_back(ctx + ": kind " +
std::to_string(e.kind) + " not in 0..4");
}
if (e.triggerKind > wowee::pipeline::WoweeCinematic::LevelUp) {
errors.push_back(ctx + ": triggerKind " +
std::to_string(e.triggerKind) + " not in 0..8");
}
// Triggers other than Manual/Login require a non-zero
// target id (questId, mapId, classId, achievementId etc).
if (e.triggerKind != wowee::pipeline::WoweeCinematic::Manual &&
e.triggerKind != wowee::pipeline::WoweeCinematic::Login &&
e.triggerKind != wowee::pipeline::WoweeCinematic::LevelUp &&
e.triggerTargetId == 0) {
errors.push_back(ctx +
": triggerKind " +
wowee::pipeline::WoweeCinematic::triggerKindName(e.triggerKind) +
" requires a non-zero triggerTargetId");
}
if (e.durationSeconds == 0) {
warnings.push_back(ctx + ": durationSeconds=0 "
"(cinematic will be skipped instantly)");
}
if (e.kind == wowee::pipeline::WoweeCinematic::PreRenderedVideo &&
e.skippable == 0) {
warnings.push_back(ctx + ": pre-rendered video is "
"non-skippable (player can't escape)");
}
for (uint32_t prev : idsSeen) {
if (prev == e.cinematicId) {
errors.push_back(ctx + ": duplicate cinematicId");
break;
}
}
idsSeen.push_back(e.cinematicId);
}
bool ok = errors.empty();
if (jsonOut) {
nlohmann::json j;
j["wcms"] = base + ".wcms";
j["ok"] = ok;
j["errors"] = errors;
j["warnings"] = warnings;
std::printf("%s\n", j.dump(2).c_str());
return ok ? 0 : 1;
}
std::printf("validate-wcms: %s.wcms\n", base.c_str());
if (ok && warnings.empty()) {
std::printf(" OK — %zu cinematics, all cinematicIds unique\n",
c.entries.size());
return 0;
}
if (!warnings.empty()) {
std::printf(" warnings (%zu):\n", warnings.size());
for (const auto& w : warnings)
std::printf(" - %s\n", w.c_str());
}
if (!errors.empty()) {
std::printf(" ERRORS (%zu):\n", errors.size());
for (const auto& e : errors)
std::printf(" - %s\n", e.c_str());
}
return ok ? 0 : 1;
}
} // namespace
bool handleCinematicsCatalog(int& i, int argc, char** argv, int& outRc) {
if (std::strcmp(argv[i], "--gen-cinematics") == 0 && i + 1 < argc) {
outRc = handleGenStarter(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--gen-cinematics-intros") == 0 && i + 1 < argc) {
outRc = handleGenIntros(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--gen-cinematics-quests") == 0 && i + 1 < argc) {
outRc = handleGenQuests(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--info-wcms") == 0 && i + 1 < argc) {
outRc = handleInfo(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--validate-wcms") == 0 && i + 1 < argc) {
outRc = handleValidate(i, argc, argv); return true;
}
return false;
}
} // namespace cli
} // namespace editor
} // namespace wowee

View file

@ -0,0 +1,11 @@
#pragma once
namespace wowee {
namespace editor {
namespace cli {
bool handleCinematicsCatalog(int& i, int argc, char** argv, int& outRc);
} // namespace cli
} // namespace editor
} // namespace wowee

View file

@ -68,6 +68,7 @@
#include "cli_pets_catalog.hpp"
#include "cli_auction_catalog.hpp"
#include "cli_channels_catalog.hpp"
#include "cli_cinematics_catalog.hpp"
#include "cli_quest_objective.hpp"
#include "cli_quest_reward.hpp"
#include "cli_clone.hpp"
@ -177,6 +178,7 @@ constexpr DispatchFn kDispatchTable[] = {
handlePetsCatalog,
handleAuctionCatalog,
handleChannelsCatalog,
handleCinematicsCatalog,
handleQuestObjective,
handleQuestReward,
handleClone,

View file

@ -1273,6 +1273,16 @@ void printUsage(const char* argv0) {
std::printf(" Print WCHN entries (id / type / faction / autoJoin/announce/moderated / level / map+area gates / name)\n");
std::printf(" --validate-wchn <wchn-base> [--json]\n");
std::printf(" Static checks: id>0+unique, name not empty, type 0..9, faction 0..2, world+area-gate combo warning\n");
std::printf(" --gen-cinematics <wcms-base> [name]\n");
std::printf(" Emit .wcms starter: 3 cinematics (pre-rendered intro / quest cutscene / login splash)\n");
std::printf(" --gen-cinematics-intros <wcms-base> [name]\n");
std::printf(" Emit .wcms 4 class intros (Warrior / Hunter / Rogue / Mage) triggered on first ClassStart\n");
std::printf(" --gen-cinematics-quests <wcms-base> [name]\n");
std::printf(" Emit .wcms 3 quest-bound cinematics referencing WQT questIds 1 / 100 / 102 (start / start / end)\n");
std::printf(" --info-wcms <wcms-base> [--json]\n");
std::printf(" Print WCMS entries (id / kind / trigger / target / duration / skippable / soundtrackId / name)\n");
std::printf(" --validate-wcms <wcms-base> [--json]\n");
std::printf(" Static checks: id>0+unique, name+mediaPath not empty, kind 0..4, trigger 0..8, target required for non-Manual triggers\n");
std::printf(" --gen-weather-temperate <wow-base> [zoneName]\n");
std::printf(" Emit .wow weather schedule: clear-dominant + occasional rain + fog (forest / grassland)\n");
std::printf(" --gen-weather-arctic <wow-base> [zoneName]\n");