From 12faffeb874e495b073107a6f772aaf981947748 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 9 May 2026 21:54:00 -0700 Subject: [PATCH] feat(editor): add WCEF (Creature Family) open catalog format Open replacement for CreatureFamily.dbc plus the per-creature family fields in Creature.dbc. Defines the family categorization that pet-able beasts share (Bear / Cat / Wolf / Boar / Crab / Raptor / Devilsaur / etc), each with its own pet talent tree (Ferocity / Tenacity / Cunning), food preferences as a bitmask (Meat / Fish / Bread / Cheese / Fruit / Fungus / Raw), the skill line that family-specific abilities reference, and the minimum hunter level required to tame it. Used by the hunter pet system to decide which talent tree a tamed pet uses, validate that a hunter can tame a creature, match feeding-table food items to pet preferences, and gate exotic-beast families behind the Beast Master 51-point talent. Cross-references back to WCRT (creature.familyId points here) and WSPL (family-specific abilities reference WSPL spellId via the skillLine field). Three preset emitters: --gen-cef (5 baseline families covering both major talent trees), --gen-cef-ferocity (4 DPS-tree pets with bleed/howl/armor-shred mechanics), --gen-cef-exotic (4 exotic Beast Master families requiring 51-point talent). Validation enforces id+name presence, familyKind 0..5, talent tree 0..3, no duplicate ids, and warns on: - NotPet families with a non-None talent tree (irrelevant) - Exotic families with minLevelForTame > 80 (level-cap unreachable) - Beast/Exotic families with no food types set (pet would starve) Wired through the cross-format table; WCEF appears automatically in all 10 cross-format utilities. Format count 71 -> 72; CLI flag count 914 -> 919. --- CMakeLists.txt | 3 + include/pipeline/wowee_creature_families.hpp | 123 ++++++++ src/pipeline/wowee_creature_families.cpp | 258 ++++++++++++++++ tools/editor/cli_arg_required.cpp | 2 + .../editor/cli_creature_families_catalog.cpp | 286 ++++++++++++++++++ .../editor/cli_creature_families_catalog.hpp | 12 + tools/editor/cli_dispatch.cpp | 2 + tools/editor/cli_format_table.cpp | 1 + tools/editor/cli_help.cpp | 10 + tools/editor/cli_list_formats.cpp | 1 + 10 files changed, 698 insertions(+) create mode 100644 include/pipeline/wowee_creature_families.hpp create mode 100644 src/pipeline/wowee_creature_families.cpp create mode 100644 tools/editor/cli_creature_families_catalog.cpp create mode 100644 tools/editor/cli_creature_families_catalog.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 70c44f1b..1ffdc0ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -660,6 +660,7 @@ set(WOWEE_SOURCES src/pipeline/wowee_spell_cast_times.cpp src/pipeline/wowee_spell_durations.cpp src/pipeline/wowee_spell_cooldowns.cpp + src/pipeline/wowee_creature_families.cpp src/pipeline/custom_zone_discovery.cpp src/pipeline/dbc_layout.cpp @@ -1476,6 +1477,7 @@ add_executable(wowee_editor 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_creature_families_catalog.cpp tools/editor/cli_quest_objective.cpp tools/editor/cli_quest_reward.cpp tools/editor/cli_clone.cpp @@ -1614,6 +1616,7 @@ add_executable(wowee_editor src/pipeline/wowee_spell_cast_times.cpp src/pipeline/wowee_spell_durations.cpp src/pipeline/wowee_spell_cooldowns.cpp + src/pipeline/wowee_creature_families.cpp src/pipeline/custom_zone_discovery.cpp src/pipeline/terrain_mesh.cpp diff --git a/include/pipeline/wowee_creature_families.hpp b/include/pipeline/wowee_creature_families.hpp new file mode 100644 index 00000000..44998809 --- /dev/null +++ b/include/pipeline/wowee_creature_families.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include + +namespace wowee { +namespace pipeline { + +// Wowee Open Creature Family catalog (.wcef) — novel +// replacement for Blizzard's CreatureFamily.dbc plus the +// per-creature family fields in Creature.dbc. Defines the +// family categorization that pet-able beasts share: Bear / +// Cat / Wolf / Boar / etc. Each family carries its own +// pet talent tree, food preferences, and the minimum +// hunter level required to tame it. +// +// Used by the hunter pet system to: +// - decide which talent tree (Ferocity / Tenacity / +// Cunning) a tamed pet uses, +// - validate that a hunter can tame a creature +// (minLevelForTame), +// - match feeding-table food items to pet preferences +// via the petFoodTypes bitmask, +// - and gate exotic-beast taming behind the Beast +// Master 51-point talent. +// +// Cross-references with previously-added formats: +// WCRT: creature.familyId points back to a WCEF entry. +// WSPL: family-specific abilities reference WSPL spellId +// via skillLine. +// +// Binary layout (little-endian): +// magic[4] = "WCEF" +// version (uint32) = current 1 +// nameLen + name (catalog label) +// entryCount (uint32) +// entries (each): +// familyId (uint32) +// nameLen + name +// descLen + description +// familyKind (uint8) / petTalentTree (uint8) +// minLevelForTame (uint8) / pad (uint8) +// skillLine (uint32) +// petFoodTypes (uint32) +// iconColorRGBA (uint32) +struct WoweeCreatureFamily { + enum FamilyKind : uint8_t { + Beast = 0, // standard tamable beast + Demon = 1, // warlock minion family (Imp, Voidwalker) + Undead = 2, // Death Knight ghoul / unholy minion + Elemental = 3, // shaman elemental totem + NotPet = 4, // creature has a family but is not pet-able + Exotic = 5, // tamable only by Beast Master 51-point talent + }; + + enum PetTalentTree : uint8_t { + TreeNone = 0, + Ferocity = 1, // damage-focused (Cat, Wolf, Raptor) + Tenacity = 2, // tank-focused (Bear, Boar, Crab) + Cunning = 3, // utility (Bird of Prey, Spider) + }; + + enum FoodType : uint32_t { + Meat = 1u << 0, + Fish = 1u << 1, + Bread = 1u << 2, + Cheese = 1u << 3, + Fruit = 1u << 4, + Fungus = 1u << 5, + Raw = 1u << 6, // raw/uncooked variant accepted + }; + + struct Entry { + uint32_t familyId = 0; + std::string name; + std::string description; + uint8_t familyKind = Beast; + uint8_t petTalentTree = TreeNone; + uint8_t minLevelForTame = 10; + uint8_t pad0 = 0; + uint32_t skillLine = 0; + uint32_t petFoodTypes = 0; + uint32_t iconColorRGBA = 0xFFFFFFFFu; + }; + + std::string name; + std::vector entries; + + bool isValid() const { return !entries.empty(); } + + const Entry* findById(uint32_t familyId) const; + + static const char* familyKindName(uint8_t k); + static const char* petTalentTreeName(uint8_t t); +}; + +class WoweeCreatureFamilyLoader { +public: + static bool save(const WoweeCreatureFamily& cat, + const std::string& basePath); + static WoweeCreatureFamily load(const std::string& basePath); + static bool exists(const std::string& basePath); + + // Preset emitters used by --gen-cef* variants. + // + // makeStarter — 5 baseline beast families (Bear, + // Cat, Wolf, Boar, Crab) covering + // one entry per pet talent tree + // plus a bonus Tenacity entry. + // makeFerocity — 4 Ferocity-tree damage pets + // (Cat, Wolf, Devilsaur, Raptor). + // makeExotic — 4 exotic Beast Master families + // (Worm, Devilsaur, Chimaera, + // CoreHound) — Exotic kind, requires + // 51-point BM talent to tame. + static WoweeCreatureFamily makeStarter(const std::string& catalogName); + static WoweeCreatureFamily makeFerocity(const std::string& catalogName); + static WoweeCreatureFamily makeExotic(const std::string& catalogName); +}; + +} // namespace pipeline +} // namespace wowee diff --git a/src/pipeline/wowee_creature_families.cpp b/src/pipeline/wowee_creature_families.cpp new file mode 100644 index 00000000..ab03be0c --- /dev/null +++ b/src/pipeline/wowee_creature_families.cpp @@ -0,0 +1,258 @@ +#include "pipeline/wowee_creature_families.hpp" + +#include +#include +#include + +namespace wowee { +namespace pipeline { + +namespace { + +constexpr char kMagic[4] = {'W', 'C', 'E', 'F'}; +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) != ".wcef") { + base += ".wcef"; + } + return base; +} + +uint32_t packRgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 0xFF) { + return (static_cast(a) << 24) | + (static_cast(b) << 16) | + (static_cast(g) << 8) | + static_cast(r); +} + +} // namespace + +const WoweeCreatureFamily::Entry* +WoweeCreatureFamily::findById(uint32_t familyId) const { + for (const auto& e : entries) + if (e.familyId == familyId) return &e; + return nullptr; +} + +const char* WoweeCreatureFamily::familyKindName(uint8_t k) { + switch (k) { + case Beast: return "beast"; + case Demon: return "demon"; + case Undead: return "undead"; + case Elemental: return "elemental"; + case NotPet: return "not-pet"; + case Exotic: return "exotic"; + default: return "unknown"; + } +} + +const char* WoweeCreatureFamily::petTalentTreeName(uint8_t t) { + switch (t) { + case TreeNone: return "none"; + case Ferocity: return "ferocity"; + case Tenacity: return "tenacity"; + case Cunning: return "cunning"; + default: return "unknown"; + } +} + +bool WoweeCreatureFamilyLoader::save(const WoweeCreatureFamily& 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.familyId); + writeStr(os, e.name); + writeStr(os, e.description); + writePOD(os, e.familyKind); + writePOD(os, e.petTalentTree); + writePOD(os, e.minLevelForTame); + writePOD(os, e.pad0); + writePOD(os, e.skillLine); + writePOD(os, e.petFoodTypes); + writePOD(os, e.iconColorRGBA); + } + return os.good(); +} + +WoweeCreatureFamily WoweeCreatureFamilyLoader::load( + const std::string& basePath) { + WoweeCreatureFamily 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.familyId)) { + out.entries.clear(); return out; + } + if (!readStr(is, e.name) || !readStr(is, e.description)) { + out.entries.clear(); return out; + } + if (!readPOD(is, e.familyKind) || + !readPOD(is, e.petTalentTree) || + !readPOD(is, e.minLevelForTame) || + !readPOD(is, e.pad0) || + !readPOD(is, e.skillLine) || + !readPOD(is, e.petFoodTypes) || + !readPOD(is, e.iconColorRGBA)) { + out.entries.clear(); return out; + } + } + return out; +} + +bool WoweeCreatureFamilyLoader::exists(const std::string& basePath) { + std::ifstream is(normalizePath(basePath), std::ios::binary); + return is.good(); +} + +WoweeCreatureFamily WoweeCreatureFamilyLoader::makeStarter( + const std::string& catalogName) { + using F = WoweeCreatureFamily; + WoweeCreatureFamily c; + c.name = catalogName; + auto add = [&](uint32_t id, const char* name, uint8_t kind, + uint8_t tree, uint8_t minLvl, uint32_t skill, + uint32_t foods, + uint8_t r, uint8_t g, uint8_t b, + const char* desc) { + F::Entry e; + e.familyId = id; e.name = name; e.description = desc; + e.familyKind = kind; + e.petTalentTree = tree; + e.minLevelForTame = minLvl; + e.skillLine = skill; + e.petFoodTypes = foods; + e.iconColorRGBA = packRgba(r, g, b); + c.entries.push_back(e); + }; + add(1, "Bear", F::Beast, F::Tenacity, 10, 208, + F::Meat | F::Fish | F::Fruit | F::Fungus | F::Raw, + 140, 100, 60, "Bear — tenacity tank pet, omnivore."); + add(2, "Cat", F::Beast, F::Ferocity, 10, 209, + F::Meat | F::Fish | F::Raw, + 220, 180, 60, "Cat — ferocity DPS pet, carnivore."); + add(3, "Wolf", F::Beast, F::Ferocity, 10, 210, + F::Meat | F::Raw, + 180, 180, 180, "Wolf — ferocity DPS pet, meat-only."); + add(4, "Boar", F::Beast, F::Tenacity, 10, 211, + F::Meat | F::Fruit | F::Fungus | F::Bread, + 160, 120, 100, "Boar — tenacity tank pet, ravenous omnivore."); + add(5, "Crab", F::Beast, F::Tenacity, 10, 212, + F::Fish | F::Meat | F::Raw, + 120, 180, 200, "Crab — tenacity tank pet, prefers fish."); + return c; +} + +WoweeCreatureFamily WoweeCreatureFamilyLoader::makeFerocity( + const std::string& catalogName) { + using F = WoweeCreatureFamily; + WoweeCreatureFamily c; + c.name = catalogName; + auto add = [&](uint32_t id, const char* name, uint8_t minLvl, + uint32_t skill, uint32_t foods, + const char* desc) { + F::Entry e; + e.familyId = id; e.name = name; e.description = desc; + e.familyKind = F::Beast; + e.petTalentTree = F::Ferocity; + e.minLevelForTame = minLvl; + e.skillLine = skill; + e.petFoodTypes = foods; + e.iconColorRGBA = packRgba(220, 60, 60); // red — DPS + c.entries.push_back(e); + }; + add(100, "Cat", 10, 209, F::Meat | F::Fish | F::Raw, + "Cat — fast attack speed, claws hit hard."); + add(101, "Wolf", 10, 210, F::Meat | F::Raw, + "Wolf — Furious Howl pack buff (10% AP raid-wide)."); + add(102, "Raptor", 10, 213, F::Meat | F::Raw, + "Raptor — bleed effect on melee strikes."); + add(103, "Devilsaur", 30, 214, F::Meat | F::Raw, + "Devilsaur — Monstrous Bite armor reduction."); + return c; +} + +WoweeCreatureFamily WoweeCreatureFamilyLoader::makeExotic( + const std::string& catalogName) { + using F = WoweeCreatureFamily; + WoweeCreatureFamily c; + c.name = catalogName; + auto add = [&](uint32_t id, const char* name, uint8_t tree, + uint8_t minLvl, uint32_t skill, uint32_t foods, + const char* desc) { + F::Entry e; + e.familyId = id; e.name = name; e.description = desc; + e.familyKind = F::Exotic; + e.petTalentTree = tree; + e.minLevelForTame = minLvl; + e.skillLine = skill; + e.petFoodTypes = foods; + e.iconColorRGBA = packRgba(200, 100, 240); // purple — exotic + c.entries.push_back(e); + }; + add(200, "Worm", F::Tenacity, 50, 220, + F::Meat | F::Fungus | F::Raw, + "Worm — exotic tenacity, Acid Spit reduces target armor."); + add(201, "Devilsaur", F::Ferocity, 60, 214, + F::Meat | F::Raw, + "Devilsaur — exotic, Monstrous Bite + huge HP pool."); + add(202, "Chimaera", F::Cunning, 60, 221, + F::Meat | F::Raw, + "Chimaera — exotic cunning, Froststorm Breath chain frost."); + add(203, "CoreHound", F::Ferocity, 60, 222, + F::Meat | F::Raw, + "Core Hound — exotic, Lava Breath + Ancient Hysteria " + "raid bloodlust."); + return c; +} + +} // namespace pipeline +} // namespace wowee diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index 81d9095d..0e9ae7a8 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -218,6 +218,8 @@ const char* const kArgRequired[] = { "--gen-cdb", "--gen-cdb-class", "--gen-cdb-items", "--info-wscd", "--validate-wscd", "--export-wscd-json", "--import-wscd-json", + "--gen-cef", "--gen-cef-ferocity", "--gen-cef-exotic", + "--info-wcef", "--validate-wcef", "--gen-weather-temperate", "--gen-weather-arctic", "--gen-weather-desert", "--gen-weather-stormy", "--gen-zone-atmosphere", diff --git a/tools/editor/cli_creature_families_catalog.cpp b/tools/editor/cli_creature_families_catalog.cpp new file mode 100644 index 00000000..50402667 --- /dev/null +++ b/tools/editor/cli_creature_families_catalog.cpp @@ -0,0 +1,286 @@ +#include "cli_creature_families_catalog.hpp" +#include "cli_arg_parse.hpp" +#include "cli_box_emitter.hpp" + +#include "pipeline/wowee_creature_families.hpp" +#include + +#include +#include +#include +#include +#include +#include + +namespace wowee { +namespace editor { +namespace cli { + +namespace { + +std::string stripWcefExt(std::string base) { + stripExt(base, ".wcef"); + return base; +} + +bool saveOrError(const wowee::pipeline::WoweeCreatureFamily& c, + const std::string& base, const char* cmd) { + if (!wowee::pipeline::WoweeCreatureFamilyLoader::save(c, base)) { + std::fprintf(stderr, "%s: failed to save %s.wcef\n", + cmd, base.c_str()); + return false; + } + return true; +} + +void printGenSummary(const wowee::pipeline::WoweeCreatureFamily& c, + const std::string& base) { + std::printf("Wrote %s.wcef\n", base.c_str()); + std::printf(" catalog : %s\n", c.name.c_str()); + std::printf(" families : %zu\n", c.entries.size()); +} + +int handleGenStarter(int& i, int argc, char** argv) { + std::string base = argv[++i]; + std::string name = "StarterFamilies"; + if (parseOptArg(i, argc, argv)) name = argv[++i]; + base = stripWcefExt(base); + auto c = wowee::pipeline::WoweeCreatureFamilyLoader::makeStarter(name); + if (!saveOrError(c, base, "gen-cef")) return 1; + printGenSummary(c, base); + return 0; +} + +int handleGenFerocity(int& i, int argc, char** argv) { + std::string base = argv[++i]; + std::string name = "FerocityPets"; + if (parseOptArg(i, argc, argv)) name = argv[++i]; + base = stripWcefExt(base); + auto c = wowee::pipeline::WoweeCreatureFamilyLoader::makeFerocity(name); + if (!saveOrError(c, base, "gen-cef-ferocity")) return 1; + printGenSummary(c, base); + return 0; +} + +int handleGenExotic(int& i, int argc, char** argv) { + std::string base = argv[++i]; + std::string name = "ExoticBeastMaster"; + if (parseOptArg(i, argc, argv)) name = argv[++i]; + base = stripWcefExt(base); + auto c = wowee::pipeline::WoweeCreatureFamilyLoader::makeExotic(name); + if (!saveOrError(c, base, "gen-cef-exotic")) return 1; + printGenSummary(c, base); + return 0; +} + +void appendFoodNames(uint32_t flags, std::string& out) { + using F = wowee::pipeline::WoweeCreatureFamily; + auto add = [&](const char* n) { + if (!out.empty()) out += "|"; + out += n; + }; + if (flags & F::Meat) add("Meat"); + if (flags & F::Fish) add("Fish"); + if (flags & F::Bread) add("Bread"); + if (flags & F::Cheese) add("Cheese"); + if (flags & F::Fruit) add("Fruit"); + if (flags & F::Fungus) add("Fungus"); + if (flags & F::Raw) add("Raw"); + if (out.empty()) out = "-"; +} + +int handleInfo(int& i, int argc, char** argv) { + std::string base = argv[++i]; + bool jsonOut = consumeJsonFlag(i, argc, argv); + base = stripWcefExt(base); + if (!wowee::pipeline::WoweeCreatureFamilyLoader::exists(base)) { + std::fprintf(stderr, "WCEF not found: %s.wcef\n", base.c_str()); + return 1; + } + auto c = wowee::pipeline::WoweeCreatureFamilyLoader::load(base); + if (jsonOut) { + nlohmann::json j; + j["wcef"] = base + ".wcef"; + j["name"] = c.name; + j["count"] = c.entries.size(); + nlohmann::json arr = nlohmann::json::array(); + for (const auto& e : c.entries) { + std::string foodNames; + appendFoodNames(e.petFoodTypes, foodNames); + arr.push_back({ + {"familyId", e.familyId}, + {"name", e.name}, + {"description", e.description}, + {"familyKind", e.familyKind}, + {"familyKindName", wowee::pipeline::WoweeCreatureFamily::familyKindName(e.familyKind)}, + {"petTalentTree", e.petTalentTree}, + {"petTalentTreeName", wowee::pipeline::WoweeCreatureFamily::petTalentTreeName(e.petTalentTree)}, + {"minLevelForTame", e.minLevelForTame}, + {"skillLine", e.skillLine}, + {"petFoodTypes", e.petFoodTypes}, + {"petFoodTypesLabels", foodNames}, + {"iconColorRGBA", e.iconColorRGBA}, + }); + } + j["entries"] = arr; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("WCEF: %s.wcef\n", base.c_str()); + std::printf(" catalog : %s\n", c.name.c_str()); + std::printf(" families : %zu\n", c.entries.size()); + if (c.entries.empty()) return 0; + std::printf(" id kind tree tameLvl skill foods name\n"); + for (const auto& e : c.entries) { + std::string foodNames; + appendFoodNames(e.petFoodTypes, foodNames); + std::printf(" %4u %-9s %-9s %5u %5u %-30s %s\n", + e.familyId, + wowee::pipeline::WoweeCreatureFamily::familyKindName(e.familyKind), + wowee::pipeline::WoweeCreatureFamily::petTalentTreeName(e.petTalentTree), + e.minLevelForTame, + e.skillLine, + foodNames.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 = stripWcefExt(base); + if (!wowee::pipeline::WoweeCreatureFamilyLoader::exists(base)) { + std::fprintf(stderr, + "validate-wcef: WCEF not found: %s.wcef\n", base.c_str()); + return 1; + } + auto c = wowee::pipeline::WoweeCreatureFamilyLoader::load(base); + std::vector errors; + std::vector warnings; + if (c.entries.empty()) { + warnings.push_back("catalog has zero entries"); + } + std::vector idsSeen; + constexpr uint32_t kKnownFoodMask = + wowee::pipeline::WoweeCreatureFamily::Meat | + wowee::pipeline::WoweeCreatureFamily::Fish | + wowee::pipeline::WoweeCreatureFamily::Bread | + wowee::pipeline::WoweeCreatureFamily::Cheese | + wowee::pipeline::WoweeCreatureFamily::Fruit | + wowee::pipeline::WoweeCreatureFamily::Fungus | + wowee::pipeline::WoweeCreatureFamily::Raw; + 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.familyId); + if (!e.name.empty()) ctx += " " + e.name; + ctx += ")"; + if (e.familyId == 0) + errors.push_back(ctx + ": familyId is 0"); + if (e.name.empty()) + errors.push_back(ctx + ": name is empty"); + if (e.familyKind > wowee::pipeline::WoweeCreatureFamily::Exotic) { + errors.push_back(ctx + ": familyKind " + + std::to_string(e.familyKind) + " not in 0..5"); + } + if (e.petTalentTree > wowee::pipeline::WoweeCreatureFamily::Cunning) { + errors.push_back(ctx + ": petTalentTree " + + std::to_string(e.petTalentTree) + " not in 0..3"); + } + if (e.petFoodTypes & ~kKnownFoodMask) { + warnings.push_back(ctx + + ": petFoodTypes has bits outside known mask " + + "(0x" + std::to_string(e.petFoodTypes & ~kKnownFoodMask) + + ") — engine will ignore unknown food types"); + } + // NotPet families should not specify a talent tree — + // confusing if they do. + if (e.familyKind == wowee::pipeline::WoweeCreatureFamily::NotPet && + e.petTalentTree != wowee::pipeline::WoweeCreatureFamily::TreeNone) { + warnings.push_back(ctx + + ": NotPet family with petTalentTree=" + + wowee::pipeline::WoweeCreatureFamily::petTalentTreeName(e.petTalentTree) + + " — talent tree is irrelevant for non-pet kinds"); + } + // Exotic families above level 80 won't be tamable + // by anyone (level cap). + if (e.familyKind == wowee::pipeline::WoweeCreatureFamily::Exotic && + e.minLevelForTame > 80) { + warnings.push_back(ctx + + ": Exotic family with minLevelForTame=" + + std::to_string(e.minLevelForTame) + + " > 80 — no hunter can reach this level"); + } + // Pet kinds with no food types set means they can't + // be fed — common bug, especially for hand-edited + // sidecars. + if ((e.familyKind == wowee::pipeline::WoweeCreatureFamily::Beast || + e.familyKind == wowee::pipeline::WoweeCreatureFamily::Exotic) && + e.petFoodTypes == 0) { + warnings.push_back(ctx + + ": pet-able family with no food types set — " + "hunter pet will starve, no food will satisfy it"); + } + for (uint32_t prev : idsSeen) { + if (prev == e.familyId) { + errors.push_back(ctx + ": duplicate familyId"); + break; + } + } + idsSeen.push_back(e.familyId); + } + bool ok = errors.empty(); + if (jsonOut) { + nlohmann::json j; + j["wcef"] = base + ".wcef"; + 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-wcef: %s.wcef\n", base.c_str()); + if (ok && warnings.empty()) { + std::printf(" OK — %zu families, all familyIds 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 handleCreatureFamiliesCatalog(int& i, int argc, char** argv, + int& outRc) { + if (std::strcmp(argv[i], "--gen-cef") == 0 && i + 1 < argc) { + outRc = handleGenStarter(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--gen-cef-ferocity") == 0 && i + 1 < argc) { + outRc = handleGenFerocity(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--gen-cef-exotic") == 0 && i + 1 < argc) { + outRc = handleGenExotic(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--info-wcef") == 0 && i + 1 < argc) { + outRc = handleInfo(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--validate-wcef") == 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_creature_families_catalog.hpp b/tools/editor/cli_creature_families_catalog.hpp new file mode 100644 index 00000000..794beb66 --- /dev/null +++ b/tools/editor/cli_creature_families_catalog.hpp @@ -0,0 +1,12 @@ +#pragma once + +namespace wowee { +namespace editor { +namespace cli { + +bool handleCreatureFamiliesCatalog(int& i, int argc, char** argv, + int& outRc); + +} // namespace cli +} // namespace editor +} // namespace wowee diff --git a/tools/editor/cli_dispatch.cpp b/tools/editor/cli_dispatch.cpp index 87a3ddf8..62bc198a 100644 --- a/tools/editor/cli_dispatch.cpp +++ b/tools/editor/cli_dispatch.cpp @@ -109,6 +109,7 @@ #include "cli_spell_cast_times_catalog.hpp" #include "cli_spell_durations_catalog.hpp" #include "cli_spell_cooldowns_catalog.hpp" +#include "cli_creature_families_catalog.hpp" #include "cli_quest_objective.hpp" #include "cli_quest_reward.hpp" #include "cli_clone.hpp" @@ -259,6 +260,7 @@ constexpr DispatchFn kDispatchTable[] = { handleSpellCastTimesCatalog, handleSpellDurationsCatalog, handleSpellCooldownsCatalog, + handleCreatureFamiliesCatalog, handleQuestObjective, handleQuestReward, handleClone, diff --git a/tools/editor/cli_format_table.cpp b/tools/editor/cli_format_table.cpp index a9c92830..f9f1b3ad 100644 --- a/tools/editor/cli_format_table.cpp +++ b/tools/editor/cli_format_table.cpp @@ -74,6 +74,7 @@ constexpr FormatMagicEntry kFormats[] = { {{'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','C','E','F'}, ".wcef", "creatures", "--info-wcef", "Creature / pet family 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"}, diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index 279bc0c4..e0363bf9 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -1745,6 +1745,16 @@ void printUsage(const char* argv0) { std::printf(" Export binary .wscd to a human-editable JSON sidecar (defaults to .wscd.json)\n"); std::printf(" --import-wscd-json [out-base]\n"); std::printf(" Import a .wscd.json sidecar back into binary .wscd (accepts bucketKind int OR name; categoryFlags as int OR pipe-separated label string)\n"); + std::printf(" --gen-cef [name]\n"); + std::printf(" Emit .wcef starter: 5 baseline beast families (Bear/Cat/Wolf/Boar/Crab) covering Tenacity + Ferocity trees\n"); + std::printf(" --gen-cef-ferocity [name]\n"); + std::printf(" Emit .wcef 4 Ferocity-tree DPS pets (Cat / Wolf / Raptor / Devilsaur) with bleed/howl/armor mechanics\n"); + std::printf(" --gen-cef-exotic [name]\n"); + std::printf(" Emit .wcef 4 exotic Beast Master families (Worm / Devilsaur / Chimaera / Core Hound) — Exotic kind, requires 51-pt BM\n"); + std::printf(" --info-wcef [--json]\n"); + std::printf(" Print WCEF entries (id / kind / talent tree / tame level / skill line / food types / name)\n"); + std::printf(" --validate-wcef [--json]\n"); + std::printf(" Static checks: id+name required, familyKind 0..5, talent tree 0..3, no duplicate ids; warns on NotPet+talent, Exotic+lvl>80, Beast/Exotic with no foods (would starve)\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"); diff --git a/tools/editor/cli_list_formats.cpp b/tools/editor/cli_list_formats.cpp index d7eae95b..baf52c38 100644 --- a/tools/editor/cli_list_formats.cpp +++ b/tools/editor/cli_list_formats.cpp @@ -96,6 +96,7 @@ constexpr FormatRow kFormats[] = { {"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"}, + {"WCEF", ".wcef", "creatures", "CreatureFamily.dbc + pet trees", "Creature / pet family catalog"}, // Additional pipeline catalogs without the alternating // gen/info/validate CLI surface (loaded by the engine