diff --git a/CMakeLists.txt b/CMakeLists.txt index 191819c2..05cafd09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -667,6 +667,7 @@ set(WOWEE_SOURCES src/pipeline/wowee_item_materials.cpp src/pipeline/wowee_player_spawn_profiles.cpp src/pipeline/wowee_talent_tabs.cpp + src/pipeline/wowee_currency_types.cpp src/pipeline/custom_zone_discovery.cpp src/pipeline/dbc_layout.cpp @@ -1492,6 +1493,7 @@ add_executable(wowee_editor tools/editor/cli_item_materials_catalog.cpp tools/editor/cli_player_spawn_profiles_catalog.cpp tools/editor/cli_talent_tabs_catalog.cpp + tools/editor/cli_currency_types_catalog.cpp tools/editor/cli_quest_objective.cpp tools/editor/cli_quest_reward.cpp tools/editor/cli_clone.cpp @@ -1637,6 +1639,7 @@ add_executable(wowee_editor src/pipeline/wowee_item_materials.cpp src/pipeline/wowee_player_spawn_profiles.cpp src/pipeline/wowee_talent_tabs.cpp + src/pipeline/wowee_currency_types.cpp src/pipeline/custom_zone_discovery.cpp src/pipeline/terrain_mesh.cpp diff --git a/include/pipeline/wowee_currency_types.hpp b/include/pipeline/wowee_currency_types.hpp new file mode 100644 index 00000000..d439e70d --- /dev/null +++ b/include/pipeline/wowee_currency_types.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include + +namespace wowee { +namespace pipeline { + +// Wowee Open Currency Type catalog (.wctr) — novel +// replacement for Blizzard's CurrencyTypes.dbc plus the +// per-currency cap tables in CurrencyCategory.dbc. +// Defines the in-game currencies that are NOT regular +// item stacks: Honor Points, Arena Points, Justice +// Points, Valor Points, Conquest Points, plus the various +// faction tokens (Champion's Seal, Wintergrasp Mark of +// Honor, Emblem of Frost, etc). +// +// Distinct from regular items in WIT — currencies are +// tracked per-character as scalar quantities with +// weekly+absolute caps, not as stackable inventory slots. +// Some currencies are still backed by a WIT item entry +// for the icon/tooltip (itemId field), while others +// (Honor, Arena) live entirely in the currency system. +// +// Cross-references with previously-added formats: +// WIT: itemId references WIT entries for the icon +// and tooltip text on currency-display tooltips. +// WFAC: currencies tied to faction reputation +// (Champion's Seal -> Argent Crusade) reference +// WFAC factionId via the categoryId field. +// +// Binary layout (little-endian): +// magic[4] = "WCTR" +// version (uint32) = current 1 +// nameLen + name (catalog label) +// entryCount (uint32) +// entries (each): +// currencyId (uint32) +// nameLen + name +// descLen + description +// itemId (uint32) // 0 if pure currency +// maxQuantity (uint32) // 0 = no cap +// maxQuantityWeekly (uint32) // 0 = no weekly cap +// categoryId (uint32) +// currencyKind (uint8) / isAccountWide (uint8) / pad[2] +// iconPathLen + iconPath +// iconColorRGBA (uint32) +struct WoweeCurrencyType { + enum CurrencyKind : uint8_t { + PvPHonor = 0, // Honor / Conquest / Arena + PvERaid = 1, // Justice / Valor / Emblems + FactionToken = 2, // faction-rep-gated token + EventToken = 3, // holiday / world-event token + Crafting = 4, // crafting reputation token + Misc = 5, // catch-all + }; + + struct Entry { + uint32_t currencyId = 0; + std::string name; + std::string description; + uint32_t itemId = 0; + uint32_t maxQuantity = 0; + uint32_t maxQuantityWeekly = 0; + uint32_t categoryId = 0; + uint8_t currencyKind = PvPHonor; + uint8_t isAccountWide = 0; + uint8_t pad0 = 0; + uint8_t pad1 = 0; + std::string iconPath; + uint32_t iconColorRGBA = 0xFFFFFFFFu; + }; + + std::string name; + std::vector entries; + + bool isValid() const { return !entries.empty(); } + + const Entry* findById(uint32_t currencyId) const; + + // Returns the smaller of (remaining weekly cap, + // remaining absolute cap) — i.e. the maximum amount a + // character can earn right now given current balances. + // Either cap is unbounded if the corresponding field + // is 0. + uint32_t earnableNow(uint32_t currencyId, + uint32_t currentTotal, + uint32_t earnedThisWeek) const; + + static const char* currencyKindName(uint8_t k); +}; + +class WoweeCurrencyTypeLoader { +public: + static bool save(const WoweeCurrencyType& cat, + const std::string& basePath); + static WoweeCurrencyType load(const std::string& basePath); + static bool exists(const std::string& basePath); + + // Preset emitters used by --gen-ctr* variants. + // + // makePvP — 4 PvP currencies (Honor Points 75k + // cap, Arena Points 5k cap weekly, + // Conquest Points 1650 weekly, + // Champion's Seal no cap). + // makePvE — 4 PvE raid currencies (Justice Points + // 4k cap, Valor Points 1k weekly, + // Emblem of Frost no weekly cap, + // Trophy of the Crusade no cap). + // makeFactionTokens — 4 faction reputation tokens + // (Hodir Spear-fragment, Cenarion Mark, + // Argent Dawn Valor Token, Wintergrasp + // Mark) — no quantity cap, gated by + // reputation thresholds. + static WoweeCurrencyType makePvP(const std::string& catalogName); + static WoweeCurrencyType makePvE(const std::string& catalogName); + static WoweeCurrencyType makeFactionTokens(const std::string& catalogName); +}; + +} // namespace pipeline +} // namespace wowee diff --git a/src/pipeline/wowee_currency_types.cpp b/src/pipeline/wowee_currency_types.cpp new file mode 100644 index 00000000..cca2d510 --- /dev/null +++ b/src/pipeline/wowee_currency_types.cpp @@ -0,0 +1,281 @@ +#include "pipeline/wowee_currency_types.hpp" + +#include +#include +#include +#include + +namespace wowee { +namespace pipeline { + +namespace { + +constexpr char kMagic[4] = {'W', 'C', 'T', 'R'}; +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) != ".wctr") { + base += ".wctr"; + } + 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 WoweeCurrencyType::Entry* +WoweeCurrencyType::findById(uint32_t currencyId) const { + for (const auto& e : entries) + if (e.currencyId == currencyId) return &e; + return nullptr; +} + +uint32_t WoweeCurrencyType::earnableNow(uint32_t currencyId, + uint32_t currentTotal, + uint32_t earnedThisWeek) const { + const Entry* e = findById(currencyId); + if (!e) return 0; + uint32_t remainAbs = (e->maxQuantity == 0) + ? UINT32_MAX + : (e->maxQuantity > currentTotal + ? e->maxQuantity - currentTotal : 0); + uint32_t remainWeek = (e->maxQuantityWeekly == 0) + ? UINT32_MAX + : (e->maxQuantityWeekly > earnedThisWeek + ? e->maxQuantityWeekly - earnedThisWeek : 0); + return std::min(remainAbs, remainWeek); +} + +const char* WoweeCurrencyType::currencyKindName(uint8_t k) { + switch (k) { + case PvPHonor: return "pvp-honor"; + case PvERaid: return "pve-raid"; + case FactionToken: return "faction-token"; + case EventToken: return "event-token"; + case Crafting: return "crafting"; + case Misc: return "misc"; + default: return "unknown"; + } +} + +bool WoweeCurrencyTypeLoader::save(const WoweeCurrencyType& 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.currencyId); + writeStr(os, e.name); + writeStr(os, e.description); + writePOD(os, e.itemId); + writePOD(os, e.maxQuantity); + writePOD(os, e.maxQuantityWeekly); + writePOD(os, e.categoryId); + writePOD(os, e.currencyKind); + writePOD(os, e.isAccountWide); + writePOD(os, e.pad0); + writePOD(os, e.pad1); + writeStr(os, e.iconPath); + writePOD(os, e.iconColorRGBA); + } + return os.good(); +} + +WoweeCurrencyType WoweeCurrencyTypeLoader::load( + const std::string& basePath) { + WoweeCurrencyType 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.currencyId)) { + out.entries.clear(); return out; + } + if (!readStr(is, e.name) || !readStr(is, e.description)) { + out.entries.clear(); return out; + } + if (!readPOD(is, e.itemId) || + !readPOD(is, e.maxQuantity) || + !readPOD(is, e.maxQuantityWeekly) || + !readPOD(is, e.categoryId) || + !readPOD(is, e.currencyKind) || + !readPOD(is, e.isAccountWide) || + !readPOD(is, e.pad0) || + !readPOD(is, e.pad1)) { + out.entries.clear(); return out; + } + if (!readStr(is, e.iconPath)) { + out.entries.clear(); return out; + } + if (!readPOD(is, e.iconColorRGBA)) { + out.entries.clear(); return out; + } + } + return out; +} + +bool WoweeCurrencyTypeLoader::exists(const std::string& basePath) { + std::ifstream is(normalizePath(basePath), std::ios::binary); + return is.good(); +} + +WoweeCurrencyType WoweeCurrencyTypeLoader::makePvP( + const std::string& catalogName) { + using C = WoweeCurrencyType; + WoweeCurrencyType c; + c.name = catalogName; + auto add = [&](uint32_t id, const char* name, uint32_t item, + uint32_t maxQ, uint32_t maxWeekly, + uint8_t kind, uint8_t accountWide, + const char* icon, const char* desc) { + C::Entry e; + e.currencyId = id; e.name = name; e.description = desc; + e.itemId = item; + e.maxQuantity = maxQ; + e.maxQuantityWeekly = maxWeekly; + e.categoryId = 1; // category 1 = PvP + e.currencyKind = kind; + e.isAccountWide = accountWide; + e.iconPath = icon; + e.iconColorRGBA = packRgba(220, 200, 100); // pvp gold + c.entries.push_back(e); + }; + // 3.3.5a Honor Points cap is 75000; Arena Points are + // weekly only (5000); Cata-style Conquest Points 1650 + // weekly cap. + add(43308, "HonorPoints", 0, 75000, 0, + C::PvPHonor, 0, "Interface\\Icons\\PVPCurrency-Honor-Alliance", + "Honor Points — earned in PvP, 75k absolute cap."); + add(390, "ArenaPoints", 29024, 0, 5000, + C::PvPHonor, 0, "Interface\\Icons\\PVPCurrency-Conquest-Alliance", + "Arena Points — weekly 5k earn cap, no absolute cap."); + add(390000,"ConquestPoints",0, 0, 1650, + C::PvPHonor, 0, "Interface\\Icons\\PVPCurrency-Conquest-Alliance", + "Conquest Points (Cata-style) — 1650 weekly cap."); + add(241, "ChampionsSeal", 44990, 0, 0, + C::PvPHonor, 0, "Interface\\Icons\\Achievement_PVP_A_18", + "Champion's Seal — Argent Tournament token, no cap."); + return c; +} + +WoweeCurrencyType WoweeCurrencyTypeLoader::makePvE( + const std::string& catalogName) { + using C = WoweeCurrencyType; + WoweeCurrencyType c; + c.name = catalogName; + auto add = [&](uint32_t id, const char* name, uint32_t item, + uint32_t maxQ, uint32_t maxWeekly, + const char* icon, const char* desc) { + C::Entry e; + e.currencyId = id; e.name = name; e.description = desc; + e.itemId = item; + e.maxQuantity = maxQ; + e.maxQuantityWeekly = maxWeekly; + e.categoryId = 2; // category 2 = PvE + e.currencyKind = C::PvERaid; + e.iconPath = icon; + e.iconColorRGBA = packRgba(180, 100, 240); // raid epic purple + c.entries.push_back(e); + }; + add(395, "JusticePoints", 0, 4000, 0, + "Interface\\Icons\\Achievement_GuildPerk_Honorable", + "Justice Points (Cata-style) — 4k absolute cap."); + add(396, "ValorPoints", 0, 0, 1000, + "Interface\\Icons\\Achievement_Reputation_07", + "Valor Points — 1k weekly cap, no absolute cap."); + add(341, "EmblemOfFrost", 49426, 0, 0, + "Interface\\Icons\\INV_Misc_Token_HonorBound", + "Emblem of Frost — no caps, ICC-tier raid currency."); + add(442, "TrophyOfCrusade", 47241, 0, 0, + "Interface\\Icons\\INV_Misc_Token_Argentdawn", + "Trophy of the Crusade — no cap, T9 token."); + return c; +} + +WoweeCurrencyType WoweeCurrencyTypeLoader::makeFactionTokens( + const std::string& catalogName) { + using C = WoweeCurrencyType; + WoweeCurrencyType c; + c.name = catalogName; + auto add = [&](uint32_t id, const char* name, uint32_t item, + uint32_t factionId, const char* icon, + const char* desc) { + C::Entry e; + e.currencyId = id; e.name = name; e.description = desc; + e.itemId = item; + e.categoryId = factionId; // categoryId references WFAC + e.currencyKind = C::FactionToken; + e.iconPath = icon; + e.iconColorRGBA = packRgba(100, 200, 100); // faction green + c.entries.push_back(e); + }; + // Faction tokens — gated by reputation, not by a cap. + // categoryId references the WFAC.factionId. + add(1000, "SpearFragmentOfHodir", 41511, 1119, + "Interface\\Icons\\INV_Spear_05", + "Spear-fragment of Hodir — Sons of Hodir rep token."); + add(1001, "MarkOfTheCenarion", 20809, 609, + "Interface\\Icons\\INV_Misc_Cape_18", + "Mark of the Cenarion — Cenarion Circle rep token."); + add(1002, "ArgentDawnValorToken", 12846, 529, + "Interface\\Icons\\INV_Jewelry_Talisman_05", + "Argent Dawn Valor Token — Argent Dawn rep token."); + add(1003, "WintergraspMark", 43589, 1156, + "Interface\\Icons\\Achievement_Zone_Wintergrasp_01", + "Wintergrasp Mark of Honor — Wintergrasp rep token."); + return c; +} + +} // namespace pipeline +} // namespace wowee diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index ad876054..ae38d126 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -240,6 +240,8 @@ const char* const kArgRequired[] = { "--gen-tle", "--gen-tle-mage", "--gen-tle-paladin", "--info-wtle", "--validate-wtle", "--export-wtle-json", "--import-wtle-json", + "--gen-ctr", "--gen-ctr-pve", "--gen-ctr-faction", + "--info-wctr", "--validate-wctr", "--gen-weather-temperate", "--gen-weather-arctic", "--gen-weather-desert", "--gen-weather-stormy", "--gen-zone-atmosphere", diff --git a/tools/editor/cli_currency_types_catalog.cpp b/tools/editor/cli_currency_types_catalog.cpp new file mode 100644 index 00000000..08be939d --- /dev/null +++ b/tools/editor/cli_currency_types_catalog.cpp @@ -0,0 +1,249 @@ +#include "cli_currency_types_catalog.hpp" +#include "cli_arg_parse.hpp" +#include "cli_box_emitter.hpp" + +#include "pipeline/wowee_currency_types.hpp" +#include + +#include +#include +#include +#include +#include +#include + +namespace wowee { +namespace editor { +namespace cli { + +namespace { + +std::string stripWctrExt(std::string base) { + stripExt(base, ".wctr"); + return base; +} + +bool saveOrError(const wowee::pipeline::WoweeCurrencyType& c, + const std::string& base, const char* cmd) { + if (!wowee::pipeline::WoweeCurrencyTypeLoader::save(c, base)) { + std::fprintf(stderr, "%s: failed to save %s.wctr\n", + cmd, base.c_str()); + return false; + } + return true; +} + +void printGenSummary(const wowee::pipeline::WoweeCurrencyType& c, + const std::string& base) { + std::printf("Wrote %s.wctr\n", base.c_str()); + std::printf(" catalog : %s\n", c.name.c_str()); + std::printf(" currencies : %zu\n", c.entries.size()); +} + +int handleGenPvP(int& i, int argc, char** argv) { + std::string base = argv[++i]; + std::string name = "PvPCurrencies"; + if (parseOptArg(i, argc, argv)) name = argv[++i]; + base = stripWctrExt(base); + auto c = wowee::pipeline::WoweeCurrencyTypeLoader::makePvP(name); + if (!saveOrError(c, base, "gen-ctr")) return 1; + printGenSummary(c, base); + return 0; +} + +int handleGenPvE(int& i, int argc, char** argv) { + std::string base = argv[++i]; + std::string name = "PvECurrencies"; + if (parseOptArg(i, argc, argv)) name = argv[++i]; + base = stripWctrExt(base); + auto c = wowee::pipeline::WoweeCurrencyTypeLoader::makePvE(name); + if (!saveOrError(c, base, "gen-ctr-pve")) return 1; + printGenSummary(c, base); + return 0; +} + +int handleGenFactionTokens(int& i, int argc, char** argv) { + std::string base = argv[++i]; + std::string name = "FactionTokens"; + if (parseOptArg(i, argc, argv)) name = argv[++i]; + base = stripWctrExt(base); + auto c = wowee::pipeline::WoweeCurrencyTypeLoader::makeFactionTokens(name); + if (!saveOrError(c, base, "gen-ctr-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 = stripWctrExt(base); + if (!wowee::pipeline::WoweeCurrencyTypeLoader::exists(base)) { + std::fprintf(stderr, "WCTR not found: %s.wctr\n", base.c_str()); + return 1; + } + auto c = wowee::pipeline::WoweeCurrencyTypeLoader::load(base); + if (jsonOut) { + nlohmann::json j; + j["wctr"] = base + ".wctr"; + j["name"] = c.name; + j["count"] = c.entries.size(); + nlohmann::json arr = nlohmann::json::array(); + for (const auto& e : c.entries) { + arr.push_back({ + {"currencyId", e.currencyId}, + {"name", e.name}, + {"description", e.description}, + {"itemId", e.itemId}, + {"maxQuantity", e.maxQuantity}, + {"maxQuantityWeekly", e.maxQuantityWeekly}, + {"categoryId", e.categoryId}, + {"currencyKind", e.currencyKind}, + {"currencyKindName", wowee::pipeline::WoweeCurrencyType::currencyKindName(e.currencyKind)}, + {"isAccountWide", e.isAccountWide != 0}, + {"iconPath", e.iconPath}, + {"iconColorRGBA", e.iconColorRGBA}, + }); + } + j["entries"] = arr; + std::printf("%s\n", j.dump(2).c_str()); + return 0; + } + std::printf("WCTR: %s.wctr\n", base.c_str()); + std::printf(" catalog : %s\n", c.name.c_str()); + std::printf(" currencies : %zu\n", c.entries.size()); + if (c.entries.empty()) return 0; + std::printf(" id kind item maxQ maxWeek cat acct name\n"); + for (const auto& e : c.entries) { + std::printf(" %7u %-13s %5u %6u %6u %4u %s %s\n", + e.currencyId, + wowee::pipeline::WoweeCurrencyType::currencyKindName(e.currencyKind), + e.itemId, + e.maxQuantity, e.maxQuantityWeekly, + e.categoryId, + e.isAccountWide ? "yes" : "no ", + 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 = stripWctrExt(base); + if (!wowee::pipeline::WoweeCurrencyTypeLoader::exists(base)) { + std::fprintf(stderr, + "validate-wctr: WCTR not found: %s.wctr\n", base.c_str()); + return 1; + } + auto c = wowee::pipeline::WoweeCurrencyTypeLoader::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.currencyId); + if (!e.name.empty()) ctx += " " + e.name; + ctx += ")"; + if (e.currencyId == 0) + errors.push_back(ctx + ": currencyId is 0"); + if (e.name.empty()) + errors.push_back(ctx + ": name is empty"); + if (e.currencyKind > wowee::pipeline::WoweeCurrencyType::Misc) { + errors.push_back(ctx + ": currencyKind " + + std::to_string(e.currencyKind) + " not in 0..5"); + } + if (e.maxQuantity != 0 && + e.maxQuantityWeekly != 0 && + e.maxQuantityWeekly > e.maxQuantity) { + warnings.push_back(ctx + + ": maxQuantityWeekly " + + std::to_string(e.maxQuantityWeekly) + + " > maxQuantity " + + std::to_string(e.maxQuantity) + + " — weekly cap exceeds absolute cap, " + "weekly cap will never be reached"); + } + // Faction tokens with no categoryId can't reference + // a faction — break the rep gate. + if (e.currencyKind == wowee::pipeline::WoweeCurrencyType::FactionToken && + e.categoryId == 0) { + warnings.push_back(ctx + + ": FactionToken kind with categoryId=0 — " + "no faction is associated, rep gate will not " + "trigger"); + } + // Currencies with no caps at all and no item backing + // are likely misconfigured. + if (e.maxQuantity == 0 && e.maxQuantityWeekly == 0 && + e.itemId == 0 && e.iconPath.empty()) { + warnings.push_back(ctx + + ": no caps + no itemId + no iconPath — " + "currency has no display data and unbounded " + "earn rate"); + } + for (uint32_t prev : idsSeen) { + if (prev == e.currencyId) { + errors.push_back(ctx + ": duplicate currencyId"); + break; + } + } + idsSeen.push_back(e.currencyId); + } + bool ok = errors.empty(); + if (jsonOut) { + nlohmann::json j; + j["wctr"] = base + ".wctr"; + 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-wctr: %s.wctr\n", base.c_str()); + if (ok && warnings.empty()) { + std::printf(" OK — %zu currencies, all currencyIds 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 handleCurrencyTypesCatalog(int& i, int argc, char** argv, + int& outRc) { + if (std::strcmp(argv[i], "--gen-ctr") == 0 && i + 1 < argc) { + outRc = handleGenPvP(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--gen-ctr-pve") == 0 && i + 1 < argc) { + outRc = handleGenPvE(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--gen-ctr-faction") == 0 && i + 1 < argc) { + outRc = handleGenFactionTokens(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--info-wctr") == 0 && i + 1 < argc) { + outRc = handleInfo(i, argc, argv); return true; + } + if (std::strcmp(argv[i], "--validate-wctr") == 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_currency_types_catalog.hpp b/tools/editor/cli_currency_types_catalog.hpp new file mode 100644 index 00000000..c3dba995 --- /dev/null +++ b/tools/editor/cli_currency_types_catalog.hpp @@ -0,0 +1,12 @@ +#pragma once + +namespace wowee { +namespace editor { +namespace cli { + +bool handleCurrencyTypesCatalog(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 d327154d..94a5bd3a 100644 --- a/tools/editor/cli_dispatch.cpp +++ b/tools/editor/cli_dispatch.cpp @@ -118,6 +118,7 @@ #include "cli_item_materials_catalog.hpp" #include "cli_player_spawn_profiles_catalog.hpp" #include "cli_talent_tabs_catalog.hpp" +#include "cli_currency_types_catalog.hpp" #include "cli_quest_objective.hpp" #include "cli_quest_reward.hpp" #include "cli_clone.hpp" @@ -277,6 +278,7 @@ constexpr DispatchFn kDispatchTable[] = { handleItemMaterialsCatalog, handlePlayerSpawnProfilesCatalog, handleTalentTabsCatalog, + handleCurrencyTypesCatalog, handleQuestObjective, handleQuestReward, handleClone, diff --git a/tools/editor/cli_format_table.cpp b/tools/editor/cli_format_table.cpp index 60b620fa..a9e33083 100644 --- a/tools/editor/cli_format_table.cpp +++ b/tools/editor/cli_format_table.cpp @@ -81,6 +81,7 @@ constexpr FormatMagicEntry kFormats[] = { {{'W','M','A','T'}, ".wmat", "items", "--info-wmat", "Item material catalog"}, {{'W','P','S','P'}, ".wpsp", "chars", "--info-wpsp", "Player spawn profile catalog"}, {{'W','T','L','E'}, ".wtle", "talents", "--info-wtle", "Talent tab / tree catalog"}, + {{'W','C','T','R'}, ".wctr", "currency", "--info-wctr", "Currency type 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 e0c0e8ba..2b53c369 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -1847,6 +1847,16 @@ void printUsage(const char* argv0) { std::printf(" Export binary .wtle to a human-editable JSON sidecar (defaults to .wtle.json)\n"); std::printf(" --import-wtle-json [out-base]\n"); std::printf(" Import a .wtle.json sidecar back into binary .wtle (accepts roleHint int OR roleHintName string)\n"); + std::printf(" --gen-ctr [name]\n"); + std::printf(" Emit .wctr 4 PvP currencies (Honor 75k cap / Arena 5k weekly / Conquest 1650 weekly / Champion's Seal no cap)\n"); + std::printf(" --gen-ctr-pve [name]\n"); + std::printf(" Emit .wctr 4 PvE raid currencies (Justice 4k cap / Valor 1k weekly / Emblem of Frost no cap / Trophy of Crusade no cap)\n"); + std::printf(" --gen-ctr-faction [name]\n"); + std::printf(" Emit .wctr 4 faction reputation tokens (Hodir / Cenarion / Argent Dawn / Wintergrasp) — categoryId references WFAC factionId\n"); + std::printf(" --info-wctr [--json]\n"); + std::printf(" Print WCTR entries (id / kind / itemId / max+weekly caps / categoryId / accountWide / name)\n"); + std::printf(" --validate-wctr [--json]\n"); + std::printf(" Static checks: id+name required, currencyKind 0..5, no duplicate ids; warns on weekly>absolute, FactionToken+cat=0, no caps+no item+no icon\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 73dbadab..dad8554e 100644 --- a/tools/editor/cli_list_formats.cpp +++ b/tools/editor/cli_list_formats.cpp @@ -103,6 +103,7 @@ constexpr FormatRow kFormats[] = { {"WMAT", ".wmat", "items", "Material.dbc + ItemDisplayInfo", "Item material catalog"}, {"WPSP", ".wpsp", "chars", "playercreateinfo SQL + StartOutfit","Player spawn profile catalog"}, {"WTLE", ".wtle", "talents", "TalentTab.dbc", "Talent tab / tree catalog"}, + {"WCTR", ".wctr", "currency", "CurrencyTypes.dbc + caps", "Currency type catalog"}, // Additional pipeline catalogs without the alternating // gen/info/validate CLI surface (loaded by the engine