Kelsidavis-WoWee/include/pipeline/wowee_loot.hpp
Kelsi ff0aa1a3c8 feat(pipeline): add WLOT (Wowee Loot Table) format
Novel open replacement for AzerothCore-style
creature_loot_template / gameobject_loot_template SQL
tables. The 13th open format added to the editor.

Pairs naturally with the WIT item catalog from the
preceding commit: each loot drop's itemId references an
entry in a WIT file, so a content pack ships both the
item definitions and the loot tables that reference them.
The runtime composes WIT + WLOT + WSPN to drive the full
"creature dies, drops items" flow without any SQL.

Format:
  • magic "WLOT", version 1, little-endian
  • per table: creatureId / flags / dropCount /
    moneyMin..Max / itemDropCount + drops[]
  • per drop: itemId / chancePercent (float, 0..100) /
    minQty / maxQty / drop_flags

Table flags: QuestOnly, GroupOnly, Pickpocket
Drop flags:  QuestRequired, GroupRollOnly, AlwaysDrop

dropCount is the slot budget — how many distinct drops
to roll per kill. Each item drop is rolled independently
against its chancePercent (so dropCount=2 with 4 candidate
drops at varying chances gives the classic "up to 2 distinct
items per kill" behavior). Drops with the AlwaysDrop flag
bypass the slot budget — used for guaranteed quest items.

API: WoweeLootLoader::save / load / exists /
findByCreatureId; presets makeStarter (1 table, 1 drop),
makeBandit (4 candidates, dropCount=2, matches the camp
spawns from WSPN at creatureId=1000), makeBoss (6 candidates
including guaranteed quest item via AlwaysDrop and a
group-only epic at 5%).

CLI added (5 flags, 486 documented total now):
  --gen-loot / --gen-loot-bandit / --gen-loot-boss
  --info-wlot / --validate-wlot

Validator catches: creatureId=0, duplicates, chance not in
0..100, NaN chance, money min > max, minQty > maxQty,
dropCount=0 with non-empty drops list (silent dead config).

All 3 presets save / load / re-validate clean. The bandit
table's creatureId=1000 deliberately matches WSPN's
makeCamp creatureId so the open-format demo content pack
already has working cross-references.
2026-05-09 15:11:08 -07:00

107 lines
3.7 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 Loot Table (.wlot) — novel replacement for the
// creature_loot_template / gameobject_loot_template SQL
// tables AzerothCore-style servers use to drive what drops
// when a creature is killed (or a chest is opened). The
// 13th open format added to the editor.
//
// Pairs naturally with the WIT item catalog from the
// previous commit: each loot drop's itemId references an
// entry in a WIT file, so a content pack ships both the
// item definitions and the loot tables that reference them.
//
// One file holds many creature loot tables in one catalog.
// Each table has a moneyMin/moneyMax range plus a list of
// possible item drops. dropCount controls how many distinct
// drops to roll per kill (one die roll per slot, independent).
//
// Binary layout (little-endian):
// magic[4] = "WLOT"
// version (uint32) = current 1
// nameLen (uint32) + name bytes -- catalog label
// entryCount (uint32)
// entries (each):
// creatureId (uint32)
// flags (uint32)
// dropCount (uint8) + pad[3]
// moneyMinCopper (uint32)
// moneyMaxCopper (uint32)
// itemDropCount (uint32)
// itemDrops (itemDropCount × {
// itemId (uint32)
// chancePercent (float) -- 0..100
// minQty (uint8)
// maxQty (uint8)
// drop_flags (uint8) + pad[1]
// })
struct WoweeLoot {
enum TableFlags : uint32_t {
QuestOnly = 0x01, // table only used while killer has matching quest
GroupOnly = 0x02, // table only used in group / raid (not solo)
Pickpocket = 0x04, // alternate table used by rogues, not normal kill
};
enum DropFlags : uint8_t {
QuestRequired = 0x01, // drop only if killer has matching quest
GroupRollOnly = 0x02, // skip on solo kills (rare/epic-tier loot)
AlwaysDrop = 0x04, // bypass dropCount slot limit
};
struct ItemDrop {
uint32_t itemId = 0;
float chancePercent = 100.0f;
uint8_t minQty = 1;
uint8_t maxQty = 1;
uint8_t flags = 0;
};
struct Entry {
uint32_t creatureId = 0;
uint32_t flags = 0;
uint8_t dropCount = 1; // distinct drops rolled per kill
uint32_t moneyMinCopper = 0;
uint32_t moneyMaxCopper = 0;
std::vector<ItemDrop> itemDrops;
};
std::string name;
std::vector<Entry> entries;
bool isValid() const { return !entries.empty(); }
// Lookup by creatureId — nullptr if not present.
const Entry* findByCreatureId(uint32_t creatureId) const;
};
class WoweeLootLoader {
public:
static bool save(const WoweeLoot& cat,
const std::string& basePath);
static WoweeLoot load(const std::string& basePath);
static bool exists(const std::string& basePath);
// Preset emitters used by --gen-loot* variants.
//
// makeStarter — minimal: 1 creature with 1 drop slot,
// 1 item @ 50% chance + 0..50 copper.
// makeBandit — bandit table: dropCount=2, 4 candidate
// items (linen, cloth, knife, ale), each
// at distinct chances; 5..50 copper.
// makeBoss — elite boss: dropCount=4, 6 candidates
// including a guaranteed quest item, plus
// GroupRollOnly epic at 5%; 50..200 silver.
static WoweeLoot makeStarter(const std::string& catalogName);
static WoweeLoot makeBandit(const std::string& catalogName);
static WoweeLoot makeBoss(const std::string& catalogName);
};
} // namespace pipeline
} // namespace wowee