Novel open replacement for Blizzard's ItemEnchantment.dbc +
GemProperties.dbc + SpellItemEnchantment.dbc. The 35th
open format added to the editor.
Defines two related kinds of item enhancement in one
catalog:
• Gems — socketable jewelry pieces with color
(red / blue / yellow / meta) that fit
into gear sockets, granting stats or
triggering passive spells when socketed
• Enchantments — persistent buffs applied to weapon /
armor pieces, either by an enchanter
spell or by an item proc (Mongoose,
Crusader, Berserking)
Cross-references with previously-added formats:
WGEM.gem.itemIdToInsert -> WIT.entry.itemId
WGEM.gem.spellId -> WSPL.entry.spellId
WGEM.enchantment.spellId -> WSPL.entry.spellId
Format:
• magic "WGEM", version 1, little-endian
• gems[]: gemId / itemIdToInsert / name / color /
statType + statValue / requiredItemQuality / spellId
• enchantments[]: enchantId / name / description /
iconPath / enchantSlot / statType + statValue /
spellId / durationSeconds / chargeCount
Enums:
• Color (8): Meta / Red / Yellow / Blue / Purple /
Green / Orange / Prismatic
• EnchantSlot (5): Permanent / Temporary / SocketColor /
Ring / Cloak
API: WoweeGemLoader::save / load / exists +
WoweeGem::findGem / findEnchant.
Three preset emitters showcase common shapes:
• makeStarter — 3 gems (one per primary color) +
2 enchantments (proc + stat)
• makeGemSet — 6-gem full color palette covering
primary + secondary combinations
• makeEnchants — 5 enchant variants spanning slots
(Mongoose / Deadly Poison / stats ring /
cloak / Berserking)
CLI added (5 flags, 642 documented total now):
--gen-gems / --gen-gems-set / --gen-gems-enchants
--info-wgem / --validate-wgem
Validator catches: ids=0 + duplicates, empty name, color /
slot out of range, stat-only entries with statValue=0 (gem
provides nothing), chargeCount > 0 on non-Temporary
enchant slots (charges silently ignored at runtime).
The validator caught a real preset issue on first run —
the proc enchants (Mongoose / Deadly Poison / Berserking)
had spellId=0 and statValue=0, providing nothing. Fixed by
adding placeholder spellIds in the 28000-29000 range, with
a comment noting they resolve to real WSPL proc spells
when the spell catalog is extended.
Novel open replacement for AzerothCore-style
mail_loot_template SQL + the in-game mail subset of the
inventory + currency systems. The 34th open format added
to the editor.
Defines templated mail messages with currency + item
attachments. Triggered by quest reward delivery (overflow
when bag is full), auction house bid wins / sales,
achievement reward attachments, GM correspondence, holiday
event mailings (Brewfest samples, Hallow's End candy), and
returned-mail-on-rejection.
Cross-references with previously-added formats:
WMAL.entry.senderNpcId -> WCRT.entry.creatureId
WMAL.entry.attachments.itemId -> WIT.entry.itemId
Format:
• magic "WMAL", version 1, little-endian
• per template: templateId / senderNpcId / subject / body /
senderName / moneyCopperAttached / categoryId / cod /
returnable / expiryDays / attachments[] (each: itemId +
quantity)
Enums:
• Category (8): QuestReward / Auction / GmCorrespondence /
AchievementReward / EventMailing / Raffle /
ScriptDelivery / ReturnedMail
API: WoweeMailLoader::save / load / exists / findById.
Three preset emitters showcase typical mail templates:
• makeStarter — 3 templates (quest overflow / auction won /
GM gift) covering the 3 most common
categories
• makeHoliday — 4 holiday samples that cross-reference the
WTKN seasonal token IDs (200=Tricky Treats,
201=Brewfest, 202=Coin of Ancestry,
203=Stranger's Gift) so the demo content
stack ships a full holiday onboarding
experience
• makeAuction — 5-template auction-house family (outbid /
won / sold / expired / cancelled) — runtime
fills in actual bid amounts / sold items
at send time
CLI added (5 flags, 635 documented total now):
--gen-mail / --gen-mail-holiday / --gen-mail-auction
--info-wmal / --validate-wmal
Validator catches: templateId=0 + duplicates, empty subject,
neither senderNpcId nor senderName set (no displayable
sender), unknown category, expiryDays=0 (mail expires
immediately), cod=1 with no money attached (free COD),
empty mail in categories where the runtime doesn't fill in
content (skips Auction / GmCorrespondence / ReturnedMail
where empty templates are intentional).
Two bugs caught + fixed during smoke-test on the auction
preset:
• print formatting glued the `0` from senderNpcId after
the senderName when no NPC was set (rendered as
"Postmaster0" instead of "Postmaster") — fixed with an
explicit if/else split
• validator's "no money + no items" warning was too
aggressive for the Auction category, where templates
are intentionally informational and the runtime fills
in the real values — added Auction + ReturnedMail to
the skip list
Novel open replacement for Blizzard's CharTitles.dbc + the
AzerothCore-style character_title SQL table. The 30th open
format added to the editor.
Defines the player-display titles awarded for completing
achievements ("the Versatile"), reaching PvP ranks
("Sergeant Major" / "Stone Guard"), participating in raids
("Champion of the Naaru"), levelling a profession ("Master
Locksmith"), or seasonal events ("Brewmaster", "the
Hallowed").
Closes a long-standing gap: WACH.entry.titleReward has been
a free-form string since batch 116 with no formal catalog
to resolve against. WTIT systematizes those strings into a
real catalog — the runtime resolves WACH.titleReward to a
WTIT entry by name, then displays the titleId in the player
title selector.
Cross-references:
WACH.entry.titleReward (string) ~= WTIT.entry.name
(string match — runtime
resolves achievement-
granted titles by
looking up matching WTIT
entry by name)
Format:
• magic "WTIT", version 1, little-endian
• per title: titleId / name / nameMale / nameFemale /
iconPath / prefix (suffix vs prefix display) /
category / sortOrder
Enums:
• Category (8): Achievement / Pvp / Raid / ClassTitle /
Event / Profession / Lore / Custom
API: WoweeTitleLoader::save / load / exists +
WoweeTitle::findById / findByName.
Three preset emitters showcase typical title catalogs:
• makeStarter — 4 titles (Versatile / Sergeant /
Champion / Hallowed) covering 4
categories
• makePvp — 28-title classic Honor System ladder
(14 Alliance ranks Private->Grand
Marshal + 14 Horde ranks Scout->High
Warlord)
• makeAchievement — 8 achievement titles including "the
Versatile" matching WACH.makeMeta's
achievement 250 titleReward + capstone
profession titles
CLI added (5 flags, 608 documented total now):
--gen-titles / --gen-titles-pvp / --gen-titles-achievement
--info-wtit / --validate-wtit
Validator catches: titleId=0 + duplicates, empty name,
unknown category, gender variants set on only one side
(causes mixed-gender display when the runtime falls back to
canonical for the unset side).
Novel open replacement for Blizzard's AreaTrigger.dbc +
AreaTriggerTeleport.dbc + the AzerothCore-style
areatrigger_template / areatrigger_teleport SQL tables.
The 29th open format added to the editor.
Defines proximity-based event zones — when a player enters
a defined region (box or sphere), the runtime fires the
trigger's action: teleport to another map, award
exploration XP for a quest, run a server script, gate an
instance entrance behind a key item, mark a PvP boundary,
or simply display a "Discovered: {area name}" banner.
Cross-references with previously-added formats — every
trigger field has a real format target:
WTRG.entry.mapId / areaId -> WMS.map.mapId / WMS.area.areaId
WTRG.actionTarget (Teleport) -> WMS.mapId
WTRG.actionTarget (QuestExploration) -> WQT.questId
WTRG.requiredQuestId -> WQT.entry.questId
WTRG.requiredItemId -> WIT.entry.itemId (key)
Format:
• magic "WTRG", version 1, little-endian
• per trigger: triggerId / mapId / areaId / name /
center vec3 / shape / kind / boxDims vec3 / radius /
actionTarget / dest vec3 / destOrientation /
requiredQuestId / requiredItemId / minLevel
Enums:
• Shape (2): Box / Sphere
• Kind (7): Teleport / QuestExploration / Script /
InstanceEntrance / AreaName / CombatStartZone /
Waypoint
API: WoweeTriggerLoader::save / load / exists / findById.
Three preset emitters showcase common trigger shapes:
• makeStarter — area-name + quest-exploration with
cross-ref to WQT 100 ("Investigate the
Camp")
• makeDungeon — outdoor area-name + portal-style
InstanceEntrance with Deadmines key
gate (WIT itemId 5200, matches
WLCK.makeDungeon's Boss Vault Seal) +
interior exit teleport back outdoors
• makeFlightPath — 2 sphere waypoints near flight masters
so the runtime can auto-open the
flight UI on proximity (matches WTAX
starter node positions)
CLI added (5 flags, 601 documented total now):
--gen-triggers / --gen-triggers-dungeon / --gen-triggers-flightpath
--info-wtrg / --validate-wtrg
Validator catches: triggerId=0 + duplicates, unknown shape /
kind, non-finite center, sphere with radius<=0, box with
all-zero half-extents, teleport / instance with dest=(0,0,0)
(silently does nothing — usually a typo), QuestExploration
without an actionTarget questId.
Novel open replacement for Blizzard's Map.dbc + AreaTable.dbc
+ the AzerothCore-style world_zone SQL tables. The 26th open
format added to the editor.
Defines two related kinds of locator in one catalog:
• Maps — top-level worlds (continents / instances / raids /
battlegrounds / arenas) with a friendly name,
type, expansion tag, and player-count cap.
• Areas — sub-zones within maps with friendly names, parent-
area chain, recommended level range, faction-
territory marker (alliance / horde / contested /
both), exploration XP, and an ambient-sound
cross-reference into WSND.
The runtime uses Areas for minimap labels, location strings
under the player frame, "Discover Sub-zone" XP gains, and
ambient-music selection on zone entry.
Cross-references with previously-added formats:
WMS.area.ambienceSoundId -> WSND.entry.soundId
WMS.area.parentAreaId -> WMS.area.areaId (intra-format
sub-zone hierarchy)
WSPN entries are tied to WMS.area boundaries by
world position (no direct ID — the runtime resolves
position -> area at lookup time)
Format:
• magic "WMSX", version 1, little-endian
• maps[] (each): mapId / name / shortName / mapType /
expansionId / maxPlayers
• areas[] (each): areaId / mapId / parentAreaId / name /
minLevel..maxLevel / factionGroup / explorationXP /
ambienceSoundId
Enums:
• MapType (5): Continent / Instance / Raid / Battleground / Arena
• ExpansionId (5): Classic / Tbc / Wotlk / Cata / Mop
• FactionGroup: Both / Alliance / Horde / Contested
(PvP-flagging zone)
API: WoweeMapsLoader::save / load / exists +
WoweeMaps::findMap / findArea.
Three preset emitters showcase the catalog shape:
• makeStarter — 1 continent + 3 areas with parent chain
(Goldshire is a sub-zone of Elwynn Forest)
• makeClassic — 2 continents + Deadmines instance + 6
areas (Stormwind/Elwynn/Goldshire/Westfall/
Duskwood/Teldrassil/Deadmines) with WSND
ambient-sound refs
• makeBgArena — Alterac Valley (40-player BG) + Nagrand
Arena (5v5 with maxPlayers=10)
CLI added (5 flags, 578 documented total now):
--gen-maps / --gen-maps-classic / --gen-maps-bgarena
--info-wms / --validate-wms
Validator catches: empty map name, unknown mapType / expansion,
BG/Arena with maxPlayers=0 (no participant cap), area ids=0
+ duplicates, empty area name, maxLevel < minLevel, areas
referencing non-existent maps, parentAreaId chains crossing
maps (sub-zones must be on the same world), self-parent.
Novel open replacement for Blizzard's TalentTab.dbc +
Talent.dbc + the AzerothCore-style talent_progression SQL
tables. The 25th open format added to the editor.
Defines class talent specialization trees: per-class set
of named tabs (Arms / Fury / Protection for warrior, Fire
/ Frost / Arcane for mage), each with talents arranged in
a row/column grid, each talent having up to 5 ranks and
an optional prerequisite chain.
Cross-references with previously-added formats:
WTAL.talent.prereqTalentId -> WTAL.talent.talentId
(intra-format chain)
WTAL.talent.rankSpellIds[] -> WSPL.entry.spellId
(spell granted at each rank)
Format:
• magic "WTAL", version 1, little-endian
• per tree: treeId / name / iconPath / requiredClassMask /
talents[] (row, col, maxRank, prereqTalentId+rank,
rankSpellIds[5] zero-padded for unused ranks)
Enums:
• ClassMask: bit positions match canonical CharClasses.dbc
classIds — Warrior / Paladin / Hunter / Rogue / Priest /
DK / Shaman / Mage / Warlock / Druid
API: WoweeTalentLoader::save / load / exists +
WoweeTalent::findTree / findTalent (global lookup across
all trees in the catalog).
Three preset emitters showcase tree shapes:
• makeStarter — 1 small tree (3-talent vertical chain)
• makeWarrior — 3 trees (Arms 4 / Fury 4 / Protection 3)
with WSPL cross-refs at capstones
(Mortal Strike -> WSPL 12294, Battle Shout
-> WSPL 6673, Thunder Clap -> WSPL 6343)
• makeMage — 3 trees (Arcane / Fire / Frost) with
capstones referencing Frostbolt 116 /
Fireball 133 / Blink 1953 from WSPL
CLI added (5 flags, 571 documented total now):
--gen-talents / --gen-talents-warrior / --gen-talents-mage
--info-wtal / --validate-wtal
Validator catches: tree+talent ids=0 or duplicates, empty
tree name, requiredClassMask=0 (every class would see this
tree — usually a typo), maxRank not in 1..5, talent listing
itself as prerequisite, prereqTalentId pointing at a
talent that doesn't exist in this catalog (intra-format
cross-reference resolution), prereqRank=0 or > the prereq
talent's maxRank (catches off-by-one references), gaps in
rankSpellIds progression (rank N has spell but rank N-1
doesn't — usually a typo).
The validator caught a real authoring bug in the makeMage /
makeWarrior presets during smoke testing — initial check
was comparing prereqRank against the WRONG talent's maxRank
(this talent's rather than the prereq's). Fixed in the same
commit by hoisting the check into the cross-reference
resolution pass where the prereq talent is in hand.
Novel open replacement for Blizzard's TaxiNodes.dbc +
TaxiPath.dbc + TaxiPathNode.dbc. The 24th open format
added to the editor.
Defines the flight-master network: a set of named nodes
(positions on the world map) plus the paths between them
(sequences of waypoints with per-segment delay and a
per-path gold cost). The same file holds both node and
path lists — flat arrays keyed by id, with intra-format
references from path.fromNodeId / toNodeId to node.nodeId.
Cross-references:
WCRT.entry (with FlightMaster npcFlag) ~= WTAX.nodeId
(matched by world
position; flight
master NPCs stand
at their nodes)
WTAX.path.fromNodeId / toNodeId -> WTAX.entry.nodeId
(intra-format graph)
Format:
• magic "WTAX", version 1, little-endian
• nodes (each): nodeId / mapId / name / iconPath /
position / faction restrictions
• paths (each): pathId / from+toNodeId / moneyCostCopper /
waypoints[] each with position + per-waypoint delaySec
API: WoweeTaxiLoader::save / load / exists +
WoweeTaxi::findNode / findPath / findPathBetween.
Three preset emitters showcase different graph shapes:
• makeStarter — 2 nodes + 2 paths (round-trip)
• makeRegion — 4 nodes at a 500m square + 4-path
directed ring (NW->NE->SE->SW->NW)
• makeContinent — 6 nodes hub-spoke + 3 perimeter
shortcuts; intermediate waypoints
climb to altitude 120m for visual
arc effect
CLI added (5 flags, 564 documented total now):
--gen-taxi / --gen-taxi-region / --gen-taxi-continent
--info-wtax / --validate-wtax
Validator catches: nodeId/pathId=0 + duplicates, empty node
name, non-finite positions, fromNodeId == toNodeId
(self-loop path), path references to non-existent nodes
(intra-format cross-reference resolution), negative
waypoint delays.
Novel open replacement for AzerothCore-style gossip_menu +
gossip_menu_option + npc_text SQL tables PLUS the Blizzard
NpcText.dbc family. The 23rd open format added to the
editor.
An NPC's dialogue tree: a menu of options the player can
pick from when right-clicking the NPC. Each option may
bridge to another menu, trigger a vendor / trainer
interaction, offer a quest, etc. The simplified per-option
model (kind + actionTarget + flags + moneyCost) covers the
common cases without needing separate npc_text condition
tables.
Closes a major cross-format gap: WCRT.entry.gossipId has
existed since batch 116 (when WCRT was added) but pointed
to a format that didn't exist yet. The innkeeper preset's
menuId=4001 deliberately matches WCRT's Bartleby NPC so
the demo content stack can wire WCRT.gossipId = 4001 once
that field is plumbed through the runtime.
Cross-references:
WCRT.entry.gossipId -> WGSP.entry.menuId
WGSP.option.actionTarget (Submenu) -> WGSP.entry.menuId
WGSP.option.actionTarget (Vendor / Trainer)
-> WTRN.entry.npcId
WGSP.option.actionTarget (Quest) -> WQT.entry.questId
Format:
• magic "WGSP", version 1, little-endian
• per menu: menuId / titleText + options[]
• per option: optionId / text / kind / actionTarget /
requiredFlags / moneyCostCopper
Enums:
• OptionKind (13): Close / Submenu / Vendor / Trainer /
Quest / Tabard / Banker / Innkeeper /
FlightMaster / TextOnly / Script /
Battlemaster / Auctioneer
• OptionFlags: AllianceOnly / HordeOnly / Coinpouch /
QuestGated / Closes
API: WoweeGossipLoader::save / load / exists / findById;
presets makeStarter (1 menu with vendor + trainer + close),
makeInnkeeper (2-menu tree: main menu 4001 with hearth /
vendor / flight / submenu options + lore submenu 4002 that
links back), makeQuestGiver (1 menu with 2 quest options
referencing WQT 1 and 100, plus a paid respec script
exercising the Coinpouch flag with a 10g cost).
CLI added (5 flags, 558 documented total now):
--gen-gossip / --gen-gossip-innkeeper / --gen-gossip-questgiver
--info-wgsp / --validate-wgsp
Validator catches: menuId=0 + duplicates, empty title /
options, unknown option kind, empty option text, Submenu
options pointing at non-existent menuIds (intra-format
cross-reference resolution), Coinpouch flag without
moneyCost (misleading UI), AllianceOnly+HordeOnly conflict.
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.
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.
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).
Novel open replacement for Blizzard's Faction.dbc +
FactionTemplate.dbc + the AzerothCore-style
reputation_reward / reputation_spillover SQL tables. The
17th open format added to the editor.
Combines the "displayable Faction" (player-facing name +
reputation thresholds for friendly/honored/revered/exalted)
with the "FactionTemplate matrix" (which factions are
hostile to which) into one entry. The runtime walks the
catalog to answer two questions:
• "Will faction A attack faction B on sight?" -> enemy list
• "What rep tier is the player with X?" -> thresholds
Cross-references with previously-added formats:
WCRT.entry.factionId -> WFAC.entry.factionId
WFAC.entry.parentFactionId -> WFAC.entry.factionId
WFAC.entry.enemies[] -> WFAC.entry.factionId
WFAC.entry.friends[] -> WFAC.entry.factionId
The starter preset's factionId 35 (Friendly) and 14
(Hostile) deliberately match the WCRT preset defaults, so
the demo content stack is consistent: WCRT.makeBandit's
factionId=14 has a real entry in WFAC.makeStarter that
declares it hostile to friendly NPCs (35) and players (1).
Format:
• magic "WFAC", version 1, little-endian
• per faction: factionId / parentFactionId / name /
description / reputationFlags / baseReputation /
7 ascending tier thresholds (hostile..exalted) /
enemies[] / friends[]
Enums:
• ReputationFlags: VisibleOnTab / AtWarDefault / Hidden /
NoReputation / IsHeader (group label)
• Tier (canonical): Hated / Hostile / Unfriendly /
Neutral / Friendly / Honored /
Revered / Exalted
API: WoweeFactionLoader::save / load / exists / findById +
WoweeFaction::isHostile(a, b); presets makeStarter (3-faction
demo matching WCRT defaults), makeAlliance (header +
Stormwind / Darnassus / Ironforge with reciprocal friend
lists + Defias enemy), makeWildlife (4 beast factions, each
hostile to player but ignoring other beasts).
CLI added (5 flags, 514 documented total now):
--gen-factions / --gen-factions-alliance / --gen-factions-wildlife
--info-wfac / --validate-wfac
Validator catches: factionId=0 + duplicates, empty name,
threshold ordering violations (hostile must be < unfriendly
< neutral < ... < exalted), self-listed as enemy or friend,
faction in both enemies and friends (incoherent).
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.
Novel open replacement for AzerothCore-style scattered
creature_template / gameobject SQL spawn tables PLUS the
ADT MDDF / MODF doodad-placement chunks. The 11th open
format, and the first that covers the live world-content
side (atmosphere + sounds + spawns now form the runtime
"what fills this zone" picture).
A WSPN file holds all spawn points for a zone in a single
table, with kind discriminating creature vs game object
vs static doodad. The same format powers:
• server runtime — knows what NPCs / objects to spawn
• editor — draws spawn markers
• renderer — reads the doodad subset directly to
draw static props without going
through a server roundtrip
Format:
• magic "WSPN", version 1, little-endian
• per entry: kind / entryId / position(3f) / rotation(3f)
/ scale / flags / respawnSec / factionId /
questIdRequired / wanderRadius / label
Flags packed: disabled (0x01), event-only (0x02),
quest-phased (0x04). Reserved bits for future per-entry
encoding extensions.
API: WoweeSpawnsLoader::save / load / exists; presets
makeStarter (1 each kind), makeCamp (4-bandit ring +
chest + 2 tents), makeVillage (6 NPCs + 2 signs + 4
corner trees).
CLI added (5 flags, 473 documented total now):
--gen-spawns / --gen-spawns-camp / --gen-spawns-village
--info-wspn / --validate-wspn
Validator catches: out-of-range kind, NaN/inf coords,
non-positive scale, doodad with non-zero respawn (static
prop misuse), creature with respawn=0 (won't respawn after
kill), entryId=0 (orphan reference).
All 3 presets save / load / re-validate clean. Doodad and
game-object entries explicitly set wanderRadius=0 so the
generated catalogs are noise-free.
Novel open replacement for Blizzard's WDT (top-level world
definition table). The 9th open format added to the editor.
A WOMX file holds the manifest of which terrain tiles exist
within a world plus a tiny bit of map-level metadata. The
runtime consults it before attempting to load any individual
tile (so missing tiles produce a clean "no data" result
instead of a file-not-found error).
Format:
• magic "WMPX", version 1, little-endian
• mapName + worldType (continent/instance/battleground/arena)
• gridSize 1..128 (typically 64 for continents)
• defaultLightId / defaultWeatherId (atmosphere preset
refs, 0 if none — wires into the WOL/WOW pair)
• packed bitmap, 1 bit per tile, row-major
• A 64x64 manifest is exactly 512 bytes of bitmap
API: WoweeWorldMapLoader::save / load / exists; presets
makeContinent (64x64 full), makeInstance (4x4 full),
makeArena (1x1 full).
CLI added (5 flags, 456 total now):
--gen-world-map <base> [name] (continent)
--gen-world-map-instance <base> [name] (4x4)
--gen-world-map-arena <base> [name] (1x1)
--info-womx <base> [--json]
--validate-womx <base> [--json]
Round-trip verified: continent + instance + arena presets
all save / load / re-validate to byte-identical state with
correct tile counts.
8th open-format addition to the Wowee pipeline. Replaces
WoW's WeatherTypes.dbc / WeatherEffect logic with a single
binary file holding a list of weather states for one zone,
each tagged with intensity bounds, a probability weight,
and duration bounds. The renderer / runtime samples one
entry at a time using weighted-random selection, drives
it for a uniform-random duration in [min, max] sec, then
re-rolls.
• Types: Clear / Rain / Snow / Storm / Sandstorm / Fog /
Blizzard (extensible enum).
• Binary format: magic "WOWA", version 1, name, N entries
each storing (typeId, minIntensity, maxIntensity, weight,
minDurationSec, maxDurationSec).
CLI:
• --info-wow <wow-base> [--json] — inspect a WOW
• --gen-weather-temperate — clear + rain + fog (forest)
• --gen-weather-arctic — snow + blizzard + fog (tundra)
• --gen-weather-desert — clear + sandstorm (dunes)
• --gen-weather-stormy — rain + storm + occasional clear
The 8th open format complementing the rest:
M2 → WOM | WMO → WOB | WMO collision → WOC | ADT → WOT
DBC → JsonDBC | BLP → PNG | Light.dbc → WOL | WeatherTypes.dbc → WOW
Smoke-tested all 4 presets + JSON output. Each preset reads
back identically with the expected entry count and weight
distribution.
Three new single-keyframe WOL presets complement the
existing 4-keyframe day/night cycle from --gen-light:
• --gen-light-cave — dim cool ambient (0.05, 0.05, 0.07)
+ heavy short-range fog (15..80)
for cave / mine interiors
• --gen-light-dungeon — warm torchlit ambient (0.18, 0.14,
0.10) + medium fog (25..200) for
dungeon / crypt interiors
• --gen-light-night — cold blue ambient (0.06, 0.07, 0.12)
+ moonlit directional + far fog
(80..500) for always-night zones
Each preset emits a single-keyframe WOL since enclosed /
fixed-time scenes don't vary with time-of-day. All three
share an emitLightPreset helper so adding more presets
(e.g. --gen-light-tundra, --gen-light-volcanic) is one
line of registration + a maker function.
All four WOL outputs validate clean under --validate-wol
(1 or 4 keyframe(s) valid).
Three additions to the Wowee Open Light format that landed
last commit:
• WoweeLightLoader::sampleAtTime(light, timeMin) returns
the linearly-interpolated keyframe at any time-of-day,
correctly handling wrap-around between the last keyframe
and the first (e.g. 21:00 blends from dusk toward
midnight by going forward through 00:00).
• --validate-wol <wol-base> [--json] walks every keyframe
and reports structural problems: time bounds (must be
[0, 1440)), strict-ascending sort order, fogEnd >
fogStart, finite color components. Exit code 0 PASS /
1 FAIL — CI-friendly.
• --info-wol-at <wol-base> <HH:MM|minutes> samples the
interpolated state at a specific time of day. Useful
for previewing what the renderer would feed in at a
given moment, debugging keyframe gaps, or previewing
a sub-range of the cycle.
Smoke-tested: dawn-to-midnight blend at 03:00 yields a
plausible mid-fade ambient (0.18, 0.16, 0.15) and dusk-to-
midnight wrap at 21:00 yields the symmetric (0.19, 0.145,
0.14). The default 4-keyframe day/night cycle from
makeDefaultDayNight passes --validate-wol cleanly.
New open replacement for WoW's Light.dbc / LightParams.dbc /
LightIntBand.dbc / LightFloatBand.dbc stack — a single .wol
file holds a list of time-of-day keyframes for one zone,
each capturing the ambient + directional + fog state at that
moment. The renderer interpolates between adjacent keyframes
by time-of-day.
Binary layout:
magic[4] = "WOLA", version (uint32),
nameLen + name bytes,
keyframeCount + keyframes (each 13 floats + 1 uint32 time)
Per keyframe:
• timeOfDayMin (0..1439 = minutes since midnight)
• ambientColor.rgb, directionalColor.rgb, directionalDir.xyz
• fogColor.rgb, fogStart, fogEnd
CLI:
• --gen-light <wol-base> [zoneName] — emit a starter file
with 4-keyframe day/night cycle (midnight/dawn/noon/dusk)
using reasonable outdoor defaults
• --info-wol <wol-base> [--json] — inspect: zone name +
per-keyframe time-of-day + colors + fog distances
The 7th open-format addition to the Wowee pipeline:
M2 → WOM (model)
WMO → WOB (building)
WMO collision → WOC
ADT → WOT (terrain)
DBC → JsonDBC
BLP → PNG
Light.dbc family → WOL ← new
Smoke-tested round-trip: gen → info shows correct 4 keyframes
at 00:00 / 06:00 / 12:00 / 18:00 with the canonical color
ramps. JSON output for tooling integration.
Closes the loop on the asset_extract --emit-terrain pipeline. The
runtime terrain loader now probes for a .whm/.wot/.woc trio in the
same directory as the resolved ADT (e.g. <data>/world/maps/foo/
foo_30_30.{whm,wot,woc}) before falling back to ADTLoader.
Hits the open-format path when:
custom_zones/<map>/<map>_X_Y.{whm,wot} exists (zone author override)
output/<map>/<map>_X_Y.{whm,wot} exists (editor export)
<data>/world/maps/<map>/<map>_X_Y.{whm,wot} (asset extractor)
-- otherwise falls through to ADTLoader::load(adtData)
Promotes AssetManager::resolveFile to public so callers (terrain
sidecar probe here, anything else later) can locate an extracted
file's directory without reading the bytes.
Servers/private servers continue to read .adt via manifest paths
unchanged. Runtime sidecar coverage now matches the extractor's
emit set across all five binary open formats.
Extends asset_extract with two more open-format emitters:
--emit-wom foo.m2 (+ foo00.skin) → foo.wom
--emit-wob foo.wmo (+ foo_NNN.wmo groups) → foo.wob
--emit-open now also turns these on
Originals are preserved so private servers still load .m2/.wmo
through the manifest path; the wowee runtime/editor pick up the
.wom/.wob next to them via the existing open-format search rules.
Implementation:
- New WoweeModelLoader::fromM2Bytes(m2Data, skinData) shares the
conversion body with fromM2(path, am) via a static helper
(convertM2ToWom). Lets the extractor convert without standing
up an AssetManager.
- fromM2(path, am) moved to a separate translation unit
(wowee_model_fromm2.cpp) so asset_extract doesn't have to
link the AssetManager dependency.
- WoweeBuildingLoader::fromWMO already takes a WMOModel directly,
so emitWobFromWmo just needs to read root + group files and
call save().
- Group sub-files (<base>_NNN.wmo) are skipped during the walk
since they're merged into the root WMO.
setPosition/setRotation/setAspectRatio/setFov now reject:
- NaN/inf inputs (would produce NaN view/proj matrix → frozen GPU
on some drivers, garbage frustum culling everywhere)
- aspectRatio <= 0 (degenerate perspective)
- fov <= 0 or >= 180 (degenerate perspective)
Camera is constructed and set from many code paths; pushing the
guards into the setters means none of them need to remember.
Mirrors the WOM tryLoadByGamePath API: probes custom_zones/buildings/ +
output/buildings/ by default, with optional extraPrefixes (e.g. per-zone
output/<map>/buildings/) checked first. Both the editor and the main
game's terrain_manager now use the helper, removing duplicate inline
lookup loops in two more places.
The shared helper only probed custom_zones/models/ + output/models/, but
the editor's exportZone writes to output/<map>/models/. Added an
extraPrefixes parameter that's tried before the defaults — main game's
terrain_manager now passes ['output/<map>/models/', 'custom_zones/<map>/
models/'] so per-zone WOM exports override globals. Also removes the
last duplicate WOM-loading code from terrain_manager.
Mirrors the editor's WMO scale fix. WMOReady gains a scale field that
is computed from the loaded MODF placement.scale (u16 / 1024) and
forwarded to wmoRenderer->createInstance(). Without this the main
game ignored MODF scale even on WotLK ADTs that use it.
WMOPlacement struct gains nameSet and scale fields (defaulting to 0 and
1024 = 1.0). The loader now reads them when the entry is the full 64
bytes (WotLK+); the writer emits the actual values rather than always
hard-coding (0, 1024). Older expansions still round-trip cleanly because
defaults match the previous behaviour.
The editor's rebuildObjects path was destroying every cached model and
re-uploading it on every (debounced) change. Added M2Renderer::clearInstances
that drops only the instance list while keeping models loaded. Editor's
clearObjects switches to clearInstances (M2) + clearInstances (WMO),
and persistent path->modelId maps survive across rebuilds. clearTerrain
fully evicts when loading a new zone.
WoweeCollision previously only contained terrain triangles; placed WMO
buildings had no collision in the exported zone, so players could walk
through walls. Added WoweeCollisionBuilder::addMesh() that transforms a
local-space mesh into world space with slope-based walkability flags,
and the editor's exportZone now walks every placed WMO and feeds each
group's geometry through it. Indoor vs outdoor groups are tagged via
the WMO group flag.
WOM1/WOM2 had a single mesh with one texture, which lost the multi-submesh
structure of complex M2 models (body+hair+eyes+armor each need different
textures and blend modes).
WOM3 adds a Batch array: each batch has indexStart/indexCount + a textureIndex
into texturePaths + blendMode + flags. Loader is fully backward compatible:
WOM1/WOM2 files still load, and WOM3 with no batches block falls back to a
single full-mesh batch. fromM2 now extracts batches with materials, and toM2
emits matching M2 batches so the renderer can draw them correctly.
Adds WoweeModelLoader::toM2() and tryLoadByGamePath() to deduplicate the
identical conversion code that lived in editor_viewport for both objects
and NPCs. Cuts ~70 lines of duplicated logic and makes WOM->M2 reusable
across the codebase.
setGhostPreview reused modelId 59999 for every preview, but loadModel
returns true without doing anything when the ID is already cached. So
selecting a new NPC kept the old ghost model in GPU memory and createInstance
used the stale model. Added M2Renderer::unloadModel public API and call it
from clearGhostPreview.
Upgrades WOM from geometry-only (WOM1) to fully animated (WOM2):
- WOM2 magic (0x324D4F57) for animated models, WOM1 for static
- Vertex extended: +boneWeights[4] +boneIndices[4] (40 bytes vs 32)
- Bone data: keyBoneId, parentBone, pivot, flags per bone
- Animation data: per-sequence per-bone keyframes with translation,
rotation (quaternion), scale at millisecond timestamps
- fromM2() now preserves all skeletal data: bone hierarchy, weights,
and per-sequence keyframes from M2 animation tracks
- Backward compatible: WOM1 files load without bone data (32-byte
vertices read and padded with default bone weights)
- FORMAT_SPEC.md updated with WOM2 binary layout
- TerrainManager loads WOC collision meshes alongside WOT/WHM terrain
from both custom_zones/ and output/ directories
- CollisionData stored per-tile with triangle array + bounds
- isPositionWalkable(x, y): returns whether a world position is on
walkable terrain (barycentric point-in-triangle test)
- getCollisionFlags(x, y): returns per-triangle flags (walkable,
water, steep, indoor) for movement system integration
- Defaults to walkable when no collision data is loaded (backward compat)
- Custom zone players now have proper terrain physics boundaries
New format: WOC (Wowee Open Collision) — binary collision mesh for
custom zone walkability. Magic WOC1 (0x31434F57).
- WoweeCollisionBuilder::fromTerrain() generates collision triangles
from terrain heightmap with slope classification (50 deg threshold)
- Per-triangle flags: walkable (0x01), water (0x02), steep (0x04)
- Respects terrain holes (skips triangles in hole regions)
- Binary save/load with bounds, tile coords, triangle data
- Auto-exported on zone save alongside WOT/WHM/WOM/WOB
- Added to content pack validation (score now 0-7)
- FORMAT_SPEC.md v1.1 updated with WOC binary layout
- 19 new test assertions: flat terrain generation (32k tris all
walkable), save/load round-trip, hole skipping
- 328 total assertions across 84 test cases
Architecture fixes for open format data fidelity:
- WOT now serializes full doodad/WMO placement arrays (positions,
rotations, scale, flags, doodad sets) — was only storing counts,
causing all placed objects to be lost on WOT round-trip
- WOT loader parses placements back into ADTTerrain for client rendering
- WOB Material struct added: preserves WMO material flags, shader type,
and blend mode during WMO→WOB conversion (was geometry-only)
- WOB doodad rotation: quaternion→euler conversion instead of hardcoded
zero (placed doodads inside buildings now retain their orientation)
- importOpen() deduplicated: delegates to pipeline::WoweeTerrainLoader
instead of duplicating 100 lines of parsing code
- Wire WOB buildings into WMO render pipeline (loads→converts→renders)
- Implement JSON DBC loading in DBCFile::loadJSON() with nlohmann/json
- Wire JSON DBC override into AssetManager (custom_zones/output scan)
- Add WMO→WOB conversion with full geometry (fromWMO)
- Replace placeholder WOB export with real WMO→WOB conversion in editor
- Add --convert-wmo CLI flag for batch WMO→WOB conversion
- Store discovered custom zones on Renderer with getCustomZones() accessor
- Add isCustomZone_ member to TerrainManager
All 6 Blizzard format replacements now fully load in the client:
ADT→WOT/WHM, WDT→zone.json, BLP→PNG, DBC→JSON, M2→WOM, WMO→WOB
- WoweeBuildingLoader::toWMOModel() converts WOB groups to WMOModel
with vertices, indices, normals, texCoords, and vertex colors
- TerrainManager now loads WOB files from custom_zones/buildings/
and converts to WMOModel for the WMO renderer pipeline
- WMOGroup indices converted from uint32 to uint16 for renderer compat
Client open format support — 4 of 6 now loading:
- FULL: WOT/WHM terrain, PNG textures, WOM models
- LOAD: WOB buildings (converts to WMOModel, render pipeline TODO)
- DETECT: zone.json (scanned), JSON DBC (scanned)
ALL 6 BLIZZARD FORMATS NOW HAVE OPEN REPLACEMENTS.
WOB format: binary building file with groups, portals, and doodads.
- WOB1 magic header
- Groups: vertices (pos+normal+uv+color), indices, texture paths,
bounding box, indoor/outdoor flag
- Portals: connect groups with polygon boundaries
- Doodad placements: reference .wom models with transform
WoweeBuildingLoader: load/save/exists for .wob files.
Complete format replacement table:
- ADT → WOT/WHM (terrain heightmaps + metadata)
- WDT → zone.json (map definition)
- BLP → PNG (textures)
- DBC → JSON (data tables)
- M2 → WOM (static models)
- WMO → WOB (buildings with groups/portals/doodads)
The wowee project now has a complete suite of novel, open file
formats for creating and distributing custom WoW content without
any dependency on Blizzard proprietary file formats.