diff --git a/tools/editor/content_pack.cpp b/tools/editor/content_pack.cpp index 4fb10141..44ac67c0 100644 --- a/tools/editor/content_pack.cpp +++ b/tools/editor/content_pack.cpp @@ -159,6 +159,26 @@ bool ContentPacker::readInfo(const std::string& wcpPath, ContentPackInfo& info) info.version = j.value("version", ""); info.format = j.value("format", ""); info.mapId = j.value("mapId", 9000u); + info.files.clear(); + if (j.contains("files") && j["files"].is_array()) { + for (const auto& jf : j["files"]) { + ContentPackInfo::FileEntry fe; + fe.path = jf.value("path", ""); + fe.size = jf.value("size", 0ULL); + auto dot = fe.path.rfind('.'); + if (dot != std::string::npos) { + std::string ext = fe.path.substr(dot); + if (ext == ".wot" || ext == ".whm") fe.category = "terrain"; + else if (ext == ".wom") fe.category = "model"; + else if (ext == ".wob") fe.category = "building"; + else if (ext == ".png") fe.category = "texture"; + else if (ext == ".json") fe.category = "data"; + else if (ext == ".adt" || ext == ".wdt") fe.category = "legacy"; + else fe.category = "other"; + } + info.files.push_back(fe); + } + } } catch (...) { return false; } diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index dd2b80fc..f1df91cd 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -1040,6 +1040,13 @@ void EditorApp::exportZone(const std::string& outputDir) { sj["textures"] = usedTextures.size(); sj["openFormatScore"] = score; sj["formats"] = validation.summary(); + sj["tiles"] = static_cast(manifest.tiles.size()); + auto* tr = viewport_.getTerrainRenderer(); + if (tr) { + sj["chunks"] = tr->getChunkCount(); + sj["triangles"] = tr->getTriangleCount(); + } + sj["editorVersion"] = "1.0.0"; std::ofstream stats(base + "/stats.json"); if (stats) stats << sj.dump(2) << "\n"; } diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index 0e1267e0..02a58f50 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -236,8 +236,11 @@ void EditorUI::renderMenuBar(EditorApp& app) { auto lastU = base.rfind('_'); auto prevU = base.rfind('_', lastU - 1); if (lastU != std::string::npos && prevU != std::string::npos) { - int tx = std::stoi(base.substr(prevU + 1, lastU - prevU - 1)); - int ty = std::stoi(base.substr(lastU + 1)); + int tx = 0, ty = 0; + try { + tx = std::stoi(base.substr(prevU + 1, lastU - prevU - 1)); + ty = std::stoi(base.substr(lastU + 1)); + } catch (...) { break; } app.createNewTerrain(z.name, tx, ty, 100.0f, Biome::Grassland); // Load the WOT/WHM data std::string wotBase = entry.path().parent_path().string() + "/" + base; @@ -289,16 +292,37 @@ void EditorUI::renderMenuBar(EditorApp& app) { app.showToast("Import failed — check path"); } } - if (ImGui::MenuItem("Inspect Pack Info")) { + if (ImGui::BeginMenu("Inspect Pack Info")) { editor::ContentPackInfo info; if (editor::ContentPacker::readInfo(wcpImportPath, info)) { - std::string msg = info.name + " v" + info.version; - if (!info.author.empty()) msg += " by " + info.author; - msg += " (" + info.format + ")"; - app.showToast(msg); + ImGui::TextColored(ImVec4(1, 0.9f, 0.3f, 1), "%s v%s", + info.name.c_str(), info.version.c_str()); + if (!info.author.empty()) + ImGui::Text("By: %s", info.author.c_str()); + if (!info.description.empty()) + ImGui::TextWrapped("%s", info.description.c_str()); + ImGui::Text("Map ID: %u, Files: %zu", info.mapId, info.files.size()); + if (!info.files.empty()) { + ImGui::Separator(); + int terrain = 0, models = 0, buildings = 0, textures = 0, data = 0; + uint64_t totalSize = 0; + for (const auto& fe : info.files) { + totalSize += fe.size; + if (fe.category == "terrain") terrain++; + else if (fe.category == "model") models++; + else if (fe.category == "building") buildings++; + else if (fe.category == "texture") textures++; + else if (fe.category == "data") data++; + } + ImGui::Text("Terrain: %d Models: %d Buildings: %d", + terrain, models, buildings); + ImGui::Text("Textures: %d Data: %d Total: %.1f KB", + textures, data, totalSize / 1024.0f); + } } else { - app.showToast("Cannot read pack — check path"); + ImGui::TextColored(ImVec4(1, 0.3f, 0.3f, 1), "Cannot read pack"); } + ImGui::EndMenu(); } ImGui::EndMenu(); } @@ -959,10 +983,18 @@ void EditorUI::renderBrushPanel(EditorApp& app) { if (ImGui::Button("Paste Stamp", ImVec2(120, 0)) && app.getTerrainEditor().hasStamp()) app.getTerrainEditor().pasteStamp(brush2.getPosition()); - if (app.getTerrainEditor().hasStamp()) + if (app.getTerrainEditor().hasStamp()) { ImGui::TextColored(ImVec4(0.5f, 0.9f, 0.5f, 1), "Stamp ready"); - else + ImGui::SameLine(); + if (ImGui::SmallButton("Save##stamp")) + if (app.getTerrainEditor().saveStamp("output/stamps/terrain_stamp.json")) + app.showToast("Stamp saved"); + } else { ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1), "No stamp copied"); + } + if (ImGui::SmallButton("Load##stamp")) + if (app.getTerrainEditor().loadStamp("output/stamps/terrain_stamp.json")) + app.showToast("Stamp loaded"); } if (ImGui::CollapsingHeader("Detail Noise")) { diff --git a/tools/editor/terrain_editor.cpp b/tools/editor/terrain_editor.cpp index 1cd20b63..2ba3b609 100644 --- a/tools/editor/terrain_editor.cpp +++ b/tools/editor/terrain_editor.cpp @@ -1,7 +1,9 @@ #include "terrain_editor.hpp" #include "core/logger.hpp" +#include #include #include +#include #include #include #include @@ -1486,6 +1488,50 @@ void TerrainEditor::pasteStamp(const glm::vec3& center) { LOG_INFO("Stamp pasted at (", center.x, ",", center.y, ")"); } +bool TerrainEditor::saveStamp(const std::string& path) const { + if (stampData_.empty()) return false; + nlohmann::json j; + j["format"] = "wowee-stamp-1.0"; + j["vertexCount"] = stampData_.size(); + nlohmann::json verts = nlohmann::json::array(); + for (const auto& sv : stampData_) + verts.push_back({sv.dx, sv.dy, sv.height}); + j["vertices"] = verts; + + namespace fs = std::filesystem; + fs::create_directories(fs::path(path).parent_path()); + std::ofstream f(path); + if (!f) return false; + f << j.dump(2) << "\n"; + LOG_INFO("Stamp saved: ", path, " (", stampData_.size(), " vertices)"); + return true; +} + +bool TerrainEditor::loadStamp(const std::string& path) { + std::ifstream f(path); + if (!f) return false; + try { + auto j = nlohmann::json::parse(f); + if (!j.contains("vertices") || !j["vertices"].is_array()) return false; + + stampData_.clear(); + for (const auto& v : j["vertices"]) { + if (!v.is_array() || v.size() < 3) continue; + StampVertex sv; + sv.dx = v[0].get(); + sv.dy = v[1].get(); + sv.height = v[2].get(); + stampData_.push_back(sv); + } + stampCenter_ = glm::vec3(0); + LOG_INFO("Stamp loaded: ", path, " (", stampData_.size(), " vertices)"); + return !stampData_.empty(); + } catch (const std::exception& e) { + LOG_ERROR("Failed to load stamp: ", e.what()); + return false; + } +} + void TerrainEditor::clampHeights(float minH, float maxH) { if (!terrain_) return; for (int ci = 0; ci < 256; ci++) { diff --git a/tools/editor/terrain_editor.hpp b/tools/editor/terrain_editor.hpp index 945c3e21..90476f56 100644 --- a/tools/editor/terrain_editor.hpp +++ b/tools/editor/terrain_editor.hpp @@ -77,6 +77,8 @@ public: // Terrain stamp: copy heights from source area, paste at destination void copyStamp(const glm::vec3& center, float radius); void pasteStamp(const glm::vec3& center); + bool saveStamp(const std::string& path) const; + bool loadStamp(const std::string& path); bool hasStamp() const { return !stampData_.empty(); } // Mirror terrain along X or Y axis through tile center