Kelsidavis-WoWee/include/pipeline/wowee_trainers.hpp
Kelsi d2ca3ea22b feat(pipeline): add WTRN (Wowee Trainer / Vendor catalog) format
Novel open replacement for AzerothCore-style npc_trainer +
npc_vendor SQL tables PLUS the Blizzard TrainerSpells.dbc
family. The 22nd open format added to the editor.

Unifies trainer spell lists and vendor item inventories
into one per-NPC entry. A creature flagged Trainer or
Vendor in WCRT references a WTRN entry that lists what they
teach / sell. The same NPC can be both — kindMask is a
bitmask covering the Trainer (0x01) and Vendor (0x02) kinds.

This format closes a major cross-format gap: WCRT.npcFlags
already had Vendor / Trainer bits, but until now there was
no format defining what a vendor sells or what a trainer
teaches. Now an NPC marked Vendor in WCRT has a real
inventory, and an NPC marked Trainer has a real spell list.

Cross-references — every WTRN field has a real format target:
  WTRN.entry.npcId           -> WCRT.entry.creatureId
  WTRN.spell.spellId         -> WSPL.entry.spellId
  WTRN.spell.requiredSkillId -> WSKL.entry.skillId
  WTRN.item.itemId           -> WIT.entry.itemId

Format:
  • magic "WTRN", version 1, little-endian
  • per NPC: npcId / kindMask / greeting + spells[] + items[]
  • per spell offer: spellId / moneyCostCopper /
    requiredSkillId / requiredSkillRank / requiredLevel
  • per item offer: itemId / stockCount (0xFFFFFFFF =
    unlimited) / restockSec / extendedCost / moneyCostCopper
    (0 = inherit from WIT.buyPrice)

API: WoweeTrainerLoader::save / load / exists / findByNpc;
presets makeStarter (innkeeper 4001 as both trainer +
vendor: teaches First Aid + sells starter items),
makeMageTrainer (NPC 4003 teaches the WSPL mage spells
at scaling cost), makeWeaponVendor (NPC 4002 sells WIT
weapons with mixed unlimited/finite stock + restock timers).

CLI added (5 flags, 551 documented total now):
  --gen-trainers / --gen-trainers-mage / --gen-trainers-weapons
  --info-wtrn / --validate-wtrn

Validator catches: npcId=0 + duplicates, kindMask=0 (NPC
offers nothing), Trainer flag without spells, Vendor flag
without items, spells/items present without the matching
kind bit (silently ignored at runtime), spellId=0 / itemId=0
in offers, finite stock with restockSec=0 (single-fill —
usually intentional but worth surfacing).

The 3 presets deliberately use npcIds matching WCRT village
merchants (4001/4002/4003) so the demo content stack is
self-consistent: WCRT 4001 has the Vendor + Trainer flag,
and WTRN 4001 actually defines what they sell and teach.
2026-05-09 16:12:58 -07:00

120 lines
4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace pipeline {
// Wowee Open Trainer / Vendor catalog (.wtrn) — novel
// replacement for AzerothCore-style npc_trainer + npc_vendor
// SQL tables PLUS the Blizzard TrainerSpells.dbc family.
// The 22nd open format added to the editor.
//
// Unifies trainer spell lists and vendor inventories into one
// per-NPC entry. A creature flagged Trainer or Vendor in WCRT
// references a WTRN entry that lists what they teach / sell.
// The same NPC can be both — kindMask is a bitmask covering
// the Trainer (0x01) and Vendor (0x02) kinds.
//
// Cross-references with previously-added formats:
// WTRN.entry.npcId → WCRT.entry.creatureId
// WTRN.spell.spellId → WSPL.entry.spellId
// WTRN.spell.requiredSkillId → WSKL.entry.skillId
// WTRN.item.itemId → WIT.entry.itemId
//
// Binary layout (little-endian):
// magic[4] = "WTRN"
// version (uint32) = current 1
// nameLen + name (catalog label)
// entryCount (uint32)
// entries (each):
// npcId (uint32)
// kindMask (uint8) + pad[3]
// greetingLen + greeting
// spellCount (uint16) + itemCount (uint16)
// spells (spellCount × {
// spellId (uint32)
// moneyCostCopper (uint32)
// requiredSkillId (uint32)
// requiredSkillRank (uint16)
// requiredLevel (uint16)
// })
// items (itemCount × {
// itemId (uint32)
// stockCount (uint32) -- 0xFFFFFFFF = unlimited
// restockSec (uint32)
// extendedCost (uint32)
// moneyCostCopper (uint32) -- 0 = use WIT.buyPrice
// })
struct WoweeTrainer {
enum KindMask : uint8_t {
Trainer = 0x01,
Vendor = 0x02,
};
static constexpr uint32_t kUnlimitedStock = 0xFFFFFFFFu;
struct SpellOffer {
uint32_t spellId = 0;
uint32_t moneyCostCopper = 0;
uint32_t requiredSkillId = 0; // 0 = no skill prerequisite
uint16_t requiredSkillRank = 0;
uint16_t requiredLevel = 1;
};
struct ItemOffer {
uint32_t itemId = 0;
uint32_t stockCount = kUnlimitedStock;
uint32_t restockSec = 0;
uint32_t extendedCost = 0; // 0 = copper-only
uint32_t moneyCostCopper = 0; // 0 = inherit from WIT
};
struct Entry {
uint32_t npcId = 0;
uint8_t kindMask = 0;
std::string greeting;
std::vector<SpellOffer> spells;
std::vector<ItemOffer> items;
};
std::string name;
std::vector<Entry> entries;
bool isValid() const { return !entries.empty(); }
// Lookup by npcId — nullptr if not present.
const Entry* findByNpc(uint32_t npcId) const;
// Decode the kindMask into a short string (e.g.
// "trainer+vendor" or just "vendor").
static std::string kindMaskName(uint8_t k);
};
class WoweeTrainerLoader {
public:
static bool save(const WoweeTrainer& cat,
const std::string& basePath);
static WoweeTrainer load(const std::string& basePath);
static bool exists(const std::string& basePath);
// Preset emitters used by --gen-trainers* variants.
//
// makeStarter — 1 NPC (Bartleby innkeeper, npcId=4001)
// acting as both vendor + trainer: sells
// 3 starter items + teaches First Aid.
// makeMageTrainer — npcId=4003 (alchemist) becomes a
// mage trainer offering Frostbolt /
// Fireball / Arcane Intellect / Blink
// at appropriate ranks.
// makeWeaponVendor — npcId=4002 (smith) sells 5 weapons
// across the WIT weapon catalog.
static WoweeTrainer makeStarter(const std::string& catalogName);
static WoweeTrainer makeMageTrainer(const std::string& catalogName);
static WoweeTrainer makeWeaponVendor(const std::string& catalogName);
};
} // namespace pipeline
} // namespace wowee