mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 00:53:52 +00:00
feat(editor): object save/load JSON, working duplicate, export objects
- Object placer save/load: objects.json persists placed M2/WMO objects across sessions (path, position, rotation, scale, type) - Fixed Duplicate button in Object panel: now actually creates a copy with correct path/type/scale instead of being a no-op stub - Export Zone now saves objects.json alongside ADT/WDT/creatures/manifest - Object JSON loader parses all fields for full round-trip
This commit is contained in:
parent
8341fb6dc9
commit
8c9407e0f5
4 changed files with 104 additions and 5 deletions
|
|
@ -597,6 +597,12 @@ void EditorApp::exportZone(const std::string& outputDir) {
|
|||
npcSpawner_.saveToFile(npcPath);
|
||||
}
|
||||
|
||||
// Save placed objects
|
||||
if (objectPlacer_.objectCount() > 0) {
|
||||
std::string objPath = base + "/objects.json";
|
||||
objectPlacer_.saveToFile(objPath);
|
||||
}
|
||||
|
||||
// Write zone manifest (for client loading)
|
||||
ZoneManifest manifest;
|
||||
manifest.mapName = loadedMap_;
|
||||
|
|
|
|||
|
|
@ -565,12 +565,17 @@ void EditorUI::renderObjectPanel(EditorApp& app) {
|
|||
ImGui::SameLine();
|
||||
if (ImGui::Button("Delete", ImVec2(100, 0))) placer.deleteSelected();
|
||||
if (ImGui::Button("Duplicate", ImVec2(100, 0))) {
|
||||
PlacedObject copy = *sel;
|
||||
copy.uniqueId = 0;
|
||||
copy.position += glm::vec3(5.0f, 5.0f, 0.0f);
|
||||
copy.selected = false;
|
||||
std::string dupPath = sel->path;
|
||||
glm::vec3 dupPos = sel->position + glm::vec3(10.0f, 10.0f, 0.0f);
|
||||
glm::vec3 dupRot = sel->rotation;
|
||||
float dupScale = sel->scale;
|
||||
auto dupType = sel->type;
|
||||
placer.clearSelection();
|
||||
// Can't easily push from here, but move slightly signals intent
|
||||
placer.setActivePath(dupPath, dupType);
|
||||
placer.setPlacementScale(dupScale);
|
||||
placer.setPlacementRotationY(dupRot.y);
|
||||
placer.placeObject(dupPos);
|
||||
app.markObjectsDirty();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Deselect", ImVec2(100, 0)))
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
#include "core/logger.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <random>
|
||||
|
||||
namespace wowee {
|
||||
|
|
@ -143,6 +145,88 @@ void ObjectPlacer::undoLastPlace() {
|
|||
}
|
||||
}
|
||||
|
||||
bool ObjectPlacer::saveToFile(const std::string& path) const {
|
||||
std::filesystem::create_directories(std::filesystem::path(path).parent_path());
|
||||
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";
|
||||
LOG_INFO("Objects saved: ", path, " (", objects_.size(), " objects)");
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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)));
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
void ObjectPlacer::syncToTerrain() {
|
||||
if (!terrain_) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ public:
|
|||
// Sync placed objects back to ADTTerrain structs
|
||||
void syncToTerrain();
|
||||
|
||||
// Save/load placed objects to JSON
|
||||
bool saveToFile(const std::string& path) const;
|
||||
bool loadFromFile(const std::string& path);
|
||||
|
||||
const std::vector<PlacedObject>& getObjects() const { return objects_; }
|
||||
size_t objectCount() const { return objects_.size(); }
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue