mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 00:53:52 +00:00
refactor: migrate all remaining JSON to nlohmann/json
- npc_spawner: save/load with proper JSON (25+ fields + patrol paths) - zone_manifest: save/load with nlohmann (was naive string concat/parse) - load now parses all fields: mapId, baseHeight, tiles, hasCreatures - custom_zone_discovery: parse zone.json with nlohmann, extract mapId and tile coordinates (was only reading name/author/description) - object_placer: save/load with nlohmann (was substring parsing) - editor_app: stats.json export uses nlohmann, score display now /6 Zero naive JSON string concatenation remains in the editor codebase.
This commit is contained in:
parent
815787933b
commit
08500384e2
5 changed files with 223 additions and 268 deletions
|
|
@ -9,6 +9,7 @@
|
|||
#include "pipeline/wowee_building.hpp"
|
||||
#include "pipeline/wmo_loader.hpp"
|
||||
#include "core/coordinates.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "rendering/vk_context.hpp"
|
||||
#include "pipeline/adt_loader.hpp"
|
||||
#include "pipeline/terrain_mesh.hpp"
|
||||
|
|
@ -895,26 +896,24 @@ void EditorApp::exportZone(const std::string& outputDir) {
|
|||
int score = validation.openFormatScore();
|
||||
// Write zone statistics JSON
|
||||
{
|
||||
nlohmann::json sj;
|
||||
sj["map"] = loadedMap_;
|
||||
sj["tile"] = {loadedTileX_, loadedTileY_};
|
||||
sj["objects"] = objectPlacer_.objectCount();
|
||||
sj["npcs"] = npcSpawner_.spawnCount();
|
||||
sj["quests"] = questEditor_.questCount();
|
||||
sj["textures"] = usedTextures.size();
|
||||
sj["openFormatScore"] = score;
|
||||
sj["formats"] = validation.summary();
|
||||
std::ofstream stats(base + "/stats.json");
|
||||
if (stats) {
|
||||
stats << "{\n";
|
||||
stats << " \"map\": \"" << loadedMap_ << "\",\n";
|
||||
stats << " \"tile\": [" << loadedTileX_ << "," << loadedTileY_ << "],\n";
|
||||
stats << " \"objects\": " << objectPlacer_.objectCount() << ",\n";
|
||||
stats << " \"npcs\": " << npcSpawner_.spawnCount() << ",\n";
|
||||
stats << " \"quests\": " << questEditor_.questCount() << ",\n";
|
||||
stats << " \"textures\": " << usedTextures.size() << ",\n";
|
||||
stats << " \"openFormatScore\": " << score << ",\n";
|
||||
stats << " \"formats\": \"" << validation.summary() << "\"\n";
|
||||
stats << "}\n";
|
||||
}
|
||||
if (stats) stats << sj.dump(2) << "\n";
|
||||
}
|
||||
|
||||
showToast("Exported " + std::to_string(fileCount) + " files (" +
|
||||
std::to_string(score) + "/5 open format)");
|
||||
std::to_string(score) + "/6 open format)");
|
||||
LOG_INFO("=== Zone Export Summary ===");
|
||||
LOG_INFO(" Output: ", base);
|
||||
LOG_INFO(" Open format score: ", score, "/5");
|
||||
LOG_INFO(" Open format score: ", score, "/6");
|
||||
LOG_INFO(" Formats: ", validation.summary());
|
||||
LOG_INFO(" Terrain: WOT/WHM + heightmap/normals PNG");
|
||||
LOG_INFO(" Textures: ", usedTextures.size(), " BLP→PNG");
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "npc_spawner.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
|
|
@ -58,46 +59,44 @@ bool NpcSpawner::saveToFile(const std::string& path) const {
|
|||
auto dir = std::filesystem::path(path).parent_path();
|
||||
if (!dir.empty()) std::filesystem::create_directories(dir);
|
||||
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& s : spawns_) {
|
||||
nlohmann::json js;
|
||||
js["name"] = s.name;
|
||||
js["model"] = s.modelPath;
|
||||
js["displayId"] = s.displayId;
|
||||
js["position"] = {s.position.x, s.position.y, s.position.z};
|
||||
js["orientation"] = s.orientation;
|
||||
js["scale"] = s.scale;
|
||||
js["level"] = s.level;
|
||||
js["health"] = s.health;
|
||||
js["mana"] = s.mana;
|
||||
js["minDamage"] = s.minDamage;
|
||||
js["maxDamage"] = s.maxDamage;
|
||||
js["armor"] = s.armor;
|
||||
js["faction"] = s.faction;
|
||||
js["behavior"] = static_cast<int>(s.behavior);
|
||||
js["wanderRadius"] = s.wanderRadius;
|
||||
js["aggroRadius"] = s.aggroRadius;
|
||||
js["leashRadius"] = s.leashRadius;
|
||||
js["respawnTimeMs"] = s.respawnTimeMs;
|
||||
js["hostile"] = s.hostile;
|
||||
js["questgiver"] = s.questgiver;
|
||||
js["vendor"] = s.vendor;
|
||||
js["flightmaster"] = s.flightmaster;
|
||||
js["innkeeper"] = s.innkeeper;
|
||||
|
||||
nlohmann::json patrol = nlohmann::json::array();
|
||||
for (const auto& p : s.patrolPath) {
|
||||
patrol.push_back({p.position.x, p.position.y, p.position.z, p.waitTimeMs});
|
||||
}
|
||||
js["patrol"] = patrol;
|
||||
arr.push_back(js);
|
||||
}
|
||||
|
||||
std::ofstream f(path);
|
||||
if (!f) { LOG_ERROR("Failed to write NPC file: ", path); return false; }
|
||||
|
||||
f << "[\n";
|
||||
for (size_t i = 0; i < spawns_.size(); i++) {
|
||||
const auto& s = spawns_[i];
|
||||
f << " {\n";
|
||||
f << " \"name\": \"" << s.name << "\",\n";
|
||||
f << " \"model\": \"" << s.modelPath << "\",\n";
|
||||
f << " \"displayId\": " << s.displayId << ",\n";
|
||||
f << " \"position\": [" << s.position.x << "," << s.position.y << "," << s.position.z << "],\n";
|
||||
f << " \"orientation\": " << s.orientation << ",\n";
|
||||
f << " \"scale\": " << s.scale << ",\n";
|
||||
f << " \"level\": " << s.level << ",\n";
|
||||
f << " \"health\": " << s.health << ",\n";
|
||||
f << " \"mana\": " << s.mana << ",\n";
|
||||
f << " \"minDamage\": " << s.minDamage << ",\n";
|
||||
f << " \"maxDamage\": " << s.maxDamage << ",\n";
|
||||
f << " \"armor\": " << s.armor << ",\n";
|
||||
f << " \"faction\": " << s.faction << ",\n";
|
||||
f << " \"behavior\": " << static_cast<int>(s.behavior) << ",\n";
|
||||
f << " \"wanderRadius\": " << s.wanderRadius << ",\n";
|
||||
f << " \"aggroRadius\": " << s.aggroRadius << ",\n";
|
||||
f << " \"leashRadius\": " << s.leashRadius << ",\n";
|
||||
f << " \"respawnTimeMs\": " << s.respawnTimeMs << ",\n";
|
||||
f << " \"hostile\": " << (s.hostile ? "true" : "false") << ",\n";
|
||||
f << " \"questgiver\": " << (s.questgiver ? "true" : "false") << ",\n";
|
||||
f << " \"vendor\": " << (s.vendor ? "true" : "false") << ",\n";
|
||||
f << " \"flightmaster\": " << (s.flightmaster ? "true" : "false") << ",\n";
|
||||
f << " \"innkeeper\": " << (s.innkeeper ? "true" : "false") << ",\n";
|
||||
f << " \"patrol\": [";
|
||||
for (size_t p = 0; p < s.patrolPath.size(); p++) {
|
||||
f << "[" << s.patrolPath[p].position.x << "," << s.patrolPath[p].position.y
|
||||
<< "," << s.patrolPath[p].position.z << "," << s.patrolPath[p].waitTimeMs << "]";
|
||||
if (p + 1 < s.patrolPath.size()) f << ",";
|
||||
}
|
||||
f << "]\n";
|
||||
f << " }" << (i + 1 < spawns_.size() ? "," : "") << "\n";
|
||||
}
|
||||
f << "]\n";
|
||||
f << arr.dump(2) << "\n";
|
||||
|
||||
LOG_INFO("NPC spawns saved: ", path, " (", spawns_.size(), " creatures)");
|
||||
return true;
|
||||
|
|
@ -124,98 +123,68 @@ bool NpcSpawner::loadFromFile(const std::string& path) {
|
|||
std::ifstream f(path);
|
||||
if (!f) { LOG_ERROR("Failed to open NPC file: ", path); return false; }
|
||||
|
||||
std::string content((std::istreambuf_iterator<char>(f)),
|
||||
std::istreambuf_iterator<char>());
|
||||
try {
|
||||
auto arr = nlohmann::json::parse(f);
|
||||
if (!arr.is_array()) return false;
|
||||
|
||||
// Minimal JSON parser — extract fields from our known format
|
||||
spawns_.clear();
|
||||
selectedIdx_ = -1;
|
||||
spawns_.clear();
|
||||
selectedIdx_ = -1;
|
||||
|
||||
auto findStr = [&](const std::string& block, const std::string& key) -> std::string {
|
||||
auto pos = block.find("\"" + key + "\"");
|
||||
if (pos == std::string::npos) return "";
|
||||
pos = block.find(':', pos);
|
||||
if (pos == std::string::npos) return "";
|
||||
pos = block.find('"', pos + 1);
|
||||
if (pos == std::string::npos) return "";
|
||||
auto end = block.find('"', pos + 1);
|
||||
if (end == std::string::npos) return "";
|
||||
return block.substr(pos + 1, end - pos - 1);
|
||||
};
|
||||
for (const auto& js : arr) {
|
||||
CreatureSpawn s;
|
||||
s.name = js.value("name", "");
|
||||
s.modelPath = js.value("model", "");
|
||||
s.displayId = js.value("displayId", 0u);
|
||||
s.orientation = js.value("orientation", 0.0f);
|
||||
s.scale = js.value("scale", 1.0f);
|
||||
if (s.scale < 0.1f) s.scale = 1.0f;
|
||||
s.level = js.value("level", 1u);
|
||||
s.health = js.value("health", 100u);
|
||||
s.mana = js.value("mana", 0u);
|
||||
s.minDamage = js.value("minDamage", 5u);
|
||||
s.maxDamage = js.value("maxDamage", 10u);
|
||||
s.armor = js.value("armor", 0u);
|
||||
s.faction = js.value("faction", 0u);
|
||||
s.behavior = static_cast<CreatureBehavior>(js.value("behavior", 0));
|
||||
s.wanderRadius = js.value("wanderRadius", 0.0f);
|
||||
s.aggroRadius = js.value("aggroRadius", 15.0f);
|
||||
s.leashRadius = js.value("leashRadius", 40.0f);
|
||||
s.respawnTimeMs = js.value("respawnTimeMs", 60000u);
|
||||
s.hostile = js.value("hostile", false);
|
||||
s.questgiver = js.value("questgiver", false);
|
||||
s.vendor = js.value("vendor", false);
|
||||
s.flightmaster = js.value("flightmaster", false);
|
||||
s.innkeeper = js.value("innkeeper", false);
|
||||
|
||||
auto findNum = [&](const std::string& block, const std::string& key) -> float {
|
||||
auto pos = block.find("\"" + key + "\"");
|
||||
if (pos == std::string::npos) return 0;
|
||||
pos = block.find(':', pos);
|
||||
if (pos == std::string::npos) return 0;
|
||||
return std::stof(block.substr(pos + 1));
|
||||
};
|
||||
if (js.contains("position") && js["position"].is_array() && js["position"].size() >= 3) {
|
||||
s.position = glm::vec3(js["position"][0].get<float>(),
|
||||
js["position"][1].get<float>(),
|
||||
js["position"][2].get<float>());
|
||||
}
|
||||
|
||||
auto findBool = [&](const std::string& block, const std::string& key) -> bool {
|
||||
auto pos = block.find("\"" + key + "\"");
|
||||
if (pos == std::string::npos) return false;
|
||||
return block.find("true", pos) < block.find('\n', pos);
|
||||
};
|
||||
|
||||
// Split by object boundaries
|
||||
size_t start = 0;
|
||||
while ((start = content.find('{', start)) != std::string::npos) {
|
||||
auto end = content.find('}', start);
|
||||
if (end == std::string::npos) break;
|
||||
std::string block = content.substr(start, end - start + 1);
|
||||
|
||||
CreatureSpawn s;
|
||||
s.name = findStr(block, "name");
|
||||
s.modelPath = findStr(block, "model");
|
||||
s.displayId = static_cast<uint32_t>(findNum(block, "displayId"));
|
||||
s.orientation = findNum(block, "orientation");
|
||||
s.scale = findNum(block, "scale");
|
||||
if (s.scale < 0.1f) s.scale = 1.0f;
|
||||
s.level = static_cast<uint32_t>(std::max(1.0f, findNum(block, "level")));
|
||||
s.health = static_cast<uint32_t>(std::max(1.0f, findNum(block, "health")));
|
||||
s.mana = static_cast<uint32_t>(findNum(block, "mana"));
|
||||
s.minDamage = static_cast<uint32_t>(findNum(block, "minDamage"));
|
||||
s.maxDamage = static_cast<uint32_t>(findNum(block, "maxDamage"));
|
||||
s.armor = static_cast<uint32_t>(findNum(block, "armor"));
|
||||
s.faction = static_cast<uint32_t>(findNum(block, "faction"));
|
||||
s.behavior = static_cast<CreatureBehavior>(static_cast<int>(findNum(block, "behavior")));
|
||||
s.wanderRadius = findNum(block, "wanderRadius");
|
||||
s.aggroRadius = findNum(block, "aggroRadius");
|
||||
s.leashRadius = findNum(block, "leashRadius");
|
||||
s.respawnTimeMs = static_cast<uint32_t>(findNum(block, "respawnTimeMs"));
|
||||
s.hostile = findBool(block, "hostile");
|
||||
s.questgiver = findBool(block, "questgiver");
|
||||
s.vendor = findBool(block, "vendor");
|
||||
s.flightmaster = findBool(block, "flightmaster");
|
||||
s.innkeeper = findBool(block, "innkeeper");
|
||||
|
||||
// Parse position array
|
||||
auto posStart = block.find("\"position\"");
|
||||
if (posStart != std::string::npos) {
|
||||
auto bk = block.find('[', posStart);
|
||||
if (bk != std::string::npos) {
|
||||
float vals[3] = {};
|
||||
int vi = 0;
|
||||
auto p = bk + 1;
|
||||
while (vi < 3 && p < block.size()) {
|
||||
vals[vi++] = std::stof(block.substr(p));
|
||||
p = block.find(',', p);
|
||||
if (p == std::string::npos) break;
|
||||
p++;
|
||||
if (js.contains("patrol") && js["patrol"].is_array()) {
|
||||
for (const auto& pt : js["patrol"]) {
|
||||
if (pt.is_array() && pt.size() >= 4) {
|
||||
PatrolPoint pp;
|
||||
pp.position = glm::vec3(pt[0].get<float>(), pt[1].get<float>(), pt[2].get<float>());
|
||||
pp.waitTimeMs = pt[3].get<uint32_t>();
|
||||
s.patrolPath.push_back(pp);
|
||||
}
|
||||
}
|
||||
s.position = glm::vec3(vals[0], vals[1], vals[2]);
|
||||
}
|
||||
|
||||
if (!s.name.empty()) {
|
||||
s.id = nextId();
|
||||
spawns_.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (!s.name.empty()) {
|
||||
s.id = nextId();
|
||||
spawns_.push_back(s);
|
||||
}
|
||||
start = end + 1;
|
||||
LOG_INFO("NPC spawns loaded: ", path, " (", spawns_.size(), " creatures)");
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Failed to parse NPC file: ", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("NPC spawns loaded: ", path, " (", spawns_.size(), " creatures)");
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "object_placer.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
|
|
@ -147,19 +148,21 @@ void ObjectPlacer::undoLastPlace() {
|
|||
|
||||
bool ObjectPlacer::saveToFile(const std::string& path) const {
|
||||
std::filesystem::create_directories(std::filesystem::path(path).parent_path());
|
||||
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& o : objects_) {
|
||||
arr.push_back({
|
||||
{"type", static_cast<int>(o.type)},
|
||||
{"path", o.path},
|
||||
{"pos", {o.position.x, o.position.y, o.position.z}},
|
||||
{"rot", {o.rotation.x, o.rotation.y, o.rotation.z}},
|
||||
{"scale", o.scale}
|
||||
});
|
||||
}
|
||||
|
||||
std::ofstream f(path);
|
||||
if (!f) return false;
|
||||
f << "[\n";
|
||||
for (size_t i = 0; i < objects_.size(); i++) {
|
||||
const auto& o = objects_[i];
|
||||
f << " {\"type\":" << static_cast<int>(o.type)
|
||||
<< ",\"path\":\"" << o.path << "\""
|
||||
<< ",\"pos\":[" << o.position.x << "," << o.position.y << "," << o.position.z << "]"
|
||||
<< ",\"rot\":[" << o.rotation.x << "," << o.rotation.y << "," << o.rotation.z << "]"
|
||||
<< ",\"scale\":" << o.scale
|
||||
<< "}" << (i + 1 < objects_.size() ? "," : "") << "\n";
|
||||
}
|
||||
f << "]\n";
|
||||
f << arr.dump(2) << "\n";
|
||||
LOG_INFO("Objects saved: ", path, " (", objects_.size(), " objects)");
|
||||
return true;
|
||||
}
|
||||
|
|
@ -167,64 +170,44 @@ bool ObjectPlacer::saveToFile(const std::string& path) const {
|
|||
bool ObjectPlacer::loadFromFile(const std::string& path) {
|
||||
std::ifstream f(path);
|
||||
if (!f) return false;
|
||||
std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
|
||||
objects_.clear();
|
||||
undoStack_.clear();
|
||||
selectedIdx_ = -1;
|
||||
try {
|
||||
auto arr = nlohmann::json::parse(f);
|
||||
if (!arr.is_array()) return false;
|
||||
|
||||
size_t start = 0;
|
||||
while ((start = content.find('{', start)) != std::string::npos) {
|
||||
auto end = content.find('}', start);
|
||||
if (end == std::string::npos) break;
|
||||
std::string block = content.substr(start, end - start + 1);
|
||||
objects_.clear();
|
||||
undoStack_.clear();
|
||||
selectedIdx_ = -1;
|
||||
|
||||
PlacedObject obj;
|
||||
// Parse type
|
||||
auto tp = block.find("\"type\":");
|
||||
if (tp != std::string::npos) obj.type = static_cast<PlaceableType>(std::stoi(block.substr(tp + 7)));
|
||||
for (const auto& jo : arr) {
|
||||
PlacedObject obj;
|
||||
obj.type = static_cast<PlaceableType>(jo.value("type", 0));
|
||||
obj.path = jo.value("path", "");
|
||||
obj.scale = jo.value("scale", 1.0f);
|
||||
|
||||
// Parse path
|
||||
auto pp = block.find("\"path\":\"");
|
||||
if (pp != std::string::npos) {
|
||||
pp += 8;
|
||||
auto pe = block.find('"', pp);
|
||||
if (pe != std::string::npos) obj.path = block.substr(pp, pe - pp);
|
||||
if (jo.contains("pos") && jo["pos"].is_array() && jo["pos"].size() >= 3) {
|
||||
obj.position = glm::vec3(jo["pos"][0].get<float>(),
|
||||
jo["pos"][1].get<float>(),
|
||||
jo["pos"][2].get<float>());
|
||||
}
|
||||
if (jo.contains("rot") && jo["rot"].is_array() && jo["rot"].size() >= 3) {
|
||||
obj.rotation = glm::vec3(jo["rot"][0].get<float>(),
|
||||
jo["rot"][1].get<float>(),
|
||||
jo["rot"][2].get<float>());
|
||||
}
|
||||
|
||||
if (!obj.path.empty()) {
|
||||
obj.uniqueId = nextUniqueId();
|
||||
objects_.push_back(obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse pos array
|
||||
auto posP = block.find("\"pos\":[");
|
||||
if (posP != std::string::npos) {
|
||||
posP += 7;
|
||||
obj.position.x = std::stof(block.substr(posP));
|
||||
posP = block.find(',', posP) + 1;
|
||||
obj.position.y = std::stof(block.substr(posP));
|
||||
posP = block.find(',', posP) + 1;
|
||||
obj.position.z = std::stof(block.substr(posP));
|
||||
}
|
||||
|
||||
// Parse rot array
|
||||
auto rotP = block.find("\"rot\":[");
|
||||
if (rotP != std::string::npos) {
|
||||
rotP += 7;
|
||||
obj.rotation.x = std::stof(block.substr(rotP));
|
||||
rotP = block.find(',', rotP) + 1;
|
||||
obj.rotation.y = std::stof(block.substr(rotP));
|
||||
rotP = block.find(',', rotP) + 1;
|
||||
obj.rotation.z = std::stof(block.substr(rotP));
|
||||
}
|
||||
|
||||
auto scP = block.find("\"scale\":");
|
||||
if (scP != std::string::npos) obj.scale = std::stof(block.substr(scP + 8));
|
||||
|
||||
if (!obj.path.empty()) {
|
||||
obj.uniqueId = nextUniqueId();
|
||||
objects_.push_back(obj);
|
||||
}
|
||||
start = end + 1;
|
||||
LOG_INFO("Objects loaded: ", path, " (", objects_.size(), " objects)");
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Failed to parse objects file: ", e.what());
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("Objects loaded: ", path, " (", objects_.size(), " objects)");
|
||||
return true;
|
||||
}
|
||||
|
||||
void ObjectPlacer::syncToTerrain() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "zone_manifest.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <chrono>
|
||||
|
|
@ -12,44 +13,40 @@ bool ZoneManifest::save(const std::string& path) const {
|
|||
auto dir = std::filesystem::path(path).parent_path();
|
||||
if (!dir.empty()) std::filesystem::create_directories(dir);
|
||||
|
||||
std::ofstream f(path);
|
||||
if (!f) { LOG_ERROR("Failed to write zone manifest: ", path); return false; }
|
||||
nlohmann::json j;
|
||||
j["mapName"] = mapName;
|
||||
j["displayName"] = displayName;
|
||||
j["mapId"] = mapId;
|
||||
j["biome"] = biome;
|
||||
j["baseHeight"] = baseHeight;
|
||||
j["hasCreatures"] = hasCreatures;
|
||||
j["description"] = description;
|
||||
j["editorVersion"] = "1.0.0";
|
||||
|
||||
f << "{\n";
|
||||
f << " \"mapName\": \"" << mapName << "\",\n";
|
||||
f << " \"displayName\": \"" << displayName << "\",\n";
|
||||
f << " \"mapId\": " << mapId << ",\n";
|
||||
f << " \"biome\": \"" << biome << "\",\n";
|
||||
f << " \"baseHeight\": " << baseHeight << ",\n";
|
||||
f << " \"hasCreatures\": " << (hasCreatures ? "true" : "false") << ",\n";
|
||||
f << " \"description\": \"" << description << "\",\n";
|
||||
f << " \"editorVersion\": \"0.9.0\",\n";
|
||||
// Add export timestamp
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time = std::chrono::system_clock::to_time_t(now);
|
||||
char timeBuf[32];
|
||||
std::strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%dT%H:%M:%S", std::localtime(&time));
|
||||
f << " \"exportTime\": \"" << timeBuf << "\",\n";
|
||||
j["exportTime"] = timeBuf;
|
||||
}
|
||||
f << " \"tiles\": [";
|
||||
for (size_t i = 0; i < tiles.size(); i++) {
|
||||
f << "[" << tiles[i].first << "," << tiles[i].second << "]";
|
||||
if (i + 1 < tiles.size()) f << ",";
|
||||
|
||||
nlohmann::json tilesArr = nlohmann::json::array();
|
||||
for (const auto& t : tiles) tilesArr.push_back({t.first, t.second});
|
||||
j["tiles"] = tilesArr;
|
||||
|
||||
nlohmann::json files;
|
||||
files["wdt"] = mapName + ".wdt";
|
||||
for (const auto& t : tiles) {
|
||||
std::string key = "adt_" + std::to_string(t.first) + "_" + std::to_string(t.second);
|
||||
files[key] = mapName + "_" + std::to_string(t.first) + "_" + std::to_string(t.second) + ".adt";
|
||||
}
|
||||
f << "],\n";
|
||||
f << " \"files\": {\n";
|
||||
f << " \"wdt\": \"" << mapName << ".wdt\",\n";
|
||||
for (size_t i = 0; i < tiles.size(); i++) {
|
||||
f << " \"adt_" << tiles[i].first << "_" << tiles[i].second << "\": \""
|
||||
<< mapName << "_" << tiles[i].first << "_" << tiles[i].second << ".adt\"";
|
||||
if (i + 1 < tiles.size() || hasCreatures) f << ",";
|
||||
f << "\n";
|
||||
}
|
||||
if (hasCreatures)
|
||||
f << " \"creatures\": \"creatures.json\"\n";
|
||||
f << " }\n";
|
||||
f << "}\n";
|
||||
if (hasCreatures) files["creatures"] = "creatures.json";
|
||||
j["files"] = files;
|
||||
|
||||
std::ofstream f(path);
|
||||
if (!f) { LOG_ERROR("Failed to write zone manifest: ", path); return false; }
|
||||
f << j.dump(2) << "\n";
|
||||
|
||||
LOG_INFO("Zone manifest saved: ", path);
|
||||
return true;
|
||||
|
|
@ -58,30 +55,32 @@ bool ZoneManifest::save(const std::string& path) const {
|
|||
bool ZoneManifest::load(const std::string& path) {
|
||||
std::ifstream f(path);
|
||||
if (!f) return false;
|
||||
std::string content((std::istreambuf_iterator<char>(f)),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
auto findStr = [&](const std::string& key) -> std::string {
|
||||
auto pos = content.find("\"" + key + "\"");
|
||||
if (pos == std::string::npos) return "";
|
||||
pos = content.find('"', content.find(':', pos) + 1);
|
||||
if (pos == std::string::npos) return "";
|
||||
auto end = content.find('"', pos + 1);
|
||||
return content.substr(pos + 1, end - pos - 1);
|
||||
};
|
||||
try {
|
||||
auto j = nlohmann::json::parse(f);
|
||||
|
||||
mapName = findStr("mapName");
|
||||
displayName = findStr("displayName");
|
||||
biome = findStr("biome");
|
||||
description = findStr("description");
|
||||
mapName = j.value("mapName", "");
|
||||
if (mapName.empty()) mapName = j.value("name", "");
|
||||
displayName = j.value("displayName", mapName);
|
||||
biome = j.value("biome", "");
|
||||
description = j.value("description", "");
|
||||
mapId = j.value("mapId", 9000u);
|
||||
baseHeight = j.value("baseHeight", 100.0f);
|
||||
hasCreatures = j.value("hasCreatures", false);
|
||||
|
||||
auto numPos = content.find("\"mapId\"");
|
||||
if (numPos != std::string::npos) {
|
||||
numPos = content.find(':', numPos);
|
||||
mapId = static_cast<uint32_t>(std::stoi(content.substr(numPos + 1)));
|
||||
tiles.clear();
|
||||
if (j.contains("tiles") && j["tiles"].is_array()) {
|
||||
for (const auto& t : j["tiles"]) {
|
||||
if (t.is_array() && t.size() >= 2)
|
||||
tiles.push_back({t[0].get<int>(), t[1].get<int>()});
|
||||
}
|
||||
}
|
||||
|
||||
return !mapName.empty();
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Failed to parse zone manifest: ", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
return !mapName.empty();
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue