Kelsidavis-WoWee/include/pipeline/wowee_locks.hpp
Kelsi 81b1897a24 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).
2026-05-09 15:44:26 -07:00

116 lines
4.3 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 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