fix(editor): ID counter resets, quest panel guard, zone rename, load paths

Bug fixes:
- ObjectPlacer::clearAll() now resets uniqueIdCounter_ to 1
- NpcSpawner::clearAll() added with idCounter_ reset (was manual clear)
- clearAllObjects() uses NpcSpawner::clearAll() instead of manual clear
- Quest panel has terrain-loaded guard (prevents crash before loading)

Features:
- Zone rename: editable map name field in Info panel (press Enter)
- Load objects/creatures/quests from both output/ and custom_zones/
  directories (WOT zones from custom_zones now get their NPCs loaded)
This commit is contained in:
Kelsi 2026-05-05 14:10:47 -07:00
parent 10a63f0581
commit 16a34afbf6
5 changed files with 31 additions and 13 deletions

View file

@ -773,14 +773,19 @@ void EditorApp::loadADT(const std::string& mapName, int tileX, int tileY) {
LOG_INFO("ADT loaded: ", mapName, " [", tileX, ",", tileY, "]");
// Try loading objects/NPCs from output directory if they exist
std::string outBase = "output/" + mapName;
if (objectPlacer_.loadFromFile(outBase + "/objects.json"))
showToast("Loaded " + std::to_string(objectPlacer_.objectCount()) + " objects");
if (npcSpawner_.loadFromFile(outBase + "/creatures.json"))
showToast("Loaded " + std::to_string(npcSpawner_.spawnCount()) + " NPCs");
if (questEditor_.loadFromFile(outBase + "/quests.json"))
showToast("Loaded " + std::to_string(questEditor_.questCount()) + " quests");
// Try loading objects/NPCs/quests from zone directories
for (const char* dir : {"output", "custom_zones"}) {
std::string zoneBase = std::string(dir) + "/" + mapName;
if (objectPlacer_.objectCount() == 0)
if (objectPlacer_.loadFromFile(zoneBase + "/objects.json"))
showToast("Loaded " + std::to_string(objectPlacer_.objectCount()) + " objects");
if (npcSpawner_.spawnCount() == 0)
if (npcSpawner_.loadFromFile(zoneBase + "/creatures.json"))
showToast("Loaded " + std::to_string(npcSpawner_.spawnCount()) + " NPCs");
if (questEditor_.questCount() == 0)
if (questEditor_.loadFromFile(zoneBase + "/quests.json"))
showToast("Loaded " + std::to_string(questEditor_.questCount()) + " quests");
}
if (objectPlacer_.objectCount() > 0 || npcSpawner_.spawnCount() > 0)
objectsDirty_ = true;
}
@ -1254,8 +1259,7 @@ void EditorApp::generateCompleteZone() {
void EditorApp::clearAllObjects() {
vkDeviceWaitIdle(window_->getVkContext()->getDevice());
objectPlacer_.clearAll();
npcSpawner_.clearSelection();
npcSpawner_.getSpawns().clear();
npcSpawner_.clearAll();
viewport_.clearObjects();
viewport_.updateNpcMarkers({});
terrainEditor_.history().clear();

View file

@ -54,6 +54,7 @@ public:
QuestEditor& getQuestEditor() { return questEditor_; }
AssetBrowser& getAssetBrowser() { return assetBrowser_; }
EditorViewport& getViewport() { return viewport_; }
void setMapName(const std::string& name) { loadedMap_ = name; }
rendering::TerrainRenderer* getTerrainRenderer();
rendering::M2Renderer* getM2Renderer() { return viewport_.getM2Renderer(); }
pipeline::AssetManager* getAssetManager() { return assetManager_.get(); }

View file

@ -1814,6 +1814,10 @@ void EditorUI::renderQuestPanel(EditorApp& app) {
ImGui::SetNextWindowPos(ImVec2(vp->Size.x - 400, 90), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(390, 600), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Quest Editor")) {
if (!app.hasTerrainLoaded()) {
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "Load or create terrain first");
ImGui::End(); return;
}
auto& qe = app.getQuestEditor();
auto& tmpl = qe.getTemplate();
@ -2245,8 +2249,16 @@ void EditorUI::renderPropertiesPanel(EditorApp& app) {
if (ImGui::Begin("Info")) {
auto* tr = app.getTerrainRenderer();
if (tr && tr->getChunkCount() > 0) {
ImGui::Text("Map: %s [%d, %d]", app.getLoadedMap().c_str(),
app.getLoadedTileX(), app.getLoadedTileY());
static char renameBuf[128] = {};
std::strncpy(renameBuf, app.getLoadedMap().c_str(), sizeof(renameBuf) - 1);
ImGui::SetNextItemWidth(140);
if (ImGui::InputText("##mapname", renameBuf, sizeof(renameBuf),
ImGuiInputTextFlags_EnterReturnsTrue)) {
app.setMapName(renameBuf);
app.showToast("Zone renamed: " + std::string(renameBuf));
}
ImGui::SameLine();
ImGui::Text("[%d, %d]", app.getLoadedTileX(), app.getLoadedTileY());
ImGui::Text("Chunks: %d Tris: %d", tr->getChunkCount(), tr->getTriangleCount());
ImGui::Text("Objects: %zu NPCs: %zu",
app.getObjectPlacer().objectCount(),

View file

@ -73,6 +73,7 @@ public:
const std::vector<CreatureSpawn>& getSpawns() const { return spawns_; }
std::vector<CreatureSpawn>& getSpawns() { return spawns_; }
size_t spawnCount() const { return spawns_.size(); }
void clearAll() { spawns_.clear(); selectedIdx_ = -1; idCounter_ = 1; }
// Serialize to/from JSON
bool saveToFile(const std::string& path) const;

View file

@ -62,7 +62,7 @@ public:
std::vector<PlacedObject>& getObjects() { return objects_; }
void selectAll();
void selectByType(PlaceableType type);
void clearAll() { objects_.clear(); undoStack_.clear(); selectedIdx_ = -1; selectedIndices_.clear(); }
void clearAll() { objects_.clear(); undoStack_.clear(); selectedIdx_ = -1; selectedIndices_.clear(); uniqueIdCounter_ = 1; }
size_t objectCount() const { return objects_.size(); }
float getPlacementRotationY() const { return placementRotY_; }