From bd5febca157a81ac9a9073d5bc7816ce1d7f5961 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 4 Apr 2026 17:25:49 -0700 Subject: [PATCH] Support Minecraft 26.1.1 world file structure in region purge In 26.1.1, dimensions moved to dimensions/minecraft// with no DIM-1/DIM1 subfolders, and player data moved to /players/data/. Update path resolution to detect and support both old and new formats. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../admin/purge/AdminPurgeRegionsCommand.java | 103 ++++++++++++++++-- 1 file changed, 93 insertions(+), 10 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeRegionsCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeRegionsCommand.java index 4b39d049f..1709ad4cf 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeRegionsCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeRegionsCommand.java @@ -41,6 +41,9 @@ public class AdminPurgeRegionsCommand extends CompositeCommand implements Listen private static final String ENTITIES = "entities"; private static final String POI = "poi"; private static final String DIM_1 = "DIM-1"; + private static final String DIM1 = "DIM1"; + private static final String PLAYERS = "players"; + private static final String PLAYERDATA = "playerdata"; private static final String IN_WORLD = " in world "; private static final String WILL_BE_DELETED = " will be deleted"; private static final String EXISTS_PREFIX = " (exists="; @@ -152,7 +155,7 @@ private boolean deleteEverything() { } private void deletePlayerFromWorldFolder(String islandID) { - File playerData = new File(getWorld().getWorldFolder(), "playerdata"); + File playerData = resolvePlayerDataFolder(); getPlugin().getIslands().getIslandById(islandID) .ifPresent(island -> island.getMemberSet() .forEach(uuid -> maybeDeletePlayerData(uuid, playerData))); @@ -197,11 +200,14 @@ private void deletePlayerFile(File file, String description) { /** * Resolves the base data folder for a world, accounting for the dimension - * subfolder that Minecraft uses for non-overworld environments. + * subfolder layout. *

- * Overworld data lives directly in the world folder, but Nether data lives - * in {@code DIM-1/} and End data lives in {@code DIM1/} subfolders - even - * when the world has its own separate folder. + * Pre-26.1 (old format): Nether data lives in {@code DIM-1/} and + * End data lives in {@code DIM1/} subfolders inside the world folder. + *

+ * 26.1.1+ (new format): Each dimension has its own world folder + * under {@code dimensions/minecraft/} and data (region/, entities/, poi/) + * lives directly in it — no DIM-1/DIM1 subfolders. * * @param world the world to resolve * @return the base folder containing region/, entities/, poi/ subfolders @@ -214,13 +220,90 @@ private File resolveDataFolder(World world) { yield dim.isDirectory() ? dim : worldFolder; } case THE_END -> { - File dim = new File(worldFolder, "DIM1"); + File dim = new File(worldFolder, DIM1); yield dim.isDirectory() ? dim : worldFolder; } default -> worldFolder; }; } + /** + * Resolves the player data folder, supporting both old and new formats. + *

+ * Pre-26.1: {@code /playerdata/} + *

+ * 26.1.1+: {@code /players/data/} (centralized) + * + * @return the folder containing player .dat files + */ + private File resolvePlayerDataFolder() { + File worldFolder = getWorld().getWorldFolder(); + // Old format + File oldPath = new File(worldFolder, PLAYERDATA); + if (oldPath.isDirectory()) { + return oldPath; + } + // New 26.1.1 format: walk up from dimensions/minecraft// to world root + File root = worldFolder.getParentFile(); // minecraft/ + if (root != null) root = root.getParentFile(); // dimensions/ + if (root != null) root = root.getParentFile(); // world root + if (root != null) { + File newPath = new File(root, PLAYERS + File.separator + "data"); + if (newPath.isDirectory()) { + return newPath; + } + } + return oldPath; // fallback + } + + /** + * Resolves the nether data folder when the Nether World object is unavailable. + * Tries the old DIM-1 subfolder first, then the 26.1.1 sibling world folder. + * + * @param overworldFolder the overworld's world folder + * @return the nether base folder (may not exist) + */ + private File resolveNetherFallback(File overworldFolder) { + // Old format: /DIM-1/ + File dim = new File(overworldFolder, DIM_1); + if (dim.isDirectory()) { + return dim; + } + // New 26.1.1 format: sibling folder _nether in same parent + File parent = overworldFolder.getParentFile(); + if (parent != null) { + File sibling = new File(parent, overworldFolder.getName() + "_nether"); + if (sibling.isDirectory()) { + return sibling; + } + } + return dim; // fallback to old path + } + + /** + * Resolves the end data folder when the End World object is unavailable. + * Tries the old DIM1 subfolder first, then the 26.1.1 sibling world folder. + * + * @param overworldFolder the overworld's world folder + * @return the end base folder (may not exist) + */ + private File resolveEndFallback(File overworldFolder) { + // Old format: /DIM1/ + File dim = new File(overworldFolder, DIM1); + if (dim.isDirectory()) { + return dim; + } + // New 26.1.1 format: sibling folder _the_end in same parent + File parent = overworldFolder.getParentFile(); + if (parent != null) { + File sibling = new File(parent, overworldFolder.getName() + "_the_end"); + if (sibling.isDirectory()) { + return sibling; + } + } + return dim; // fallback to old path + } + /** * Deletes a file if it exists, logging an error if deletion fails. * Does not log if the parent folder does not exist (normal for entities/poi). @@ -261,13 +344,13 @@ private boolean deleteRegionFiles() { File overworldPoi = new File(base, POI); World netherWorld = getPlugin().getIWM().getNetherWorld(world); - File netherBase = netherWorld != null ? resolveDataFolder(netherWorld) : new File(base, DIM_1); + File netherBase = netherWorld != null ? resolveDataFolder(netherWorld) : resolveNetherFallback(base); File netherRegion = new File(netherBase, REGION); File netherEntities = new File(netherBase, ENTITIES); File netherPoi = new File(netherBase, POI); World endWorld = getPlugin().getIWM().getEndWorld(world); - File endBase = endWorld != null ? resolveDataFolder(endWorld) : new File(base, "DIM1"); + File endBase = endWorld != null ? resolveDataFolder(endWorld) : resolveEndFallback(base); File endRegion = new File(endBase, REGION); File endEntities = new File(endBase, ENTITIES); File endPoi = new File(endBase, POI); @@ -550,11 +633,11 @@ private List> findOldRegions(int days) { File overworldRegion = new File(worldDir, REGION); World netherWorld = getPlugin().getIWM().getNetherWorld(world); - File netherBase = netherWorld != null ? resolveDataFolder(netherWorld) : new File(worldDir, DIM_1); + File netherBase = netherWorld != null ? resolveDataFolder(netherWorld) : resolveNetherFallback(worldDir); File netherRegion = new File(netherBase, REGION); World endWorld = getPlugin().getIWM().getEndWorld(world); - File endBase = endWorld != null ? resolveDataFolder(endWorld) : new File(worldDir, "DIM1"); + File endBase = endWorld != null ? resolveDataFolder(endWorld) : resolveEndFallback(worldDir); File endRegion = new File(endBase, REGION); long cutoffMillis = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(days);