mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-11 19:43:52 +00:00
feat(pipeline): add WOMX (Wowee World Map index) format
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.
This commit is contained in:
parent
6c3f5cb33f
commit
db47f00657
8 changed files with 527 additions and 0 deletions
207
src/pipeline/wowee_world_map.cpp
Normal file
207
src/pipeline/wowee_world_map.cpp
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
#include "pipeline/wowee_world_map.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kMagic[4] = {'W', 'M', 'P', 'X'};
|
||||
constexpr uint32_t kVersion = 1;
|
||||
|
||||
template <typename T>
|
||||
void writePOD(std::ofstream& os, const T& v) {
|
||||
os.write(reinterpret_cast<const char*>(&v), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool readPOD(std::ifstream& is, T& v) {
|
||||
is.read(reinterpret_cast<char*>(&v), sizeof(T));
|
||||
return is.gcount() == static_cast<std::streamsize>(sizeof(T));
|
||||
}
|
||||
|
||||
std::string normalizePath(std::string base) {
|
||||
if (base.size() < 5 || base.substr(base.size() - 5) != ".womx") {
|
||||
base += ".womx";
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
size_t bitmapBytesFor(uint32_t gridSize) {
|
||||
return (static_cast<size_t>(gridSize) * gridSize + 7) / 8;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool WoweeWorldMap::hasTile(uint32_t x, uint32_t y) const {
|
||||
if (x >= gridSize || y >= gridSize) return false;
|
||||
size_t bit = static_cast<size_t>(y) * gridSize + x;
|
||||
size_t byte = bit / 8;
|
||||
if (byte >= tileBitmap.size()) return false;
|
||||
return (tileBitmap[byte] >> (bit & 7)) & 1;
|
||||
}
|
||||
|
||||
void WoweeWorldMap::setTile(uint32_t x, uint32_t y, bool present) {
|
||||
if (x >= gridSize || y >= gridSize) return;
|
||||
size_t bit = static_cast<size_t>(y) * gridSize + x;
|
||||
size_t byte = bit / 8;
|
||||
if (tileBitmap.size() <= byte) tileBitmap.resize(byte + 1, 0);
|
||||
uint8_t mask = static_cast<uint8_t>(1u << (bit & 7));
|
||||
if (present) tileBitmap[byte] |= mask;
|
||||
else tileBitmap[byte] &= static_cast<uint8_t>(~mask);
|
||||
}
|
||||
|
||||
uint32_t WoweeWorldMap::countTiles() const {
|
||||
uint32_t n = 0;
|
||||
size_t totalBits = static_cast<size_t>(gridSize) * gridSize;
|
||||
for (size_t bit = 0; bit < totalBits; ++bit) {
|
||||
size_t byte = bit / 8;
|
||||
if (byte >= tileBitmap.size()) break;
|
||||
if ((tileBitmap[byte] >> (bit & 7)) & 1) n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
const char* WoweeWorldMap::worldTypeName(uint8_t t) {
|
||||
switch (t) {
|
||||
case Continent: return "continent";
|
||||
case Instance: return "instance";
|
||||
case Battleground: return "battleground";
|
||||
case Arena: return "arena";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
bool WoweeWorldMapLoader::save(const WoweeWorldMap& m,
|
||||
const std::string& basePath) {
|
||||
if (m.gridSize == 0 || m.gridSize > 128) return false;
|
||||
std::ofstream os(normalizePath(basePath), std::ios::binary);
|
||||
if (!os) return false;
|
||||
os.write(kMagic, 4);
|
||||
writePOD(os, kVersion);
|
||||
uint32_t nameLen = static_cast<uint32_t>(m.name.size());
|
||||
writePOD(os, nameLen);
|
||||
if (nameLen > 0) os.write(m.name.data(), nameLen);
|
||||
writePOD(os, m.worldType);
|
||||
writePOD(os, m.gridSize);
|
||||
uint16_t pad = 0;
|
||||
writePOD(os, pad);
|
||||
writePOD(os, m.defaultLightId);
|
||||
writePOD(os, m.defaultWeatherId);
|
||||
uint32_t reserved = 0;
|
||||
writePOD(os, reserved);
|
||||
writePOD(os, reserved);
|
||||
writePOD(os, reserved);
|
||||
size_t expectedBytes = bitmapBytesFor(m.gridSize);
|
||||
uint32_t bitmapBytes = static_cast<uint32_t>(expectedBytes);
|
||||
writePOD(os, bitmapBytes);
|
||||
// Pad bitmap up to expectedBytes if caller under-sized it
|
||||
// (setTile may not have grown it to the full grid coverage).
|
||||
if (m.tileBitmap.size() >= expectedBytes) {
|
||||
os.write(reinterpret_cast<const char*>(m.tileBitmap.data()),
|
||||
expectedBytes);
|
||||
} else {
|
||||
if (!m.tileBitmap.empty()) {
|
||||
os.write(reinterpret_cast<const char*>(m.tileBitmap.data()),
|
||||
m.tileBitmap.size());
|
||||
}
|
||||
std::vector<uint8_t> tail(expectedBytes - m.tileBitmap.size(), 0);
|
||||
os.write(reinterpret_cast<const char*>(tail.data()), tail.size());
|
||||
}
|
||||
return os.good();
|
||||
}
|
||||
|
||||
WoweeWorldMap WoweeWorldMapLoader::load(const std::string& basePath) {
|
||||
WoweeWorldMap out;
|
||||
std::ifstream is(normalizePath(basePath), std::ios::binary);
|
||||
if (!is) return out;
|
||||
char magic[4];
|
||||
is.read(magic, 4);
|
||||
if (std::memcmp(magic, kMagic, 4) != 0) return out;
|
||||
uint32_t version = 0;
|
||||
if (!readPOD(is, version) || version != kVersion) return out;
|
||||
uint32_t nameLen = 0;
|
||||
if (!readPOD(is, nameLen)) return out;
|
||||
if (nameLen > 0) {
|
||||
out.name.resize(nameLen);
|
||||
is.read(out.name.data(), nameLen);
|
||||
if (is.gcount() != static_cast<std::streamsize>(nameLen)) {
|
||||
out.name.clear();
|
||||
return out;
|
||||
}
|
||||
}
|
||||
if (!readPOD(is, out.worldType)) return out;
|
||||
if (!readPOD(is, out.gridSize)) return out;
|
||||
uint16_t pad = 0;
|
||||
if (!readPOD(is, pad)) return out;
|
||||
if (!readPOD(is, out.defaultLightId)) return out;
|
||||
if (!readPOD(is, out.defaultWeatherId)) return out;
|
||||
uint32_t reserved = 0;
|
||||
if (!readPOD(is, reserved)) return out;
|
||||
if (!readPOD(is, reserved)) return out;
|
||||
if (!readPOD(is, reserved)) return out;
|
||||
uint32_t bitmapBytes = 0;
|
||||
if (!readPOD(is, bitmapBytes)) return out;
|
||||
// Cap to a sane upper bound in case the file is corrupted —
|
||||
// a 128×128 grid is 2048 bytes, so anything > 4 KiB is a sign
|
||||
// of trouble.
|
||||
if (bitmapBytes > 4096) {
|
||||
out.gridSize = 0;
|
||||
return out;
|
||||
}
|
||||
out.tileBitmap.resize(bitmapBytes);
|
||||
if (bitmapBytes > 0) {
|
||||
is.read(reinterpret_cast<char*>(out.tileBitmap.data()), bitmapBytes);
|
||||
if (is.gcount() != static_cast<std::streamsize>(bitmapBytes)) {
|
||||
out.tileBitmap.clear();
|
||||
out.gridSize = 0;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool WoweeWorldMapLoader::exists(const std::string& basePath) {
|
||||
std::ifstream is(normalizePath(basePath), std::ios::binary);
|
||||
return is.good();
|
||||
}
|
||||
|
||||
WoweeWorldMap WoweeWorldMapLoader::makeContinent(const std::string& mapName) {
|
||||
WoweeWorldMap m;
|
||||
m.name = mapName;
|
||||
m.worldType = WoweeWorldMap::Continent;
|
||||
m.gridSize = 64;
|
||||
m.tileBitmap.assign(bitmapBytesFor(64), 0xFF);
|
||||
// Last byte may have spare bits past 64*64 — but 64*64 is
|
||||
// a multiple of 8 (4096), so this is exact and no masking
|
||||
// is needed.
|
||||
return m;
|
||||
}
|
||||
|
||||
WoweeWorldMap WoweeWorldMapLoader::makeInstance(const std::string& mapName) {
|
||||
WoweeWorldMap m;
|
||||
m.name = mapName;
|
||||
m.worldType = WoweeWorldMap::Instance;
|
||||
m.gridSize = 4;
|
||||
m.tileBitmap.assign(bitmapBytesFor(4), 0);
|
||||
for (uint32_t y = 0; y < 4; ++y)
|
||||
for (uint32_t x = 0; x < 4; ++x)
|
||||
m.setTile(x, y, true);
|
||||
return m;
|
||||
}
|
||||
|
||||
WoweeWorldMap WoweeWorldMapLoader::makeArena(const std::string& mapName) {
|
||||
WoweeWorldMap m;
|
||||
m.name = mapName;
|
||||
m.worldType = WoweeWorldMap::Arena;
|
||||
m.gridSize = 1;
|
||||
m.tileBitmap.assign(bitmapBytesFor(1), 0);
|
||||
m.setTile(0, 0, true);
|
||||
return m;
|
||||
}
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
Loading…
Add table
Add a link
Reference in a new issue