mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-11 11:33:52 +00:00
feat(editor): add WSCD (Spell Cooldown Category) open catalog format
Open replacement for SpellCooldown.dbc plus the per-spell category-cooldown fields in Spell.dbc. Defines the shared-cooldown buckets that related spells reference: casting one spell triggers a cooldown on every other spell in the same bucket. Mage Polymorph variants (Sheep / Pig / Turtle / Cat) all share one bucket so morphing a target locks all variants at once. Healing potions and mana potions share the SharedWithItems bucket so consuming one locks the other. Distinct from WSDR (which times how long an aura stays on a target) — WSCD times how long before a spell can be cast again. The global cooldown (GCD) is itself just one bucket of this kind, flagged with OnGCDStart so the engine triggers it at cast start rather than cast finish. Three preset emitters: --gen-cdb (4 baseline buckets including GCD), --gen-cdb-class (5 mage-specific class cooldowns including the Polymorph family), --gen-cdb-items (5 item cooldowns including the heal/mana potion shared bucket and the 60min Hearthstone family). Validation enforces id+name presence, bucketKind 0..4, no duplicate ids, and warns on Global without OnGCDStart (engine wouldn't trigger on cast start) and Spell kind with SharedWithItems (contradictory). categoryFlags is a bitfield (AffectedByHaste / SharedWithItems / OnGCDStart / IgnoresCooldownReduction); --info-wscd decodes the bits to label list. Wired through the cross-format table; WSCD appears automatically in all 9 cross-format utilities. Format count 70 -> 71; CLI flag count 907 -> 912.
This commit is contained in:
parent
824b6ebf53
commit
493db026dd
10 changed files with 645 additions and 0 deletions
|
|
@ -659,6 +659,7 @@ set(WOWEE_SOURCES
|
|||
src/pipeline/wowee_spell_ranges.cpp
|
||||
src/pipeline/wowee_spell_cast_times.cpp
|
||||
src/pipeline/wowee_spell_durations.cpp
|
||||
src/pipeline/wowee_spell_cooldowns.cpp
|
||||
src/pipeline/custom_zone_discovery.cpp
|
||||
src/pipeline/dbc_layout.cpp
|
||||
|
||||
|
|
@ -1474,6 +1475,7 @@ add_executable(wowee_editor
|
|||
tools/editor/cli_spell_ranges_catalog.cpp
|
||||
tools/editor/cli_spell_cast_times_catalog.cpp
|
||||
tools/editor/cli_spell_durations_catalog.cpp
|
||||
tools/editor/cli_spell_cooldowns_catalog.cpp
|
||||
tools/editor/cli_quest_objective.cpp
|
||||
tools/editor/cli_quest_reward.cpp
|
||||
tools/editor/cli_clone.cpp
|
||||
|
|
@ -1611,6 +1613,7 @@ add_executable(wowee_editor
|
|||
src/pipeline/wowee_spell_ranges.cpp
|
||||
src/pipeline/wowee_spell_cast_times.cpp
|
||||
src/pipeline/wowee_spell_durations.cpp
|
||||
src/pipeline/wowee_spell_cooldowns.cpp
|
||||
src/pipeline/custom_zone_discovery.cpp
|
||||
src/pipeline/terrain_mesh.cpp
|
||||
|
||||
|
|
|
|||
111
include/pipeline/wowee_spell_cooldowns.hpp
Normal file
111
include/pipeline/wowee_spell_cooldowns.hpp
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
// Wowee Open Spell Cooldown Category catalog (.wscd) —
|
||||
// novel replacement for Blizzard's SpellCooldown.dbc plus
|
||||
// the per-spell category-cooldown fields in Spell.dbc.
|
||||
// Defines the shared-cooldown buckets that related spells
|
||||
// reference, so casting one spell triggers a cooldown on
|
||||
// every other spell in the same bucket. Examples: every
|
||||
// Mage Polymorph variant (Sheep / Pig / Turtle / Cat)
|
||||
// shares a single bucket so polymorphing a target locks
|
||||
// all the morph spells, not just the one cast. Healing
|
||||
// potions, mana potions, and similar consumables work the
|
||||
// same way.
|
||||
//
|
||||
// Distinct from WSDR (Spell Duration), which times how
|
||||
// long an aura stays on a target. WSCD times how long
|
||||
// before a spell can be cast again, applied to everyone in
|
||||
// the bucket — the global cooldown (GCD) is the most
|
||||
// common bucket of all.
|
||||
//
|
||||
// Cross-references with previously-added formats:
|
||||
// None — this catalog is consumed directly by the spell
|
||||
// engine. WSPL spell entries reference cooldownBucketId.
|
||||
//
|
||||
// Binary layout (little-endian):
|
||||
// magic[4] = "WSCD"
|
||||
// version (uint32) = current 1
|
||||
// nameLen + name (catalog label)
|
||||
// entryCount (uint32)
|
||||
// entries (each):
|
||||
// bucketId (uint32)
|
||||
// nameLen + name
|
||||
// descLen + description
|
||||
// bucketKind (uint8) / pad[3]
|
||||
// cooldownMs (uint32)
|
||||
// categoryFlags (uint32)
|
||||
// iconColorRGBA (uint32)
|
||||
struct WoweeSpellCooldown {
|
||||
enum BucketKind : uint8_t {
|
||||
Spell = 0, // spell-only cooldown bucket
|
||||
Item = 1, // item-only cooldown bucket
|
||||
Class = 2, // class-shared bucket (e.g. all mage AOE)
|
||||
Global = 3, // global cooldown — all combat spells
|
||||
Misc = 4, // catch-all (engineering trinkets, etc.)
|
||||
};
|
||||
|
||||
enum CategoryFlag : uint32_t {
|
||||
AffectedByHaste = 1u << 0, // cooldown shrinks with haste
|
||||
SharedWithItems = 1u << 1, // spells + items share bucket
|
||||
OnGCDStart = 1u << 2, // triggers at GCD start, not cast finish
|
||||
IgnoresCooldownReduction = 1u << 3, // not affected by CDR talents
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
uint32_t bucketId = 0;
|
||||
std::string name;
|
||||
std::string description;
|
||||
uint8_t bucketKind = Spell;
|
||||
uint32_t cooldownMs = 0;
|
||||
uint32_t categoryFlags = 0;
|
||||
uint32_t iconColorRGBA = 0xFFFFFFFFu;
|
||||
};
|
||||
|
||||
std::string name;
|
||||
std::vector<Entry> entries;
|
||||
|
||||
bool isValid() const { return !entries.empty(); }
|
||||
|
||||
const Entry* findById(uint32_t bucketId) const;
|
||||
|
||||
static const char* bucketKindName(uint8_t k);
|
||||
};
|
||||
|
||||
class WoweeSpellCooldownLoader {
|
||||
public:
|
||||
static bool save(const WoweeSpellCooldown& cat,
|
||||
const std::string& basePath);
|
||||
static WoweeSpellCooldown load(const std::string& basePath);
|
||||
static bool exists(const std::string& basePath);
|
||||
|
||||
// Preset emitters used by --gen-cdb* variants.
|
||||
//
|
||||
// makeStarter — 4 baseline buckets (GlobalCooldown
|
||||
// 1.5s, ShortItem 5s, MediumItem 30s,
|
||||
// LongItem 60s) covering the most
|
||||
// common cooldown tiers including the
|
||||
// GCD itself.
|
||||
// makeClass — 5 mage-specific buckets for spells
|
||||
// that share cooldowns within a class
|
||||
// (PolymorphFamily 0s — instant on hit
|
||||
// but exclusive, AlterTime 90s,
|
||||
// Counterspell 24s, Blink 15s,
|
||||
// IceBlock 5min).
|
||||
// makeItems — 5 item-cooldown buckets (HealingPot
|
||||
// 60s, ManaPot 60s, ManaJade 1.5s
|
||||
// GCD-only, EngineerTrinket 60s,
|
||||
// HearthstoneFamily 60min).
|
||||
static WoweeSpellCooldown makeStarter(const std::string& catalogName);
|
||||
static WoweeSpellCooldown makeClass(const std::string& catalogName);
|
||||
static WoweeSpellCooldown makeItems(const std::string& catalogName);
|
||||
};
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
244
src/pipeline/wowee_spell_cooldowns.cpp
Normal file
244
src/pipeline/wowee_spell_cooldowns.cpp
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
#include "pipeline/wowee_spell_cooldowns.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kMagic[4] = {'W', 'S', 'C', 'D'};
|
||||
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) != ".wscd") {
|
||||
base += ".wscd";
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
uint32_t packRgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 0xFF) {
|
||||
return (static_cast<uint32_t>(a) << 24) |
|
||||
(static_cast<uint32_t>(b) << 16) |
|
||||
(static_cast<uint32_t>(g) << 8) |
|
||||
static_cast<uint32_t>(r);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const WoweeSpellCooldown::Entry*
|
||||
WoweeSpellCooldown::findById(uint32_t bucketId) const {
|
||||
for (const auto& e : entries)
|
||||
if (e.bucketId == bucketId) return &e;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* WoweeSpellCooldown::bucketKindName(uint8_t k) {
|
||||
switch (k) {
|
||||
case Spell: return "spell";
|
||||
case Item: return "item";
|
||||
case Class: return "class";
|
||||
case Global: return "global";
|
||||
case Misc: return "misc";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
bool WoweeSpellCooldownLoader::save(const WoweeSpellCooldown& 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.bucketId);
|
||||
writeStr(os, e.name);
|
||||
writeStr(os, e.description);
|
||||
writePOD(os, e.bucketKind);
|
||||
uint8_t pad3[3] = {0, 0, 0};
|
||||
os.write(reinterpret_cast<const char*>(pad3), 3);
|
||||
writePOD(os, e.cooldownMs);
|
||||
writePOD(os, e.categoryFlags);
|
||||
writePOD(os, e.iconColorRGBA);
|
||||
}
|
||||
return os.good();
|
||||
}
|
||||
|
||||
WoweeSpellCooldown WoweeSpellCooldownLoader::load(
|
||||
const std::string& basePath) {
|
||||
WoweeSpellCooldown 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.bucketId)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
if (!readStr(is, e.name) || !readStr(is, e.description)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
if (!readPOD(is, e.bucketKind)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
uint8_t pad3[3];
|
||||
is.read(reinterpret_cast<char*>(pad3), 3);
|
||||
if (is.gcount() != 3) { out.entries.clear(); return out; }
|
||||
if (!readPOD(is, e.cooldownMs) ||
|
||||
!readPOD(is, e.categoryFlags) ||
|
||||
!readPOD(is, e.iconColorRGBA)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool WoweeSpellCooldownLoader::exists(const std::string& basePath) {
|
||||
std::ifstream is(normalizePath(basePath), std::ios::binary);
|
||||
return is.good();
|
||||
}
|
||||
|
||||
WoweeSpellCooldown WoweeSpellCooldownLoader::makeStarter(
|
||||
const std::string& catalogName) {
|
||||
WoweeSpellCooldown c;
|
||||
c.name = catalogName;
|
||||
auto add = [&](uint32_t id, const char* name, uint8_t kind,
|
||||
uint32_t cdMs, uint32_t flags,
|
||||
uint8_t r, uint8_t g, uint8_t b,
|
||||
const char* desc) {
|
||||
WoweeSpellCooldown::Entry e;
|
||||
e.bucketId = id; e.name = name; e.description = desc;
|
||||
e.bucketKind = kind;
|
||||
e.cooldownMs = cdMs;
|
||||
e.categoryFlags = flags;
|
||||
e.iconColorRGBA = packRgba(r, g, b);
|
||||
c.entries.push_back(e);
|
||||
};
|
||||
add(1, "GlobalCooldown", WoweeSpellCooldown::Global,
|
||||
1500,
|
||||
WoweeSpellCooldown::AffectedByHaste |
|
||||
WoweeSpellCooldown::OnGCDStart,
|
||||
220, 220, 220, "Global cooldown — 1.5s, hasted, applies to "
|
||||
"every combat spell cast.");
|
||||
add(2, "ShortItemCD", WoweeSpellCooldown::Item,
|
||||
5000, 0,
|
||||
180, 240, 180, "Short item cooldown — 5s (low-tier consumables).");
|
||||
add(3, "MediumItemCD", WoweeSpellCooldown::Item,
|
||||
30000, 0,
|
||||
180, 240, 100, "Medium item cooldown — 30s (mid-tier "
|
||||
"consumables / wands).");
|
||||
add(4, "LongItemCD", WoweeSpellCooldown::Item,
|
||||
60000, WoweeSpellCooldown::SharedWithItems,
|
||||
240, 220, 100, "Long item cooldown — 60s, shared between "
|
||||
"healing/mana potions.");
|
||||
return c;
|
||||
}
|
||||
|
||||
WoweeSpellCooldown WoweeSpellCooldownLoader::makeClass(
|
||||
const std::string& catalogName) {
|
||||
WoweeSpellCooldown c;
|
||||
c.name = catalogName;
|
||||
auto add = [&](uint32_t id, const char* name, uint32_t cdMs,
|
||||
uint32_t flags, const char* desc) {
|
||||
WoweeSpellCooldown::Entry e;
|
||||
e.bucketId = id; e.name = name; e.description = desc;
|
||||
e.bucketKind = WoweeSpellCooldown::Class;
|
||||
e.cooldownMs = cdMs;
|
||||
e.categoryFlags = flags;
|
||||
e.iconColorRGBA = packRgba(100, 200, 240); // mage blue
|
||||
c.entries.push_back(e);
|
||||
};
|
||||
add(100, "PolymorphFamily", 0,
|
||||
0, "Mage Polymorph variants (Sheep / Pig / Turtle / Cat) — "
|
||||
"0ms cooldown but exclusive: only one variant active per "
|
||||
"target.");
|
||||
add(101, "AlterTime", 90000,
|
||||
WoweeSpellCooldown::AffectedByHaste,
|
||||
"Alter Time — 90s, hasted by spell haste.");
|
||||
add(102, "Counterspell", 24000,
|
||||
0, "Counterspell — 24s, fixed cooldown.");
|
||||
add(103, "Blink", 15000,
|
||||
0, "Blink — 15s, fixed cooldown.");
|
||||
add(104, "IceBlock", 300000,
|
||||
WoweeSpellCooldown::IgnoresCooldownReduction,
|
||||
"Ice Block — 5min, not affected by Cold Snap or CDR.");
|
||||
return c;
|
||||
}
|
||||
|
||||
WoweeSpellCooldown WoweeSpellCooldownLoader::makeItems(
|
||||
const std::string& catalogName) {
|
||||
WoweeSpellCooldown c;
|
||||
c.name = catalogName;
|
||||
auto add = [&](uint32_t id, const char* name, uint32_t cdMs,
|
||||
uint32_t flags, const char* desc) {
|
||||
WoweeSpellCooldown::Entry e;
|
||||
e.bucketId = id; e.name = name; e.description = desc;
|
||||
e.bucketKind = WoweeSpellCooldown::Item;
|
||||
e.cooldownMs = cdMs;
|
||||
e.categoryFlags = flags;
|
||||
e.iconColorRGBA = packRgba(240, 200, 100); // gold for items
|
||||
c.entries.push_back(e);
|
||||
};
|
||||
add(200, "HealingPotion", 60000,
|
||||
WoweeSpellCooldown::SharedWithItems,
|
||||
"Healing potion family — 60s shared with mana potions.");
|
||||
add(201, "ManaPotion", 60000,
|
||||
WoweeSpellCooldown::SharedWithItems,
|
||||
"Mana potion family — 60s shared with healing potions.");
|
||||
add(202, "ManaJade", 1500,
|
||||
WoweeSpellCooldown::OnGCDStart,
|
||||
"Mana Jade / oil flasks — GCD-only, no item cooldown.");
|
||||
add(203, "EngineerTrinket", 60000, 0,
|
||||
"Engineer trinket — 60s standalone bucket.");
|
||||
add(204, "HearthstoneFamily", 3600000, 0,
|
||||
"Hearthstone — 60min, exclusive across alt-bind variants.");
|
||||
return c;
|
||||
}
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
|
|
@ -215,6 +215,8 @@ const char* const kArgRequired[] = {
|
|||
"--gen-sdr", "--gen-sdr-buffs", "--gen-sdr-dot",
|
||||
"--info-wsdr", "--validate-wsdr",
|
||||
"--export-wsdr-json", "--import-wsdr-json",
|
||||
"--gen-cdb", "--gen-cdb-class", "--gen-cdb-items",
|
||||
"--info-wscd", "--validate-wscd",
|
||||
"--gen-weather-temperate", "--gen-weather-arctic",
|
||||
"--gen-weather-desert", "--gen-weather-stormy",
|
||||
"--gen-zone-atmosphere",
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@
|
|||
#include "cli_spell_ranges_catalog.hpp"
|
||||
#include "cli_spell_cast_times_catalog.hpp"
|
||||
#include "cli_spell_durations_catalog.hpp"
|
||||
#include "cli_spell_cooldowns_catalog.hpp"
|
||||
#include "cli_quest_objective.hpp"
|
||||
#include "cli_quest_reward.hpp"
|
||||
#include "cli_clone.hpp"
|
||||
|
|
@ -257,6 +258,7 @@ constexpr DispatchFn kDispatchTable[] = {
|
|||
handleSpellRangesCatalog,
|
||||
handleSpellCastTimesCatalog,
|
||||
handleSpellDurationsCatalog,
|
||||
handleSpellCooldownsCatalog,
|
||||
handleQuestObjective,
|
||||
handleQuestReward,
|
||||
handleClone,
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ constexpr FormatMagicEntry kFormats[] = {
|
|||
{{'W','S','R','G'}, ".wsrg", "spells", "--info-wsrg", "Spell range bucket catalog"},
|
||||
{{'W','S','C','T'}, ".wsct", "spells", "--info-wsct", "Spell cast time bucket catalog"},
|
||||
{{'W','S','D','R'}, ".wsdr", "spells", "--info-wsdr", "Spell duration bucket catalog"},
|
||||
{{'W','S','C','D'}, ".wscd", "spells", "--info-wscd", "Spell cooldown category catalog"},
|
||||
{{'W','F','A','C'}, ".wfac", "factions", nullptr, "Faction catalog"},
|
||||
{{'W','L','C','K'}, ".wlck", "locks", nullptr, "Lock catalog"},
|
||||
{{'W','S','K','L'}, ".wskl", "skills", nullptr, "Skill catalog"},
|
||||
|
|
|
|||
|
|
@ -1731,6 +1731,16 @@ void printUsage(const char* argv0) {
|
|||
std::printf(" Export binary .wsdr to a human-editable JSON sidecar (defaults to <base>.wsdr.json)\n");
|
||||
std::printf(" --import-wsdr-json <json-path> [out-base]\n");
|
||||
std::printf(" Import a .wsdr.json sidecar back into binary .wsdr (accepts durationKind int OR durationKindName string)\n");
|
||||
std::printf(" --gen-cdb <wscd-base> [name]\n");
|
||||
std::printf(" Emit .wscd starter: 4 baseline cooldown buckets (GlobalCooldown 1.5s / ShortItem 5s / MediumItem 30s / LongItem 60s shared)\n");
|
||||
std::printf(" --gen-cdb-class <wscd-base> [name]\n");
|
||||
std::printf(" Emit .wscd 5 mage class buckets (Polymorph family / AlterTime 90s / Counterspell 24s / Blink 15s / IceBlock 5min)\n");
|
||||
std::printf(" --gen-cdb-items <wscd-base> [name]\n");
|
||||
std::printf(" Emit .wscd 5 item buckets (HealingPot+ManaPot 60s shared / ManaJade GCD-only / EngineerTrinket 60s / Hearthstone 60min)\n");
|
||||
std::printf(" --info-wscd <wscd-base> [--json]\n");
|
||||
std::printf(" Print WSCD entries (id / kind / cooldownMs / category flags / name) — flags decoded as label list\n");
|
||||
std::printf(" --validate-wscd <wscd-base> [--json]\n");
|
||||
std::printf(" Static checks: id+name required, bucketKind 0..4, no duplicate ids; warns on Global without OnGCDStart and Spell with SharedWithItems\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");
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ constexpr FormatRow kFormats[] = {
|
|||
{"WSRG", ".wsrg", "spells", "SpellRange.dbc + per-spell range", "Spell range bucket catalog"},
|
||||
{"WSCT", ".wsct", "spells", "SpellCastTimes.dbc + cast scaling","Spell cast time bucket catalog"},
|
||||
{"WSDR", ".wsdr", "spells", "SpellDuration.dbc + per-spell dur","Spell duration bucket catalog"},
|
||||
{"WSCD", ".wscd", "spells", "SpellCooldown.dbc + shared cd grp","Spell cooldown category catalog"},
|
||||
|
||||
// Additional pipeline catalogs without the alternating
|
||||
// gen/info/validate CLI surface (loaded by the engine
|
||||
|
|
|
|||
259
tools/editor/cli_spell_cooldowns_catalog.cpp
Normal file
259
tools/editor/cli_spell_cooldowns_catalog.cpp
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
#include "cli_spell_cooldowns_catalog.hpp"
|
||||
#include "cli_arg_parse.hpp"
|
||||
#include "cli_box_emitter.hpp"
|
||||
|
||||
#include "pipeline/wowee_spell_cooldowns.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 stripWscdExt(std::string base) {
|
||||
stripExt(base, ".wscd");
|
||||
return base;
|
||||
}
|
||||
|
||||
bool saveOrError(const wowee::pipeline::WoweeSpellCooldown& c,
|
||||
const std::string& base, const char* cmd) {
|
||||
if (!wowee::pipeline::WoweeSpellCooldownLoader::save(c, base)) {
|
||||
std::fprintf(stderr, "%s: failed to save %s.wscd\n",
|
||||
cmd, base.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void printGenSummary(const wowee::pipeline::WoweeSpellCooldown& c,
|
||||
const std::string& base) {
|
||||
std::printf("Wrote %s.wscd\n", base.c_str());
|
||||
std::printf(" catalog : %s\n", c.name.c_str());
|
||||
std::printf(" buckets : %zu\n", c.entries.size());
|
||||
}
|
||||
|
||||
int handleGenStarter(int& i, int argc, char** argv) {
|
||||
std::string base = argv[++i];
|
||||
std::string name = "StarterCooldowns";
|
||||
if (parseOptArg(i, argc, argv)) name = argv[++i];
|
||||
base = stripWscdExt(base);
|
||||
auto c = wowee::pipeline::WoweeSpellCooldownLoader::makeStarter(name);
|
||||
if (!saveOrError(c, base, "gen-cdb")) return 1;
|
||||
printGenSummary(c, base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handleGenClass(int& i, int argc, char** argv) {
|
||||
std::string base = argv[++i];
|
||||
std::string name = "MageClassCooldowns";
|
||||
if (parseOptArg(i, argc, argv)) name = argv[++i];
|
||||
base = stripWscdExt(base);
|
||||
auto c = wowee::pipeline::WoweeSpellCooldownLoader::makeClass(name);
|
||||
if (!saveOrError(c, base, "gen-cdb-class")) return 1;
|
||||
printGenSummary(c, base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handleGenItems(int& i, int argc, char** argv) {
|
||||
std::string base = argv[++i];
|
||||
std::string name = "ItemCooldowns";
|
||||
if (parseOptArg(i, argc, argv)) name = argv[++i];
|
||||
base = stripWscdExt(base);
|
||||
auto c = wowee::pipeline::WoweeSpellCooldownLoader::makeItems(name);
|
||||
if (!saveOrError(c, base, "gen-cdb-items")) return 1;
|
||||
printGenSummary(c, base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void appendFlagNames(uint32_t flags, std::string& out) {
|
||||
using F = wowee::pipeline::WoweeSpellCooldown;
|
||||
auto add = [&](const char* n) {
|
||||
if (!out.empty()) out += "|";
|
||||
out += n;
|
||||
};
|
||||
if (flags & F::AffectedByHaste) add("AffectedByHaste");
|
||||
if (flags & F::SharedWithItems) add("SharedWithItems");
|
||||
if (flags & F::OnGCDStart) add("OnGCDStart");
|
||||
if (flags & F::IgnoresCooldownReduction) add("IgnoresCooldownReduction");
|
||||
if (out.empty()) out = "-";
|
||||
}
|
||||
|
||||
int handleInfo(int& i, int argc, char** argv) {
|
||||
std::string base = argv[++i];
|
||||
bool jsonOut = consumeJsonFlag(i, argc, argv);
|
||||
base = stripWscdExt(base);
|
||||
if (!wowee::pipeline::WoweeSpellCooldownLoader::exists(base)) {
|
||||
std::fprintf(stderr, "WSCD not found: %s.wscd\n", base.c_str());
|
||||
return 1;
|
||||
}
|
||||
auto c = wowee::pipeline::WoweeSpellCooldownLoader::load(base);
|
||||
if (jsonOut) {
|
||||
nlohmann::json j;
|
||||
j["wscd"] = base + ".wscd";
|
||||
j["name"] = c.name;
|
||||
j["count"] = c.entries.size();
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& e : c.entries) {
|
||||
std::string flagNames;
|
||||
appendFlagNames(e.categoryFlags, flagNames);
|
||||
arr.push_back({
|
||||
{"bucketId", e.bucketId},
|
||||
{"name", e.name},
|
||||
{"description", e.description},
|
||||
{"bucketKind", e.bucketKind},
|
||||
{"bucketKindName", wowee::pipeline::WoweeSpellCooldown::bucketKindName(e.bucketKind)},
|
||||
{"cooldownMs", e.cooldownMs},
|
||||
{"categoryFlags", e.categoryFlags},
|
||||
{"categoryFlagsLabels", flagNames},
|
||||
{"iconColorRGBA", e.iconColorRGBA},
|
||||
});
|
||||
}
|
||||
j["entries"] = arr;
|
||||
std::printf("%s\n", j.dump(2).c_str());
|
||||
return 0;
|
||||
}
|
||||
std::printf("WSCD: %s.wscd\n", base.c_str());
|
||||
std::printf(" catalog : %s\n", c.name.c_str());
|
||||
std::printf(" buckets : %zu\n", c.entries.size());
|
||||
if (c.entries.empty()) return 0;
|
||||
std::printf(" id kind cooldownMs flags name\n");
|
||||
for (const auto& e : c.entries) {
|
||||
std::string flagNames;
|
||||
appendFlagNames(e.categoryFlags, flagNames);
|
||||
std::printf(" %4u %-7s %10u %-32s %s\n",
|
||||
e.bucketId,
|
||||
wowee::pipeline::WoweeSpellCooldown::bucketKindName(e.bucketKind),
|
||||
e.cooldownMs,
|
||||
flagNames.c_str(),
|
||||
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 = stripWscdExt(base);
|
||||
if (!wowee::pipeline::WoweeSpellCooldownLoader::exists(base)) {
|
||||
std::fprintf(stderr,
|
||||
"validate-wscd: WSCD not found: %s.wscd\n", base.c_str());
|
||||
return 1;
|
||||
}
|
||||
auto c = wowee::pipeline::WoweeSpellCooldownLoader::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;
|
||||
constexpr uint32_t kKnownFlagMask =
|
||||
wowee::pipeline::WoweeSpellCooldown::AffectedByHaste |
|
||||
wowee::pipeline::WoweeSpellCooldown::SharedWithItems |
|
||||
wowee::pipeline::WoweeSpellCooldown::OnGCDStart |
|
||||
wowee::pipeline::WoweeSpellCooldown::IgnoresCooldownReduction;
|
||||
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.bucketId);
|
||||
if (!e.name.empty()) ctx += " " + e.name;
|
||||
ctx += ")";
|
||||
if (e.bucketId == 0)
|
||||
errors.push_back(ctx + ": bucketId is 0");
|
||||
if (e.name.empty())
|
||||
errors.push_back(ctx + ": name is empty");
|
||||
if (e.bucketKind > wowee::pipeline::WoweeSpellCooldown::Misc) {
|
||||
errors.push_back(ctx + ": bucketKind " +
|
||||
std::to_string(e.bucketKind) + " not in 0..4");
|
||||
}
|
||||
if (e.categoryFlags & ~kKnownFlagMask) {
|
||||
warnings.push_back(ctx +
|
||||
": categoryFlags has bits outside known mask " +
|
||||
"(0x" + std::to_string(e.categoryFlags & ~kKnownFlagMask) +
|
||||
") — engine will ignore unknown flags");
|
||||
}
|
||||
// Global bucket should be GCD-marked. Otherwise the
|
||||
// engine wouldn't trigger it on cast start.
|
||||
if (e.bucketKind == wowee::pipeline::WoweeSpellCooldown::Global &&
|
||||
!(e.categoryFlags & wowee::pipeline::WoweeSpellCooldown::OnGCDStart)) {
|
||||
warnings.push_back(ctx +
|
||||
": Global kind without OnGCDStart flag — "
|
||||
"engine will not trigger this on cast start");
|
||||
}
|
||||
// SharedWithItems on a Spell-only bucket is
|
||||
// contradictory.
|
||||
if (e.bucketKind == wowee::pipeline::WoweeSpellCooldown::Spell &&
|
||||
(e.categoryFlags & wowee::pipeline::WoweeSpellCooldown::SharedWithItems)) {
|
||||
warnings.push_back(ctx +
|
||||
": Spell kind with SharedWithItems flag — "
|
||||
"switch kind to Item or Misc, or drop the flag");
|
||||
}
|
||||
for (uint32_t prev : idsSeen) {
|
||||
if (prev == e.bucketId) {
|
||||
errors.push_back(ctx + ": duplicate bucketId");
|
||||
break;
|
||||
}
|
||||
}
|
||||
idsSeen.push_back(e.bucketId);
|
||||
}
|
||||
bool ok = errors.empty();
|
||||
if (jsonOut) {
|
||||
nlohmann::json j;
|
||||
j["wscd"] = base + ".wscd";
|
||||
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-wscd: %s.wscd\n", base.c_str());
|
||||
if (ok && warnings.empty()) {
|
||||
std::printf(" OK — %zu buckets, all bucketIds 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 handleSpellCooldownsCatalog(int& i, int argc, char** argv,
|
||||
int& outRc) {
|
||||
if (std::strcmp(argv[i], "--gen-cdb") == 0 && i + 1 < argc) {
|
||||
outRc = handleGenStarter(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--gen-cdb-class") == 0 && i + 1 < argc) {
|
||||
outRc = handleGenClass(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--gen-cdb-items") == 0 && i + 1 < argc) {
|
||||
outRc = handleGenItems(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--info-wscd") == 0 && i + 1 < argc) {
|
||||
outRc = handleInfo(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--validate-wscd") == 0 && i + 1 < argc) {
|
||||
outRc = handleValidate(i, argc, argv); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
12
tools/editor/cli_spell_cooldowns_catalog.hpp
Normal file
12
tools/editor/cli_spell_cooldowns_catalog.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
bool handleSpellCooldownsCatalog(int& i, int argc, char** argv,
|
||||
int& outRc);
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
Loading…
Add table
Add a link
Reference in a new issue