- Implemented multi-layer tilemap functionality, allowing for the reading and saving of Tiled JSON formats. - Added support for various tile projections (orthogonal, isometric, staggered, hexagonal) and depth calculations. - Introduced object layer parsing and caching for improved map object management. - Updated the rendering system to unify 2D depth queues and enhance sprite handling. - Added new methods for accessing object properties, including position and dimensions. This update significantly improves the map module's capabilities and integration with the rendering system.
742 lines
26 KiB
C++
742 lines
26 KiB
C++
#include "map/TileConfig.h"
|
|
#include "map/TileOrientation.h"
|
|
|
|
#include "common/Module.h"
|
|
#include "data/DataModule.h"
|
|
#include "data/JsonDocument.h"
|
|
#include "filesystem/Filesystem.h"
|
|
#include "filesystem/FileData.h"
|
|
#include "filesystem/HotReload.h"
|
|
#include "graphics/Graphics.h"
|
|
|
|
#include <Poco/Dynamic/Var.h>
|
|
#include <Poco/JSON/Array.h>
|
|
#include <Poco/JSON/Object.h>
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
|
|
namespace eve::map {
|
|
namespace {
|
|
|
|
float asFloat(const Poco::Dynamic::Var &v, float fallback) {
|
|
try {
|
|
if (v.isEmpty()) return fallback;
|
|
return static_cast<float>(v.convert<double>());
|
|
} catch (...) {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
int asInt(const Poco::Dynamic::Var &v, int fallback) {
|
|
try {
|
|
if (v.isEmpty()) return fallback;
|
|
return v.convert<int>();
|
|
} catch (...) {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
bool asBool(const Poco::Dynamic::Var &v, bool fallback) {
|
|
try {
|
|
if (v.isEmpty()) return fallback;
|
|
return v.convert<bool>();
|
|
} catch (...) {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
std::string asString(const Poco::Dynamic::Var &v) {
|
|
try {
|
|
if (v.isEmpty()) return {};
|
|
return v.convert<std::string>();
|
|
} catch (...) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
bool readVec2(Poco::JSON::Object::Ptr o, const char *key, float &a, float &b) {
|
|
if (!o || !o->has(key)) return false;
|
|
Poco::JSON::Array::Ptr arr;
|
|
try {
|
|
arr = o->getArray(key);
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
if (!arr || arr->size() < 2) return false;
|
|
a = asFloat(arr->get(0), a);
|
|
b = asFloat(arr->get(1), b);
|
|
return true;
|
|
}
|
|
|
|
bool readVec4(Poco::JSON::Object::Ptr o, const char *key, float &a, float &b, float &c, float &d) {
|
|
if (!o || !o->has(key)) return false;
|
|
Poco::JSON::Array::Ptr arr;
|
|
try {
|
|
arr = o->getArray(key);
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
if (!arr || arr->size() < 4) return false;
|
|
a = asFloat(arr->get(0), a);
|
|
b = asFloat(arr->get(1), b);
|
|
c = asFloat(arr->get(2), c);
|
|
d = asFloat(arr->get(3), d);
|
|
return true;
|
|
}
|
|
|
|
int64_t fileModtime(const std::string &path) {
|
|
auto *fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>("Filesystem");
|
|
if (!fs) fs = eve::filesystem::Filesystem::create();
|
|
eve::filesystem::Filesystem::Info info{};
|
|
if (!fs->getInfo(path, info)) return -1;
|
|
return info.modtime;
|
|
}
|
|
|
|
graphics::Texture *tryLoadTexture(const std::string &path) {
|
|
if (path.empty()) return nullptr;
|
|
auto *gfx = eve::ModuleManager::getInstance<eve::graphics::Graphics>("Graphics");
|
|
if (!gfx) return nullptr;
|
|
try {
|
|
graphics::Texture *tex = gfx->newTextureFromFile(path);
|
|
if (auto *hot = eve::ModuleManager::getInstance<eve::filesystem::HotReload>("HotReload"))
|
|
hot->bind(path, "texture");
|
|
return tex;
|
|
} catch (...) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
struct TilesetInfo {
|
|
std::string image;
|
|
int firstGid = 1;
|
|
int columns = 1;
|
|
int tileW = 32;
|
|
int tileH = 32;
|
|
int margin = 0;
|
|
int spacing = 0;
|
|
};
|
|
|
|
TilesetInfo readTilesetObject(Poco::JSON::Object::Ptr o) {
|
|
TilesetInfo info;
|
|
if (!o) return info;
|
|
if (o->has("image")) info.image = asString(o->get("image"));
|
|
else if (o->has("texture")) info.image = asString(o->get("texture"));
|
|
if (o->has("firstgid")) info.firstGid = asInt(o->get("firstgid"), 1);
|
|
else if (o->has("firstGid")) info.firstGid = asInt(o->get("firstGid"), 1);
|
|
if (o->has("columns")) info.columns = asInt(o->get("columns"), 1);
|
|
if (o->has("tilewidth")) info.tileW = asInt(o->get("tilewidth"), 32);
|
|
else if (o->has("tileWidth")) info.tileW = asInt(o->get("tileWidth"), 32);
|
|
if (o->has("tileheight")) info.tileH = asInt(o->get("tileheight"), 32);
|
|
else if (o->has("tileHeight")) info.tileH = asInt(o->get("tileHeight"), 32);
|
|
if (o->has("margin")) info.margin = asInt(o->get("margin"), 0);
|
|
if (o->has("spacing")) info.spacing = asInt(o->get("spacing"), 0);
|
|
if (info.columns <= 0 && o->has("imagewidth") && info.tileW > 0) {
|
|
const int iw = asInt(o->get("imagewidth"), 0);
|
|
if (iw > 0) info.columns = std::max(1, (iw - info.margin) / (info.tileW + info.spacing));
|
|
}
|
|
return info;
|
|
}
|
|
|
|
void applyTileset(TileLayer *layer, const TilesetInfo &info) {
|
|
if (!layer) return;
|
|
layer->setTilesetTileSize(info.tileW, info.tileH);
|
|
graphics::Texture *tex = tryLoadTexture(info.image);
|
|
layer->resource()->texturePath = info.image;
|
|
layer->setTileset(tex, info.firstGid, info.columns, info.margin, info.spacing);
|
|
}
|
|
|
|
bool decodeLayerData(Poco::JSON::Object::Ptr layerObj, size_t expectedCount,
|
|
std::vector<uint32_t> &out, std::string *error) {
|
|
if (!layerObj || !layerObj->has("data")) {
|
|
if (error) *error = "missing data";
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
if (layerObj->isArray("data")) {
|
|
auto arr = layerObj->getArray("data");
|
|
if (!arr) {
|
|
if (error) *error = "invalid data array";
|
|
return false;
|
|
}
|
|
out.resize(arr->size());
|
|
for (size_t i = 0; i < arr->size(); ++i) out[i] = uint32_t(asInt(arr->get(i), 0));
|
|
if (expectedCount > 0 && out.size() != expectedCount) {
|
|
if (error) *error = "gid count mismatch";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
} catch (...) {
|
|
}
|
|
|
|
std::string encoding = layerObj->has("encoding") ? asString(layerObj->get("encoding")) : "";
|
|
std::string compression =
|
|
layerObj->has("compression") ? asString(layerObj->get("compression")) : "";
|
|
if (encoding != "base64") {
|
|
if (error) *error = "unsupported encoding";
|
|
return false;
|
|
}
|
|
if (compression == "zstd") {
|
|
if (error) *error = "zstd not supported; export as zlib";
|
|
return false;
|
|
}
|
|
|
|
const std::string b64 = asString(layerObj->get("data"));
|
|
size_t decodedLen = 0;
|
|
std::unique_ptr<char[]> decoded(eve::data::decode("base64", b64.data(), b64.size(), decodedLen));
|
|
if (!decoded) {
|
|
if (error) *error = "base64 decode failed";
|
|
return false;
|
|
}
|
|
|
|
const char *bytes = decoded.get();
|
|
size_t nbytes = decodedLen;
|
|
std::unique_ptr<char[]> inflated;
|
|
if (!compression.empty()) {
|
|
if (compression != "zlib" && compression != "gzip") {
|
|
if (error) *error = "unsupported compression";
|
|
return false;
|
|
}
|
|
size_t rawsize = expectedCount * 4;
|
|
try {
|
|
inflated.reset(eve::data::decompress(compression, bytes, nbytes, rawsize));
|
|
} catch (...) {
|
|
if (error) *error = "decompress failed";
|
|
return false;
|
|
}
|
|
if (!inflated) {
|
|
if (error) *error = "decompress failed";
|
|
return false;
|
|
}
|
|
bytes = inflated.get();
|
|
nbytes = rawsize;
|
|
}
|
|
|
|
if (expectedCount > 0 && nbytes != expectedCount * 4) {
|
|
if (error) *error = "gid byte length mismatch";
|
|
return false;
|
|
}
|
|
const size_t count = nbytes / 4;
|
|
out.resize(count);
|
|
for (size_t i = 0; i < count; ++i) {
|
|
const unsigned char *p = reinterpret_cast<const unsigned char *>(bytes) + i * 4;
|
|
out[i] = uint32_t(p[0]) | (uint32_t(p[1]) << 8) | (uint32_t(p[2]) << 16) |
|
|
(uint32_t(p[3]) << 24);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void applyLayerDraw(TileLayer *layer, Poco::JSON::Object::Ptr layerObj) {
|
|
if (!layer || !layerObj) return;
|
|
if (layerObj->has("visible"))
|
|
layer->setVisible(asBool(layerObj->get("visible"), true));
|
|
if (layerObj->has("layer"))
|
|
layer->setLayer(asInt(layerObj->get("layer"), layer->getLayer()));
|
|
else if (layerObj->has("id"))
|
|
layer->setLayer(asInt(layerObj->get("id"), layer->getLayer()));
|
|
float r = 1, g = 1, b = 1, a = 1;
|
|
if (readVec4(layerObj, "tint", r, g, b, a))
|
|
layer->setTint(r, g, b, a);
|
|
else if (layerObj->has("opacity")) {
|
|
a = asFloat(layerObj->get("opacity"), 1.f);
|
|
layer->setTint(1.f, 1.f, 1.f, a);
|
|
}
|
|
float ox = layer->getX(), oy = layer->getY();
|
|
if (layerObj->has("x")) ox = asFloat(layerObj->get("x"), ox);
|
|
if (layerObj->has("offsetx")) ox += asFloat(layerObj->get("offsetx"), 0.f);
|
|
if (layerObj->has("y")) oy = asFloat(layerObj->get("y"), oy);
|
|
if (layerObj->has("offsety")) oy += asFloat(layerObj->get("offsety"), 0.f);
|
|
if (layerObj->has("x") || layerObj->has("y") || layerObj->has("offsetx") ||
|
|
layerObj->has("offsety"))
|
|
layer->setOrigin(ox, oy);
|
|
}
|
|
|
|
bool isTileLayerObject(Poco::JSON::Object::Ptr o) {
|
|
if (!o) return false;
|
|
if (o->has("type")) {
|
|
const std::string t = asString(o->get("type"));
|
|
if (!t.empty() && t != "tilelayer") return false;
|
|
}
|
|
return o->has("data");
|
|
}
|
|
|
|
bool isObjectGroup(Poco::JSON::Object::Ptr o) {
|
|
if (!o) return false;
|
|
if (!o->has("type")) return false;
|
|
return asString(o->get("type")) == "objectgroup";
|
|
}
|
|
|
|
bool parseOrientation(Poco::JSON::Object::Ptr root, TileLayer::Config *cfg, std::string *error) {
|
|
if (!root || !cfg) return false;
|
|
if (!root->has("orientation")) return true;
|
|
const std::string o = asString(root->get("orientation"));
|
|
if (o.empty() || o == "orthogonal")
|
|
cfg->orientation = MapOrientation::Orthogonal;
|
|
else if (o == "isometric")
|
|
cfg->orientation = MapOrientation::Isometric;
|
|
else if (o == "staggered")
|
|
cfg->orientation = MapOrientation::Staggered;
|
|
else if (o == "hexagonal")
|
|
cfg->orientation = MapOrientation::Hexagonal;
|
|
else {
|
|
if (error) *error = "unknown orientation: " + o;
|
|
return false;
|
|
}
|
|
if (root->has("staggeraxis")) {
|
|
const std::string a = asString(root->get("staggeraxis"));
|
|
cfg->staggerAxis = (a == "x") ? StaggerAxis::X : StaggerAxis::Y;
|
|
}
|
|
if (root->has("staggerindex")) {
|
|
const std::string i = asString(root->get("staggerindex"));
|
|
cfg->staggerIndex = (i == "even") ? StaggerIndex::Even : StaggerIndex::Odd;
|
|
}
|
|
if (root->has("hexsidelength"))
|
|
cfg->hexSideLength = asFloat(root->get("hexsidelength"), 0.f);
|
|
return true;
|
|
}
|
|
|
|
bool applyMapGlobals(TileLayer *layer, Poco::JSON::Object::Ptr root, std::string *error) {
|
|
if (!layer || !root) return false;
|
|
|
|
int mapW = layer->getMapWidth();
|
|
int mapH = layer->getMapHeight();
|
|
float tileW = layer->getTileWidth();
|
|
float tileH = layer->getTileHeight();
|
|
|
|
if (root->has("width")) mapW = asInt(root->get("width"), mapW);
|
|
if (root->has("height")) mapH = asInt(root->get("height"), mapH);
|
|
if (root->has("tilewidth")) tileW = asFloat(root->get("tilewidth"), tileW);
|
|
else if (root->has("tileWidth")) tileW = asFloat(root->get("tileWidth"), tileW);
|
|
if (root->has("tileheight")) tileH = asFloat(root->get("tileheight"), tileH);
|
|
else if (root->has("tileHeight")) tileH = asFloat(root->get("tileHeight"), tileH);
|
|
|
|
if (mapW != layer->getMapWidth() || mapH != layer->getMapHeight()) layer->resize(mapW, mapH);
|
|
layer->setTileSize(tileW, tileH);
|
|
|
|
if (!parseOrientation(root, &(*layer->config()), error)) return false;
|
|
|
|
float ox = layer->getX(), oy = layer->getY();
|
|
if (readVec2(root, "origin", ox, oy))
|
|
layer->setOrigin(ox, oy);
|
|
else {
|
|
if (root->has("x")) ox = asFloat(root->get("x"), ox);
|
|
if (root->has("y")) oy = asFloat(root->get("y"), oy);
|
|
if (root->has("x") || root->has("y")) layer->setOrigin(ox, oy);
|
|
}
|
|
|
|
if (root->has("layer"))
|
|
layer->setLayer(asInt(root->get("layer"), layer->getLayer()));
|
|
if (root->has("visible"))
|
|
layer->setVisible(asBool(root->get("visible"), layer->isVisible()));
|
|
if (root->has("autoReload"))
|
|
layer->resource()->autoReload = asBool(root->get("autoReload"), true);
|
|
|
|
float r = 1, g = 1, b = 1, a = 1;
|
|
if (readVec4(root, "tint", r, g, b, a)) layer->setTint(r, g, b, a);
|
|
|
|
if (root->has("tileset") && !root->isArray("tileset")) {
|
|
try {
|
|
auto o = root->getObject("tileset");
|
|
if (o && !o->has("source")) applyTileset(layer, readTilesetObject(o));
|
|
} catch (...) {
|
|
}
|
|
} else if (root->has("tilesets")) {
|
|
try {
|
|
auto arr = root->getArray("tilesets");
|
|
if (arr) {
|
|
for (size_t i = 0; i < arr->size(); ++i) {
|
|
try {
|
|
auto o = arr->getObject(i);
|
|
if (o && o->has("source")) continue;
|
|
applyTileset(layer, readTilesetObject(o));
|
|
break;
|
|
} catch (...) {
|
|
}
|
|
}
|
|
}
|
|
} catch (...) {
|
|
}
|
|
} else if (root->has("image") || root->has("texture")) {
|
|
applyTileset(layer, readTilesetObject(root));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool applyFlatLayerData(TileLayer *layer, Poco::JSON::Object::Ptr root, std::string *error) {
|
|
const int mapW = layer->getMapWidth();
|
|
const int mapH = layer->getMapHeight();
|
|
const size_t need = size_t(std::max(0, mapW) * std::max(0, mapH));
|
|
if (need == 0) {
|
|
if (error) *error = "empty map size";
|
|
return false;
|
|
}
|
|
std::vector<uint32_t> data;
|
|
if (!decodeLayerData(root, need, data, error)) return false;
|
|
auto &gids = layer->tiles()->gids;
|
|
gids.assign(need, 0u);
|
|
const size_t n = std::min(need, data.size());
|
|
for (size_t i = 0; i < n; ++i) gids[i] = data[i];
|
|
return true;
|
|
}
|
|
|
|
bool applyOneLayerObject(TileLayer *layer, Poco::JSON::Object::Ptr layerObj, int mapW, int mapH,
|
|
std::string *error) {
|
|
if (!layer || !layerObj) return false;
|
|
int w = mapW, h = mapH;
|
|
if (layerObj->has("width")) w = asInt(layerObj->get("width"), w);
|
|
if (layerObj->has("height")) h = asInt(layerObj->get("height"), h);
|
|
if (w != layer->getMapWidth() || h != layer->getMapHeight()) layer->resize(w, h);
|
|
|
|
const size_t need =
|
|
size_t(std::max(0, layer->getMapWidth()) * std::max(0, layer->getMapHeight()));
|
|
std::vector<uint32_t> data;
|
|
if (!decodeLayerData(layerObj, need, data, error)) return false;
|
|
auto &gids = layer->tiles()->gids;
|
|
gids.assign(need, 0u);
|
|
const size_t n = std::min(need, data.size());
|
|
for (size_t i = 0; i < n; ++i) gids[i] = data[i];
|
|
|
|
applyLayerDraw(layer, layerObj);
|
|
return true;
|
|
}
|
|
|
|
void parseObjectGroup(Poco::JSON::Object::Ptr group, std::vector<MapObject> &out) {
|
|
if (!group || !group->has("objects")) return;
|
|
Poco::JSON::Array::Ptr arr;
|
|
try {
|
|
arr = group->getArray("objects");
|
|
} catch (...) {
|
|
return;
|
|
}
|
|
if (!arr) return;
|
|
for (size_t i = 0; i < arr->size(); ++i) {
|
|
Poco::JSON::Object::Ptr o;
|
|
try {
|
|
o = arr->getObject(i);
|
|
} catch (...) {
|
|
continue;
|
|
}
|
|
if (!o) continue;
|
|
MapObject mo;
|
|
if (o->has("name")) mo.name = asString(o->get("name"));
|
|
if (o->has("type")) mo.type = asString(o->get("type"));
|
|
else if (o->has("class")) mo.type = asString(o->get("class"));
|
|
mo.x = o->has("x") ? asFloat(o->get("x"), 0.f) : 0.f;
|
|
mo.y = o->has("y") ? asFloat(o->get("y"), 0.f) : 0.f;
|
|
mo.width = o->has("width") ? asFloat(o->get("width"), 0.f) : 0.f;
|
|
mo.height = o->has("height") ? asFloat(o->get("height"), 0.f) : 0.f;
|
|
if (o->has("gid")) mo.gid = uint32_t(asInt(o->get("gid"), 0));
|
|
out.push_back(std::move(mo));
|
|
}
|
|
}
|
|
|
|
void abandonLayers(std::vector<TileLayer *> &layers) {
|
|
for (TileLayer *layer : layers) {
|
|
if (!layer) continue;
|
|
layer->clear();
|
|
layer->setVisible(false);
|
|
}
|
|
layers.clear();
|
|
}
|
|
|
|
TilesetInfo readDefaultTileset(Poco::JSON::Object::Ptr root) {
|
|
TilesetInfo defaultTs;
|
|
if (root->has("tilesets")) {
|
|
try {
|
|
auto arr = root->getArray("tilesets");
|
|
if (arr) {
|
|
for (size_t i = 0; i < arr->size(); ++i) {
|
|
try {
|
|
auto o = arr->getObject(i);
|
|
if (o && o->has("source")) continue;
|
|
return readTilesetObject(o);
|
|
} catch (...) {
|
|
}
|
|
}
|
|
}
|
|
} catch (...) {
|
|
}
|
|
} else if (root->has("tileset") && !root->isArray("tileset")) {
|
|
try {
|
|
auto o = root->getObject("tileset");
|
|
if (o && !o->has("source")) return readTilesetObject(o);
|
|
} catch (...) {
|
|
}
|
|
} else if (root->has("image") || root->has("texture")) {
|
|
return readTilesetObject(root);
|
|
}
|
|
return defaultTs;
|
|
}
|
|
|
|
std::vector<TileLayer *> loadMapObject(Poco::JSON::Object::Ptr root, const std::string &path,
|
|
eve::filesystem::Filesystem *fs,
|
|
std::vector<MapObject> *objects, std::string *error) {
|
|
std::vector<TileLayer *> out;
|
|
if (!root) {
|
|
if (error) *error = "config root must be object";
|
|
return out;
|
|
}
|
|
|
|
int mapW = 10, mapH = 10;
|
|
float tileW = 32.f, tileH = 32.f;
|
|
if (root->has("width")) mapW = asInt(root->get("width"), mapW);
|
|
if (root->has("height")) mapH = asInt(root->get("height"), mapH);
|
|
if (root->has("tilewidth")) tileW = asFloat(root->get("tilewidth"), tileW);
|
|
else if (root->has("tileWidth")) tileW = asFloat(root->get("tileWidth"), tileW);
|
|
if (root->has("tileheight")) tileH = asFloat(root->get("tileheight"), tileH);
|
|
else if (root->has("tileHeight")) tileH = asFloat(root->get("tileHeight"), tileH);
|
|
|
|
// Validate orientation early on a temp config.
|
|
TileLayer::Config orientCheck;
|
|
if (!parseOrientation(root, &orientCheck, error)) return out;
|
|
|
|
TilesetInfo defaultTs = readDefaultTileset(root);
|
|
if (objects) objects->clear();
|
|
|
|
auto bindResource = [&](TileLayer *layer) {
|
|
auto res = layer->resource();
|
|
res->path = path;
|
|
if (!path.empty()) {
|
|
res->modtime = fileModtime(path);
|
|
if (fs) {
|
|
fs->watch(path);
|
|
if (auto *hot =
|
|
eve::ModuleManager::getInstance<eve::filesystem::HotReload>("HotReload"))
|
|
hot->bind(path, "tilemap");
|
|
}
|
|
}
|
|
if (root->has("autoReload"))
|
|
res->autoReload = asBool(root->get("autoReload"), true);
|
|
};
|
|
|
|
if (root->has("layers")) {
|
|
try {
|
|
auto arr = root->getArray("layers");
|
|
if (arr) {
|
|
int sort = 0;
|
|
for (size_t i = 0; i < arr->size(); ++i) {
|
|
Poco::JSON::Object::Ptr lo;
|
|
try {
|
|
lo = arr->getObject(i);
|
|
} catch (...) {
|
|
continue;
|
|
}
|
|
if (objects && isObjectGroup(lo)) {
|
|
parseObjectGroup(lo, *objects);
|
|
continue;
|
|
}
|
|
if (!isTileLayerObject(lo)) continue;
|
|
TileLayer *layer = TileLayer::createLayer(mapW, mapH, tileW, tileH);
|
|
if (!applyMapGlobals(layer, root, error)) {
|
|
abandonLayers(out);
|
|
layer->clear();
|
|
layer->setVisible(false);
|
|
return {};
|
|
}
|
|
applyTileset(layer, defaultTs);
|
|
if (!applyOneLayerObject(layer, lo, mapW, mapH, error)) {
|
|
abandonLayers(out);
|
|
layer->clear();
|
|
layer->setVisible(false);
|
|
return {};
|
|
}
|
|
if (!lo->has("layer") && !lo->has("id")) layer->setLayer(sort);
|
|
++sort;
|
|
bindResource(layer);
|
|
out.push_back(layer);
|
|
}
|
|
}
|
|
} catch (...) {
|
|
}
|
|
if (!out.empty()) return out;
|
|
}
|
|
|
|
TileLayer *layer = TileLayer::createLayer(mapW, mapH, tileW, tileH);
|
|
if (!applyMapGlobals(layer, root, error)) {
|
|
layer->clear();
|
|
layer->setVisible(false);
|
|
return {};
|
|
}
|
|
applyTileset(layer, defaultTs);
|
|
if (root->has("data")) {
|
|
if (!applyFlatLayerData(layer, root, error)) {
|
|
layer->clear();
|
|
layer->setVisible(false);
|
|
return {};
|
|
}
|
|
}
|
|
bindResource(layer);
|
|
out.push_back(layer);
|
|
return out;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool applyConfigDocument(TileLayer *layer, data::JsonDocument *doc) {
|
|
if (!layer || !doc || !doc->isObject()) return false;
|
|
auto root = doc->object();
|
|
if (!root) return false;
|
|
|
|
std::string err;
|
|
if (!applyMapGlobals(layer, root, &err)) return false;
|
|
|
|
if (root->has("layers")) {
|
|
try {
|
|
auto arr = root->getArray("layers");
|
|
if (arr) {
|
|
for (size_t i = 0; i < arr->size(); ++i) {
|
|
Poco::JSON::Object::Ptr lo;
|
|
try {
|
|
lo = arr->getObject(i);
|
|
} catch (...) {
|
|
continue;
|
|
}
|
|
if (!isTileLayerObject(lo)) continue;
|
|
return applyOneLayerObject(layer, lo, layer->getMapWidth(),
|
|
layer->getMapHeight(), nullptr);
|
|
}
|
|
}
|
|
} catch (...) {
|
|
}
|
|
}
|
|
|
|
if (root->has("data")) return applyFlatLayerData(layer, root, nullptr);
|
|
return true;
|
|
}
|
|
|
|
bool applyConfigText(TileLayer *layer, const std::string &json, std::string *error) {
|
|
auto *dm = eve::data::DataModule::create();
|
|
std::string err;
|
|
std::unique_ptr<data::JsonDocument> doc(dm->decodeJson(json, &err));
|
|
if (!doc) {
|
|
if (error) *error = err.empty() ? "invalid json" : err;
|
|
return false;
|
|
}
|
|
if (!doc->isObject()) {
|
|
if (error) *error = "config root must be object";
|
|
return false;
|
|
}
|
|
auto root = doc->object();
|
|
if (!root) {
|
|
if (error) *error = "config root must be object";
|
|
return false;
|
|
}
|
|
if (!applyMapGlobals(layer, root, error)) return false;
|
|
if (root->has("layers")) {
|
|
try {
|
|
auto arr = root->getArray("layers");
|
|
if (arr) {
|
|
for (size_t i = 0; i < arr->size(); ++i) {
|
|
Poco::JSON::Object::Ptr lo;
|
|
try {
|
|
lo = arr->getObject(i);
|
|
} catch (...) {
|
|
continue;
|
|
}
|
|
if (!isTileLayerObject(lo)) continue;
|
|
return applyOneLayerObject(layer, lo, layer->getMapWidth(),
|
|
layer->getMapHeight(), error);
|
|
}
|
|
}
|
|
} catch (...) {
|
|
}
|
|
}
|
|
if (root->has("data")) return applyFlatLayerData(layer, root, error);
|
|
return true;
|
|
}
|
|
|
|
bool loadConfigFile(TileLayer *layer, const std::string &path, std::string *error) {
|
|
if (!layer || path.empty()) {
|
|
if (error) *error = "empty path";
|
|
return false;
|
|
}
|
|
auto *fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>("Filesystem");
|
|
if (!fs) fs = eve::filesystem::Filesystem::create();
|
|
|
|
std::unique_ptr<eve::filesystem::FileData> data;
|
|
try {
|
|
data.reset(fs->read(path));
|
|
} catch (...) {
|
|
if (error) *error = "read failed: " + path;
|
|
return false;
|
|
}
|
|
if (!data || data->getSize() == 0) {
|
|
if (error) *error = "empty file: " + path;
|
|
return false;
|
|
}
|
|
|
|
std::string text(static_cast<const char *>(data->getData()), data->getSize());
|
|
if (!applyConfigText(layer, text, error)) return false;
|
|
|
|
auto res = layer->resource();
|
|
res->path = path;
|
|
res->modtime = fileModtime(path);
|
|
fs->watch(path);
|
|
if (auto *hot = eve::ModuleManager::getInstance<eve::filesystem::HotReload>("HotReload"))
|
|
hot->bind(path, "tilemap");
|
|
return true;
|
|
}
|
|
|
|
bool reloadConfigFile(TileLayer *layer, std::string *error) {
|
|
if (!layer) return false;
|
|
const std::string &path = layer->resource()->path;
|
|
if (path.empty()) {
|
|
if (error) *error = "no config path";
|
|
return false;
|
|
}
|
|
return loadConfigFile(layer, path, error);
|
|
}
|
|
|
|
std::vector<TileLayer *> loadMapText(const std::string &json, std::vector<MapObject> *objects,
|
|
std::string *error) {
|
|
auto *dm = eve::data::DataModule::create();
|
|
std::string err;
|
|
std::unique_ptr<data::JsonDocument> doc(dm->decodeJson(json, &err));
|
|
if (!doc || !doc->isObject()) {
|
|
if (error) *error = err.empty() ? "invalid json" : err;
|
|
return {};
|
|
}
|
|
return loadMapObject(doc->object(), {}, nullptr, objects, error);
|
|
}
|
|
|
|
std::vector<TileLayer *> loadMapFile(const std::string &path, std::vector<MapObject> *objects,
|
|
std::string *error) {
|
|
if (path.empty()) {
|
|
if (error) *error = "empty path";
|
|
return {};
|
|
}
|
|
|
|
auto *fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>("Filesystem");
|
|
if (!fs) fs = eve::filesystem::Filesystem::create();
|
|
|
|
std::unique_ptr<eve::filesystem::FileData> data;
|
|
try {
|
|
data.reset(fs->read(path));
|
|
} catch (...) {
|
|
if (error) *error = "read failed: " + path;
|
|
return {};
|
|
}
|
|
if (!data || data->getSize() == 0) {
|
|
if (error) *error = "empty file: " + path;
|
|
return {};
|
|
}
|
|
|
|
std::string text(static_cast<const char *>(data->getData()), data->getSize());
|
|
auto *dm = eve::data::DataModule::create();
|
|
std::string err;
|
|
std::unique_ptr<data::JsonDocument> doc(dm->decodeJson(text, &err));
|
|
if (!doc || !doc->isObject()) {
|
|
if (error) *error = err.empty() ? "invalid json" : err;
|
|
return {};
|
|
}
|
|
return loadMapObject(doc->object(), path, fs, objects, error);
|
|
}
|
|
|
|
std::vector<TileLayer *> loadMapFile(const std::string &path, std::string *error) {
|
|
return loadMapFile(path, nullptr, error);
|
|
}
|
|
|
|
} // namespace eve::map
|