Moves the two audio-config audit handlers (--info-zone-audio,
--info-project-audio) out of main.cpp into a new
cli_info_audio.{hpp,cpp} module. Both surface the music /
day-ambience / night-ambience / volume settings stored in
zone.json so designers can spot zones still missing audio
assignment before a release pass.
main.cpp shrinks by 127 lines (7,053 to 6,926). Drops the
dead `label` lambda inside info-project-audio that the
unused-but-set-variable warning had been flagging. Both
--json output modes preserved.
Moves the two content-density audit handlers
(--info-zone-density, --info-project-density) out of main.cpp
into a new cli_info_density.{hpp,cpp} module. Both count
creatures/objects/quests per tile to surface sparse zones
(boring) and over-stuffed ones (frame-rate bombs); the project
variant rolls per-zone numbers into a project-wide table with
per-zone averages for content-pacing reviews.
main.cpp shrinks by 201 lines (7,254 to 7,053). Both --json
output modes preserved for balance-audit pipelines.
Moves the two water-layer audit handlers (--info-zone-water,
--info-project-water) out of main.cpp into a new
cli_info_water.{hpp,cpp} module. Both aggregate liquid data
(water/ocean/magma/slime) across MH2O chunks; the project
variant rolls per-zone counts into a project-wide total with
height range and per-type histogram.
main.cpp shrinks by 209 lines (7,463 to 7,254). Both --json
output modes preserved for capacity-planning pipelines.
Moves the two spatial-bounds info handlers (--info-zone-extents,
--info-project-extents) out of main.cpp into a new
cli_info_extents.{hpp,cpp} module. Both compute world-space XYZ
bounding boxes from manifest tile coords + per-chunk height
samples; the project variant unions every zone's box into one.
main.cpp shrinks by 232 lines (7,695 to 7,463). Both --json
output modes preserved for camera-framing pipelines.
Moves the two disk-byte audit handlers (--info-zone-bytes,
--info-project-bytes) out of main.cpp into a new
cli_info_bytes.{hpp,cpp} module. Both categorize files by
extension into open / proprietary / derived buckets — the
project-wide variant is the headline metric for tracking
the open-format migration's progress against the .m2/.wmo/
.blp/.dbc baseline.
main.cpp shrinks by 244 lines (7,939 to 7,695). Both --json
output modes preserved for machine-readable reports.
Moves the two tree-style content browser handlers
(--info-zone-tree, --info-project-tree) out of main.cpp into
a new cli_info_tree.{hpp,cpp} module. Both render
Unix-`tree`-style hierarchical views — one drilling into a
single zone (manifest, tiles, creatures, objects, quests,
files) and one giving a bird's-eye view of every zone in a
project with bake/viewer status.
main.cpp shrinks by 201 lines (8,140 to 7,939). The remaining
info-zone/-project-* pairs (bytes, extents, water, density,
audio) form the next natural extraction batch.
Moves all six world-asset interchange handlers (--export-wob-glb,
--export-wob-obj, --import-wob-obj, --export-whm-glb,
--export-whm-obj, --export-woc-obj) out of main.cpp into a new
cli_world_io.{hpp,cpp} module. WOB / WHM / WOC are our open
replacements for proprietary WMO / ADT-heightmap / ADT-collision
data; these are the bridge that lets the open formats round-trip
through Blender, MeshLab, Three.js, and the rest of the standard
3D toolchain.
main.cpp shrinks by 858 lines (8,997 to 8,140). The single-mesh
--import-obj handler stays inline for now -- it shadow-mirrors
cli_wom_io's --import-stl semantics and will move there next.
Moves the four WOM interchange-format handlers (--export-obj,
--export-glb, --export-stl, --import-stl) out of main.cpp into
a new cli_wom_io.{hpp,cpp} module. WOM is our open M2
replacement; these are the bridge that lets it round-trip
through every external 3D tool — Blender, Three.js, slicers,
CAD packages — so the open format is actually useful.
main.cpp shrinks by 467 lines (9,464 to 8,997). The five WOB
and WHM exporters (--export-wob-glb, --export-whm-glb, etc.)
remain inline for a follow-up extraction.
Moves all 4 GLB introspection handlers (--validate-glb /
--info-glb shared, --info-glb-tree, --info-glb-bytes,
--check-glb-bounds) out of main.cpp into a new
cli_glb_inspect.{hpp,cpp} module. GLB is our open replacement
for proprietary M2/WMO bake outputs, so these belong with the
other open-format tooling.
main.cpp shrinks by 657 lines (10,121 to 9,464). Every
handler preserves its --json output mode for machine-readable
reports.
Moves the four structural validators for INTEROP file formats
(--validate-stl, --validate-png, --validate-blp, --validate-jsondbc)
out of main.cpp into a new cli_validate_interop.{hpp,cpp} module.
These check files coming in/out of wowee from third-party tools,
distinct from cli_format_validate.cpp which validates the native
open formats (WOM, WOB, WOC, WHM).
main.cpp shrinks by 524 lines (10,644 to 10,121). Each validator
preserves its --json output mode for machine-readable reports.
Moves the five single-file format converters (--convert-m2,
--convert-wmo, --convert-dbc-json, --convert-json-dbc,
--convert-blp-png) out of two dedicated post-loop blocks in
main.cpp into a new cli_convert_single.{hpp,cpp} module. The
batch wrappers in cli_convert.cpp keep working — they shell
out to wowee_editor with these flags.
main.cpp shrinks by ~335 lines (10,979 → 10,644). The two
trailing for-loops were dead code after wiring the dispatcher
into the main loop, so they're gone too.
Moves the four schema-migration handlers (--migrate-wom,
--migrate-zone, --migrate-project, --migrate-jsondbc) out of
main.cpp into a new cli_migrate.{hpp,cpp} module. Same
behavior — they're idempotent in-place upgraders for older
WOM revisions and JSON DBC sidecar schemas. main.cpp shrinks
by 282 lines (11,261 → 10,979).
Moves the 3D-export bake handlers out of main.cpp:
--bake-zone-glb --bake-zone-stl --bake-zone-obj
--bake-project-obj --bake-project-stl --bake-project-glb
The STL + GLB project bakes share a combined dispatcher (one
function with internal STL-vs-GLB branching) since they walk
the same per-zone asset list and only differ in the output
emission code.
main.cpp drops 12,119 → 11,261 lines (-858). The combined-OR
opener spanning multiple lines created a parse-error fragment
in the extraction; caught + manually fixed before commit
(same pattern as the WOM info attachments/particles/sequences
extraction).
Moves the report-export handlers (md / csv / html / sha256 /
graphviz) for zone & project audits out of main.cpp:
--export-zone-summary-md --export-zone-csv
--export-zone-checksum --export-project-checksum
--validate-project-checksum --export-zone-html
--export-project-html --export-project-md
--export-quest-graph
Also moves the file-scope wowee_sha256 namespace (the SHA-256
implementation that the checksum exporters use) into the new
module's anonymous namespace — it had no other callers in
main.cpp so no cross-TU coupling needed.
main.cpp drops 13,120 → 12,119 lines (-1,001). Build error
during extraction (missing #include <unordered_set>) caught
and fixed.
Moves the four extracted-Data-tree audit handlers out of main:
--info-extract (per-extension counts + bytes)
--info-extract-tree (per-directory rollup)
--info-extract-budget (proprietary share + open-format gap)
--list-missing-sidecars (find unconverted .m2/.wmo/.blp/.dbc)
All four operate on a Blizzard-format extracted Data tree —
they audit what's there and what's missing in the migration
from proprietary formats to open ones.
main.cpp drops 13,485 → 13,120 lines (-365). Behavior verified
by re-running --info-extract on a test zone (same output).
Moves the items.json read-only inspection handlers out of
main.cpp:
--list-items --info-item
--validate-items --validate-project-items
--info-project-items
Item editing handlers (--add-item, --set-item, --remove-item,
--add-quest-reward-item) stay in main.cpp since they share
state with quest reward editing logic and would need a
broader extraction.
main.cpp drops 13,887 → 13,485 lines (-402). Behavior
verified by re-running --list-items on a non-items zone
(same error message).
Moves the NPC spawn / object placer audit + ground-snap
handlers out of main.cpp:
--snap-zone-to-ground --snap-project-to-ground
--audit-zone-spawns --audit-project-spawns
--list-zone-spawns --list-project-spawns
--diff-zone-spawns --info-spawn
All operate on creatures.json + objects.json sidecars and
the WHM terrain heightfield via WoweeTerrainLoader.
main.cpp drops 14,628 → 13,887 lines (-741). Behavior verified
by re-running --audit-zone-spawns on a test zone (PASSED with
0 issues, same as before).
Moves the file-comparison handlers out of main.cpp into their
own translation unit:
--diff-wcp --diff-zone
--diff-glb --diff-wom
--diff-wob --diff-whm
--diff-woc --diff-jsondbc
--diff-extract --diff-checksum
main.cpp drops 15,653 → 14,732 lines (-921). One build error
during extraction (missing #include for NpcSpawner /
ObjectPlacer / QuestEditor used by --diff-zone) caught by
build and fixed.
Moves the seven proprietary-data-tree handlers out of main.cpp:
--migrate-data-tree --bench-migrate-data-tree
--list-data-tree-largest --export-data-tree-md
--info-data-tree --strip-data-tree
--audit-data-tree
All operate on a Blizzard-format extracted Data tree (the .m2/
.skin/.wmo/.blp/.dbc files) — they audit, migrate, or strip
proprietary-format files in support of the open-format
migration story.
Original placement spanned two sub-blocks (12546-12892 and
13093-13417 in main.cpp) interrupted by --gen-texture and
--add-texture-to-zone in the middle. Extraction collapses
both sub-blocks into one cohesive translation unit.
main.cpp drops 16,321 → 15,653 lines (-668). Behavior verified
by re-running --info-data-tree against a missing directory.
Moves the three zone & project metadata inspection handlers
out of main.cpp:
--info-zone (single zone.json print)
--info-zone-overview (high-level zone digest)
--info-project-overview (per-zone summary table for a project)
All three load zone.json via wowee::editor::ZoneManifest.
main.cpp drops 16,564 → 16,321 lines (-243). Behavior verified
by re-running --info-zone + --info-project-overview against
existing test zones.
Moves the six WoWee Content Pack (.wcp) handlers out of
main.cpp:
--list-wcp --info-wcp
--info-pack-budget --info-pack-tree
--pack-wcp --unpack-wcp
All six defer to wowee::editor::ContentPacker for actual pack
I/O; the handlers just parse args and format output, so the
extraction has no behavioral impact on the pack format itself.
main.cpp drops 17,766 → 17,505 lines (-261). Behavior verified
by re-running --info-wcp on a missing file (same error message).
Moves the proprietary-format inspection commands out of main.cpp
into their own translation unit. Each reads a Blizzard-format
file and prints its structure:
--info-png --info-blp
--info-m2 --info-wmo
--info-adt --info-jsondbc
main.cpp drops 18,198 → 17,766 lines (-432). Behavior verified
by re-running --info-png on a generated noise texture.
Moves the four bulk format-conversion handlers out of main.cpp:
--convert-m2-batch (M2 → WOM)
--convert-wmo-batch (WMO → WOB)
--convert-blp-batch (BLP → PNG)
--convert-dbc-batch (DBC → JSON)
These all share the same pattern: walk srcDir recursively for
files of the input extension and fan out to the single-file
--convert-* counterpart via subprocess (preserving the existing
per-file logic as the source of truth, no duplication).
Single-file converters (--convert-m2, --convert-wmo, etc.) and
the --migrate-* meta-commands still live in main.cpp; they're
in dedicated argv-rescan loops that need a different extraction
approach.
main.cpp drops 18,396 → 18,198 lines (-198). Behavior verified
by re-running --convert-blp-batch with a missing directory and
confirming the same error message.
Moves the open-format validation + project-audit handlers out
of main.cpp:
--validate --validate-wom
--validate-wob --validate-woc
--validate-whm --validate-all
--validate-project --validate-project-open-only
--audit-project --bench-audit-project
--bench-validate-project
Also moves the four shared validate*Errors helpers (validateWom/
Wob/Woc/WhmErrors, ~365 lines) into the same module's anonymous
namespace — they were file-scope helpers in main.cpp used only
by these handlers, so co-locating eliminates the cross-TU
coupling.
main.cpp drops 19,446 → 18,396 lines (-1,050). Two build errors
caught during extraction (wrong include path for the WHM loader
header; missing #include for ContentPacker / std::set / std::map);
all fixed before commit.
Moves the WOM model inspection commands out of main.cpp:
--info (bare WOM summary)
--info-batches (per-batch material info)
--info-textures (texture path list)
--info-doodads (WOB doodad set / instance list)
--info-attachments ⎫ combined handler with same M2 load +
--info-particles ⎬ skin merge but different sub-array
--info-sequences ⎭ iteration
--info-bones (bone hierarchy)
--export-bones-dot (Graphviz DOT output)
main.cpp drops 20,005 → 19,446 lines (-559). Behavior verified
by running --info, --info-batches, --info-textures on a fresh
WOM. Build error during extraction (combined-or handler header
spanned 4 lines, the transform script only stripped the first;
also missing #include for WoweeBuildingLoader) caught by build
and fixed before commit.
Moves the WOM mesh editing/transform handlers out of main.cpp:
--add-texture-to-mesh --scale-mesh
--translate-mesh --strip-mesh
--rotate-mesh --center-mesh
--flip-mesh-normals --mirror-mesh
--smooth-mesh-normals --merge-meshes
These are the post-generation manipulators (load → mutate → save)
distinct from the geometry generators in cli_gen_mesh.cpp and
the heightmap I/O in cli_mesh_io.cpp.
main.cpp drops 20,741 → 20,005 lines (-736). Behavior verified
by piping a fresh cube through scale → translate → center.
Moves the three mesh-PNG bridge handlers (--displace-mesh,
--gen-mesh-from-heightmap, --export-mesh-heightmap) out of
main.cpp into their own translation unit. These three are
distinct from the gen-mesh-* primitive generators in that
they read or write external image files rather than synthesize
geometry from parameters alone.
main.cpp drops 21,061 → 20,741 lines (-320). Behavior verified
by re-running gen-mesh-from-heightmap → validate-wom → and
displace-mesh on a fresh plane.
Moves the recently-added composite-prop mesh handlers (rock,
pillar, bridge, tower, house, fountain, statue, altar, portal,
archway, barrel, chest) into their own translation unit. These
12 handlers were the most contiguous block of similar-shaped
mesh code in main.cpp.
Other mesh handlers (--gen-mesh dispatcher, fence, tree, grid,
stairs, disc, tube, capsule, arch, pyramid, from-heightmap,
textured) still live in main.cpp and may be migrated in
subsequent batches.
main.cpp drops 24,270 → 22,681 lines (-1,589). Behavior
verified by re-running rock/chest/archway/fountain.
Moves the Worley/cellular-noise-based texture handlers
(--gen-texture-cobble, -marble, -metal, -leather, -sand,
-snow, -lava) into their own translation unit. Each handler
previously had its own ~33-line copy of the parseHex lambda;
all 7 copies are replaced with a single shared parseHex
helper at file scope.
Older simpler generators (gradient/noise/radial/stripes/dots/
rings/checker/brick/wood/grass/fabric) still live in main.cpp
and will be migrated in subsequent batches.
main.cpp drops 26,286 → 25,494 lines (-792). Behavior
unchanged across all 7 handlers (re-verified).
Pulls the 597-line block of printf calls that emits the --help
text out of main.cpp into its own translation unit. printUsage
is the longest single function in main.cpp by far and was pure
boilerplate (no logic, just a flat list of help lines).
Function moved verbatim to wowee::editor::cli::printUsage; all
6 in-tree callers (--list-commands, --info-cli-stats,
--info-cli-categories, --info-cli-help, --validate-cli-help,
and the bare --help/-h handler) updated to the namespaced name.
The CLI-meta commands continue to capture printUsage's stdout
the same way, so behavior is identical (verified by re-running
--validate-cli-help, --info-cli-stats, --list-commands).
main.cpp drops 26,650 → 26,286 lines (-364 net; -597 from the
removal, +233 from the include and namespace-prefixing the
six call sites... wait, no, +6). Actually main.cpp net delta
matches the body extraction.
Pairs with the per-zone inventory module from c0f2ab75. Moves
the five project-wide inventory handlers into their own file:
--list-project-meshes (per-zone WOM aggregate)
--list-project-meshes-detail (per-mesh sorted listing)
--list-project-audio (per-zone WAV aggregate)
--list-project-textures (per-zone + global texture refs)
--info-project-summary (BOOTSTRAPPED/PARTIAL/EMPTY rollup)
All five enumerate zones the same way; consolidated that into
a shared enumerateZones() helper at file scope.
main.cpp drops 27,200 → 26,650 lines (-550). Behavior unchanged
across all 5 commands (verified against /tmp/proj_starter).
Continues the modularization. Moves the four per-zone inventory
handlers into their own file:
--list-zone-meshes (WOM stats)
--list-zone-audio (WAV header parse)
--list-zone-textures (texture-ref histogram)
--info-zone-summary (BOOTSTRAPPED/PARTIAL/EMPTY status)
Each consumes a trailing --json flag the same way; consolidated
that check into a consumeJsonFlag helper at the top.
main.cpp drops 27,445 → 27,200 lines (-245). Per-zone inventory
behavior unchanged (re-verified all 4 commands).
A pre-existing duplicate --list-zone-meshes handler (the older
detailed per-mesh listing, similar to the --list-project-meshes
collision fixed in 976549fe) is now dead code in the in-line
chain since the extracted module dispatches first. Will rename
or remove that duplicate in a follow-up cleanup.
Continues the modularization. Moves --gen-zone-readme and
--gen-project-readme into their own translation unit using the
established handle<Family> dispatch pattern. Consolidated the
trailing --out flag parser into a single parseOutFlag helper
(was duplicated between the two handlers).
main.cpp drops 27,736 → 27,445 lines (-291). Behavior unchanged
(verified by re-generating README and PROJECT.md, contents
identical to pre-refactor output).
Continues the modularization. Moves the four audit handlers
(--validate-zone-pack, --validate-project-packs, --info-zone-deps,
--info-project-deps) into their own file using the same
handle<Family>(int& i, int argc, char** argv, int& outRc) pattern.
Side-cleanup: the two project-scope audits had identical
subprocess-fanout structure (enumerate zones → run per-zone
command → tally PASS/FAIL → print summary). Consolidated that
into a shared runPerZoneAudit helper. Saves ~80 lines of
duplicated dispatch code.
main.cpp drops 28,070 → 27,736 lines (-334). Audit family is
fully self-contained (~330 lines), behavior unchanged
(verified all 4 commands against existing test zones).
Continues the modularization started in 6c9ab6fa. Moves the four
pack-orchestrator handlers (--gen-zone-texture-pack, -mesh-pack,
-starter-pack, --gen-project-starter-pack) into their own file
following the same handle<Family> pattern.
Side cleanup:
- Consolidated the duplicated --seed flag parser into a single
parseSeedFlag helper
- Consolidated the std::system + > /dev/null wrap into runSilently
main.cpp drops 28,329 → 28,070 lines (-259). Pack family is fully
self-contained (~260 lines), behavior unchanged (verified by
re-running gen-zone-starter-pack and confirming 6 PNGs + 5 WOMs).
main.cpp had grown past 28k lines, with each new procedural-
generation command adding 100-200 lines to the inline if/else
dispatch chain. This commit starts breaking that up by moving
the four audio-related handlers (--gen-audio-tone, -noise,
-sweep, --gen-zone-audio-pack) into their own translation unit.
Pattern established here for future family extractions:
- Family lives in cli_<family>.{hpp,cpp}
- Single dispatch entry point: bool handle<Family>(int& i, int argc,
char** argv, int& outRc) — true if matched (writes outRc), false
to fall through.
- main.cpp's argv loop calls each family's dispatcher first and
returns its outRc on match, before the legacy in-line chain.
Side-benefit: consolidated the duplicated 25-line WAV header
writer + 5ms attack/release envelope into shared helpers
(writeWavMono16, applyEdgeEnvelope) at the top of the new file.
main.cpp drops from 28,943 → 28,329 lines (-614). Audio family
is fully self-contained (~440 lines), behavior unchanged
(verified by re-running tone/noise/sweep + zone-audio-pack).
Final piece of the open-format emit pipeline:
--emit-terrain foo.adt → foo.whm + foo.wot + foo.woc
With this, --emit-open now produces a fully open-format zone
alongside every Blizzard MPQ extraction:
BLP → PNG (textures)
DBC → JSON (data tables)
M2 → WOM (models, with skin merge)
WMO → WOB (buildings, with group merge)
ADT → WHM/WOT (terrain heights + metadata)
→ WOC (collision mesh derived from heights)
Originals stay on disk and indexed by manifest.json so private
servers continue to load proprietary formats; wowee runtime/editor
read the open formats directly. One extraction now feeds both
audiences with no separate conversion pass.
Implementation:
- Inline WHM+WOT writer in open_format_emitter.cpp (mirrors the
editor's WoweeTerrain::exportOpen but without the PNG-preview /
normal-map deps so the extractor stays editor-independent).
- Tile coords (x,y) parsed from <map>_<x>_<y>.adt filename.
- Collision mesh derived via WoweeCollisionBuilder::fromTerrain
(terrain triangles only — WMO collision overlays would need
asset manager and aren't worth the extractor complexity).
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.
The asset_extract tool now optionally writes wowee open-format
copies next to each extracted proprietary file:
--emit-png foo.blp → foo.png
--emit-json-dbc foo.dbc → foo.json
--emit-open shortcut for both
Originals are left untouched, so private servers (AzerothCore,
TrinityCore) that load from the manifest's .blp/.dbc paths
continue to work unchanged. The wowee runtime / editor can now
consume the open formats directly without an extra conversion pass.
Implementation:
- New tools/asset_extract/open_format_emitter.{hpp,cpp} encapsulates
the post-extract walk + per-file conversion.
- BLP→PNG uses BLPLoader::load + stbi_write_png with the same
dimension/buffer-size sanity guards the editor's texture exporter
applies.
- DBC→JSON mirrors the editor's DBCExporter::exportAsJson schema
(string/float/uint heuristic) so the runtime DBC overlay loader
can consume the output drop-in.
asset_extract pulls in src/pipeline/dbc_loader.cpp which #includes
<nlohmann/json.hpp>, but the tool's include directories didn't list
extern/ where the header lives. Build succeeded if asset_extract was
disabled (no StormLib) but failed otherwise. Added the extern/ system
include so the tool builds wherever StormLib is found.
One-click generation of a complete AzerothCore/TrinityCore server module
from editor zone data. File > Generate Server Module creates:
- sql/01_map.sql: map_dbc + area_table_dbc registration
- sql/02_spawns.sql: creature_template + creature + waypoint_data + quests
- sql/03_teleport.sql: game_tele entry for .tele command
- sql/04_zone_flags.sql: sanctuary/PvP area flags
- conf/mod_wowee.conf.dist: worldserver.conf snippet with zone settings
- README.md: step-by-step server admin installation guide
- module.json: machine-readable module manifest
Server admins can import the SQL files, add the config snippet, and
restart their server to have the custom zone fully operational with
NPC spawns, patrol paths, quests, teleport commands, and zone flags.
Private server integration: export creature spawns, patrol waypoints,
and quest definitions as ready-to-import SQL for AzerothCore/TrinityCore.
- creature_template: name, level, health, mana, damage, armor, faction,
npcflag (questgiver/vendor/flightmaster/innkeeper), displayId, scale
- creature: spawn position, orientation, respawn time, wander distance,
movement type (stationary/wander/patrol)
- creature_addon + waypoint_data: patrol path waypoints with delays
- quest_template: title, description, completion text, level, XP, money
- All use ON DUPLICATE KEY UPDATE for safe re-imports
- Auto-exported as spawns.sql alongside other assets on zone save
- File > Export Server SQL menu item for standalone export
- Map ID from zone metadata panel used in spawn table
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
- CHANGELOG: add World Editor section (12.5k+ lines, 6 modes, 30+ tools)
and Novel Open Formats section (6/6 replacements, 309 test assertions)
- README: add World Editor section with build/run/CLI examples, format
summary, and reference to FORMAT_SPEC.md
- Fix dbc_to_csv build: add extern/ to include path for nlohmann/json
(broke when dbc_loader.cpp gained JSON DBC loading support)
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.
- TextureExporter: converts Blizzard BLP textures to standard PNG
for fully open redistribution of custom zones
- collectUsedTextures(): finds all texture paths referenced by terrain
- exportTexturesAsPng(): loads BLP via asset manager, writes RGBA PNG
using stb_image_write to output/MapName/textures/
- Zone export now automatically converts all used textures to PNG
- Client's PNG override system already loads these automatically
(checks for .png alongside .blp before loading)
Format replacement progress:
- DONE: ADT→WOT/WHM (terrain)
- DONE: WDT→zone.json (map definition)
- DONE: BLP→PNG (textures — auto-exported on zone save)
- TODO: DBC→JSON, M2→open model, WMO→open building
- File > Load Custom Zone scans output/ and custom_zones/ for zone.json
- Lists all discovered custom zones with name and quest indicator [Q]
- Tooltip shows description and author on hover
- Click to load the zone's WOT/WHM terrain into the editor
- Parses tile coords from WOT filename for correct positioning
- Full round-trip: export → discover → reload for iterative editing