feat(editor): add standalone world editor (rough/WIP)
Standalone wowee_editor tool for creating custom WoW zones.
This is a rough initial implementation — many features work but
M2/WMO rendering still has issues (frame sync, texture layout
transitions) and needs further polish.
Terrain:
- Create new blank terrain with 10 biome types (Grassland, Forest,
Jungle, Desert, Barrens, Snow, Swamp, Rocky, Beach, Volcanic)
- Load existing ADT tiles from extracted game data
- Sculpt brushes: Raise, Lower, Smooth, Flatten, Level
- Chunk edge stitching prevents seams between tiles
- Undo/redo (100-deep stack, Ctrl+Z/Ctrl+Shift+Z)
- Save to WoW ADT/WDT format
Texture Painting:
- Paint/Erase/Replace Base modes
- Full tileset texture browser (1285 textures from manifest)
- Per-zone directory filtering and search
- Alpha map editing with 4-layer limit (auto-replaces weakest)
Object Placement:
- M2 and WMO model placement with full manifest browser (11k M2s, 2k WMOs)
- M2Renderer + WMORenderer integrated (loads .skin files for WotLK)
- Ghost preview follows cursor before placing
- Ctrl+click selection, right-click context menu
- Transform gizmo (Move/Rotate/Scale with axis constraints)
- Position/rotation/scale editing in properties panel
NPC/Monster System:
- 631 creature presets scanned from manifest, categorized
(Critters, Beasts, Humanoids, Undead, Demons, etc.)
- Stats editor: level, health, mana, damage, armor, faction
- Behavior: Stationary, Patrol, Wander, Scripted
- Aggro/leash radius, respawn time, flags (hostile/vendor/etc.)
- Save creature spawns to JSON
Water:
- Place water at configurable height per chunk
- Liquid types: Water, Ocean, Magma, Slime
- Rendered as translucent colored quads
- Saved in ADT MH2O format
Infrastructure:
- Free-fly camera (WASD/QE, right-drag look, scroll speed)
- 5-mode toolbar: Sculpt | Paint | Objects | Water | NPCs
- Asset browser indexes full manifest on startup
- Editor water/marker shaders (pos+color vertex format)
- forceNoCull added to M2Renderer for editor use
- AssetManifest::getEntries() and AssetManager::getManifest() exposed
Known issues:
- M2/WMO rendering may not display on first placement (frame index
sync between update/render was misaligned — now fixed but untested
end-to-end)
- Validation layer errors on shutdown (resource cleanup ordering)
- Object placement on steep terrain can miss raycast
- No undo for texture painting or object placement yet
2026-05-05 03:47:03 -07:00
|
|
|
|
#include "adt_writer.hpp"
|
|
|
|
|
|
#include "core/logger.hpp"
|
|
|
|
|
|
#include <fstream>
|
|
|
|
|
|
#include <cstring>
|
|
|
|
|
|
#include <filesystem>
|
2026-05-06 06:49:51 -07:00
|
|
|
|
#include <cmath>
|
feat(editor): add standalone world editor (rough/WIP)
Standalone wowee_editor tool for creating custom WoW zones.
This is a rough initial implementation — many features work but
M2/WMO rendering still has issues (frame sync, texture layout
transitions) and needs further polish.
Terrain:
- Create new blank terrain with 10 biome types (Grassland, Forest,
Jungle, Desert, Barrens, Snow, Swamp, Rocky, Beach, Volcanic)
- Load existing ADT tiles from extracted game data
- Sculpt brushes: Raise, Lower, Smooth, Flatten, Level
- Chunk edge stitching prevents seams between tiles
- Undo/redo (100-deep stack, Ctrl+Z/Ctrl+Shift+Z)
- Save to WoW ADT/WDT format
Texture Painting:
- Paint/Erase/Replace Base modes
- Full tileset texture browser (1285 textures from manifest)
- Per-zone directory filtering and search
- Alpha map editing with 4-layer limit (auto-replaces weakest)
Object Placement:
- M2 and WMO model placement with full manifest browser (11k M2s, 2k WMOs)
- M2Renderer + WMORenderer integrated (loads .skin files for WotLK)
- Ghost preview follows cursor before placing
- Ctrl+click selection, right-click context menu
- Transform gizmo (Move/Rotate/Scale with axis constraints)
- Position/rotation/scale editing in properties panel
NPC/Monster System:
- 631 creature presets scanned from manifest, categorized
(Critters, Beasts, Humanoids, Undead, Demons, etc.)
- Stats editor: level, health, mana, damage, armor, faction
- Behavior: Stationary, Patrol, Wander, Scripted
- Aggro/leash radius, respawn time, flags (hostile/vendor/etc.)
- Save creature spawns to JSON
Water:
- Place water at configurable height per chunk
- Liquid types: Water, Ocean, Magma, Slime
- Rendered as translucent colored quads
- Saved in ADT MH2O format
Infrastructure:
- Free-fly camera (WASD/QE, right-drag look, scroll speed)
- 5-mode toolbar: Sculpt | Paint | Objects | Water | NPCs
- Asset browser indexes full manifest on startup
- Editor water/marker shaders (pos+color vertex format)
- forceNoCull added to M2Renderer for editor use
- AssetManifest::getEntries() and AssetManager::getManifest() exposed
Known issues:
- M2/WMO rendering may not display on first placement (frame index
sync between update/render was misaligned — now fixed but untested
end-to-end)
- Validation layer errors on shutdown (resource cleanup ordering)
- Object placement on steep terrain can miss raycast
- No undo for texture painting or object placement yet
2026-05-05 03:47:03 -07:00
|
|
|
|
|
|
|
|
|
|
namespace wowee {
|
|
|
|
|
|
namespace editor {
|
|
|
|
|
|
|
|
|
|
|
|
// ADT chunk magics (little-endian as read from file)
|
|
|
|
|
|
static constexpr uint32_t MVER = 0x4D564552;
|
|
|
|
|
|
static constexpr uint32_t MHDR = 0x4D484452;
|
|
|
|
|
|
static constexpr uint32_t MCIN = 0x4D43494E;
|
|
|
|
|
|
static constexpr uint32_t MTEX = 0x4D544558;
|
|
|
|
|
|
static constexpr uint32_t MMDX = 0x4D4D4458;
|
|
|
|
|
|
static constexpr uint32_t MMID = 0x4D4D4944;
|
|
|
|
|
|
static constexpr uint32_t MWMO = 0x4D574D4F;
|
|
|
|
|
|
static constexpr uint32_t MWID = 0x4D574944;
|
|
|
|
|
|
static constexpr uint32_t MDDF = 0x4D444446;
|
|
|
|
|
|
static constexpr uint32_t MODF = 0x4D4F4446;
|
|
|
|
|
|
static constexpr uint32_t MCNK = 0x4D434E4B;
|
|
|
|
|
|
static constexpr uint32_t MCVT = 0x4D435654;
|
|
|
|
|
|
static constexpr uint32_t MCNR = 0x4D434E52;
|
|
|
|
|
|
static constexpr uint32_t MCLY = 0x4D434C59;
|
|
|
|
|
|
static constexpr uint32_t MCAL = 0x4D43414C;
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::writeChunkHeader(std::vector<uint8_t>& buf, uint32_t magic, uint32_t size) {
|
|
|
|
|
|
writeU32(buf, magic);
|
|
|
|
|
|
writeU32(buf, size);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::writeU32(std::vector<uint8_t>& buf, uint32_t val) {
|
|
|
|
|
|
buf.push_back(val & 0xFF);
|
|
|
|
|
|
buf.push_back((val >> 8) & 0xFF);
|
|
|
|
|
|
buf.push_back((val >> 16) & 0xFF);
|
|
|
|
|
|
buf.push_back((val >> 24) & 0xFF);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::writeU16(std::vector<uint8_t>& buf, uint16_t val) {
|
|
|
|
|
|
buf.push_back(val & 0xFF);
|
|
|
|
|
|
buf.push_back((val >> 8) & 0xFF);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::writeFloat(std::vector<uint8_t>& buf, float val) {
|
2026-05-06 06:49:51 -07:00
|
|
|
|
// Reject NaN/inf — would silently turn into invisible terrain or
|
|
|
|
|
|
// off-map placements after the next ADT load.
|
|
|
|
|
|
if (!std::isfinite(val)) val = 0.0f;
|
feat(editor): add standalone world editor (rough/WIP)
Standalone wowee_editor tool for creating custom WoW zones.
This is a rough initial implementation — many features work but
M2/WMO rendering still has issues (frame sync, texture layout
transitions) and needs further polish.
Terrain:
- Create new blank terrain with 10 biome types (Grassland, Forest,
Jungle, Desert, Barrens, Snow, Swamp, Rocky, Beach, Volcanic)
- Load existing ADT tiles from extracted game data
- Sculpt brushes: Raise, Lower, Smooth, Flatten, Level
- Chunk edge stitching prevents seams between tiles
- Undo/redo (100-deep stack, Ctrl+Z/Ctrl+Shift+Z)
- Save to WoW ADT/WDT format
Texture Painting:
- Paint/Erase/Replace Base modes
- Full tileset texture browser (1285 textures from manifest)
- Per-zone directory filtering and search
- Alpha map editing with 4-layer limit (auto-replaces weakest)
Object Placement:
- M2 and WMO model placement with full manifest browser (11k M2s, 2k WMOs)
- M2Renderer + WMORenderer integrated (loads .skin files for WotLK)
- Ghost preview follows cursor before placing
- Ctrl+click selection, right-click context menu
- Transform gizmo (Move/Rotate/Scale with axis constraints)
- Position/rotation/scale editing in properties panel
NPC/Monster System:
- 631 creature presets scanned from manifest, categorized
(Critters, Beasts, Humanoids, Undead, Demons, etc.)
- Stats editor: level, health, mana, damage, armor, faction
- Behavior: Stationary, Patrol, Wander, Scripted
- Aggro/leash radius, respawn time, flags (hostile/vendor/etc.)
- Save creature spawns to JSON
Water:
- Place water at configurable height per chunk
- Liquid types: Water, Ocean, Magma, Slime
- Rendered as translucent colored quads
- Saved in ADT MH2O format
Infrastructure:
- Free-fly camera (WASD/QE, right-drag look, scroll speed)
- 5-mode toolbar: Sculpt | Paint | Objects | Water | NPCs
- Asset browser indexes full manifest on startup
- Editor water/marker shaders (pos+color vertex format)
- forceNoCull added to M2Renderer for editor use
- AssetManifest::getEntries() and AssetManager::getManifest() exposed
Known issues:
- M2/WMO rendering may not display on first placement (frame index
sync between update/render was misaligned — now fixed but untested
end-to-end)
- Validation layer errors on shutdown (resource cleanup ordering)
- Object placement on steep terrain can miss raycast
- No undo for texture painting or object placement yet
2026-05-05 03:47:03 -07:00
|
|
|
|
uint32_t bits;
|
|
|
|
|
|
std::memcpy(&bits, &val, 4);
|
|
|
|
|
|
writeU32(buf, bits);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::writeBytes(std::vector<uint8_t>& buf, const void* data, size_t size) {
|
|
|
|
|
|
const uint8_t* p = static_cast<const uint8_t*>(data);
|
|
|
|
|
|
buf.insert(buf.end(), p, p + size);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::patchSize(std::vector<uint8_t>& buf, size_t headerOffset) {
|
|
|
|
|
|
uint32_t size = static_cast<uint32_t>(buf.size() - headerOffset - 8);
|
|
|
|
|
|
std::memcpy(buf.data() + headerOffset + 4, &size, 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::writeMVER(std::vector<uint8_t>& buf) {
|
|
|
|
|
|
writeChunkHeader(buf, MVER, 4);
|
|
|
|
|
|
writeU32(buf, 18); // ADT version
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::writeMHDR(std::vector<uint8_t>& buf, size_t& mhdrOffset) {
|
|
|
|
|
|
mhdrOffset = buf.size();
|
|
|
|
|
|
writeChunkHeader(buf, MHDR, 64);
|
|
|
|
|
|
// 16 uint32 fields — all zeros for now (offsets filled later if needed)
|
|
|
|
|
|
for (int i = 0; i < 16; i++) writeU32(buf, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::writeMTEX(std::vector<uint8_t>& buf, const pipeline::ADTTerrain& terrain) {
|
|
|
|
|
|
size_t start = buf.size();
|
|
|
|
|
|
writeChunkHeader(buf, MTEX, 0);
|
|
|
|
|
|
for (const auto& tex : terrain.textures) {
|
|
|
|
|
|
writeBytes(buf, tex.c_str(), tex.size() + 1); // null-terminated
|
|
|
|
|
|
}
|
|
|
|
|
|
patchSize(buf, start);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::writeMMDX(std::vector<uint8_t>& buf, const pipeline::ADTTerrain& terrain) {
|
|
|
|
|
|
size_t start = buf.size();
|
|
|
|
|
|
writeChunkHeader(buf, MMDX, 0);
|
|
|
|
|
|
for (const auto& name : terrain.doodadNames) {
|
|
|
|
|
|
writeBytes(buf, name.c_str(), name.size() + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
patchSize(buf, start);
|
|
|
|
|
|
|
|
|
|
|
|
// MMID: offsets into MMDX
|
|
|
|
|
|
size_t mmidStart = buf.size();
|
|
|
|
|
|
writeChunkHeader(buf, MMID, 0);
|
|
|
|
|
|
uint32_t offset = 0;
|
|
|
|
|
|
for (const auto& name : terrain.doodadNames) {
|
|
|
|
|
|
writeU32(buf, offset);
|
|
|
|
|
|
offset += static_cast<uint32_t>(name.size() + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
patchSize(buf, mmidStart);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::writeMWMO(std::vector<uint8_t>& buf, const pipeline::ADTTerrain& terrain) {
|
|
|
|
|
|
size_t start = buf.size();
|
|
|
|
|
|
writeChunkHeader(buf, MWMO, 0);
|
|
|
|
|
|
for (const auto& name : terrain.wmoNames) {
|
|
|
|
|
|
writeBytes(buf, name.c_str(), name.size() + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
patchSize(buf, start);
|
|
|
|
|
|
|
|
|
|
|
|
// MWID: offsets into MWMO
|
|
|
|
|
|
size_t mwidStart = buf.size();
|
|
|
|
|
|
writeChunkHeader(buf, MWID, 0);
|
|
|
|
|
|
uint32_t offset = 0;
|
|
|
|
|
|
for (const auto& name : terrain.wmoNames) {
|
|
|
|
|
|
writeU32(buf, offset);
|
|
|
|
|
|
offset += static_cast<uint32_t>(name.size() + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
patchSize(buf, mwidStart);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::writeMDDF(std::vector<uint8_t>& buf, const pipeline::ADTTerrain& terrain) {
|
|
|
|
|
|
size_t start = buf.size();
|
|
|
|
|
|
writeChunkHeader(buf, MDDF, 0);
|
|
|
|
|
|
for (const auto& p : terrain.doodadPlacements) {
|
|
|
|
|
|
writeU32(buf, p.nameId);
|
|
|
|
|
|
writeU32(buf, p.uniqueId);
|
|
|
|
|
|
writeFloat(buf, p.position[0]);
|
|
|
|
|
|
writeFloat(buf, p.position[1]);
|
|
|
|
|
|
writeFloat(buf, p.position[2]);
|
|
|
|
|
|
writeFloat(buf, p.rotation[0]);
|
|
|
|
|
|
writeFloat(buf, p.rotation[1]);
|
|
|
|
|
|
writeFloat(buf, p.rotation[2]);
|
|
|
|
|
|
writeU16(buf, p.scale);
|
|
|
|
|
|
writeU16(buf, p.flags);
|
|
|
|
|
|
}
|
|
|
|
|
|
patchSize(buf, start);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::writeMODF(std::vector<uint8_t>& buf, const pipeline::ADTTerrain& terrain) {
|
|
|
|
|
|
size_t start = buf.size();
|
|
|
|
|
|
writeChunkHeader(buf, MODF, 0);
|
|
|
|
|
|
for (const auto& p : terrain.wmoPlacements) {
|
|
|
|
|
|
writeU32(buf, p.nameId);
|
|
|
|
|
|
writeU32(buf, p.uniqueId);
|
|
|
|
|
|
writeFloat(buf, p.position[0]);
|
|
|
|
|
|
writeFloat(buf, p.position[1]);
|
|
|
|
|
|
writeFloat(buf, p.position[2]);
|
|
|
|
|
|
writeFloat(buf, p.rotation[0]);
|
|
|
|
|
|
writeFloat(buf, p.rotation[1]);
|
|
|
|
|
|
writeFloat(buf, p.rotation[2]);
|
|
|
|
|
|
writeFloat(buf, p.extentLower[0]);
|
|
|
|
|
|
writeFloat(buf, p.extentLower[1]);
|
|
|
|
|
|
writeFloat(buf, p.extentLower[2]);
|
|
|
|
|
|
writeFloat(buf, p.extentUpper[0]);
|
|
|
|
|
|
writeFloat(buf, p.extentUpper[1]);
|
|
|
|
|
|
writeFloat(buf, p.extentUpper[2]);
|
|
|
|
|
|
writeU16(buf, p.flags);
|
|
|
|
|
|
writeU16(buf, p.doodadSet);
|
2026-05-06 03:37:13 -07:00
|
|
|
|
// MODF entry is 64 bytes total; the trailing nameSet + scale slots are
|
|
|
|
|
|
// populated when present (WotLK+) and default to (0, 1024) otherwise.
|
|
|
|
|
|
writeU16(buf, p.nameSet);
|
|
|
|
|
|
writeU16(buf, p.scale != 0 ? p.scale : 1024);
|
feat(editor): add standalone world editor (rough/WIP)
Standalone wowee_editor tool for creating custom WoW zones.
This is a rough initial implementation — many features work but
M2/WMO rendering still has issues (frame sync, texture layout
transitions) and needs further polish.
Terrain:
- Create new blank terrain with 10 biome types (Grassland, Forest,
Jungle, Desert, Barrens, Snow, Swamp, Rocky, Beach, Volcanic)
- Load existing ADT tiles from extracted game data
- Sculpt brushes: Raise, Lower, Smooth, Flatten, Level
- Chunk edge stitching prevents seams between tiles
- Undo/redo (100-deep stack, Ctrl+Z/Ctrl+Shift+Z)
- Save to WoW ADT/WDT format
Texture Painting:
- Paint/Erase/Replace Base modes
- Full tileset texture browser (1285 textures from manifest)
- Per-zone directory filtering and search
- Alpha map editing with 4-layer limit (auto-replaces weakest)
Object Placement:
- M2 and WMO model placement with full manifest browser (11k M2s, 2k WMOs)
- M2Renderer + WMORenderer integrated (loads .skin files for WotLK)
- Ghost preview follows cursor before placing
- Ctrl+click selection, right-click context menu
- Transform gizmo (Move/Rotate/Scale with axis constraints)
- Position/rotation/scale editing in properties panel
NPC/Monster System:
- 631 creature presets scanned from manifest, categorized
(Critters, Beasts, Humanoids, Undead, Demons, etc.)
- Stats editor: level, health, mana, damage, armor, faction
- Behavior: Stationary, Patrol, Wander, Scripted
- Aggro/leash radius, respawn time, flags (hostile/vendor/etc.)
- Save creature spawns to JSON
Water:
- Place water at configurable height per chunk
- Liquid types: Water, Ocean, Magma, Slime
- Rendered as translucent colored quads
- Saved in ADT MH2O format
Infrastructure:
- Free-fly camera (WASD/QE, right-drag look, scroll speed)
- 5-mode toolbar: Sculpt | Paint | Objects | Water | NPCs
- Asset browser indexes full manifest on startup
- Editor water/marker shaders (pos+color vertex format)
- forceNoCull added to M2Renderer for editor use
- AssetManifest::getEntries() and AssetManager::getManifest() exposed
Known issues:
- M2/WMO rendering may not display on first placement (frame index
sync between update/render was misaligned — now fixed but untested
end-to-end)
- Validation layer errors on shutdown (resource cleanup ordering)
- Object placement on steep terrain can miss raycast
- No undo for texture painting or object placement yet
2026-05-05 03:47:03 -07:00
|
|
|
|
}
|
|
|
|
|
|
patchSize(buf, start);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ADTWriter::writeMCNK(std::vector<uint8_t>& buf, const pipeline::MapChunk& chunk,
|
|
|
|
|
|
int chunkX, int chunkY) {
|
|
|
|
|
|
size_t mcnkStart = buf.size();
|
|
|
|
|
|
writeChunkHeader(buf, MCNK, 0);
|
|
|
|
|
|
|
|
|
|
|
|
// MCNK header (128 bytes)
|
|
|
|
|
|
writeU32(buf, chunk.flags);
|
|
|
|
|
|
writeU32(buf, chunkX);
|
|
|
|
|
|
writeU32(buf, chunkY);
|
|
|
|
|
|
writeU32(buf, static_cast<uint32_t>(chunk.layers.size()));
|
|
|
|
|
|
writeU32(buf, 0); // nDoodadRefs
|
|
|
|
|
|
// Offsets within MCNK — filled with placeholder (parser uses sub-chunk magic scanning)
|
|
|
|
|
|
for (int i = 0; i < 5; i++) writeU32(buf, 0); // ofsHeight, ofsNormal, ofsLayer, ofsRefs, ofsAlpha
|
|
|
|
|
|
writeU32(buf, 0); // sizeAlpha
|
|
|
|
|
|
writeU32(buf, 0); // ofsShadow
|
|
|
|
|
|
writeU32(buf, 0); // sizeShadow
|
|
|
|
|
|
writeU32(buf, 0); // areaid
|
|
|
|
|
|
writeU32(buf, 0); // nMapObjRefs
|
|
|
|
|
|
writeU16(buf, chunk.holes);
|
|
|
|
|
|
writeU16(buf, 0); // padding
|
|
|
|
|
|
// 16 bytes of low-quality texture map (doodadStencil)
|
|
|
|
|
|
for (int i = 0; i < 4; i++) writeU32(buf, 0);
|
|
|
|
|
|
writeU32(buf, 0); // predTex
|
|
|
|
|
|
writeU32(buf, 0); // noEffectDoodad
|
|
|
|
|
|
writeU32(buf, 0); // ofsSndEmitters
|
|
|
|
|
|
writeU32(buf, 0); // nSndEmitters
|
|
|
|
|
|
writeU32(buf, 0); // ofsLiquid
|
|
|
|
|
|
writeU32(buf, 0); // sizeLiquid
|
|
|
|
|
|
writeFloat(buf, chunk.position[0]);
|
|
|
|
|
|
writeFloat(buf, chunk.position[1]);
|
|
|
|
|
|
writeFloat(buf, chunk.position[2]);
|
|
|
|
|
|
writeU32(buf, 0); // ofsMCCV
|
|
|
|
|
|
writeU32(buf, 0); // ofsMCLV
|
|
|
|
|
|
writeU32(buf, 0); // unused
|
|
|
|
|
|
|
|
|
|
|
|
// MCVT sub-chunk (145 floats = 580 bytes)
|
|
|
|
|
|
writeChunkHeader(buf, MCVT, 145 * 4);
|
|
|
|
|
|
for (int i = 0; i < 145; i++) {
|
|
|
|
|
|
writeFloat(buf, chunk.heightMap.heights[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MCNR sub-chunk (145 * 3 = 435 bytes + 13 pad = 448)
|
|
|
|
|
|
writeChunkHeader(buf, MCNR, 435 + 13);
|
|
|
|
|
|
writeBytes(buf, chunk.normals.data(), 435);
|
|
|
|
|
|
for (int i = 0; i < 13; i++) buf.push_back(0); // padding
|
|
|
|
|
|
|
|
|
|
|
|
// MCLY sub-chunk
|
|
|
|
|
|
{
|
|
|
|
|
|
size_t mclyStart = buf.size();
|
|
|
|
|
|
writeChunkHeader(buf, MCLY, 0);
|
|
|
|
|
|
for (const auto& layer : chunk.layers) {
|
|
|
|
|
|
writeU32(buf, layer.textureId);
|
|
|
|
|
|
writeU32(buf, layer.flags);
|
|
|
|
|
|
writeU32(buf, layer.offsetMCAL);
|
|
|
|
|
|
writeU32(buf, layer.effectId);
|
|
|
|
|
|
}
|
|
|
|
|
|
patchSize(buf, mclyStart);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MCAL sub-chunk (alpha maps)
|
|
|
|
|
|
{
|
|
|
|
|
|
size_t mcalStart = buf.size();
|
|
|
|
|
|
writeChunkHeader(buf, MCAL, 0);
|
|
|
|
|
|
if (!chunk.alphaMap.empty()) {
|
|
|
|
|
|
writeBytes(buf, chunk.alphaMap.data(), chunk.alphaMap.size());
|
|
|
|
|
|
}
|
|
|
|
|
|
patchSize(buf, mcalStart);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
patchSize(buf, mcnkStart);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> ADTWriter::serialize(const pipeline::ADTTerrain& terrain) {
|
|
|
|
|
|
std::vector<uint8_t> buf;
|
|
|
|
|
|
buf.reserve(2 * 1024 * 1024);
|
|
|
|
|
|
|
|
|
|
|
|
writeMVER(buf);
|
|
|
|
|
|
|
|
|
|
|
|
size_t mhdrOffset = 0;
|
|
|
|
|
|
writeMHDR(buf, mhdrOffset);
|
|
|
|
|
|
|
|
|
|
|
|
// MCIN placeholder (256 entries × 16 bytes = 4096 bytes)
|
|
|
|
|
|
size_t mcinStart = buf.size();
|
|
|
|
|
|
writeChunkHeader(buf, MCIN, 4096);
|
|
|
|
|
|
for (int i = 0; i < 256 * 4; i++) writeU32(buf, 0);
|
|
|
|
|
|
|
|
|
|
|
|
writeMTEX(buf, terrain);
|
|
|
|
|
|
writeMMDX(buf, terrain);
|
|
|
|
|
|
writeMWMO(buf, terrain);
|
|
|
|
|
|
writeMDDF(buf, terrain);
|
|
|
|
|
|
writeMODF(buf, terrain);
|
|
|
|
|
|
|
|
|
|
|
|
// Write 256 MCNK chunks and record offsets
|
|
|
|
|
|
std::vector<size_t> mcnkOffsets(256);
|
|
|
|
|
|
std::vector<uint32_t> mcnkSizes(256);
|
|
|
|
|
|
for (int y = 0; y < 16; y++) {
|
|
|
|
|
|
for (int x = 0; x < 16; x++) {
|
|
|
|
|
|
int idx = y * 16 + x;
|
|
|
|
|
|
mcnkOffsets[idx] = buf.size();
|
|
|
|
|
|
writeMCNK(buf, terrain.chunks[idx], x, y);
|
|
|
|
|
|
mcnkSizes[idx] = static_cast<uint32_t>(buf.size() - mcnkOffsets[idx]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Patch MCIN with offsets and sizes
|
|
|
|
|
|
for (int i = 0; i < 256; i++) {
|
|
|
|
|
|
size_t entryOffset = mcinStart + 8 + i * 16;
|
|
|
|
|
|
uint32_t offset = static_cast<uint32_t>(mcnkOffsets[i]);
|
|
|
|
|
|
uint32_t size = mcnkSizes[i];
|
|
|
|
|
|
std::memcpy(buf.data() + entryOffset, &offset, 4);
|
|
|
|
|
|
std::memcpy(buf.data() + entryOffset + 4, &size, 4);
|
|
|
|
|
|
// flags and asyncId stay 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ADTWriter::write(const pipeline::ADTTerrain& terrain, const std::string& path) {
|
|
|
|
|
|
auto data = serialize(terrain);
|
|
|
|
|
|
|
|
|
|
|
|
auto dir = std::filesystem::path(path).parent_path();
|
|
|
|
|
|
if (!dir.empty()) {
|
|
|
|
|
|
std::filesystem::create_directories(dir);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::ofstream file(path, std::ios::binary);
|
|
|
|
|
|
if (!file) {
|
|
|
|
|
|
LOG_ERROR("Failed to open file for writing: ", path);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
file.write(reinterpret_cast<const char*>(data.data()), data.size());
|
|
|
|
|
|
LOG_INFO("ADT written: ", path, " (", data.size(), " bytes)");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ADTWriter::writeWDT(const std::string& mapName, int tileX, int tileY,
|
|
|
|
|
|
const std::string& path) {
|
|
|
|
|
|
std::vector<uint8_t> buf;
|
|
|
|
|
|
buf.reserve(32768);
|
|
|
|
|
|
|
|
|
|
|
|
// MVER
|
|
|
|
|
|
writeChunkHeader(buf, MVER, 4);
|
|
|
|
|
|
writeU32(buf, 18);
|
|
|
|
|
|
|
|
|
|
|
|
// MPHD (map header — 32 bytes, all zeros = no special flags)
|
|
|
|
|
|
writeChunkHeader(buf, 0x4D504844, 32);
|
|
|
|
|
|
for (int i = 0; i < 8; i++) writeU32(buf, 0);
|
|
|
|
|
|
|
|
|
|
|
|
// MAIN (64×64 grid of 8-byte entries: flags + asyncId)
|
|
|
|
|
|
writeChunkHeader(buf, 0x4D41494E, 64 * 64 * 8);
|
|
|
|
|
|
for (int y = 0; y < 64; y++) {
|
|
|
|
|
|
for (int x = 0; x < 64; x++) {
|
|
|
|
|
|
if (x == tileX && y == tileY) {
|
|
|
|
|
|
writeU32(buf, 1); // FLAG_EXISTS
|
|
|
|
|
|
} else {
|
|
|
|
|
|
writeU32(buf, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
writeU32(buf, 0); // asyncId
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto dir = std::filesystem::path(path).parent_path();
|
|
|
|
|
|
if (!dir.empty()) std::filesystem::create_directories(dir);
|
|
|
|
|
|
|
|
|
|
|
|
std::ofstream file(path, std::ios::binary);
|
|
|
|
|
|
if (!file) {
|
|
|
|
|
|
LOG_ERROR("Failed to write WDT: ", path);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
file.write(reinterpret_cast<const char*>(buf.data()), buf.size());
|
|
|
|
|
|
LOG_INFO("WDT written: ", path, " (", buf.size(), " bytes, map=", mapName, ")");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace editor
|
|
|
|
|
|
} // namespace wowee
|