From 92327fc0c0581e9e610f4ea62b7b1ed529767257 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 9 May 2026 18:55:22 -0700 Subject: [PATCH] feat(pipeline): add WGLY (Wowee Glyph) catalog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 42nd open format — replaces GlyphProperties.dbc + GlyphSlot.dbc plus the AzerothCore-style glyph_properties SQL table. Defines the WotLK glyph system: per-class inscribable glyphs that modify spell behavior. Each entry pairs a glyph item (the inscriber's craft output) with the spell aura that applies the modification, tagged with a Major / Minor / Prime slot type and a classMask of allowed classes. Cross-references with prior formats — spellId points at WSPL.spellId (the aura), itemId points at WIT.itemId (the inscribed item), and classMask bit positions match the WCHC classId enum (1=Warrior, 2=Paladin, 3=Hunter, etc). CLI: --gen-glyphs (3-entry per-role starter), --gen-glyphs-warrior (6-entry full warrior allotment), --gen-glyphs-universal (4 classMask=All utility glyphs), --info-wgly, --validate-wgly with --json variants. Validator catches id=0/duplicates, empty name, spellId=0 (glyph applies no aura), classMask=0 (no class can inscribe), itemId=0 warning, requiredLevel<25 warning (below WotLK glyph threshold), and glyphType out-of-range. --- CMakeLists.txt | 3 + include/pipeline/wowee_glyphs.hpp | 110 ++++++++++++ src/pipeline/wowee_glyphs.cpp | 251 ++++++++++++++++++++++++++++ tools/editor/cli_arg_required.cpp | 2 + tools/editor/cli_dispatch.cpp | 2 + tools/editor/cli_glyphs_catalog.cpp | 233 ++++++++++++++++++++++++++ tools/editor/cli_glyphs_catalog.hpp | 11 ++ tools/editor/cli_help.cpp | 10 ++ 8 files changed, 622 insertions(+) create mode 100644 include/pipeline/wowee_glyphs.hpp create mode 100644 src/pipeline/wowee_glyphs.cpp create mode 100644 tools/editor/cli_glyphs_catalog.cpp create mode 100644 tools/editor/cli_glyphs_catalog.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f6b029d4..18281c23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -629,6 +629,7 @@ set(WOWEE_SOURCES src/pipeline/wowee_auction.cpp src/pipeline/wowee_channels.cpp src/pipeline/wowee_cinematics.cpp + src/pipeline/wowee_glyphs.cpp src/pipeline/custom_zone_discovery.cpp src/pipeline/dbc_layout.cpp @@ -1404,6 +1405,7 @@ add_executable(wowee_editor tools/editor/cli_auction_catalog.cpp tools/editor/cli_channels_catalog.cpp tools/editor/cli_cinematics_catalog.cpp + tools/editor/cli_glyphs_catalog.cpp tools/editor/cli_quest_objective.cpp tools/editor/cli_quest_reward.cpp tools/editor/cli_clone.cpp @@ -1511,6 +1513,7 @@ add_executable(wowee_editor src/pipeline/wowee_auction.cpp src/pipeline/wowee_channels.cpp src/pipeline/wowee_cinematics.cpp + src/pipeline/wowee_glyphs.cpp src/pipeline/custom_zone_discovery.cpp src/pipeline/terrain_mesh.cpp diff --git a/include/pipeline/wowee_glyphs.hpp b/include/pipeline/wowee_glyphs.hpp new file mode 100644 index 00000000..f7e7bfd0 --- /dev/null +++ b/include/pipeline/wowee_glyphs.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include + +namespace wowee { +namespace pipeline { + +// Wowee Open Glyph catalog (.wgly) — novel replacement for +// Blizzard's GlyphProperties.dbc + GlyphSlot.dbc plus the +// AzerothCore-style glyph_properties SQL tables. The 42nd +// open format added to the editor. +// +// Defines the WotLK-introduced glyph system: per-class +// inscribable glyphs that modify spell behavior. Each entry +// pairs a glyph item (the inscription crafted by an inscriber) +// with the spell aura that applies the modification, and tags +// it with a glyph slot type (Major / Minor / Prime) and a +// classMask of allowed classes. +// +// Cross-references with previously-added formats: +// WGLY.entry.spellId → WSPL.spellId (the aura applied) +// WGLY.entry.itemId → WIT.itemId (the inscribed glyph +// item that teaches it) +// WGLY.entry.classMask bit positions match WCHC.class.classId +// (1=Warrior, 2=Paladin, +// 3=Hunter, 4=Rogue, ...) +// +// Binary layout (little-endian): +// magic[4] = "WGLY" +// version (uint32) = current 1 +// nameLen + name (catalog label) +// entryCount (uint32) +// entries (each): +// glyphId (uint32) +// nameLen + name +// descLen + description +// iconLen + iconPath +// glyphType (uint8) / pad[3] +// spellId (uint32) +// itemId (uint32) +// classMask (uint32) +// requiredLevel (uint16) / pad[2] +struct WoweeGlyph { + enum GlyphType : uint8_t { + Major = 0, // Major glyph slot — gameplay-defining + Minor = 1, // Minor glyph slot — convenience / cosmetic + Prime = 2, // Prime slot (Cataclysm-style backport) + }; + + // Convenient class-bit constants matching WCHC.classId. + static constexpr uint32_t kClassWarrior = 1u << 1; + static constexpr uint32_t kClassPaladin = 1u << 2; + static constexpr uint32_t kClassHunter = 1u << 3; + static constexpr uint32_t kClassRogue = 1u << 4; + static constexpr uint32_t kClassPriest = 1u << 5; + static constexpr uint32_t kClassDeathKnight = 1u << 6; + static constexpr uint32_t kClassShaman = 1u << 7; + static constexpr uint32_t kClassMage = 1u << 8; + static constexpr uint32_t kClassWarlock = 1u << 9; + static constexpr uint32_t kClassDruid = 1u << 11; + static constexpr uint32_t kClassAll = 0xFFFFFFFFu; + + struct Entry { + uint32_t glyphId = 0; + std::string name; + std::string description; + std::string iconPath; + uint8_t glyphType = Major; + uint32_t spellId = 0; // WSPL cross-ref + uint32_t itemId = 0; // WIT cross-ref (inscribed glyph item) + uint32_t classMask = 0; // bit per WCHC.classId + uint16_t requiredLevel = 25; // WotLK glyphs unlock at 25/50/70/80 + }; + + std::string name; + std::vector entries; + + bool isValid() const { return !entries.empty(); } + + const Entry* findById(uint32_t glyphId) const; + + static const char* glyphTypeName(uint8_t t); +}; + +class WoweeGlyphLoader { +public: + static bool save(const WoweeGlyph& cat, + const std::string& basePath); + static WoweeGlyph load(const std::string& basePath); + static bool exists(const std::string& basePath); + + // Preset emitters used by --gen-glyphs* variants. + // + // makeStarter — 3 glyphs (1 warrior, 1 mage, 1 rogue) + // showing a representative Major slot + // pick per role. + // makeWarrior — 6 warrior glyphs (3 major + 3 minor) + // demonstrating a per-class allotment. + // makeUniversal — 4 glyphs with classMask=kClassAll + // (utility / generic effects that any + // class can inscribe). + static WoweeGlyph makeStarter(const std::string& catalogName); + static WoweeGlyph makeWarrior(const std::string& catalogName); + static WoweeGlyph makeUniversal(const std::string& catalogName); +}; + +} // namespace pipeline +} // namespace wowee diff --git a/src/pipeline/wowee_glyphs.cpp b/src/pipeline/wowee_glyphs.cpp new file mode 100644 index 00000000..7d3c0736 --- /dev/null +++ b/src/pipeline/wowee_glyphs.cpp @@ -0,0 +1,251 @@ +#include "pipeline/wowee_glyphs.hpp" + +#include +#include +#include + +namespace wowee { +namespace pipeline { + +namespace { + +constexpr char kMagic[4] = {'W', 'G', 'L', 'Y'}; +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) != ".wgly") { + base += ".wgly"; + } + return base; +} + +} // namespace + +const WoweeGlyph::Entry* +WoweeGlyph::findById(uint32_t glyphId) const { + for (const auto& e : entries) if (e.glyphId == glyphId) return &e; + return nullptr; +} + +const char* WoweeGlyph::glyphTypeName(uint8_t t) { + switch (t) { + case Major: return "major"; + case Minor: return "minor"; + case Prime: return "prime"; + default: return "unknown"; + } +} + +bool WoweeGlyphLoader::save(const WoweeGlyph& 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.glyphId); + writeStr(os, e.name); + writeStr(os, e.description); + writeStr(os, e.iconPath); + writePOD(os, e.glyphType); + uint8_t pad[3] = {0, 0, 0}; + os.write(reinterpret_cast(pad), 3); + writePOD(os, e.spellId); + writePOD(os, e.itemId); + writePOD(os, e.classMask); + writePOD(os, e.requiredLevel); + uint8_t pad2[2] = {0, 0}; + os.write(reinterpret_cast(pad2), 2); + } + return os.good(); +} + +WoweeGlyph WoweeGlyphLoader::load(const std::string& basePath) { + WoweeGlyph 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.glyphId)) { + out.entries.clear(); return out; + } + if (!readStr(is, e.name) || !readStr(is, e.description) || + !readStr(is, e.iconPath)) { + out.entries.clear(); return out; + } + if (!readPOD(is, e.glyphType)) { + out.entries.clear(); return out; + } + uint8_t pad[3]; + is.read(reinterpret_cast(pad), 3); + if (is.gcount() != 3) { out.entries.clear(); return out; } + if (!readPOD(is, e.spellId) || + !readPOD(is, e.itemId) || + !readPOD(is, e.classMask) || + !readPOD(is, e.requiredLevel)) { + out.entries.clear(); return out; + } + uint8_t pad2[2]; + is.read(reinterpret_cast(pad2), 2); + if (is.gcount() != 2) { out.entries.clear(); return out; } + } + return out; +} + +bool WoweeGlyphLoader::exists(const std::string& basePath) { + std::ifstream is(normalizePath(basePath), std::ios::binary); + return is.good(); +} + +WoweeGlyph WoweeGlyphLoader::makeStarter(const std::string& catalogName) { + WoweeGlyph c; + c.name = catalogName; + { + WoweeGlyph::Entry e; + e.glyphId = 1; e.name = "Glyph of Cleaving"; + e.description = "Increases Cleave target count by one."; + e.iconPath = "Interface/Icons/Inv_Glyph_MajorWarrior.blp"; + e.glyphType = WoweeGlyph::Major; + e.spellId = 56308; // canonical WotLK spellId + e.itemId = 43412; // canonical inscription item + e.classMask = WoweeGlyph::kClassWarrior; + e.requiredLevel = 25; + c.entries.push_back(e); + } + { + WoweeGlyph::Entry e; + e.glyphId = 2; e.name = "Glyph of Polymorph"; + e.description = "Polymorph cleanses DoT effects on the target."; + e.iconPath = "Interface/Icons/Inv_Glyph_MajorMage.blp"; + e.glyphType = WoweeGlyph::Major; + e.spellId = 56375; + e.itemId = 42735; + e.classMask = WoweeGlyph::kClassMage; + e.requiredLevel = 25; + c.entries.push_back(e); + } + { + WoweeGlyph::Entry e; + e.glyphId = 3; e.name = "Glyph of Vanish"; + e.description = "Vanish increases your run speed for 6 sec."; + e.iconPath = "Interface/Icons/Inv_Glyph_MajorRogue.blp"; + e.glyphType = WoweeGlyph::Major; + e.spellId = 56474; + e.itemId = 42964; + e.classMask = WoweeGlyph::kClassRogue; + e.requiredLevel = 25; + c.entries.push_back(e); + } + return c; +} + +WoweeGlyph WoweeGlyphLoader::makeWarrior(const std::string& catalogName) { + WoweeGlyph c; + c.name = catalogName; + auto add = [&](uint32_t id, const char* name, uint8_t type, + uint32_t spellId, uint32_t itemId, + uint16_t reqLevel, const char* desc) { + WoweeGlyph::Entry e; + e.glyphId = id; e.name = name; e.description = desc; + e.iconPath = std::string("Interface/Icons/Inv_Glyph_") + + (type == WoweeGlyph::Major ? "MajorWarrior" : "MinorWarrior") + + ".blp"; + e.glyphType = type; + e.spellId = spellId; e.itemId = itemId; + e.classMask = WoweeGlyph::kClassWarrior; + e.requiredLevel = reqLevel; + c.entries.push_back(e); + }; + // 3 majors. + add(100, "Glyph of Cleaving", WoweeGlyph::Major, + 56308, 43412, 25, "Cleave hits +1 target."); + add(101, "Glyph of Bloodthirst", WoweeGlyph::Major, + 58368, 43395, 50, "Bloodthirst heals you for additional health."); + add(102, "Glyph of Heroic Strike", WoweeGlyph::Major, + 58367, 43399, 70, "Heroic Strike refunds rage on critical hits."); + // 3 minors. + add(110, "Glyph of Battle", WoweeGlyph::Minor, + 58095, 43395, 25, "Battle Shout duration increased by 30 min."); + add(111, "Glyph of Charge", WoweeGlyph::Minor, + 58097, 43413, 50, "Charge range increased by 5 yards."); + add(112, "Glyph of Mocking Blow", WoweeGlyph::Minor, + 58099, 43421, 70, "Mocking Blow taunt duration extended by 4 sec."); + return c; +} + +WoweeGlyph WoweeGlyphLoader::makeUniversal(const std::string& catalogName) { + WoweeGlyph c; + c.name = catalogName; + auto add = [&](uint32_t id, const char* name, uint8_t type, + uint32_t spellId, uint32_t itemId, + uint16_t reqLevel, const char* desc) { + WoweeGlyph::Entry e; + e.glyphId = id; e.name = name; e.description = desc; + e.iconPath = "Interface/Icons/Inv_Glyph_Generic.blp"; + e.glyphType = type; + e.spellId = spellId; e.itemId = itemId; + e.classMask = WoweeGlyph::kClassAll; // any class + e.requiredLevel = reqLevel; + c.entries.push_back(e); + }; + add(200, "Glyph of Hearthstone", WoweeGlyph::Minor, + 59672, 43906, 25, + "Reduces Hearthstone cooldown by 5 minutes."); + add(201, "Glyph of Levitate", WoweeGlyph::Minor, + 59673, 43911, 50, + "Levitate also makes you weightless underwater."); + add(202, "Glyph of Mounting", WoweeGlyph::Minor, + 59674, 43912, 50, + "Reduces mount cast time by 1 second."); + add(203, "Glyph of Salvation", WoweeGlyph::Major, + 59675, 43913, 70, + "Once per minute, automatically dispels a fear effect."); + return c; +} + +} // namespace pipeline +} // namespace wowee diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index 1842a336..0cdb6109 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -121,6 +121,8 @@ const char* const kArgRequired[] = { "--export-wchn-json", "--import-wchn-json", "--gen-cinematics", "--gen-cinematics-intros", "--gen-cinematics-quests", "--info-wcms", "--validate-wcms", + "--gen-glyphs", "--gen-glyphs-warrior", "--gen-glyphs-universal", + "--info-wgly", "--validate-wgly", "--gen-weather-temperate", "--gen-weather-arctic", "--gen-weather-desert", "--gen-weather-stormy", "--gen-zone-atmosphere", diff --git a/tools/editor/cli_dispatch.cpp b/tools/editor/cli_dispatch.cpp index 63fcb393..ddd339b3 100644 --- a/tools/editor/cli_dispatch.cpp +++ b/tools/editor/cli_dispatch.cpp @@ -69,6 +69,7 @@ #include "cli_auction_catalog.hpp" #include "cli_channels_catalog.hpp" #include "cli_cinematics_catalog.hpp" +#include "cli_glyphs_catalog.hpp" #include "cli_quest_objective.hpp" #include "cli_quest_reward.hpp" #include "cli_clone.hpp" @@ -179,6 +180,7 @@ constexpr DispatchFn kDispatchTable[] = { handleAuctionCatalog, handleChannelsCatalog, handleCinematicsCatalog, + handleGlyphsCatalog, handleQuestObjective, handleQuestReward, handleClone, diff --git a/tools/editor/cli_glyphs_catalog.cpp b/tools/editor/cli_glyphs_catalog.cpp new file mode 100644 index 00000000..33b521d2 --- /dev/null +++ b/tools/editor/cli_glyphs_catalog.cpp @@ -0,0 +1,233 @@ +#include "cli_glyphs_catalog.hpp" +#include "cli_arg_parse.hpp" +#include "cli_box_emitter.hpp" + +#include "pipeline/wowee_glyphs.hpp" +#include + +#include +#include +#include +#include +#include +#include + +namespace wowee { +namespace editor { +namespace cli { + +namespace { + +std::string stripWglyExt(std::string base) { + stripExt(base, ".wgly"); + return base; +} + +bool saveOrError(const wowee::pipeline::WoweeGlyph& c, + const std::string& base, const char* cmd) { + if (!wowee::pipeline::WoweeGlyphLoader::save(c, base)) { + std::fprintf(stderr, "%s: failed to save %s.wgly\n", + cmd, base.c_str()); + return false; + } + return true; +} + +void printGenSummary(const wowee::pipeline::WoweeGlyph& c, + const std::string& base) { + std::printf("Wrote %s.wgly\n", base.c_str()); + std::printf(" catalog : %s\n", c.name.c_str()); + std::printf(" glyphs : %zu\n", c.entries.size()); +} + +int handleGenStarter(int& i, int argc, char** argv) { + std::string base = argv[++i]; + std::string name = "StarterGlyphs"; + if (parseOptArg(i, argc, argv)) name = argv[++i]; + base = stripWglyExt(base); + auto c = wowee::pipeline::WoweeGlyphLoader::makeStarter(name); + if (!saveOrError(c, base, "gen-glyphs")) return 1; + printGenSummary(c, base); + return 0; +} + +int handleGenWarrior(int& i, int argc, char** argv) { + std::string base = argv[++i]; + std::string name = "WarriorGlyphs"; + if (parseOptArg(i, argc, argv)) name = argv[++i]; + base = stripWglyExt(base); + auto c = wowee::pipeline::WoweeGlyphLoader::makeWarrior(name); + if (!saveOrError(c, base, "gen-glyphs-warrior")) return 1; + printGenSummary(c, base); + return 0; +} + +int handleGenUniversal(int& i, int argc, char** argv) { + std::string base = argv[++i]; + std::string name = "UniversalGlyphs"; + if (parseOptArg(i, argc, argv)) name = argv[++i]; + base = stripWglyExt(base); + auto c = wowee::pipeline::WoweeGlyphLoader::makeUniversal(name); + if (!saveOrError(c, base, "gen-glyphs-universal")) 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 = stripWglyExt(base); + if (!wowee::pipeline::WoweeGlyphLoader::exists(base)) { + std::fprintf(stderr, "WGLY not found: %s.wgly\n", base.c_str()); + return 1; + } + auto c = wowee::pipeline::WoweeGlyphLoader::load(base); + if (jsonOut) { + nlohmann::json j; + j["wgly"] = base + ".wgly"; + j["name"] = c.name; + j["count"] = c.entries.size(); + nlohmann::json arr = nlohmann::json::array(); + for (const auto& e : c.entries) { + arr.push_back({ + {"glyphId", e.glyphId}, + {"name", e.name}, + {"description", e.description}, + {"iconPath", e.iconPath}, + {"glyphType", e.glyphType}, + {"glyphTypeName", wowee::pipeline::WoweeGlyph::glyphTypeName(e.glyphType)}, + {"spellId", e.spellId}, + {"itemId", e.itemId}, + {"classMask", e.classMask}, + {"requiredLevel", e.requiredLevel}, + }); + } + j["entries"] = arr; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("WGLY: %s.wgly\n", base.c_str()); + std::printf(" catalog : %s\n", c.name.c_str()); + std::printf(" glyphs : %zu\n", c.entries.size()); + if (c.entries.empty()) return 0; + std::printf(" id type spell item classMask lvl name\n"); + for (const auto& e : c.entries) { + std::printf(" %4u %-5s %5u %5u 0x%08x %3u %s\n", + e.glyphId, + wowee::pipeline::WoweeGlyph::glyphTypeName(e.glyphType), + e.spellId, e.itemId, e.classMask, + e.requiredLevel, 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 = stripWglyExt(base); + if (!wowee::pipeline::WoweeGlyphLoader::exists(base)) { + std::fprintf(stderr, + "validate-wgly: WGLY not found: %s.wgly\n", base.c_str()); + return 1; + } + auto c = wowee::pipeline::WoweeGlyphLoader::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.glyphId); + if (!e.name.empty()) ctx += " " + e.name; + ctx += ")"; + if (e.glyphId == 0) + errors.push_back(ctx + ": glyphId is 0"); + if (e.name.empty()) + errors.push_back(ctx + ": name is empty"); + if (e.spellId == 0) + errors.push_back(ctx + ": spellId is 0 " + "(glyph applies no aura)"); + if (e.glyphType > wowee::pipeline::WoweeGlyph::Prime) { + errors.push_back(ctx + ": glyphType " + + std::to_string(e.glyphType) + " not in 0..2"); + } + if (e.classMask == 0) { + errors.push_back(ctx + + ": classMask=0 (no class can use this glyph)"); + } + if (e.itemId == 0) { + warnings.push_back(ctx + ": itemId=0 " + "(no inscribed item — glyph can't be taught)"); + } + // WotLK glyphs unlock at character level 25 (minor), + // 50 (major), 70 (major), 80 (prime). Anything below + // 15 is suspicious. + if (e.requiredLevel != 0 && e.requiredLevel < 15) { + warnings.push_back(ctx + ": requiredLevel=" + + std::to_string(e.requiredLevel) + + " below WotLK glyph threshold (25)"); + } + for (uint32_t prev : idsSeen) { + if (prev == e.glyphId) { + errors.push_back(ctx + ": duplicate glyphId"); + break; + } + } + idsSeen.push_back(e.glyphId); + } + bool ok = errors.empty(); + if (jsonOut) { + nlohmann::json j; + j["wgly"] = base + ".wgly"; + 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-wgly: %s.wgly\n", base.c_str()); + if (ok && warnings.empty()) { + std::printf(" OK — %zu glyphs, all glyphIds 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 handleGlyphsCatalog(int& i, int argc, char** argv, int& outRc) { + if (std::strcmp(argv[i], "--gen-glyphs") == 0 && i + 1 < argc) { + outRc = handleGenStarter(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--gen-glyphs-warrior") == 0 && i + 1 < argc) { + outRc = handleGenWarrior(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--gen-glyphs-universal") == 0 && i + 1 < argc) { + outRc = handleGenUniversal(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--info-wgly") == 0 && i + 1 < argc) { + outRc = handleInfo(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--validate-wgly") == 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_glyphs_catalog.hpp b/tools/editor/cli_glyphs_catalog.hpp new file mode 100644 index 00000000..bdf49869 --- /dev/null +++ b/tools/editor/cli_glyphs_catalog.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace wowee { +namespace editor { +namespace cli { + +bool handleGlyphsCatalog(int& i, int argc, char** argv, int& outRc); + +} // namespace cli +} // namespace editor +} // namespace wowee diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index 0bf9828a..7f1849b7 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -1287,6 +1287,16 @@ void printUsage(const char* argv0) { 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-glyphs [name]\n"); + std::printf(" Emit .wgly starter: 3 glyphs (1 warrior + 1 mage + 1 rogue) showing per-class Major slot picks\n"); + std::printf(" --gen-glyphs-warrior [name]\n"); + std::printf(" Emit .wgly 6 warrior glyphs (3 major + 3 minor) demonstrating per-class allotment\n"); + std::printf(" --gen-glyphs-universal [name]\n"); + std::printf(" Emit .wgly 4 universal glyphs (classMask=All) covering hearthstone/levitate/mounting/salvation\n"); + std::printf(" --info-wgly [--json]\n"); + std::printf(" Print WGLY entries (id / type / spellId / itemId / classMask / requiredLevel / name)\n"); + std::printf(" --validate-wgly [--json]\n"); + std::printf(" Static checks: id>0+unique, name+spellId not empty, type 0..2, classMask>0, level<25 warning, missing-itemId warning\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");