mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-10 02:53:51 +00:00
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:
parent
89fcd4d4cb
commit
0be9bf86db
8 changed files with 645 additions and 0 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
117
include/pipeline/wowee_cinematics.hpp
Normal file
117
include/pipeline/wowee_cinematics.hpp
Normal 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
|
||||
254
src/pipeline/wowee_cinematics.cpp
Normal file
254
src/pipeline/wowee_cinematics.cpp
Normal 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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
246
tools/editor/cli_cinematics_catalog.cpp
Normal file
246
tools/editor/cli_cinematics_catalog.cpp
Normal 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
|
||||
11
tools/editor/cli_cinematics_catalog.hpp
Normal file
11
tools/editor/cli_cinematics_catalog.hpp
Normal 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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue