diff --git a/src/main/java/noppes/npcs/controllers/DialogController.java b/src/main/java/noppes/npcs/controllers/DialogController.java index b9a416762..b65e1ef5e 100644 --- a/src/main/java/noppes/npcs/controllers/DialogController.java +++ b/src/main/java/noppes/npcs/controllers/DialogController.java @@ -84,14 +84,22 @@ public class DialogController implements IDialogHandler { lastUsedDialogID = id; Dialog dialog = category.dialogs.get(id); if (dialogs.containsKey(id)) { - // Find the next free ID, skipping any already claimed slots. - int newId = lastUsedDialogID; - do { newId++; } while (dialogs.containsKey(newId)); - lastUsedDialogID = newId; - // Dialog IDs live in the filename, not inside the JSON body, // so a simple rename permanently resolves the conflict. File catDir = new File(getDir(), category.title); + + // Find the next free ID. It has to clear three things: the ids already + // registered globally, the ids this category still holds but has not + // reached yet, and any file already on disk - renameTo overwrites + // silently on POSIX, so landing on an existing file would destroy it. + int newId = lastUsedDialogID; + do { + newId++; + } while (dialogs.containsKey(newId) + || category.dialogs.containsKey(newId) + || new File(catDir, newId + ".json").exists()); + lastUsedDialogID = newId; + File oldFile = new File(catDir, id + ".json"); File newFile = new File(catDir, newId + ".json"); boolean renamed = oldFile.renameTo(newFile); diff --git a/src/main/java/noppes/npcs/controllers/QuestController.java b/src/main/java/noppes/npcs/controllers/QuestController.java index 69bbe88d0..3d9b707b3 100644 --- a/src/main/java/noppes/npcs/controllers/QuestController.java +++ b/src/main/java/noppes/npcs/controllers/QuestController.java @@ -70,6 +70,7 @@ public class QuestController implements IQuestHandler { if (!file.isDirectory()) continue; QuestCategory category = loadCategoryDir(file); + List reassigned = new ArrayList(); Iterator ite = category.quests.keySet().iterator(); while (ite.hasNext()) { int id = ite.next(); @@ -77,8 +78,39 @@ public class QuestController implements IQuestHandler { lastUsedQuestID = id; Quest quest = category.quests.get(id); if (quests.containsKey(id)) { - LogWriter.error("Duplicate id " + quest.id + " from category " + category.title); + // Quest IDs live in the filename, not inside the JSON body, + // so a simple rename permanently resolves the conflict. + File catDir = new File(getDir(), category.title); + + // Find the next free ID. It has to clear three things: the ids already + // registered globally, the ids this category still holds but has not + // reached yet, and any file already on disk - renameTo overwrites + // silently on POSIX, so landing on an existing file would destroy it. + int newId = lastUsedQuestID; + do { + newId++; + } while (quests.containsKey(newId) + || category.quests.containsKey(newId) + || new File(catDir, newId + ".json").exists()); + lastUsedQuestID = newId; + + File oldFile = new File(catDir, id + ".json"); + File newFile = new File(catDir, newId + ".json"); + boolean renamed = oldFile.renameTo(newFile); + + quest.id = newId; ite.remove(); + reassigned.add(quest); + + if (renamed) { + LogWriter.info("Quest ID conflict: reassigned \"" + quest.title + + "\" from id " + id + " to " + newId + + " in category \"" + category.title + "\""); + } else { + LogWriter.error("Quest ID conflict: could not rename file for \"" + + quest.title + "\" (id " + id + + " in category \"" + category.title + "\") to " + newId); + } } else { quests.put(id, quest); if (quest.profileOptions.enableOptions @@ -88,6 +120,16 @@ public class QuestController implements IQuestHandler { } } } + // Re-register reassigned quests under their new IDs. + for (Quest quest : reassigned) { + quests.put(quest.id, quest); + category.quests.put(quest.id, quest); + if (quest.profileOptions.enableOptions + && (quest.profileOptions.completeControl == EnumProfileSync.Shared + || quest.profileOptions.cooldownControl == EnumProfileSync.Shared)) { + sharedQuests.put(quest.id, quest); + } + } lastUsedCatID++; category.id = lastUsedCatID; categories.put(category.id, category);