feat(editor): add WIQR (Item Quality) open catalog format

Open replacement for the hardcoded item quality tiers in the
WoW client (Poor / Common / Uncommon / Rare / Epic / Legendary
/ Artifact / Heirloom). Defines each tier's tooltip text color,
inventory slot border color, vendor price multiplier, drop-level
gating, and disenchant eligibility.

The hardcoded client uses a fixed color table (gray/white/green/
blue/purple/orange/red/gold). This catalog lets server admins:
  - retune the colors (rename "Epic" to "Tier 1" with custom hex)
  - add server-custom tiers above Heirloom
  - change vendor markup per tier (legendary 50x base price)
  - gate quality drops by character level (Heirlooms unlock 80)

The standard preset reproduces the canonical 8-tier scale with
exact hex values from the live client (#9d9d9d through #00ccff)
and standard disenchant rules (Common+ disenchantable, Legendary
and Artifact aren't). The server-custom preset shows 4 tiers
above the standard range with non-standard pricing (Junk 0.1x,
QuestLocked 0.0x unsellable). The raid preset gates 4
progression tiers behind minLevelToDrop=60 with escalating
vendor multipliers up to 50x for Legendary.

Cross-references back to WIT — item entries reference qualityId
here for tooltip color and sort order. canDropAtLevel(id, lvl)
is the engine helper used by loot generation.

Validation enforces name presence, no duplicate ids,
vendorPriceMultiplier >= 0, minLevelToDrop <= maxLevelToDrop;
warns on:
  - minLevelToDrop > 80 (unreachable at WotLK cap)
  - vendorPriceMultiplier > 100x (sanity check the economy)
  - nameColorRGBA with alpha=0 (text would be invisible in
    tooltips — common bug when copy-pasting RGB hex without
    alpha byte)

Wired through the cross-format table; WIQR appears automatically
in all 15 cross-format utilities. Format count 83 -> 84; CLI
flag count 1003 -> 1008.
This commit is contained in:
Kelsi 2026-05-09 22:59:27 -07:00
parent 18f07f1b8a
commit efb88be366
10 changed files with 652 additions and 0 deletions

View file

@ -672,6 +672,7 @@ set(WOWEE_SOURCES
src/pipeline/wowee_achievement_criteria.cpp
src/pipeline/wowee_spell_effect_types.cpp
src/pipeline/wowee_spell_aura_types.cpp
src/pipeline/wowee_item_qualities.cpp
src/pipeline/custom_zone_discovery.cpp
src/pipeline/dbc_layout.cpp
@ -1504,6 +1505,7 @@ add_executable(wowee_editor
tools/editor/cli_achievement_criteria_catalog.cpp
tools/editor/cli_spell_effect_types_catalog.cpp
tools/editor/cli_spell_aura_types_catalog.cpp
tools/editor/cli_item_qualities_catalog.cpp
tools/editor/cli_quest_objective.cpp
tools/editor/cli_quest_reward.cpp
tools/editor/cli_clone.cpp
@ -1654,6 +1656,7 @@ add_executable(wowee_editor
src/pipeline/wowee_achievement_criteria.cpp
src/pipeline/wowee_spell_effect_types.cpp
src/pipeline/wowee_spell_aura_types.cpp
src/pipeline/wowee_item_qualities.cpp
src/pipeline/custom_zone_discovery.cpp
src/pipeline/terrain_mesh.cpp

View file

@ -0,0 +1,116 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace pipeline {
// Wowee Open Item Quality catalog (.wiqr) — novel
// replacement for the hardcoded item quality tiers in
// the WoW client (Poor / Common / Uncommon / Rare / Epic
// / Legendary / Artifact / Heirloom). Defines each tier's
// tooltip text color, inventory slot border color,
// vendor price multiplier, drop-level gating, and
// disenchant eligibility.
//
// The hardcoded client uses a static color table:
// Poor = gray #9d9d9d
// Common = white #ffffff
// Uncommon = green #1eff00
// Rare = blue #0070dd
// Epic = purple #a335ee
// Legendary = orange #ff8000
// Artifact = red #e6cc80
// Heirloom = gold #00ccff
//
// This catalog lets server admins:
// - retune the colors (rename "Epic" to "Tier 1" with a
// custom orange-red, etc.)
// - add server-custom tiers above Heirloom
// (Donator / Weekly / Anniversary)
// - change vendor markup per tier (legendary sells for
// 20x base price)
// - gate quality drops by character level (Heirlooms
// unlock at lvl 80)
//
// Cross-references with previously-added formats:
// WIT: item entries reference qualityId here for
// tooltip color and sort order.
//
// Binary layout (little-endian):
// magic[4] = "WIQR"
// version (uint32) = current 1
// nameLen + name (catalog label)
// entryCount (uint32)
// entries (each):
// qualityId (uint32)
// nameLen + name
// descLen + description
// nameColorRGBA (uint32)
// borderColorRGBA (uint32)
// vendorPriceMultiplier (float)
// minLevelToDrop (uint8) / maxLevelToDrop (uint8)
// canBeDisenchanted (uint8) / pad (uint8)
// borderTexLen + inventoryBorderTexture
struct WoweeItemQuality {
struct Entry {
uint32_t qualityId = 0;
std::string name;
std::string description;
uint32_t nameColorRGBA = 0xFFFFFFFFu;
uint32_t borderColorRGBA = 0xFFFFFFFFu;
float vendorPriceMultiplier = 1.0f;
uint8_t minLevelToDrop = 1;
uint8_t maxLevelToDrop = 0; // 0 = no max
uint8_t canBeDisenchanted = 0; // 0/1 bool
uint8_t pad0 = 0;
std::string inventoryBorderTexture;
};
std::string name;
std::vector<Entry> entries;
bool isValid() const { return !entries.empty(); }
const Entry* findById(uint32_t qualityId) const;
// Returns true if an item of this quality can drop
// for a character of the given level (gated by
// [minLevelToDrop, maxLevelToDrop] when maxLevelToDrop
// is non-zero).
bool canDropAtLevel(uint32_t qualityId,
uint8_t characterLevel) const;
};
class WoweeItemQualityLoader {
public:
static bool save(const WoweeItemQuality& cat,
const std::string& basePath);
static WoweeItemQuality load(const std::string& basePath);
static bool exists(const std::string& basePath);
// Preset emitters used by --gen-iqr* variants.
//
// makeStandard — 8 canonical WoW item quality
// tiers (Poor through Heirloom)
// with their standard hex colors
// and disenchant rules. Heirloom
// gated to lvl 80.
// makeServerCustom — 4 server-custom tiers
// (Junk / Weekly / QuestLocked /
// Donator) with custom colors and
// non-standard vendor multipliers.
// makeRaidTiers — 4 raid progression tiers
// (T1 lvl 60 / T2 lvl 60 / T3
// lvl 60 / Legendary lvl 60+)
// gated by minLevelToDrop and
// priced for server economy.
static WoweeItemQuality makeStandard(const std::string& catalogName);
static WoweeItemQuality makeServerCustom(const std::string& catalogName);
static WoweeItemQuality makeRaidTiers(const std::string& catalogName);
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,263 @@
#include "pipeline/wowee_item_qualities.hpp"
#include <cstdio>
#include <cstring>
#include <fstream>
namespace wowee {
namespace pipeline {
namespace {
constexpr char kMagic[4] = {'W', 'I', 'Q', 'R'};
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) != ".wiqr") {
base += ".wiqr";
}
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 WoweeItemQuality::Entry*
WoweeItemQuality::findById(uint32_t qualityId) const {
for (const auto& e : entries)
if (e.qualityId == qualityId) return &e;
return nullptr;
}
bool WoweeItemQuality::canDropAtLevel(uint32_t qualityId,
uint8_t characterLevel) const {
const Entry* e = findById(qualityId);
if (!e) return false;
if (characterLevel < e->minLevelToDrop) return false;
if (e->maxLevelToDrop != 0 && characterLevel > e->maxLevelToDrop)
return false;
return true;
}
bool WoweeItemQualityLoader::save(const WoweeItemQuality& 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.qualityId);
writeStr(os, e.name);
writeStr(os, e.description);
writePOD(os, e.nameColorRGBA);
writePOD(os, e.borderColorRGBA);
writePOD(os, e.vendorPriceMultiplier);
writePOD(os, e.minLevelToDrop);
writePOD(os, e.maxLevelToDrop);
writePOD(os, e.canBeDisenchanted);
writePOD(os, e.pad0);
writeStr(os, e.inventoryBorderTexture);
}
return os.good();
}
WoweeItemQuality WoweeItemQualityLoader::load(
const std::string& basePath) {
WoweeItemQuality 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.qualityId)) {
out.entries.clear(); return out;
}
if (!readStr(is, e.name) || !readStr(is, e.description)) {
out.entries.clear(); return out;
}
if (!readPOD(is, e.nameColorRGBA) ||
!readPOD(is, e.borderColorRGBA) ||
!readPOD(is, e.vendorPriceMultiplier) ||
!readPOD(is, e.minLevelToDrop) ||
!readPOD(is, e.maxLevelToDrop) ||
!readPOD(is, e.canBeDisenchanted) ||
!readPOD(is, e.pad0)) {
out.entries.clear(); return out;
}
if (!readStr(is, e.inventoryBorderTexture)) {
out.entries.clear(); return out;
}
}
return out;
}
bool WoweeItemQualityLoader::exists(const std::string& basePath) {
std::ifstream is(normalizePath(basePath), std::ios::binary);
return is.good();
}
WoweeItemQuality WoweeItemQualityLoader::makeStandard(
const std::string& catalogName) {
using Q = WoweeItemQuality;
WoweeItemQuality c;
c.name = catalogName;
auto add = [&](uint32_t id, const char* name,
uint8_t r, uint8_t g, uint8_t b,
float vendorMul, uint8_t minLvl, uint8_t maxLvl,
uint8_t disenchant, const char* texture,
const char* desc) {
Q::Entry e;
e.qualityId = id; e.name = name; e.description = desc;
e.nameColorRGBA = packRgba(r, g, b);
e.borderColorRGBA = packRgba(r, g, b);
e.vendorPriceMultiplier = vendorMul;
e.minLevelToDrop = minLvl;
e.maxLevelToDrop = maxLvl;
e.canBeDisenchanted = disenchant;
e.inventoryBorderTexture = texture;
c.entries.push_back(e);
};
// The canonical WoW item quality scale, with hex colors
// matching the live client. Heirlooms (id 7) are gated
// to character level 80 in WotLK.
add(0, "Poor", 0x9d, 0x9d, 0x9d, 0.5f, 1, 0, 0,
"", "Poor (gray) — junk loot; vendor sells at half price.");
add(1, "Common", 0xff, 0xff, 0xff, 1.0f, 1, 0, 1,
"", "Common (white) — basic gear; standard vendor pricing.");
add(2, "Uncommon", 0x1e, 0xff, 0x00, 1.5f, 1, 0, 1,
"Border-Uncommon",
"Uncommon (green) — early-tier quest reward; 50% markup.");
add(3, "Rare", 0x00, 0x70, 0xdd, 2.0f, 1, 0, 1,
"Border-Rare",
"Rare (blue) — dungeon-tier; 2x markup, can be disenchanted.");
add(4, "Epic", 0xa3, 0x35, 0xee, 4.0f, 60, 0, 1,
"Border-Epic",
"Epic (purple) — raid-tier; 4x markup, disenchants to high-tier dust.");
add(5, "Legendary", 0xff, 0x80, 0x00, 8.0f, 60, 0, 0,
"Border-Legendary",
"Legendary (orange) — extremely rare; cannot be disenchanted.");
add(6, "Artifact", 0xe6, 0xcc, 0x80, 16.0f, 80, 0, 0,
"Border-Artifact",
"Artifact (red-gold) — unique, account-bound.");
add(7, "Heirloom", 0x00, 0xcc, 0xff, 1.0f, 80, 0, 0,
"Border-Heirloom",
"Heirloom (cyan) — scales to character level, lvl 80+ unlock.");
return c;
}
WoweeItemQuality WoweeItemQualityLoader::makeServerCustom(
const std::string& catalogName) {
using Q = WoweeItemQuality;
WoweeItemQuality c;
c.name = catalogName;
auto add = [&](uint32_t id, const char* name,
uint8_t r, uint8_t g, uint8_t b,
float vendorMul, uint8_t disenchant,
const char* desc) {
Q::Entry e;
e.qualityId = id; e.name = name; e.description = desc;
e.nameColorRGBA = packRgba(r, g, b);
e.borderColorRGBA = packRgba(r, g, b);
e.vendorPriceMultiplier = vendorMul;
e.canBeDisenchanted = disenchant;
c.entries.push_back(e);
};
// 4 server-custom tiers above the standard 0..7 range.
add(100, "Junk", 0x33, 0x33, 0x33, 0.1f, 0,
"Server-custom: cosmetic junk, near-zero vendor price.");
add(101, "Weekly", 0x80, 0xff, 0x80, 5.0f, 0,
"Server-custom: drops only from weekly raids, "
"premium pricing.");
add(102, "QuestLocked",0xff, 0xff, 0x40, 0.0f, 0,
"Server-custom: quest-bound, cannot be sold.");
add(103, "Donator", 0xff, 0x40, 0xff, 0.0f, 0,
"Server-custom: donor reward, soulbound, unsellable.");
return c;
}
WoweeItemQuality WoweeItemQualityLoader::makeRaidTiers(
const std::string& catalogName) {
using Q = WoweeItemQuality;
WoweeItemQuality c;
c.name = catalogName;
auto add = [&](uint32_t id, const char* name,
uint8_t r, uint8_t g, uint8_t b,
float vendorMul, uint8_t minLvl,
const char* desc) {
Q::Entry e;
e.qualityId = id; e.name = name; e.description = desc;
e.nameColorRGBA = packRgba(r, g, b);
e.borderColorRGBA = packRgba(r, g, b);
e.vendorPriceMultiplier = vendorMul;
e.minLevelToDrop = minLvl;
e.canBeDisenchanted = 1;
c.entries.push_back(e);
};
// Vanilla raid progression tiers as alternative quality
// markers — each tier gates at a higher minLevelToDrop
// and commands a higher vendor multiplier.
add(200, "Tier1Raid", 0xa3, 0x35, 0xee, 4.0f, 60,
"Tier 1 raid set (MC / Onyxia) — Epic-color, lvl 60.");
add(201, "Tier2Raid", 0xc8, 0x4c, 0xff, 6.0f, 60,
"Tier 2 raid set (BWL) — slightly brighter purple, "
"premium pricing.");
add(202, "Tier3Raid", 0xff, 0x80, 0xc8, 10.0f, 60,
"Tier 3 raid set (Naxx pre-WotLK) — pink-orange, "
"rarest pre-TBC tier.");
add(203, "Legendary", 0xff, 0x80, 0x00, 50.0f, 60,
"Tier-equivalent legendary (Thunderfury / Sulfuras / "
"Atiesh) — premium economy pricing.");
return c;
}
} // namespace pipeline
} // namespace wowee

View file

@ -257,6 +257,8 @@ const char* const kArgRequired[] = {
"--gen-aur", "--gen-aur-stats", "--gen-aur-movement",
"--info-waur", "--validate-waur",
"--export-waur-json", "--import-waur-json",
"--gen-iqr", "--gen-iqr-server", "--gen-iqr-raid",
"--info-wiqr", "--validate-wiqr",
"--gen-weather-temperate", "--gen-weather-arctic",
"--gen-weather-desert", "--gen-weather-stormy",
"--gen-zone-atmosphere",

View file

@ -125,6 +125,7 @@
#include "cli_achievement_criteria_catalog.hpp"
#include "cli_spell_effect_types_catalog.hpp"
#include "cli_spell_aura_types_catalog.hpp"
#include "cli_item_qualities_catalog.hpp"
#include "cli_quest_objective.hpp"
#include "cli_quest_reward.hpp"
#include "cli_clone.hpp"
@ -291,6 +292,7 @@ constexpr DispatchFn kDispatchTable[] = {
handleAchievementCriteriaCatalog,
handleSpellEffectTypesCatalog,
handleSpellAuraTypesCatalog,
handleItemQualitiesCatalog,
handleQuestObjective,
handleQuestReward,
handleClone,

View file

@ -86,6 +86,7 @@ constexpr FormatMagicEntry kFormats[] = {
{{'W','A','C','R'}, ".wacr", "achieve", "--info-wacr", "Achievement criteria catalog"},
{{'W','S','E','F'}, ".wsef", "spells", "--info-wsef", "Spell effect type catalog"},
{{'W','A','U','R'}, ".waur", "spells", "--info-waur", "Spell aura type catalog"},
{{'W','I','Q','R'}, ".wiqr", "items", "--info-wiqr", "Item quality tier 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"},

View file

@ -1923,6 +1923,16 @@ void printUsage(const char* argv0) {
std::printf(" Export binary .waur to a human-editable JSON sidecar (defaults to <base>.waur.json)\n");
std::printf(" --import-waur-json <json-path> [out-base]\n");
std::printf(" Import a .waur.json sidecar back into binary .waur (accepts auraKind/targetingHint int OR name; isStackable bool OR int)\n");
std::printf(" --gen-iqr <wiqr-base> [name]\n");
std::printf(" Emit .wiqr 8 standard quality tiers (Poor through Heirloom) with canonical hex colors and disenchant rules\n");
std::printf(" --gen-iqr-server <wiqr-base> [name]\n");
std::printf(" Emit .wiqr 4 server-custom tiers (Junk / Weekly / QuestLocked / Donator) with custom colors and non-standard markups\n");
std::printf(" --gen-iqr-raid <wiqr-base> [name]\n");
std::printf(" Emit .wiqr 4 raid progression tiers (T1/T2/T3/Legendary lvl 60+) gated by minLevelToDrop and priced for server economy\n");
std::printf(" --info-wiqr <wiqr-base> [--json]\n");
std::printf(" Print WIQR entries (id / name / nameColor RGBA / vendor multiplier / minLevel / maxLevel / disenchant flag / border texture)\n");
std::printf(" --validate-wiqr <wiqr-base> [--json]\n");
std::printf(" Static checks: name required, no duplicate ids, vendor>=0, min<=max; warns on lvl>80 (unreachable), vendor>100x (sanity), alpha=0 nameColor (invisible)\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");

View file

@ -0,0 +1,242 @@
#include "cli_item_qualities_catalog.hpp"
#include "cli_arg_parse.hpp"
#include "cli_box_emitter.hpp"
#include "pipeline/wowee_item_qualities.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 stripWiqrExt(std::string base) {
stripExt(base, ".wiqr");
return base;
}
bool saveOrError(const wowee::pipeline::WoweeItemQuality& c,
const std::string& base, const char* cmd) {
if (!wowee::pipeline::WoweeItemQualityLoader::save(c, base)) {
std::fprintf(stderr, "%s: failed to save %s.wiqr\n",
cmd, base.c_str());
return false;
}
return true;
}
void printGenSummary(const wowee::pipeline::WoweeItemQuality& c,
const std::string& base) {
std::printf("Wrote %s.wiqr\n", base.c_str());
std::printf(" catalog : %s\n", c.name.c_str());
std::printf(" tiers : %zu\n", c.entries.size());
}
int handleGenStandard(int& i, int argc, char** argv) {
std::string base = argv[++i];
std::string name = "StandardQualities";
if (parseOptArg(i, argc, argv)) name = argv[++i];
base = stripWiqrExt(base);
auto c = wowee::pipeline::WoweeItemQualityLoader::makeStandard(name);
if (!saveOrError(c, base, "gen-iqr")) return 1;
printGenSummary(c, base);
return 0;
}
int handleGenServerCustom(int& i, int argc, char** argv) {
std::string base = argv[++i];
std::string name = "ServerCustomQualities";
if (parseOptArg(i, argc, argv)) name = argv[++i];
base = stripWiqrExt(base);
auto c = wowee::pipeline::WoweeItemQualityLoader::makeServerCustom(name);
if (!saveOrError(c, base, "gen-iqr-server")) return 1;
printGenSummary(c, base);
return 0;
}
int handleGenRaidTiers(int& i, int argc, char** argv) {
std::string base = argv[++i];
std::string name = "RaidProgressionQualities";
if (parseOptArg(i, argc, argv)) name = argv[++i];
base = stripWiqrExt(base);
auto c = wowee::pipeline::WoweeItemQualityLoader::makeRaidTiers(name);
if (!saveOrError(c, base, "gen-iqr-raid")) 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 = stripWiqrExt(base);
if (!wowee::pipeline::WoweeItemQualityLoader::exists(base)) {
std::fprintf(stderr, "WIQR not found: %s.wiqr\n", base.c_str());
return 1;
}
auto c = wowee::pipeline::WoweeItemQualityLoader::load(base);
if (jsonOut) {
nlohmann::json j;
j["wiqr"] = base + ".wiqr";
j["name"] = c.name;
j["count"] = c.entries.size();
nlohmann::json arr = nlohmann::json::array();
for (const auto& e : c.entries) {
arr.push_back({
{"qualityId", e.qualityId},
{"name", e.name},
{"description", e.description},
{"nameColorRGBA", e.nameColorRGBA},
{"borderColorRGBA", e.borderColorRGBA},
{"vendorPriceMultiplier", e.vendorPriceMultiplier},
{"minLevelToDrop", e.minLevelToDrop},
{"maxLevelToDrop", e.maxLevelToDrop},
{"canBeDisenchanted", e.canBeDisenchanted != 0},
{"inventoryBorderTexture", e.inventoryBorderTexture},
});
}
j["entries"] = arr;
std::printf("%s\n", j.dump(2).c_str());
return 0;
}
std::printf("WIQR: %s.wiqr\n", base.c_str());
std::printf(" catalog : %s\n", c.name.c_str());
std::printf(" tiers : %zu\n", c.entries.size());
if (c.entries.empty()) return 0;
std::printf(" id name nameColor vendorMul minLvl maxLvl DE border\n");
for (const auto& e : c.entries) {
std::printf(" %4u %-10s 0x%08x %6.2fx %4u %4u %s %s\n",
e.qualityId, e.name.c_str(),
e.nameColorRGBA, e.vendorPriceMultiplier,
e.minLevelToDrop, e.maxLevelToDrop,
e.canBeDisenchanted ? "yes" : "no ",
e.inventoryBorderTexture.empty() ? "(none)" :
e.inventoryBorderTexture.c_str());
}
return 0;
}
int handleValidate(int& i, int argc, char** argv) {
std::string base = argv[++i];
bool jsonOut = consumeJsonFlag(i, argc, argv);
base = stripWiqrExt(base);
if (!wowee::pipeline::WoweeItemQualityLoader::exists(base)) {
std::fprintf(stderr,
"validate-wiqr: WIQR not found: %s.wiqr\n", base.c_str());
return 1;
}
auto c = wowee::pipeline::WoweeItemQualityLoader::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.qualityId);
if (!e.name.empty()) ctx += " " + e.name;
ctx += ")";
if (e.name.empty())
errors.push_back(ctx + ": name is empty");
if (e.vendorPriceMultiplier < 0.0f) {
errors.push_back(ctx +
": vendorPriceMultiplier < 0 — vendor would "
"pay the player to take items");
}
if (e.maxLevelToDrop != 0 &&
e.minLevelToDrop > e.maxLevelToDrop) {
errors.push_back(ctx + ": minLevelToDrop " +
std::to_string(e.minLevelToDrop) +
" > maxLevelToDrop " +
std::to_string(e.maxLevelToDrop) +
" — quality will never drop");
}
if (e.minLevelToDrop > 80) {
warnings.push_back(ctx +
": minLevelToDrop " +
std::to_string(e.minLevelToDrop) +
" > 80 — quality unreachable at WotLK cap");
}
if (e.vendorPriceMultiplier > 100.0f) {
warnings.push_back(ctx +
": vendorPriceMultiplier " +
std::to_string(e.vendorPriceMultiplier) +
"x is very high — sanity check the economy");
}
// Pure transparent color is suspicious (alpha=0).
if ((e.nameColorRGBA & 0xFF000000u) == 0) {
warnings.push_back(ctx +
": nameColorRGBA has alpha=0 — text will be "
"invisible in tooltips");
}
for (uint32_t prev : idsSeen) {
if (prev == e.qualityId) {
errors.push_back(ctx + ": duplicate qualityId");
break;
}
}
idsSeen.push_back(e.qualityId);
}
bool ok = errors.empty();
if (jsonOut) {
nlohmann::json j;
j["wiqr"] = base + ".wiqr";
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-wiqr: %s.wiqr\n", base.c_str());
if (ok && warnings.empty()) {
std::printf(" OK — %zu tiers, all qualityIds 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 handleItemQualitiesCatalog(int& i, int argc, char** argv,
int& outRc) {
if (std::strcmp(argv[i], "--gen-iqr") == 0 && i + 1 < argc) {
outRc = handleGenStandard(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--gen-iqr-server") == 0 && i + 1 < argc) {
outRc = handleGenServerCustom(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--gen-iqr-raid") == 0 && i + 1 < argc) {
outRc = handleGenRaidTiers(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--info-wiqr") == 0 && i + 1 < argc) {
outRc = handleInfo(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--validate-wiqr") == 0 && i + 1 < argc) {
outRc = handleValidate(i, argc, argv); return true;
}
return false;
}
} // namespace cli
} // namespace editor
} // namespace wowee

View file

@ -0,0 +1,12 @@
#pragma once
namespace wowee {
namespace editor {
namespace cli {
bool handleItemQualitiesCatalog(int& i, int argc, char** argv,
int& outRc);
} // namespace cli
} // namespace editor
} // namespace wowee

View file

@ -108,6 +108,7 @@ constexpr FormatRow kFormats[] = {
{"WACR", ".wacr", "achieve", "Achievement_Criteria.dbc", "Achievement criteria catalog"},
{"WSEF", ".wsef", "spells", "SpellEffect.Effect dispatch", "Spell effect type catalog"},
{"WAUR", ".waur", "spells", "SpellEffect.EffectAuraType", "Spell aura type catalog"},
{"WIQR", ".wiqr", "items", "Item quality tier colors+rules", "Item quality tier catalog"},
// Additional pipeline catalogs without the alternating
// gen/info/validate CLI surface (loaded by the engine