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
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue