diff --git a/CMakeLists.txt b/CMakeLists.txt index 370d24ee..f6b029d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/include/pipeline/wowee_cinematics.hpp b/include/pipeline/wowee_cinematics.hpp new file mode 100644 index 00000000..7370b819 --- /dev/null +++ b/include/pipeline/wowee_cinematics.hpp @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include + +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 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 diff --git a/src/pipeline/wowee_cinematics.cpp b/src/pipeline/wowee_cinematics.cpp new file mode 100644 index 00000000..e3f05879 --- /dev/null +++ b/src/pipeline/wowee_cinematics.cpp @@ -0,0 +1,254 @@ +#include "pipeline/wowee_cinematics.hpp" + +#include +#include +#include + +namespace wowee { +namespace pipeline { + +namespace { + +constexpr char kMagic[4] = {'W', 'C', 'M', 'S'}; +constexpr uint32_t kVersion = 1; + +template +void writePOD(std::ofstream& os, const T& v) { + os.write(reinterpret_cast(&v), sizeof(T)); +} + +template +bool readPOD(std::ifstream& is, T& v) { + is.read(reinterpret_cast(&v), sizeof(T)); + return is.gcount() == static_cast(sizeof(T)); +} + +void writeStr(std::ofstream& os, const std::string& s) { + uint32_t n = static_cast(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(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(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 diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index 3588d41d..add2863d 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -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", diff --git a/tools/editor/cli_cinematics_catalog.cpp b/tools/editor/cli_cinematics_catalog.cpp new file mode 100644 index 00000000..d143b5c5 --- /dev/null +++ b/tools/editor/cli_cinematics_catalog.cpp @@ -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 + +#include +#include +#include +#include +#include +#include + +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 errors; + std::vector warnings; + if (c.entries.empty()) { + warnings.push_back("catalog has zero entries"); + } + std::vector 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 diff --git a/tools/editor/cli_cinematics_catalog.hpp b/tools/editor/cli_cinematics_catalog.hpp new file mode 100644 index 00000000..a43fcefd --- /dev/null +++ b/tools/editor/cli_cinematics_catalog.hpp @@ -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 diff --git a/tools/editor/cli_dispatch.cpp b/tools/editor/cli_dispatch.cpp index 95381fe9..63fcb393 100644 --- a/tools/editor/cli_dispatch.cpp +++ b/tools/editor/cli_dispatch.cpp @@ -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, diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index 327f4a92..aa32f14d 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -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 [--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 [name]\n"); + std::printf(" Emit .wcms starter: 3 cinematics (pre-rendered intro / quest cutscene / login splash)\n"); + std::printf(" --gen-cinematics-intros [name]\n"); + std::printf(" Emit .wcms 4 class intros (Warrior / Hunter / Rogue / Mage) triggered on first ClassStart\n"); + std::printf(" --gen-cinematics-quests [name]\n"); + std::printf(" Emit .wcms 3 quest-bound cinematics referencing WQT questIds 1 / 100 / 102 (start / start / end)\n"); + std::printf(" --info-wcms [--json]\n"); + std::printf(" Print WCMS entries (id / kind / trigger / target / duration / skippable / soundtrackId / name)\n"); + std::printf(" --validate-wcms [--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 [zoneName]\n"); std::printf(" Emit .wow weather schedule: clear-dominant + occasional rain + fog (forest / grassland)\n"); std::printf(" --gen-weather-arctic [zoneName]\n");