From df1eed1c4290aa4b8877ae4e1254f8a4116e7f51 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 04:53:09 -0700 Subject: [PATCH] fix(editor): preserve CreatureSpawn.id across JSON save/load Quest links (questGiverNpcId, turnInNpcId, KillCreature targetName) all key off CreatureSpawn.id, but loadFromFile always assigned new ids via nextId(). So saving and reloading would silently break every quest hook in the zone. Now JSON stores id, the loader reads it back when present (legacy files fall back to nextId()), and idCounter_ is bumped past loaded values to prevent future collisions. Same fix as the recent PlacedObject.uniqueId one. --- tools/editor/npc_spawner.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/editor/npc_spawner.cpp b/tools/editor/npc_spawner.cpp index 67627f4d..bcb2dcd4 100644 --- a/tools/editor/npc_spawner.cpp +++ b/tools/editor/npc_spawner.cpp @@ -62,6 +62,7 @@ bool NpcSpawner::saveToFile(const std::string& path) const { nlohmann::json arr = nlohmann::json::array(); for (const auto& s : spawns_) { nlohmann::json js; + js["id"] = s.id; js["name"] = s.name; js["model"] = s.modelPath; js["displayId"] = s.displayId; @@ -183,7 +184,16 @@ bool NpcSpawner::loadFromFile(const std::string& path) { } if (!s.name.empty()) { - s.id = nextId(); + // Preserve original id from JSON if present so quest hooks + // (questGiverNpcId, turnInNpcId, KillCreature targetName) + // remain stable across save/load. Bump idCounter past any + // loaded value to avoid collisions with future placements. + if (js.contains("id")) { + s.id = js["id"].get(); + if (s.id >= idCounter_) idCounter_ = s.id + 1; + } else { + s.id = nextId(); + } spawns_.push_back(s); } }