12#include <Poco/Dynamic/Var.h>
13#include <Poco/JSON/Array.h>
14#include <Poco/JSON/Object.h>
22float asFloat(
const Poco::Dynamic::Var &
v,
float fallback) {
24 if (
v.isEmpty())
return fallback;
25 return static_cast<float>(
v.convert<
double>());
31int asInt(
const Poco::Dynamic::Var &
v,
int fallback) {
33 if (
v.isEmpty())
return fallback;
34 return v.convert<
int>();
40bool asBool(
const Poco::Dynamic::Var &
v,
bool fallback) {
42 if (
v.isEmpty())
return fallback;
43 return v.convert<
bool>();
49std::string asString(
const Poco::Dynamic::Var &
v) {
51 if (
v.isEmpty())
return {};
52 return v.convert<std::string>();
58bool readVec2(Poco::JSON::Object::Ptr o,
const char *key,
float &
a,
float &
b) {
59 if (!o || !o->has(key))
return false;
60 Poco::JSON::Array::Ptr arr;
62 arr = o->getArray(key);
66 if (!arr || arr->size() < 2)
return false;
67 a = asFloat(arr->get(0),
a);
68 b = asFloat(arr->get(1),
b);
72bool readVec4(Poco::JSON::Object::Ptr o,
const char *key,
float &
a,
float &
b,
float &
c,
float &
d) {
73 if (!o || !o->has(key))
return false;
74 Poco::JSON::Array::Ptr arr;
76 arr = o->getArray(key);
80 if (!arr || arr->size() < 4)
return false;
81 a = asFloat(arr->get(0),
a);
82 b = asFloat(arr->get(1),
b);
83 c = asFloat(arr->get(2),
c);
84 d = asFloat(arr->get(3),
d);
88int64_t fileModtime(
const std::string &path) {
89 auto *fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>(
"Filesystem");
90 if (!fs) fs = eve::filesystem::Filesystem::create();
92 if (!fs->getInfo(path, info))
return -1;
96graphics::Texture *tryLoadTexture(
const std::string &path) {
97 if (path.empty())
return nullptr;
98 auto *gfx = eve::ModuleManager::getInstance<eve::graphics::Graphics>(
"Graphics");
99 if (!gfx)
return nullptr;
101 graphics::Texture *tex = gfx->newTextureFromFile(path);
102 if (
auto *hot = eve::ModuleManager::getInstance<eve::filesystem::HotReload>(
"HotReload"))
103 hot->bind(path,
"texture");
120TilesetInfo readTilesetObject(Poco::JSON::Object::Ptr o) {
123 if (o->has(
"image")) info.image = asString(o->get(
"image"));
124 else if (o->has(
"texture")) info.image = asString(o->get(
"texture"));
125 if (o->has(
"firstgid")) info.firstGid = asInt(o->get(
"firstgid"), 1);
126 else if (o->has(
"firstGid")) info.firstGid = asInt(o->get(
"firstGid"), 1);
127 if (o->has(
"columns")) info.columns = asInt(o->get(
"columns"), 1);
128 if (o->has(
"tilewidth")) info.tileW = asInt(o->get(
"tilewidth"), 32);
129 else if (o->has(
"tileWidth")) info.tileW = asInt(o->get(
"tileWidth"), 32);
130 if (o->has(
"tileheight")) info.tileH = asInt(o->get(
"tileheight"), 32);
131 else if (o->has(
"tileHeight")) info.tileH = asInt(o->get(
"tileHeight"), 32);
132 if (o->has(
"margin")) info.margin = asInt(o->get(
"margin"), 0);
133 if (o->has(
"spacing")) info.spacing = asInt(o->get(
"spacing"), 0);
134 if (info.columns <= 0 && o->has(
"imagewidth") && info.tileW > 0) {
135 const int iw = asInt(o->get(
"imagewidth"), 0);
136 if (iw > 0) info.columns = std::max(1, (iw - info.margin) / (info.tileW + info.spacing));
141void applyTileset(TileLayer *
layer,
const TilesetInfo &info) {
144 graphics::Texture *tex = tryLoadTexture(info.image);
145 layer->resource()->texturePath = info.image;
146 layer->
setTileset(tex, info.firstGid, info.columns, info.margin, info.spacing);
149bool decodeLayerData(Poco::JSON::Object::Ptr layerObj,
size_t expectedCount,
150 std::vector<uint32_t> &out, std::string *
error) {
151 if (!layerObj || !layerObj->has(
"data")) {
157 if (layerObj->isArray(
"data")) {
158 auto arr = layerObj->getArray(
"data");
163 out.resize(arr->size());
164 for (
size_t i = 0; i < arr->size(); ++i) out[i] = uint32_t(asInt(arr->get(i), 0));
165 if (expectedCount > 0 && out.size() != expectedCount) {
174 std::string encoding = layerObj->has(
"encoding") ? asString(layerObj->get(
"encoding")) :
"";
175 std::string compression =
176 layerObj->has(
"compression") ? asString(layerObj->get(
"compression")) :
"";
177 if (encoding !=
"base64") {
181 if (compression ==
"zstd") {
182 if (
error) *
error =
"zstd not supported; export as zlib";
186 const std::string b64 = asString(layerObj->get(
"data"));
187 size_t decodedLen = 0;
188 std::unique_ptr<char[]> decoded(
eve::data::decode(
"base64", b64.data(), b64.size(), decodedLen));
194 const char *bytes = decoded.get();
195 size_t nbytes = decodedLen;
196 std::unique_ptr<char[]> inflated;
197 if (!compression.empty()) {
198 if (compression !=
"zlib" && compression !=
"gzip") {
199 if (
error) *
error =
"unsupported compression";
202 size_t rawsize = expectedCount * 4;
213 bytes = inflated.get();
217 if (expectedCount > 0 && nbytes != expectedCount * 4) {
218 if (
error) *
error =
"gid byte length mismatch";
221 const size_t count = nbytes / 4;
223 for (
size_t i = 0; i < count; ++i) {
224 const unsigned char *
p =
reinterpret_cast<const unsigned char *
>(bytes) + i * 4;
225 out[i] = uint32_t(
p[0]) | (uint32_t(
p[1]) << 8) | (uint32_t(
p[2]) << 16) |
226 (uint32_t(
p[3]) << 24);
231void applyLayerDraw(TileLayer *
layer, Poco::JSON::Object::Ptr layerObj) {
232 if (!
layer || !layerObj)
return;
233 if (layerObj->has(
"visible"))
235 if (layerObj->has(
"layer"))
237 else if (layerObj->has(
"id"))
239 float r = 1, g = 1,
b = 1,
a = 1;
240 if (readVec4(layerObj,
"tint", r, g,
b,
a))
242 else if (layerObj->has(
"opacity")) {
243 a = asFloat(layerObj->get(
"opacity"), 1.f);
247 if (layerObj->has(
"x")) ox = asFloat(layerObj->get(
"x"), ox);
248 if (layerObj->has(
"offsetx")) ox += asFloat(layerObj->get(
"offsetx"), 0.f);
249 if (layerObj->has(
"y")) oy = asFloat(layerObj->get(
"y"), oy);
250 if (layerObj->has(
"offsety")) oy += asFloat(layerObj->get(
"offsety"), 0.f);
251 if (layerObj->has(
"x") || layerObj->has(
"y") || layerObj->has(
"offsetx") ||
252 layerObj->has(
"offsety"))
256bool isTileLayerObject(Poco::JSON::Object::Ptr o) {
257 if (!o)
return false;
258 if (o->has(
"type")) {
259 const std::string t = asString(o->get(
"type"));
260 if (!t.empty() && t !=
"tilelayer")
return false;
262 return o->has(
"data");
265bool isObjectGroup(Poco::JSON::Object::Ptr o) {
266 if (!o)
return false;
267 if (!o->has(
"type"))
return false;
268 return asString(o->get(
"type")) ==
"objectgroup";
271bool parseOrientation(Poco::JSON::Object::Ptr root, TileLayer::Config *cfg, std::string *
error) {
272 if (!root || !cfg)
return false;
273 if (!root->has(
"orientation"))
return true;
274 const std::string o = asString(root->get(
"orientation"));
275 if (o.empty() || o ==
"orthogonal")
277 else if (o ==
"isometric")
279 else if (o ==
"staggered")
281 else if (o ==
"hexagonal")
284 if (
error) *
error =
"unknown orientation: " + o;
287 if (root->has(
"staggeraxis")) {
288 const std::string
a = asString(root->get(
"staggeraxis"));
291 if (root->has(
"staggerindex")) {
292 const std::string i = asString(root->get(
"staggerindex"));
295 if (root->has(
"hexsidelength"))
296 cfg->hexSideLength = asFloat(root->get(
"hexsidelength"), 0.f);
300bool applyMapGlobals(TileLayer *
layer, Poco::JSON::Object::Ptr root, std::string *
error) {
301 if (!
layer || !root)
return false;
308 if (root->has(
"width")) mapW = asInt(root->get(
"width"), mapW);
309 if (root->has(
"height")) mapH = asInt(root->get(
"height"), mapH);
310 if (root->has(
"tilewidth"))
tileW = asFloat(root->get(
"tilewidth"),
tileW);
311 else if (root->has(
"tileWidth"))
tileW = asFloat(root->get(
"tileWidth"),
tileW);
312 if (root->has(
"tileheight"))
tileH = asFloat(root->get(
"tileheight"),
tileH);
313 else if (root->has(
"tileHeight"))
tileH = asFloat(root->get(
"tileHeight"),
tileH);
318 if (!parseOrientation(root, &(*
layer->config()),
error))
return false;
321 if (readVec2(root,
"origin", ox, oy))
324 if (root->has(
"x")) ox = asFloat(root->get(
"x"), ox);
325 if (root->has(
"y")) oy = asFloat(root->get(
"y"), oy);
329 if (root->has(
"layer"))
331 if (root->has(
"visible"))
333 if (root->has(
"autoReload"))
334 layer->resource()->autoReload = asBool(root->get(
"autoReload"),
true);
336 float r = 1, g = 1,
b = 1,
a = 1;
339 if (root->has(
"tileset") && !root->isArray(
"tileset")) {
341 auto o = root->getObject(
"tileset");
342 if (o && !o->has(
"source")) applyTileset(
layer, readTilesetObject(o));
345 }
else if (root->has(
"tilesets")) {
347 auto arr = root->getArray(
"tilesets");
349 for (
size_t i = 0; i < arr->size(); ++i) {
351 auto o = arr->getObject(i);
352 if (o && o->has(
"source"))
continue;
353 applyTileset(
layer, readTilesetObject(o));
361 }
else if (root->has(
"image") || root->has(
"texture")) {
362 applyTileset(
layer, readTilesetObject(root));
367bool applyFlatLayerData(TileLayer *
layer, Poco::JSON::Object::Ptr root, std::string *
error) {
370 const size_t need = size_t(std::max(0, mapW) * std::max(0, mapH));
375 std::vector<uint32_t>
data;
376 if (!decodeLayerData(root, need, data,
error))
return false;
377 auto &gids =
layer->tiles()->gids;
378 gids.assign(need, 0
u);
379 const size_t n = std::min(need,
data.size());
380 for (
size_t i = 0; i <
n; ++i) gids[i] = data[i];
384bool applyOneLayerObject(TileLayer *
layer, Poco::JSON::Object::Ptr layerObj,
int mapW,
int mapH,
385 std::string *
error) {
386 if (!
layer || !layerObj)
return false;
387 int w = mapW,
h = mapH;
388 if (layerObj->has(
"width"))
w = asInt(layerObj->get(
"width"),
w);
389 if (layerObj->has(
"height"))
h = asInt(layerObj->get(
"height"),
h);
394 std::vector<uint32_t>
data;
395 if (!decodeLayerData(layerObj, need, data,
error))
return false;
396 auto &gids =
layer->tiles()->gids;
397 gids.assign(need, 0
u);
398 const size_t n = std::min(need,
data.size());
399 for (
size_t i = 0; i <
n; ++i) gids[i] = data[i];
401 applyLayerDraw(
layer, layerObj);
405void parseObjectGroup(Poco::JSON::Object::Ptr
group, std::vector<MapObject> &out) {
407 Poco::JSON::Array::Ptr arr;
409 arr =
group->getArray(
"objects");
414 for (
size_t i = 0; i < arr->size(); ++i) {
415 Poco::JSON::Object::Ptr o;
417 o = arr->getObject(i);
423 if (o->has(
"name")) mo.name = asString(o->get(
"name"));
424 if (o->has(
"type")) mo.type = asString(o->get(
"type"));
425 else if (o->has(
"class")) mo.type = asString(o->get(
"class"));
426 mo.x = o->has(
"x") ? asFloat(o->get(
"x"), 0.f) : 0.f;
427 mo.y = o->has(
"y") ? asFloat(o->get(
"y"), 0.f) : 0.f;
428 mo.width = o->has(
"width") ? asFloat(o->get(
"width"), 0.f) : 0.f;
429 mo.height = o->has(
"height") ? asFloat(o->get(
"height"), 0.f) : 0.f;
430 if (o->has(
"gid")) mo.gid = uint32_t(asInt(o->get(
"gid"), 0));
431 out.push_back(std::move(mo));
435void abandonLayers(std::vector<TileLayer *> &layers) {
436 for (TileLayer *
layer : layers) {
437 if (!
layer)
continue;
444TilesetInfo readDefaultTileset(Poco::JSON::Object::Ptr root) {
445 TilesetInfo defaultTs;
446 if (root->has(
"tilesets")) {
448 auto arr = root->getArray(
"tilesets");
450 for (
size_t i = 0; i < arr->size(); ++i) {
452 auto o = arr->getObject(i);
453 if (o && o->has(
"source"))
continue;
454 return readTilesetObject(o);
461 }
else if (root->has(
"tileset") && !root->isArray(
"tileset")) {
463 auto o = root->getObject(
"tileset");
464 if (o && !o->has(
"source"))
return readTilesetObject(o);
467 }
else if (root->has(
"image") || root->has(
"texture")) {
468 return readTilesetObject(root);
473std::vector<TileLayer *> loadMapObject(Poco::JSON::Object::Ptr root,
const std::string &path,
475 std::vector<MapObject> *objects, std::string *
error) {
476 std::vector<TileLayer *> out;
478 if (
error) *
error =
"config root must be object";
482 int mapW = 10, mapH = 10;
484 if (root->has(
"width")) mapW = asInt(root->get(
"width"), mapW);
485 if (root->has(
"height")) mapH = asInt(root->get(
"height"), mapH);
486 if (root->has(
"tilewidth"))
tileW = asFloat(root->get(
"tilewidth"),
tileW);
487 else if (root->has(
"tileWidth"))
tileW = asFloat(root->get(
"tileWidth"),
tileW);
488 if (root->has(
"tileheight"))
tileH = asFloat(root->get(
"tileheight"),
tileH);
489 else if (root->has(
"tileHeight"))
tileH = asFloat(root->get(
"tileHeight"),
tileH);
492 TileLayer::Config orientCheck;
493 if (!parseOrientation(root, &orientCheck,
error))
return out;
495 TilesetInfo defaultTs = readDefaultTileset(root);
496 if (objects) objects->clear();
498 auto bindResource = [&](TileLayer *
layer) {
499 auto res =
layer->resource();
502 res->modtime = fileModtime(path);
506 eve::ModuleManager::getInstance<eve::filesystem::HotReload>(
"HotReload"))
507 hot->bind(path,
"tilemap");
510 if (root->has(
"autoReload"))
511 res->autoReload = asBool(root->get(
"autoReload"),
true);
514 if (root->has(
"layers")) {
516 auto arr = root->getArray(
"layers");
519 for (
size_t i = 0; i < arr->size(); ++i) {
520 Poco::JSON::Object::Ptr lo;
522 lo = arr->getObject(i);
526 if (objects && isObjectGroup(lo)) {
527 parseObjectGroup(lo, *objects);
530 if (!isTileLayerObject(lo))
continue;
538 applyTileset(
layer, defaultTs);
539 if (!applyOneLayerObject(
layer, lo, mapW, mapH,
error)) {
545 if (!lo->has(
"layer") && !lo->has(
"id"))
layer->
setLayer(sort);
548 out.push_back(
layer);
553 if (!out.empty())
return out;
562 applyTileset(
layer, defaultTs);
563 if (root->has(
"data")) {
564 if (!applyFlatLayerData(
layer, root,
error)) {
571 out.push_back(
layer);
579 auto root = doc->
object();
580 if (!root)
return false;
583 if (!applyMapGlobals(
layer, root, &err))
return false;
585 if (root->has(
"layers")) {
587 auto arr = root->getArray(
"layers");
589 for (
size_t i = 0; i < arr->size(); ++i) {
590 Poco::JSON::Object::Ptr lo;
592 lo = arr->getObject(i);
596 if (!isTileLayerObject(lo))
continue;
605 if (root->has(
"data"))
return applyFlatLayerData(
layer, root,
nullptr);
610 auto *dm = eve::data::DataModule::create();
612 std::unique_ptr<data::JsonDocument> doc(dm->decodeJson(json, &err));
614 if (
error) *
error = err.empty() ?
"invalid json" : err;
617 if (!doc->isObject()) {
618 if (
error) *
error =
"config root must be object";
621 auto root = doc->object();
623 if (
error) *
error =
"config root must be object";
626 if (!applyMapGlobals(
layer, root,
error))
return false;
627 if (root->has(
"layers")) {
629 auto arr = root->getArray(
"layers");
631 for (
size_t i = 0; i < arr->size(); ++i) {
632 Poco::JSON::Object::Ptr lo;
634 lo = arr->getObject(i);
638 if (!isTileLayerObject(lo))
continue;
646 if (root->has(
"data"))
return applyFlatLayerData(
layer, root,
error);
651 if (!
layer || path.empty()) {
655 auto *fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>(
"Filesystem");
656 if (!fs) fs = eve::filesystem::Filesystem::create();
658 std::unique_ptr<eve::filesystem::FileData>
data;
665 if (!
data ||
data->getSize() == 0) {
670 std::string text(
static_cast<const char *
>(
data->getData()),
data->getSize());
673 auto res =
layer->resource();
675 res->modtime = fileModtime(path);
677 if (
auto *hot = eve::ModuleManager::getInstance<eve::filesystem::HotReload>(
"HotReload"))
678 hot->bind(path,
"tilemap");
683 if (!
layer)
return false;
684 const std::string &path =
layer->resource()->path;
692std::vector<TileLayer *>
loadMapText(
const std::string &json, std::vector<MapObject> *objects,
693 std::string *
error) {
694 auto *dm = eve::data::DataModule::create();
696 std::unique_ptr<data::JsonDocument> doc(dm->decodeJson(json, &err));
697 if (!doc || !doc->isObject()) {
698 if (
error) *
error = err.empty() ?
"invalid json" : err;
701 return loadMapObject(doc->object(), {},
nullptr, objects,
error);
704std::vector<TileLayer *>
loadMapFile(
const std::string &path, std::vector<MapObject> *objects,
705 std::string *
error) {
711 auto *fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>(
"Filesystem");
712 if (!fs) fs = eve::filesystem::Filesystem::create();
714 std::unique_ptr<eve::filesystem::FileData>
data;
721 if (!
data ||
data->getSize() == 0) {
726 std::string text(
static_cast<const char *
>(
data->getData()),
data->getSize());
727 auto *dm = eve::data::DataModule::create();
729 std::unique_ptr<data::JsonDocument> doc(dm->decodeJson(text, &err));
730 if (!doc || !doc->isObject()) {
731 if (
error) *
error = err.empty() ?
"invalid json" : err;
734 return loadMapObject(doc->object(), path, fs, objects,
error);
Thin RAII wrapper over a Poco JSON value (object/array/scalar).
bool isObject() const
Root type predicates.
Poco::JSON::Object::Ptr object()
Root as JSON object/array (may be null when the type differs).
virtual FileData * read(std::string filename, int64_t size=File::ALL) const =0
Reads data from a file.
virtual bool watch(std::string path)=0
Watch a virtual or absolute OS path (file or directory). File watches monitor the parent directory an...
ECS tile layer entity. Script mutates tile GIDs / tileset / draw; TileRenderSystem batch-draws atlas ...
void clear()
Clears the layer to empty.
float getTileWidth()
Tile size in pixels.
void setOrigin(float x, float y)
World origin of the layer (pixels).
void setTileset(graphics::Texture *texture, int firstGid, int columns, int margin=0, int spacing=0)
Binds the atlas texture and tile table.
void resize(int mapW, int mapH)
Resizes the tile grid (existing GIDs preserved where possible).
void setTilesetTileSize(int tileW, int tileH)
Tile size in pixels of the bound tileset.
void setLayer(int layer)
Draw order layer / visibility / tint.
static TileLayer * createLayer(int mapW, int mapH, float tileW=32.f, float tileH=32.f)
void setTileSize(float tileW, float tileH)
void setVisible(bool visible)
int getMapWidth()
Map dimensions in tiles.
void setTint(float r, float g, float b, float a=1.f)
char * decompress(CompressedData *data, size_t &decompressedsize)
Decompresses existing compressed data into raw bytes.
char * decode(std::string format, const char *src, size_t srclen, size_t &dstlen)
放置世界:格子占用(多通道)+ 地形语义 + 已放置建筑实例。 行为由 PlacementSystem 提供;本类暴露便于脚本绑定的薄封装方法。 坐标换算统一走 eve::grid(支持 rectang...
bool reloadConfigFile(TileLayer *layer, std::string *error)
std::vector< TileLayer * > loadMapText(const std::string &json, std::vector< MapObject > *objects, std::string *error)
Parse map JSON text (no filesystem). Same semantics as loadMapFile.
bool applyConfigDocument(TileLayer *layer, data::JsonDocument *doc)
Apply map/layer JSON onto an existing TileLayer (Config + Tiles + Tileset + Draw)....
bool applyConfigText(TileLayer *layer, const std::string &json, std::string *error)
Parse JSON text and apply onto one layer.
StaggerIndex
Which rows/columns are offset by half a tile.
std::vector< TileLayer * > loadMapFile(const std::string &path, std::vector< MapObject > *objects, std::string *error)
StaggerAxis
Stagger axis for staggered/hex maps (shift every other row/column).
bool loadConfigFile(TileLayer *layer, const std::string &path, std::string *error)
Read path via Filesystem, apply onto layer, bind Resource.path + modtime for hot reload....