feat(pipeline): add WLCK (Wowee Lock Template) format

Novel open replacement for Blizzard's Lock.dbc. The 18th
open format added to the editor. Closes the cross-reference
gap from WGOT.entry.lockId — until now that field pointed
to a format that didn't exist yet.

A lock is a multi-channel security check. Each lock has up
to 5 independent channels; a player can open the lock by
satisfying ANY ONE channel:
  • Item     — requires a specific key item (WIT cross-ref)
  • Lockpick — requires the lockpicking skill at minimum rank
                (rogue / engineering profession)
  • Spell    — requires casting a specific spell
  • Damage   — can be forced open with attack damage

Cross-references with previously-added formats:
  WGOT.entry.lockId               -> WLCK.entry.lockId
  WLCK.channel.targetId (Item)    -> WIT.entry.itemId
  WLCK.channel.targetId (Lockpick) -> future WSKL skillId
  WLCK.channel.targetId (Spell)   -> future WSPL spellId

The starter and dungeon presets' lockIds (1 and 2)
deliberately match WGOT.makeDungeon's iron-door lockId=1
and bandit-strongbox lockId=2, so the demo content stack
already wires together: WSPN spawn -> WGOT object template
-> WLCK lock template -> WIT key items.

Format:
  • magic "WLCK", version 1, little-endian
  • per lock: lockId / name / flags / 5 fixed channel slots
  • per channel: kind / skillRequired / targetId
  • all 5 slots written even when unused (kind=None +
    zeroed fields), keeping the per-entry size constant for
    fast random access

Enums:
  • ChannelKind: None / Item / Lockpick / Spell / Damage
  • Flags:       DestructOnOpen / RespawnOnKey / TrapOnFail

API: WoweeLockLoader::save / load / exists / findById;
presets makeStarter (Iron Door + Wooden Chest), makeDungeon
(matches WGOT cross-references; light/heavy lockpicks +
boss-key-only seal), makeProfessions (4-tier rogue lockpick
progression at ranks 1/100/175/250).

CLI added (5 flags, 521 documented total now):
  --gen-locks / --gen-locks-dungeon / --gen-locks-professions
  --info-wlck / --validate-wlck

Validator catches: lockId=0 + duplicates, all-None channels
(lock can never open), Item/Spell/Lockpick channels with
targetId=0 (no resource referenced), unknown channel kind,
skillRequired set on non-Lockpick channel (silently ignored
at runtime — flag as warning).
This commit is contained in:
Kelsi 2026-05-09 15:44:26 -07:00
parent 062258de8a
commit 81b1897a24
8 changed files with 621 additions and 0 deletions

View file

@ -0,0 +1,116 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace pipeline {
// Wowee Open Lock Template (.wlck) — novel replacement for
// Blizzard's Lock.dbc. The 18th open format added to the
// editor. Closes the cross-reference gap from WGOT.entry.lockId
// (and the future WIT lockbox subset) — until now those
// fields pointed to a format that didn't exist yet.
//
// A lock is a multi-channel security check. Each lock has up
// to 5 independent channels; a player can open the lock by
// satisfying ANY ONE channel. Channels can be:
// • Item — requires a specific key item (WIT cross-ref)
// • Lockpick — requires the lockpicking skill at a minimum
// rank (rogue / engineering profession)
// • Spell — requires casting a specific spell
// • Damage — can be forced open with attack damage
//
// Cross-references with previously-added formats:
// WGOT.entry.lockId → WLCK.entry.lockId
// WLCK.channel.targetId (kind=Item) → WIT.entry.itemId
// WLCK.channel.targetId (kind=Skill) → future WSKL skillId
// WLCK.channel.targetId (kind=Spell) → future WSPL spellId
//
// Binary layout (little-endian):
// magic[4] = "WLCK"
// version (uint32) = current 1
// nameLen + name (catalog label)
// entryCount (uint32)
// entries (each):
// lockId (uint32)
// nameLen + name
// flags (uint32)
// -- 5 channel slots, all written even when unused; an
// unused slot has kind=None and zeroed fields.
// channels[5] × {
// kind (uint8) + pad[1]
// skillRequired (uint16)
// targetId (uint32)
// }
struct WoweeLock {
static constexpr int kChannelSlots = 5;
enum ChannelKind : uint8_t {
ChannelNone = 0,
ChannelItem = 1, // requires a key item (targetId = WIT itemId)
ChannelLockpick = 2, // requires lockpicking skill (targetId = skill ID)
ChannelSpell = 3, // requires casting a spell (targetId = spell ID)
ChannelDamage = 4, // can be forced open with damage (targetId unused)
};
enum Flags : uint32_t {
DestructOnOpen = 0x01, // lock destroyed after one successful open
RespawnOnKey = 0x02, // re-locks itself after key use (timed)
TrapOnFail = 0x04, // failure triggers a trap (script-handled)
};
struct Channel {
uint8_t kind = ChannelNone;
uint16_t skillRequired = 0; // only meaningful for ChannelLockpick
uint32_t targetId = 0; // item / skill / spell id
};
struct Entry {
uint32_t lockId = 0;
std::string name;
uint32_t flags = 0;
Channel channels[kChannelSlots] = {};
};
std::string name;
std::vector<Entry> entries;
bool isValid() const { return !entries.empty(); }
// Lookup by lockId — nullptr if not present.
const Entry* findById(uint32_t lockId) const;
static const char* channelKindName(uint8_t k);
};
class WoweeLockLoader {
public:
static bool save(const WoweeLock& cat,
const std::string& basePath);
static WoweeLock load(const std::string& basePath);
static bool exists(const std::string& basePath);
// Preset emitters used by --gen-locks* variants.
//
// makeStarter — 2 locks: a basic wooden chest lock
// (lockId=1, requires no skill, can be
// forced open) plus a small key-required
// lockbox (lockId=2). lockId=1 matches
// WGOT.makeDungeon's iron-door lockId.
// makeDungeon — 3 dungeon-tier locks: light lockpick
// (lockId=2 matching WGOT bandit chest),
// steel chest (heavy lockpick OR specific
// key), and a quest-key-only seal.
// makeProfessions — 4 profession-keyed locks: lockpick at
// ranks 1/100/175/250 covering the
// classic-tier rogue / engineering
// progression curve.
static WoweeLock makeStarter(const std::string& catalogName);
static WoweeLock makeDungeon(const std::string& catalogName);
static WoweeLock makeProfessions(const std::string& catalogName);
};
} // namespace pipeline
} // namespace wowee