mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-11 03:23:51 +00:00
53rd open format — replaces the companion-pet portions of CreatureFamily.dbc plus the AzerothCore-style critter / vanity- pet SQL data. Distinct from WPET (which covers hunter combat pets and warlock minions); WCMP covers non-combat "vanity" pets that follow the player around for cosmetic reasons — Mechanical Squirrel, Mini Diablo, Panda Cub, dragon hatchlings. 8 companion kinds (Critter, Mechanical, DragonHatchling, Demonic, Spectral, Elemental, Plush, UndeadCritter), 4 rarity tiers (Common / Uncommon / Rare / Epic), and 3 faction restrictions (AnyFaction / AllianceOnly / HordeOnly). Cross-references with prior formats — creatureId points at WCRT.creatureId (the rendered model), learnSpellId at WSPL.spellId (the spell that summons the pet), itemId at WIT.itemId (the item that teaches the spell), and idleSoundId at WSND.soundId (idle ambient noise). CLI: --gen-cmp (3 common vendor pets), --gen-cmp-rare (4 promo / collector pets at Epic rarity — Mini Diablo, Panda Cub, Zergling, Murky), --gen-cmp-faction (3 faction- restricted Alliance Lion Cub / Horde Mottled Boar / neutral Argent Squire), --info-wcmp, --validate-wcmp with --json variants. Validator catches id+name+creatureId+ learnSpellId required, kind 0..7 / rarity 0..3 / faction 0..2 range, and Epic-rarity-no-itemId warning (most promo pets need a redemption-code item). Format graph: 52 → 53 binary formats. CLI flag count: 777 → 782.
243 lines
8.9 KiB
C++
243 lines
8.9 KiB
C++
#include "cli_companions_catalog.hpp"
|
|
#include "cli_arg_parse.hpp"
|
|
#include "cli_box_emitter.hpp"
|
|
|
|
#include "pipeline/wowee_companions.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 stripWcmpExt(std::string base) {
|
|
stripExt(base, ".wcmp");
|
|
return base;
|
|
}
|
|
|
|
bool saveOrError(const wowee::pipeline::WoweeCompanion& c,
|
|
const std::string& base, const char* cmd) {
|
|
if (!wowee::pipeline::WoweeCompanionLoader::save(c, base)) {
|
|
std::fprintf(stderr, "%s: failed to save %s.wcmp\n",
|
|
cmd, base.c_str());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void printGenSummary(const wowee::pipeline::WoweeCompanion& c,
|
|
const std::string& base) {
|
|
std::printf("Wrote %s.wcmp\n", base.c_str());
|
|
std::printf(" catalog : %s\n", c.name.c_str());
|
|
std::printf(" companions : %zu\n", c.entries.size());
|
|
}
|
|
|
|
int handleGenStarter(int& i, int argc, char** argv) {
|
|
std::string base = argv[++i];
|
|
std::string name = "StarterCompanions";
|
|
if (parseOptArg(i, argc, argv)) name = argv[++i];
|
|
base = stripWcmpExt(base);
|
|
auto c = wowee::pipeline::WoweeCompanionLoader::makeStarter(name);
|
|
if (!saveOrError(c, base, "gen-cmp")) return 1;
|
|
printGenSummary(c, base);
|
|
return 0;
|
|
}
|
|
|
|
int handleGenRare(int& i, int argc, char** argv) {
|
|
std::string base = argv[++i];
|
|
std::string name = "RareCompanions";
|
|
if (parseOptArg(i, argc, argv)) name = argv[++i];
|
|
base = stripWcmpExt(base);
|
|
auto c = wowee::pipeline::WoweeCompanionLoader::makeRare(name);
|
|
if (!saveOrError(c, base, "gen-cmp-rare")) return 1;
|
|
printGenSummary(c, base);
|
|
return 0;
|
|
}
|
|
|
|
int handleGenFaction(int& i, int argc, char** argv) {
|
|
std::string base = argv[++i];
|
|
std::string name = "FactionCompanions";
|
|
if (parseOptArg(i, argc, argv)) name = argv[++i];
|
|
base = stripWcmpExt(base);
|
|
auto c = wowee::pipeline::WoweeCompanionLoader::makeFaction(name);
|
|
if (!saveOrError(c, base, "gen-cmp-faction")) 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 = stripWcmpExt(base);
|
|
if (!wowee::pipeline::WoweeCompanionLoader::exists(base)) {
|
|
std::fprintf(stderr, "WCMP not found: %s.wcmp\n", base.c_str());
|
|
return 1;
|
|
}
|
|
auto c = wowee::pipeline::WoweeCompanionLoader::load(base);
|
|
if (jsonOut) {
|
|
nlohmann::json j;
|
|
j["wcmp"] = base + ".wcmp";
|
|
j["name"] = c.name;
|
|
j["count"] = c.entries.size();
|
|
nlohmann::json arr = nlohmann::json::array();
|
|
for (const auto& e : c.entries) {
|
|
arr.push_back({
|
|
{"companionId", e.companionId},
|
|
{"creatureId", e.creatureId},
|
|
{"name", e.name},
|
|
{"description", e.description},
|
|
{"iconPath", e.iconPath},
|
|
{"companionKind", e.companionKind},
|
|
{"companionKindName", wowee::pipeline::WoweeCompanion::companionKindName(e.companionKind)},
|
|
{"rarity", e.rarity},
|
|
{"rarityName", wowee::pipeline::WoweeCompanion::rarityName(e.rarity)},
|
|
{"factionRestriction", e.factionRestriction},
|
|
{"factionRestrictionName", wowee::pipeline::WoweeCompanion::factionRestrictionName(e.factionRestriction)},
|
|
{"learnSpellId", e.learnSpellId},
|
|
{"itemId", e.itemId},
|
|
{"idleSoundId", e.idleSoundId},
|
|
});
|
|
}
|
|
j["entries"] = arr;
|
|
std::printf("%s\n", j.dump(2).c_str());
|
|
return 0;
|
|
}
|
|
std::printf("WCMP: %s.wcmp\n", base.c_str());
|
|
std::printf(" catalog : %s\n", c.name.c_str());
|
|
std::printf(" companions : %zu\n", c.entries.size());
|
|
if (c.entries.empty()) return 0;
|
|
std::printf(" id creature kind rarity faction spell item sound name\n");
|
|
for (const auto& e : c.entries) {
|
|
std::printf(" %4u %5u %-10s %-8s %-8s %5u %5u %5u %s\n",
|
|
e.companionId, e.creatureId,
|
|
wowee::pipeline::WoweeCompanion::companionKindName(e.companionKind),
|
|
wowee::pipeline::WoweeCompanion::rarityName(e.rarity),
|
|
wowee::pipeline::WoweeCompanion::factionRestrictionName(e.factionRestriction),
|
|
e.learnSpellId, e.itemId, e.idleSoundId,
|
|
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 = stripWcmpExt(base);
|
|
if (!wowee::pipeline::WoweeCompanionLoader::exists(base)) {
|
|
std::fprintf(stderr,
|
|
"validate-wcmp: WCMP not found: %s.wcmp\n", base.c_str());
|
|
return 1;
|
|
}
|
|
auto c = wowee::pipeline::WoweeCompanionLoader::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.companionId);
|
|
if (!e.name.empty()) ctx += " " + e.name;
|
|
ctx += ")";
|
|
if (e.companionId == 0)
|
|
errors.push_back(ctx + ": companionId is 0");
|
|
if (e.name.empty())
|
|
errors.push_back(ctx + ": name is empty");
|
|
if (e.creatureId == 0)
|
|
errors.push_back(ctx +
|
|
": creatureId is 0 (companion has no rendered model)");
|
|
if (e.learnSpellId == 0)
|
|
errors.push_back(ctx +
|
|
": learnSpellId is 0 (no spell to summon companion)");
|
|
if (e.companionKind > wowee::pipeline::WoweeCompanion::UndeadCritter) {
|
|
errors.push_back(ctx + ": companionKind " +
|
|
std::to_string(e.companionKind) + " not in 0..7");
|
|
}
|
|
if (e.rarity > wowee::pipeline::WoweeCompanion::Epic) {
|
|
errors.push_back(ctx + ": rarity " +
|
|
std::to_string(e.rarity) + " not in 0..3");
|
|
}
|
|
if (e.factionRestriction > wowee::pipeline::WoweeCompanion::HordeOnly) {
|
|
errors.push_back(ctx + ": factionRestriction " +
|
|
std::to_string(e.factionRestriction) + " not in 0..2");
|
|
}
|
|
// Epic rarity without an itemId is unusual — promo
|
|
// pets typically have a redemption code item or
|
|
// collector's edition box.
|
|
if (e.rarity == wowee::pipeline::WoweeCompanion::Epic &&
|
|
e.itemId == 0) {
|
|
warnings.push_back(ctx +
|
|
": Epic rarity but itemId=0 (no source item — "
|
|
"verify intentional for code-only redemption)");
|
|
}
|
|
for (uint32_t prev : idsSeen) {
|
|
if (prev == e.companionId) {
|
|
errors.push_back(ctx + ": duplicate companionId");
|
|
break;
|
|
}
|
|
}
|
|
idsSeen.push_back(e.companionId);
|
|
}
|
|
bool ok = errors.empty();
|
|
if (jsonOut) {
|
|
nlohmann::json j;
|
|
j["wcmp"] = base + ".wcmp";
|
|
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-wcmp: %s.wcmp\n", base.c_str());
|
|
if (ok && warnings.empty()) {
|
|
std::printf(" OK — %zu companions, all companionIds 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 handleCompanionsCatalog(int& i, int argc, char** argv, int& outRc) {
|
|
if (std::strcmp(argv[i], "--gen-cmp") == 0 && i + 1 < argc) {
|
|
outRc = handleGenStarter(i, argc, argv); return true;
|
|
}
|
|
if (std::strcmp(argv[i], "--gen-cmp-rare") == 0 && i + 1 < argc) {
|
|
outRc = handleGenRare(i, argc, argv); return true;
|
|
}
|
|
if (std::strcmp(argv[i], "--gen-cmp-faction") == 0 && i + 1 < argc) {
|
|
outRc = handleGenFaction(i, argc, argv); return true;
|
|
}
|
|
if (std::strcmp(argv[i], "--info-wcmp") == 0 && i + 1 < argc) {
|
|
outRc = handleInfo(i, argc, argv); return true;
|
|
}
|
|
if (std::strcmp(argv[i], "--validate-wcmp") == 0 && i + 1 < argc) {
|
|
outRc = handleValidate(i, argc, argv); return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace cli
|
|
} // namespace editor
|
|
} // namespace wowee
|