feat(pipeline): add WACH (Wowee Achievement Catalog) format

Novel open replacement for Blizzard's Achievement.dbc +
AchievementCriteria.dbc + AchievementCategory.dbc + the
AzerothCore-style character_achievement /
character_achievement_progress SQL tables. The 21st open
format added to the editor.

Each achievement carries display metadata (name, description,
icon, points, faction restriction) plus a list of criteria
the player must satisfy. Criteria mirror the WQT objective
model (kind + targetId + quantity), so the runtime can
reuse the same progress-tracking machinery for both quests
and achievements.

Cross-references with previously-added formats — every
criterion kind has a real format target:
  WACH.criteria.targetId (kind=KillCreature)    -> WCRT.creatureId
  WACH.criteria.targetId (kind=CompleteQuest)   -> WQT.questId
  WACH.criteria.targetId (kind=LootItem)        -> WIT.itemId
  WACH.criteria.targetId (kind=CastSpell)       -> WSPL.spellId
  WACH.criteria.targetId (kind=ReachSkillLevel) -> WSKL.skillId
  WACH.criteria.targetId (kind=EarnReputation)  -> WFAC.factionId
  WACH.criteria.targetId (kind=CompleteAchievement) -> WACH.achievementId
                                                       (meta-achievements)

Format:
  • magic "WACH", version 1, little-endian
  • per achievement: id / categoryId / name / description /
    iconPath / titleReward / points / minLevel / faction /
    flags / criteria[]
  • per criterion: criteriaId / kind / targetId / quantity /
    description

Enums:
  • CriteriaKind (9): KillCreature / CompleteQuest / LootItem /
                      ReachLevel / EarnReputation / CastSpell /
                      ReachSkillLevel / VisitArea /
                      CompleteAchievement
  • Faction:    Both / Alliance / Horde
  • Flags:      HiddenUntilEarned / ServerFirst / RealmFirst /
                 Tracking / Counter / Account

API: WoweeAchievementLoader::save / load / exists /
findById; presets makeStarter (3 simple kill/quest/level
demos), makeBandit (3 with WCRT/WGOT/WQT cross-refs),
makeMeta (3 base + 1 meta-achievement granting "the
Versatile" title, exercising CompleteAchievement criterion
kind that lets achievements depend on other achievements).

CLI added (5 flags, 542 documented total now):
  --gen-achievements / --gen-achievements-bandit / --gen-achievements-meta
  --info-wach / --validate-wach

Validator catches: achievementId=0 + duplicates, empty name,
faction out of range, no criteria (achievement can never
be earned), criterion quantity=0, unknown criterion kind,
targetId=0 on criterion kinds that need a real resource
reference (everything except ReachLevel which uses the
quantity field for the level number).

The bandit preset's cross-references close the gameplay
graph end-to-end: kill 50 creatureId=1000 (matches WCRT/
WSPN/WLOT bandit), loot objectId=2000 (matches WGOT bandit
strongbox), complete questId=1 (matches WQT Bandit Trouble).
The meta preset closes a separate loop: 3 sub-achievements
covering Mining (skillId=186), Lockpicking (skillId=633),
and Frostbolt cast count (spellId=116) — each pointing at
a real WSKL/WSPL entry that already exists in the demo
content stack.
This commit is contained in:
Kelsi 2026-05-09 16:04:30 -07:00
parent f4f9804000
commit e5eaf13866
8 changed files with 763 additions and 0 deletions

View file

@ -0,0 +1,140 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace pipeline {
// Wowee Open Achievement Catalog (.wach) — novel replacement
// for Blizzard's Achievement.dbc + AchievementCriteria.dbc +
// AchievementCategory.dbc + the AzerothCore-style
// character_achievement / character_achievement_progress
// SQL tables. The 21st open format added to the editor.
//
// Each achievement carries display metadata (name, description,
// icon, points, faction restriction) plus a list of criteria
// the player must satisfy. Criteria mirror the WQT objective
// model (kind + targetId + quantity), which means the runtime
// can reuse the same progress-tracking machinery for both.
//
// Cross-references with previously-added formats:
// WACH.criteria.targetId (kind=KillCreature) → WCRT.creatureId
// WACH.criteria.targetId (kind=CompleteQuest) → WQT.questId
// WACH.criteria.targetId (kind=LootItem) → WIT.itemId
// WACH.criteria.targetId (kind=CastSpell) → WSPL.spellId
// WACH.criteria.targetId (kind=ReachSkillLevel) → WSKL.skillId
// WACH.criteria.targetId (kind=EarnReputation) → WFAC.factionId
// WACH.entry.categoryId → WACH.entry.achievementId (for header
// rows; achievements can be parented
// to other achievements as headers)
//
// Binary layout (little-endian):
// magic[4] = "WACH"
// version (uint32) = current 1
// nameLen + name (catalog label)
// entryCount (uint32)
// entries (each):
// achievementId (uint32)
// categoryId (uint32)
// nameLen + name
// descLen + description
// iconLen + iconPath
// titleLen + titleReward
// points (uint32)
// minLevel (uint16) / faction (uint8) / criteriaCount (uint8)
// flags (uint32)
// criteria (criteriaCount × {
// criteriaId (uint32)
// kind (uint8) + pad[3]
// targetId (uint32)
// quantity (uint32)
// descLen + description
// })
struct WoweeAchievement {
enum CriteriaKind : uint8_t {
KillCreature = 0,
CompleteQuest = 1,
LootItem = 2,
ReachLevel = 3,
EarnReputation = 4,
CastSpell = 5,
ReachSkillLevel = 6,
VisitArea = 7,
CompleteAchievement = 8, // meta-achievements
};
enum Faction : uint8_t {
FactionBoth = 0,
FactionAlliance = 1,
FactionHorde = 2,
};
enum Flags : uint32_t {
HiddenUntilEarned = 0x01, // not shown in panel until completed
ServerFirst = 0x02, // first-on-server rewards a global tag
RealmFirst = 0x04,
Tracking = 0x08, // shows progress UI in the panel
Counter = 0x10, // counts up forever (Pet Battles wins)
Account = 0x20, // account-wide, not per-character
};
struct Criterion {
uint32_t criteriaId = 0;
uint8_t kind = KillCreature;
uint32_t targetId = 0;
uint32_t quantity = 1;
std::string description;
};
struct Entry {
uint32_t achievementId = 0;
uint32_t categoryId = 0; // 0 = top-level
std::string name;
std::string description;
std::string iconPath;
std::string titleReward; // empty = no title
uint32_t points = 10;
uint16_t minLevel = 1;
uint8_t faction = FactionBoth;
uint32_t flags = 0;
std::vector<Criterion> criteria;
};
std::string name;
std::vector<Entry> entries;
bool isValid() const { return !entries.empty(); }
// Lookup by achievementId — nullptr if not present.
const Entry* findById(uint32_t achievementId) const;
static const char* criteriaKindName(uint8_t k);
static const char* factionName(uint8_t f);
};
class WoweeAchievementLoader {
public:
static bool save(const WoweeAchievement& cat,
const std::string& basePath);
static WoweeAchievement load(const std::string& basePath);
static bool exists(const std::string& basePath);
// Preset emitters used by --gen-achievements* variants.
//
// makeStarter — 3 demo achievements covering kill / quest
// completion / level reached criteria.
// makeBandit — bandit-themed: "Slay 50 Defias Bandits"
// + "Loot the Bandit Strongbox" + "Complete
// Bandit Trouble" — all referencing the
// WCRT/WGOT/WQT/WIT cross-referenced demo IDs.
// makeMeta — 3 base achievements + 1 meta-achievement
// that requires completing the others.
static WoweeAchievement makeStarter(const std::string& catalogName);
static WoweeAchievement makeBandit(const std::string& catalogName);
static WoweeAchievement makeMeta(const std::string& catalogName);
};
} // namespace pipeline
} // namespace wowee