Users who set a custom Map ID via the Zone Metadata panel saw it ignored
when exporting the WCP — the pack info would always say mapId=9000.
Now reads from zoneManifest_, falling back to 9000 only when the field
is unset (0).
The MODF scale field (u16 / 1024 = float) is now propagated in both
directions: load reads wp.scale -> obj.scale, syncToTerrain converts
obj.scale * 1024 -> wp.scale (clamped to u16). Combined with the prior
loader/writer changes this means non-1.0 WMO scales (used by some
WotLK content) survive a save/reload cycle.
The new persistent path->modelId map keeps models alive across rebuilds,
which is great for the common case of moving an instance, but means
models that lost all references stay in GPU memory forever. Added a
30s timer that calls m2Renderer->cleanupUnusedModels(), which has its
own 60s grace period before actual eviction — so models stick around
~60-90s after their last instance is removed and then get freed.
quickSave was the only path that cleared the flag, but exportZone is
also reachable through 'Export Open Format' and exportContentPack
without going through quickSave. Now any successful zone export clears
the dirty state so the asterisk and quit-confirm dialog reset properly.
Previously only terrain edits would trigger the 'unsaved changes' prompt
on quit, so a user who only added NPCs or quests could lose their work
by closing the window. Now checks autoSavePendingChanges_ alongside
terrain dirty state.
Camera::getForward = (cos(yaw), sin(yaw), sin(pitch)) — to make it
parallel to a direction vector we need atan2(y, x). The implementation
had x and y swapped, causing Fly To to point the camera 90deg off from
the target so the user often saw nothing.
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.
Previously just teleported camera 30u directly above the target — the user
still had to manually look down to see anything. Now positions the camera
back along the current view direction, slightly elevated, and aims it at
the selection so the target is visible immediately. Removes the round-trip
through manual rotation after every Fly To.
Marker geometry now reacts to npc.selected: 2.5x base radius (vs 1.5x),
saturated yellow with cyan tinge, and full alpha. Marker rebuild also
fires on selection change so the highlight appears immediately rather
than only after the next placement.
Auto-save was gated on terrainEditor.hasUnsavedChanges() so a session
where the user only edited NPCs or quests would lose data on crash.
Added autoSavePendingChanges_ flag flipped by every objectsDirty_ = true
site (and markObjectsDirty), cleared by quickSave. Auto-save now fires
on either dirty signal.
Placed WMO buildings reference textures (walls, floors, decorations) that
were not being exported alongside the WOB files. Added collectWMOTextures()
which loads the root WMO + all group files and gathers every texture path,
then folds these into the same PNG export pass that handles terrain and M2
textures. Exported zones now have every texture they need across all model
types.
Selected NPCs now display the move/rotate/scale gizmo and respond to
drag operations. Rotate uses only the yaw axis (NPCs have a single
orientation field, not full euler rotation). Move/scale work the same
way as objects.
Plain left-click within 4u of an existing NPC now selects that NPC rather
than dropping a new spawn on top. Shift+click forces placement at the cursor
even if it overlaps an existing NPC, preserving the rapid-placement workflow.
When an NPC with Patrol behavior is selected in NPC mode, pressing W
appends a waypoint at the current cursor position. Faster than clicking
the panel button for laying out long routes.
TextureExporter::collectUsedTextures only picked up terrain textures, so
exported zones were missing every texture referenced by NPC creature models
and placed M2 doodads. Added collectM2Textures() and unified the export
collection to include terrain + all referenced M2 paths, so the rendered
zone is fully self-contained in the PNG/WOM open formats.
Previously only placed M2 objects were converted to the WOM open format.
NPC creature models stayed as references to game M2/skin files, which
meant exported zones still depended on proprietary Blizzard assets to
render their NPCs. Now the exporter walks both placed objects and NPC
spawns and emits a WOM for every unique M2 path, making zones fully
self-contained.
Adds setPatrolPath() that draws a multi-segment orange ribbon between the
NPC's spawn position and each waypoint, plus diamond markers at each point
(green for start = NPC home, white for waypoints). Renders only while the
NPC is selected and has a patrol path defined.
Added orientation slider in NPC panel with random button. Ctrl+Wheel now
rotates the placement preview (objects and NPCs) instead of zooming —
Shift makes the step finer (5 deg vs 15 deg). Ghost preview now shows
the actual orientation that the placed NPC will have.
ROOT CAUSE of NPC models not rendering: every NPC placement triggered
an immediate full clear+rebuild of ALL M2 models. During rapid clicking,
this created a destroy-reload cycle where models were cleared faster
than they could render — the log showed rebuild firing every ~200ms
with models loading OK but being destroyed before the next frame.
Fix: debounce rebuilds with a 0.5s timer. Multiple rapid placements
reset the timer, so the rebuild only fires once after the user stops
clicking. Models stay loaded and visible between placements.
Before: click → clear all → reload all → click → clear all → reload...
After: click → click → click → (0.5s pause) → single rebuild
ROOT CAUSE of NPCs not rendering: rebuildObjects() called createInstance()
BEFORE beginFrame(), causing instance SSBO writes to use the wrong frame
index. The M2 renderer writes instance transforms to a per-frame buffer
indexed by getCurrentFrame(), but the frame index isn't valid until after
beginFrame() returns.
This is the same bug documented in the project memory:
"M2 models not rendering (draws=0): update() was called before
beginFrame(), causing frame index mismatch in instance SSBO"
Models loaded correctly (log confirmed 2192v 7926i 8b) but instances
were invisible because their transform data was written to the wrong
frame buffer.
Fix: move the rebuild block after beginFrame(), alongside update().
Many WoW instances (Dire Maul, Blackrock Depths, etc.) are WMO-only
maps with no ADT terrain tiles. The editor now handles these:
- loadWMOInstance(): reads WDT, detects WDTF_GLOBAL_WMO flag, loads
the root WMO path from MWMO chunk, places it as an object with
correct position/rotation from MODF chunk
- Automatic fallback: when loadADT fails to find tiles, tries
WMO-only loading before showing error
- Load dialog: "Find Tile" detects WMO-only instances and shows
"WMO-only instance — click Load to open" instead of "not found"
- Camera positioned near the WMO for immediate editing
- Blank terrain floor generated as ground reference
Two bugs fixed:
- Loading a new ADT tile now clears all previous objects, NPCs, quests,
path state, and terrain before loading. Was accumulating old state
across multiple loads
- ADT doodad/WMO rotation conversion now matches client's transform:
renderRotX = -adtRotZ, renderRotY = -adtRotX, renderRotZ = adtRotY+180
Was copying raw ADT rotations without coordinate system conversion,
causing models to appear at wrong orientations
When loading ADT tiles, the editor overrides terrain_.coord with the
filename-derived tile coordinates (instanced maps have arbitrary
internal values). But it wasn't recomputing the per-chunk world
positions to match, causing terrain to render at wrong coordinates.
Now recalculates all 256 chunk positions from the corrected tile
coordinates using the standard WoW formula:
chunkX = (32 - tileX) * 533.33 - cx * 33.33
chunkY = (32 - tileY) * 533.33 - cy * 33.33
This fixes terrain appearing at the wrong location in the editor
when loading instanced maps or tiles with mismatched internal coords.
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
- exportZoneMap(): renders terrain as colored top-down image with
height-based coloring (blue lowlands → green plains → brown hills →
white peaks), water overlay, hole visualization, doodad markers
- Configurable resolution (128-2048px, default 512)
- Auto-exported as zone_map.png alongside other assets on save
- File > Export Zone Map menu with resolution slider
- Useful for documentation, server admin tools, custom map websites
- Zone manifest gains audio fields: musicTrack, ambienceDay,
ambienceNight, musicVolume, ambienceVolume
- Serialized to/from zone.json under "audio" key
- Info panel: collapsable "Zone Audio" section with text inputs for
music/ambience paths and volume sliders
- Preset selector: Elwynn Forest, Durotar, Darkshore, Dungeon, None
- ZoneManifest stored persistently on EditorApp so audio settings
survive between exports (was recreated each save)
- Custom zones can now specify their own background music and ambient
soundscapes via zone.json
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
- Ctrl+Shift+E keyboard shortcut for WCP content pack export
- Help panel expanded: object tools (snap/align/flatten/scatter/select
by type), all terrain generators, stamp save/load, export shortcuts
- ObjectPlacer::loadFromFile restores activePath_ from loaded objects
so users can continue placing the same type after loading
- WCP export menu item now shows Ctrl+Shift+E hint
- Terrain stamps save/load to JSON: reuse terrain features across zones
and sessions. Save/Load buttons in Sculpt > Stamp/Clone panel
- WCP Inspect now shows full file breakdown: terrain/model/building/
texture/data counts with total size. Powered by readInfo file list
parsing with auto-categorization by extension
- stats.json now includes chunk count, triangle count, tile count, and
editor version alongside existing object/NPC/quest/texture counts
- Fix unprotected std::stoi in custom zone WOT filename parser
- Flatten Ground: flattens terrain to object height with smooth falloff
around placed buildings/structures (undoable). Button in object panel
- Scatter auto-align: checkbox enables terrain snapping + slope alignment
for scattered objects (trees snap to ground and lean with hillsides)
- Batch convert now falls back to asset manifest when filesystem dir is
empty — converts M2/WMO from game data without filesystem extraction
- Public terrain editor wrappers: beginGeneratorUndo, endGeneratorUndo,
markDirty, stitchChunkEdges, getChunkVertexWorldPos
New features:
- Align to Slope: rotates objects to match terrain surface normal at
their position (trees on hillsides lean naturally). Works with
multi-select. Available in object panel and right-click context menu
- Batch Convert Assets: File menu option to recursively convert all
M2→WOM and WMO→WOB files in a data directory to open format
- Import & Load: one-click WCP unpack + auto-open the imported zone
- sampleTerrainNormal() for slope detection via height differencing
- Zone load error toasts for missing/corrupt files
Bug fixes:
- ObjectPlacer::clearAll() now resets uniqueIdCounter_ to 1
- NpcSpawner::clearAll() added with idCounter_ reset (was manual clear)
- clearAllObjects() uses NpcSpawner::clearAll() instead of manual clear
- Quest panel has terrain-loaded guard (prevents crash before loading)
Features:
- Zone rename: editable map name field in Info panel (press Enter)
- Load objects/creatures/quests from both output/ and custom_zones/
directories (WOT zones from custom_zones now get their NPCs loaded)
Bug fixes:
- Fix README.txt version from v0.8.0 to v1.0.0
- Reset NPC idCounter_ on loadFromFile to prevent ID collisions
- WCP content pack uses project author/description if loaded instead
of hardcoded "Kelsi Davis"
New features:
- Select by Type: context menu items for "Select All M2 Models" and
"Select All WMO Buildings" for batch type-based selection
- selectByType(PlaceableType) added to ObjectPlacer
- Export summary toast now shows object/NPC/quest counts alongside
file count and open format score (5s duration)
- Auto-save toast notification when auto-save fires
- Edit > Auto-Save Settings: enable/disable toggle, interval slider
(60-900s), countdown timer display
- Zone manifest now scans output directory for all exported ADT tiles
and includes them in zone.json (adjacent tiles no longer orphaned)
- Auto-save interval and enabled state exposed via EditorApp accessors
- Adjacent tile export now stitches border heights from current tile
for seamless edges, exports both ADT and WOT/WHM open format
- Escape key now clears NPC selection and path capture state in
addition to object selection and gizmo mode
- Ctrl+A selects all placed objects, context menu has Select All item
- selectAll() added to ObjectPlacer, works with multi-select transforms
- Recent Zones submenu in File menu (last 8 loaded zones, deduplicated)
- Minimap: selected objects shown as white dots with gold ring outline
vs yellow dots for unselected objects
- Help panel updated with Ctrl+A and Ctrl+Shift+Click documentation
- Multi-select: Ctrl+Shift+Click adds objects to selection, transforms
(move/rotate/scale/delete) operate on all selected objects at once
- Time-of-day slider (0-24h) with automatic sun angle, light color,
ambient, fog, and sky color transitions (dawn/day/dusk/night)
- View > Sky/Lighting menu: color pickers for light/ambient/fog, fog
distance sliders, preset buttons (Dawn/Noon/Dusk/Night)
- loadADT prefers WOT/WHM open format from custom_zones/output dirs
- Selection count display when multiple objects selected
- setSkyPreset now delegates to setTimeOfDay for consistency
- Texture eyedropper: pickTextureAt() samples dominant texture at world
position by reading chunk alpha maps. Alt+Click in paint mode or
click the Eyedropper button to activate
- loadADT now checks custom_zones/ and output/ for WOT/WHM open format
files first, falling back to ADT binary only if not found
- Fix unused variable warning in scatterPatches
- Document Alt+Click in help panel
- River/road tool now shows translucent blue path preview ribbon with
edge lines between start and end points before applying
- Preview follows cursor when waiting for end point, locks when set
- Undo support for all remaining operations: rotateTerrain90, mirrorX,
mirrorY, scaleHeights, offsetHeights, invertHeights, smoothBeaches
- Every terrain-modifying operation in the editor is now undoable
- All terrain generators now undoable: crater, mesa, hill, voronoi,
dunes, detail noise, thermal erosion, canyon, island, ridge, road,
river, perlin noise — all wrapped with recordGeneratorUndo/commit
- Unsaved changes warning on quit: Save & Quit / Quit / Cancel dialog
- createNewTerrain clears quest editor and path capture state
- recordGeneratorUndo/commitGeneratorUndo helper methods snapshot all
256 chunks before/after any generator operation
- River/Road tool now uses click-capture mode instead of button-based
cursor position — click terrain directly to set start and end points
- 3-step flow: Click Start → Click End → Apply Path (with preview text)
- Cancel button available at each step
- Path state tracked on EditorUI with setPathPoint()/clearPath()
- Intercepts terrain clicks before mode-specific handling when active
- Extend undo/redo to snapshot alpha maps and texture layers alongside
heights — texture painting operations are now fully undoable
- Bracket paint mode with beginStroke/endStroke like sculpt mode
- Fix stale static char buffer in quest objective loop (showed wrong
objective's description when editing multiple objectives)
- Zero-initialize all quest UI text buffers for null termination safety
- Fix unused variable warnings in terrain_editor.cpp
- Quest editor: add loadFromFile() with nlohmann/json, chain validation
with circular reference detection, wire into ADT load and save pipeline
- Project: replace naive substring JSON parsing with nlohmann/json for
both save() and load(), fix shell injection in gitCommit()
- Content pack: replace manual JSON with nlohmann/json, validate binary
format magic numbers (WHM1/WOM1/WOB1), add WOB to openFormatScore
(now scores 0-6), mark invalid files with (!) in summary