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:
Kelsi 2026-05-05 13:10:07 -07:00
parent 815787933b
commit 08500384e2
5 changed files with 223 additions and 268 deletions

View file

@ -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() {