From fd2ef9e9bc7cbee76514dc4e2659404d64811449 Mon Sep 17 00:00:00 2001 From: BehrRiley Date: Mon, 20 Jul 2026 20:11:15 -0400 Subject: [PATCH 1/3] add `copy_to` argument to `createworld` command to support custom datapack dimension paths --- .../commands/world/CreateWorldCommand.java | 54 +++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CreateWorldCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CreateWorldCommand.java index eb708a55bc..d439a8236b 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CreateWorldCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CreateWorldCommand.java @@ -26,17 +26,17 @@ public class CreateWorldCommand extends AbstractCommand implements Holdable { public CreateWorldCommand() { setName("createworld"); - setSyntax("createworld [] (generator:) (worldtype:) (environment:) (copy_from:) (seed:) (settings:) (generate_structures:true/false)"); - setRequiredArguments(1, 8); - setPrefixesHandled("generator", "worldtype", "environment", "copy_from", "seed", "settings", "generate_structures"); + setSyntax("createworld [] (generator:) (worldtype:) (environment:) (copy_from:) (seed:) (settings:) (generate_structures:true/false) (copy_to:)"); + setRequiredArguments(1, 9); + setPrefixesHandled("generator", "worldtype", "environment", "copy_from", "seed", "settings", "generate_structures", "copy_to"); isProcedural = false; } // <--[command] // @Name CreateWorld - // @Syntax createworld [] (generator:) (worldtype:) (environment:) (copy_from:) (seed:) (settings:) (generate_structures:true/false) + // @Syntax createworld [] (generator:) (worldtype:) (environment:) (copy_from:) (seed:) (settings:) (generate_structures:true/false) (copy_to:) // @Required 1 - // @Maximum 8 + // @Maximum 9 // @Short Creates a new world, or loads an existing world. // @Synonyms LoadWorld // @Group world @@ -60,6 +60,8 @@ public CreateWorldCommand() { // Optionally specify an existing world to copy files from. // The 'copy_from' argument is ~waitable. Refer to <@link language ~waitable>. // + // Optionally specify a 'copy_to' path, which overrides the folder name the files are copied into (useful for datapack dimensions). + // // It's often ideal to put this command inside <@link event server prestart>. // // @Tags @@ -121,17 +123,21 @@ public void execute(ScriptEntry scriptEntry) { ElementTag worldType = scriptEntry.argForPrefixAsElement("worldtype", "NORMAL"); ElementTag environment = scriptEntry.argForPrefixAsElement("environment", "NORMAL"); ElementTag copy_from = scriptEntry.argForPrefixAsElement("copy_from", null); + ElementTag copy_to = scriptEntry.argForPrefixAsElement("copy_to", null); ElementTag settings = scriptEntry.argForPrefixAsElement("settings", null); ElementTag seed = scriptEntry.argForPrefixAsElement("seed", null); ElementTag generateStructures = scriptEntry.argForPrefixAsElement("generate_structures", null); if (scriptEntry.dbCallShouldDebug()) { - Debug.report(scriptEntry, getName(), worldName, generator, environment, copy_from, settings, worldType, seed, generateStructures); + Debug.report(scriptEntry, getName(), worldName, generator, environment, copy_from, copy_to, settings, worldType, seed, generateStructures); } if (Bukkit.getWorld(worldName.asString()) != null) { Debug.echoDebug(scriptEntry, "CreateWorld doing nothing, world by that name already loaded."); scriptEntry.setFinished(true); return; } + String targetFolder = copy_to != null ? copy_to.asString() : worldName.asString(); + String sourceFolder = copy_from != null ? copy_from.asString().replace("w@", "") : null; + if (!Settings.cache_createWorldSymbols) { if (forbiddenSymbols.containsAnyMatch(worldName.asString())) { Debug.echoError("Cannot use world names with non-alphanumeric symbols due to security settings in Denizen/config.yml."); @@ -140,7 +146,7 @@ public void execute(ScriptEntry scriptEntry) { } } else if (!Settings.cache_createWorldWeirdPaths) { - String cleaned = worldName.asLowerString().replace('\\', '/'); + String cleaned = targetFolder.toLowerCase().replace('\\', '/'); while (cleaned.contains("//")) { cleaned = cleaned.replace("//", "/"); } @@ -157,8 +163,28 @@ else if (!Settings.cache_createWorldWeirdPaths) { scriptEntry.setFinished(true); return; } + + if (sourceFolder != null) { + String cleanedSource = sourceFolder.toLowerCase().replace('\\', '/'); + while (cleanedSource.contains("//")) { + cleanedSource = cleanedSource.replace("//", "/"); + } + if (cleanedSource.startsWith("/")) { + cleanedSource = cleanedSource.substring(1); + } + if (cleanedSource.startsWith("plugins/")) { + Debug.echoError("CreateWorld cannot copy from a folder inside plugins due to security settings in Denizen/config.yml."); + scriptEntry.setFinished(true); + return; + } + if (cleanedSource.startsWith("..")) { + Debug.echoError("CreateWorld cannot copy from a world with a raised path (contains '..') due to security settings in Denizen/config.yml."); + scriptEntry.setFinished(true); + return; + } + } } - final File newFolder = new File(Bukkit.getWorldContainer(), worldName.asString()); + final File newFolder = new File(Bukkit.getWorldContainer(), targetFolder); if (!Utilities.canWriteToFile(newFolder)) { Debug.echoError("Cannot copy to that new folder path due to security settings in Denizen/config.yml."); scriptEntry.setFinished(true); @@ -175,14 +201,10 @@ else if (!Settings.cache_createWorldWeirdPaths) { scriptEntry.setFinished(true); return; } - if (copy_from != null && !Settings.cache_createWorldSymbols && forbiddenSymbols.containsAnyMatch(copy_from.asString())) { - Debug.echoError("Cannot use copy_from world names with non-alphanumeric symbols due to security settings in Denizen/config.yml."); - scriptEntry.setFinished(true); - return; - } + Supplier copyRunnable = () -> { try { - File folder = new File(Bukkit.getWorldContainer(), copy_from.asString().replace("w@", "")); + File folder = new File(Bukkit.getWorldContainer(), sourceFolder); if (!Utilities.canReadFile(folder)) { Debug.echoError(scriptEntry, "Cannot copy from that folder path due to security settings in Denizen/config.yml."); return false; @@ -197,11 +219,11 @@ else if (!Settings.cache_createWorldWeirdPaths) { } CoreUtilities.copyDirectory(folder, newFolder, excludedExtensionsForCopyFrom); Debug.echoDebug(scriptEntry, "Copied " + folder.getName() + " to " + newFolder.getName()); - File file = new File(Bukkit.getWorldContainer(), worldName.asString() + "/uid.dat"); + File file = new File(Bukkit.getWorldContainer(), targetFolder + "/uid.dat"); if (file.exists()) { file.delete(); } - File file2 = new File(Bukkit.getWorldContainer(), worldName.asString() + "/session.lock"); + File file2 = new File(Bukkit.getWorldContainer(), targetFolder + "/session.lock"); if (file2.exists()) { file2.delete(); } From 1d1810742ae5b1f4178e3fed970e608d28e0fac1 Mon Sep 17 00:00:00 2001 From: BehrRiley Date: Tue, 25 Aug 2026 13:11:27 -0400 Subject: [PATCH 2/3] Update CreateWorldCommand.java --- .../commands/world/CreateWorldCommand.java | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CreateWorldCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CreateWorldCommand.java index d439a8236b..1ae1b5b73f 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CreateWorldCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CreateWorldCommand.java @@ -26,17 +26,17 @@ public class CreateWorldCommand extends AbstractCommand implements Holdable { public CreateWorldCommand() { setName("createworld"); - setSyntax("createworld [] (generator:) (worldtype:) (environment:) (copy_from:) (seed:) (settings:) (generate_structures:true/false) (copy_to:)"); - setRequiredArguments(1, 9); - setPrefixesHandled("generator", "worldtype", "environment", "copy_from", "seed", "settings", "generate_structures", "copy_to"); + setSyntax("createworld [] (generator:) (worldtype:) (environment:) (copy_from:) (seed:) (settings:) (generate_structures:true/false)"); + setRequiredArguments(1, 8); + setPrefixesHandled("generator", "worldtype", "environment", "copy_from", "seed", "settings", "generate_structures"); isProcedural = false; } // <--[command] // @Name CreateWorld - // @Syntax createworld [] (generator:) (worldtype:) (environment:) (copy_from:) (seed:) (settings:) (generate_structures:true/false) (copy_to:) + // @Syntax createworld [] (generator:) (worldtype:) (environment:) (copy_from:) (seed:) (settings:) (generate_structures:true/false) // @Required 1 - // @Maximum 9 + // @Maximum 8 // @Short Creates a new world, or loads an existing world. // @Synonyms LoadWorld // @Group world @@ -60,8 +60,6 @@ public CreateWorldCommand() { // Optionally specify an existing world to copy files from. // The 'copy_from' argument is ~waitable. Refer to <@link language ~waitable>. // - // Optionally specify a 'copy_to' path, which overrides the folder name the files are copied into (useful for datapack dimensions). - // // It's often ideal to put this command inside <@link event server prestart>. // // @Tags @@ -128,25 +126,25 @@ public void execute(ScriptEntry scriptEntry) { ElementTag seed = scriptEntry.argForPrefixAsElement("seed", null); ElementTag generateStructures = scriptEntry.argForPrefixAsElement("generate_structures", null); if (scriptEntry.dbCallShouldDebug()) { - Debug.report(scriptEntry, getName(), worldName, generator, environment, copy_from, copy_to, settings, worldType, seed, generateStructures); + Debug.report(scriptEntry, getName(), worldName, generator, environment, copy_from, settings, worldType, seed, generateStructures); } if (Bukkit.getWorld(worldName.asString()) != null) { Debug.echoDebug(scriptEntry, "CreateWorld doing nothing, world by that name already loaded."); scriptEntry.setFinished(true); return; } - String targetFolder = copy_to != null ? copy_to.asString() : worldName.asString(); + String worldNameStr = worldName.asString(); String sourceFolder = copy_from != null ? copy_from.asString().replace("w@", "") : null; if (!Settings.cache_createWorldSymbols) { - if (forbiddenSymbols.containsAnyMatch(worldName.asString())) { + if (forbiddenSymbols.containsAnyMatch(worldNameStr)) { Debug.echoError("Cannot use world names with non-alphanumeric symbols due to security settings in Denizen/config.yml."); scriptEntry.setFinished(true); return; } } else if (!Settings.cache_createWorldWeirdPaths) { - String cleaned = targetFolder.toLowerCase().replace('\\', '/'); + String cleaned = worldNameStr.toLowerCase().replace('\\', '/'); while (cleaned.contains("//")) { cleaned = cleaned.replace("//", "/"); } @@ -184,7 +182,15 @@ else if (!Settings.cache_createWorldWeirdPaths) { } } } - final File newFolder = new File(Bukkit.getWorldContainer(), targetFolder); + final File newFolder; + if (worldNameStr.contains(":")) { + String[] split = worldNameStr.split(":", 2); + newFolder = new File(new File(new File(Bukkit.getWorlds().get(0).getWorldFolder(), "dimensions"), split[0]), split[1]); + } + else { + newFolder = new File(Bukkit.getWorldContainer(), worldNameStr); + } + if (!Utilities.canWriteToFile(newFolder)) { Debug.echoError("Cannot copy to that new folder path due to security settings in Denizen/config.yml."); scriptEntry.setFinished(true); @@ -219,11 +225,11 @@ else if (!Settings.cache_createWorldWeirdPaths) { } CoreUtilities.copyDirectory(folder, newFolder, excludedExtensionsForCopyFrom); Debug.echoDebug(scriptEntry, "Copied " + folder.getName() + " to " + newFolder.getName()); - File file = new File(Bukkit.getWorldContainer(), targetFolder + "/uid.dat"); + File file = new File(newFolder, "uid.dat"); if (file.exists()) { file.delete(); } - File file2 = new File(Bukkit.getWorldContainer(), targetFolder + "/session.lock"); + File file2 = new File(newFolder, "session.lock"); if (file2.exists()) { file2.delete(); } From d09268d68049b1b006ec0625cebf07a326dffe5e Mon Sep 17 00:00:00 2001 From: BehrRiley Date: Mon, 14 Sep 2026 02:57:00 -0400 Subject: [PATCH 3/3] infer namespaced `createworld` paths - resolve namespaced worlds under the main world's `dimensions` folder - keep normal world paths unchanged - resolve namespaced `copy_from` sources - validate namespaced world keys --- .../commands/world/CreateWorldCommand.java | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CreateWorldCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CreateWorldCommand.java index 1ae1b5b73f..cc2816fc7a 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CreateWorldCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/world/CreateWorldCommand.java @@ -184,8 +184,13 @@ else if (!Settings.cache_createWorldWeirdPaths) { } final File newFolder; if (worldNameStr.contains(":")) { - String[] split = worldNameStr.split(":", 2); - newFolder = new File(new File(new File(Bukkit.getWorlds().get(0).getWorldFolder(), "dimensions"), split[0]), split[1]); + org.bukkit.NamespacedKey key = org.bukkit.NamespacedKey.fromString(worldNameStr); + if (key == null) { + Debug.echoError("CreateWorld failed: '" + worldNameStr + "' is not a valid NamespacedKey."); + scriptEntry.setFinished(true); + return; + } + newFolder = new File(new File(new File(new File(Bukkit.getWorldContainer(), Bukkit.getWorlds().get(0).getName()), "dimensions"), key.getNamespace()), key.getKey()); } else { newFolder = new File(Bukkit.getWorldContainer(), worldNameStr); @@ -196,6 +201,29 @@ else if (!Settings.cache_createWorldWeirdPaths) { scriptEntry.setFinished(true); return; } + final File folder; + if (sourceFolder != null) { + if (sourceFolder.contains(":")) { + org.bukkit.NamespacedKey sourceKey = org.bukkit.NamespacedKey.fromString(sourceFolder); + if (sourceKey == null) { + Debug.echoError("CreateWorld failed: copy_from '" + sourceFolder + "' is not a valid NamespacedKey."); + scriptEntry.setFinished(true); + return; + } + folder = new File(new File(new File(new File(Bukkit.getWorldContainer(), Bukkit.getWorlds().get(0).getName()), "dimensions"), sourceKey.getNamespace()), sourceKey.getKey()); + } + else { + folder = new File(Bukkit.getWorldContainer(), sourceFolder); + } + if (!Utilities.canReadFile(folder)) { + Debug.echoError("Cannot copy from that folder path due to security settings in Denizen/config.yml."); + scriptEntry.setFinished(true); + return; + } + } + else { + folder = null; + } WorldType enumWorldType; World.Environment enumEnvironment; try { @@ -210,11 +238,6 @@ else if (!Settings.cache_createWorldWeirdPaths) { Supplier copyRunnable = () -> { try { - File folder = new File(Bukkit.getWorldContainer(), sourceFolder); - if (!Utilities.canReadFile(folder)) { - Debug.echoError(scriptEntry, "Cannot copy from that folder path due to security settings in Denizen/config.yml."); - return false; - } if (!folder.exists() || !folder.isDirectory()) { Debug.echoError(scriptEntry, "Invalid copy from world folder - does not exist!"); return false;