The editor's M2 placement path tries WOM (custom_zones/models/, output/
models/) before falling back to game M2 files. WMO placement just went
straight to game files. Now mirrors the M2 path: probes
custom_zones/buildings/ + output/buildings/ for a .wob, converts via
toWMOModel, falls back to MPQ-extracted WMO only on miss. Lets exported
zones render their custom buildings without needing the original WMO.
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.
WMORenderer::createInstance accepts a scale parameter (default 1.0),
but the editor's call site ignored obj.scale. So a WMO sized to 2.0 in
the panel still rendered at 1.0. Now passes obj.scale through, so the
loaded MODF scale + any user gizmo scaling work end-to-end.
Helps users understand the search base — 'Pool: 1234 M2 56 WMO' tells
them why their filter might be matching too many results, and gives a
quick view into how much asset variety the loaded data has.
Two related fixes:
1. loadADT now resets zoneManifest_ at the top so the previous zone's
mapId/displayName/flags/audio don't bleed into the new export.
2. When loading a zone that has a previously-exported zone.json on disk,
call manifest.load() to restore the user-customized metadata. Without
this every reload would reset Map ID back to 9000 etc.
Two related zone-manifest bugs:
1. displayName was always overwritten with the .adt prefix on every
export, throwing away whatever the user typed in the Zone Metadata
panel.
2. tiles vector was push_back'd to without clearing, so re-exporting the
same zone would accumulate duplicate tile entries.
Both fixed by checking displayName.empty() before assignment and calling
tiles.clear() before the rebuild loop.
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.
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 stores quest hooks (questGiverNpcId, turnInNpcId, KillCreature
targetName) as the spawner's per-spawn .id sequence. The SQL exporter
writes creature_template entries as 'creatureStartEntry + index'. The
two number spaces are different, so quest links pointed at non-existent
creature entries. Added a spawn.id -> SQL entry map built from the
spawns vector and used it in:
- RequiredNpcOrGo[1..4] for KillCreature objectives
- creature_queststarter / creature_questender
Template was a SliderFloat 0.5-10 while the selected-NPC editor uses
DragFloat 0.1-50. Inconsistent ceilings made it surprising that an NPC
could be scaled higher after placement than during placement. Now both
use DragFloat with the same range.
New TEST_CASE exercises the full toWMOModel restore path: materials
deduped across groups, outdoor flag preserved per group, portal vertex
+ groupA/groupB refs reconstructed, doodad path .wom->.m2 conversion,
default doodadSet emission. 113 assertions across 16 test cases now.
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.
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.
Companion to --info for WOM. Reports name, group/portal/doodad counts,
total vertex/triangle/material counts. Useful for verifying converted
WMOs and debugging building rendering issues without launching the GUI.
Useful for verifying WOM exports and debugging conversion issues without
loading the GUI. Accepts either /path/to/file.wom or /path/to/file
(loader expects no extension). Reports version, name, geometry counts,
texture/bone/animation/batch counts, and bound radius.
Previously waypoints could only be appended or removed; reordering meant
clearing and re-adding the whole path. Now each waypoint row has up/dn
swap buttons and a +after that inserts a new waypoint at the current
brush cursor right after this index — slicing long segments doesn't
require redoing the rest of the path.
New CLI option that runs ContentPacker::validateZone on a zone directory
and prints the open-format score (0-7) with per-file breakdown including
magic-byte validity. Exits 0 if 7/7, 1 otherwise — useful for CI checks
on exported zones.
Manual qx*qy*qz product was XYZ intrinsic, but fromWMO uses
glm::eulerAngles which returns YXZ Tait-Bryan extrinsic. The mismatch
introduced rotation drift on every save/load cycle. Using glm::quat
constructed from euler radians directly applies the inverse convention
of eulerAngles so the round-trip is clean.
The previous implementation required the .blp to exist in the asset
manifest before the PNG sidecar could be found. That prevented
custom-zone exports from shipping PNG-only textures (which is the whole
point of the open-format pipeline). Now if the manifest lookup fails
the loader probes well-known custom-zone roots so PNG-only assets work
out of the box.
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.
- WOC: add note that addMesh() also appends placed WMO group geometry
- New section on SQL server export covering coord/orientation conversion
rules, table list, and how quest objectives map to RequiredNpcOrGo/
RequiredItem slots.
Cam in dim yellow, cursor (when brush is on terrain) in cyan. Useful for
quickly noting positions where to drop spawns/objects without flying over
to read the brush coords manually.
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.
Replaces hand-written x/y swap with the canonical helper from
include/core/coordinates.hpp so the conversion stays in sync if the
coord convention changes.
The SQL exporter writes objective targets to RequiredNpcOrGo/RequiredItem
slots based on the objective's targetName field, but the UI never let
the user fill that field. Added an InputText for Target ID and, for Kill
objectives, a dropdown that auto-fills with the entry of any placed NPC.
Path direction was ambiguous from a static screenshot: ribbon and waypoints
were uniform orange/white. Now ribbons fade from bright at the start to
dim at the end, and waypoints go green (NPC home) -> yellow/orange
(intermediate) -> red (last) so direction of travel reads at a glance.
Editor's orientation is measured from +renderX (which maps to west in WoW
canonical), but AzerothCore creature.orientation is the WoW yaw measured
from +X (north). Without conversion an editor 0deg NPC ended up facing
west in-game, off by 90deg even after the radians fix. Now applies
`wowYaw = π/2 - editorYaw` and normalizes to [0, 2π).
Editor stores positions in render coords (renderX=wowY=west, renderY=wowX
=north, renderZ=wowZ=up) but AzerothCore creature.position_x/y are in
WoW canonical space (X=north-south, Y=west-east). Without the swap every
exported creature appeared on the wrong end of the map. Same swap now
applied to creature spawns AND waypoint_data path points.
Two new TEST_CASEs verify WoweeCollisionBuilder::addMesh marks flat
triangles walkable and steep ones not, and that caller-supplied flags
(e.g. indoor 0x08) are OR'd onto the slope-derived flags. 98 assertions
across 15 test cases now.
quest_template now writes up to 4 KillCreature objectives into the
RequiredNpcOrGo/RequiredNpcOrGoCount slots and up to 6 CollectItem
objectives into the RequiredItemId/RequiredItemCount slots. The numeric
target ID is parsed from the objective's targetName field — empty/non-
numeric targets emit 0 (objective hooked up but unwired).
Previously only group[0]'s materials were emitted. Buildings with
group-specific materials (e.g., the indoor groups using a different
texture set than outdoor) lost those materials, leaving batches in
later groups pointing to materialIndex out of range. Now dedupes by
(texture, blend, flags) across every group.
quest_template alone tells the server about the quest but not who hands
it out. Without creature_queststarter/questender entries, players can't
acquire or turn in the quest. Now writes both tables when the quest has
questGiverNpcId / turnInNpcId set.
A zone with 50+ creatures made finding a specific spawn tedious. Added
a case-insensitive name-filter input above the list, capped at 200
visible entries with a 'refine filter' hint when exceeded.
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.
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.
ContentPacker.validateZone only matched WOM1 magic (0x314D4F57). Any zone
exported with animated (WOM2) or multi-batch (WOM3) models was scored as
having invalid WOM files, lowering the open-format score from 7/7 to 6/7
even though everything is correct. Now accepts the WOM family.
Same fix as WoB: M2Renderer's PNG override is keyed on .blp extension.
fromM2 writes .png paths to signal intent (PNG export pipeline), but
toM2 must convert back so the runtime engages the override and finds
the actual texture file.
WMORenderer's PNG override system only triggers when the requested
texture path ends in .blp — it strips the .blp and probes for .png.
WoB stores .png paths (because fromWMO converts .blp->.png at conversion
time), so passing them straight through to the WMOModel meant the
override wasn't engaged and textures didn't load. Now converts back to
.blp so the existing override pipeline picks up the PNG file.
WotLK M2s store the header in .m2 but geometry in .skin. fromM2 only
loaded the skin when isValid() returned false, which it does for those
WotLK files — but missed the case where M2Loader::load happened to
populate enough that isValid() was true (older format M2s with newer
features). Now always merges skin data when present, matching the
editor's viewport loader behaviour.
Helpful for navigation and noting positions for spawn placement, plus
diagnostic value when debugging coordinate issues. Shown only after a
terrain is loaded.