Compare commits

...
4 Commits
Author SHA1 Message Date
Ben Woo 5e8e9380e3 Merge pull request #3440 from Multiverse/fix/26.1-folders-lowercased
Fix world folder being lowercased due to namespace changes in 26.1
2026-04-08 16:14:28 +08:00
Ben Woo 4a16fa2297 Fix world folder being lowercased due to namespace changes in 26.1 2026-04-08 14:20:01 +08:00
Ben Woo b9e07a85d0 Merge pull request #3439 from Multiverse/fix/papermc-26.1-migration
Add support for detecting PaperMC 26.1 folder migration
2026-04-08 11:11:29 +08:00
Ben Woo 53c7459c1d Add support for detecting PaperMC 26.1 folder migration 2026-04-08 11:00:25 +08:00
3 changed files with 83 additions and 11 deletions

No files matched your search

@@ -14,6 +14,8 @@ import java.nio.file.Path;
/**
* Compatibility class used to handle API changes in {@link Bukkit} class.
*
* @since 5.6
*/
@ApiStatus.AvailableSince("5.6")
public final class BukkitCompatibility {
@@ -26,6 +28,23 @@ public final class BukkitCompatibility {
GET_WORLD_NAMESPACED_KEY_METHOD = Option.of(ReflectHelper.getMethod(Bukkit.class, "getWorld", NamespacedKey.class));
}
/**
* Check whether the server is using the new dimension storage system introduced in PaperMC 26.1.
* <br />
* This is based of whether getLevelDirectory method exists in the server class,which is the main API change for
* the new dimension storage system.
*
* @return True if the server is using the new dimension storage system, else false.
*
* @since 5.6
*/
@ApiStatus.AvailableSince("5.6")
public static boolean isUsingNewDimensionStorage() {
return GET_LEVEL_DIRECTORY_METHOD
.flatMap(method -> Option.of(ReflectHelper.invokeMethod(Bukkit.getServer(), method)))
.isDefined();
}
/**
* Gets the folder where all the worlds will be store. Before 26.1, all worlds are stored in the root directory
* of the server, which can be obtained by {@link Server#getWorldContainer()}.
@@ -34,6 +53,8 @@ public final class BukkitCompatibility {
* level directory, which needs to be manually parsed.
*
* @return The location where all the worlds folders should be, depending on server's mc version.
*
* @since 5.6
*/
@ApiStatus.AvailableSince("5.6")
@NotNull
@@ -55,6 +76,8 @@ public final class BukkitCompatibility {
*
* @param nameOrKey Either a name or namespaced key string representation.
* @return The world if it exists
*
* @since 5.6
*/
@ApiStatus.AvailableSince("5.6")
@NotNull
@@ -317,8 +317,17 @@ public final class WorldManager {
String worldName = options.worldName();
if (!worldNameChecker.isValidWorldName(worldName)) {
return worldActionResult(ImportFailureReason.INVALID_WORLDNAME, worldName);
} else if (options.doFolderCheck() && !worldNameChecker.isValidWorldFolder(worldName)) {
return worldActionResult(ImportFailureReason.WORLD_FOLDER_INVALID, worldName);
} else if (options.doFolderCheck()) {
//todo This is a duplicate of folder check in load world
WorldNameChecker.FolderStatus folderStatus = worldNameChecker.checkFolder(options.worldName());
if (!folderStatus.isLoadable()) {
return worldActionResult(ImportFailureReason.WORLD_FOLDER_INVALID, options.worldName());
}
if (folderStatus == WorldNameChecker.FolderStatus.REQUIRES_MIGRATION) {
Logging.info("World '%s' will be automatically migrated by PaperMC to the new dimension " +
"location. If you face any issue with migration, please contact PaperMC support!",
options.worldName());
}
}
return worldActionResult(options);
}
@@ -480,8 +489,16 @@ public final class WorldManager {
return doLoadBukkitWorld(bukkitWorld, mvWorld);
}
if (options.doFolderCheck() && !worldNameChecker.isValidWorldFolder(mvWorld.getName())) {
return worldActionResult(LoadFailureReason.WORLD_FOLDER_INVALID, mvWorld.getName());
if (options.doFolderCheck()) {
WorldNameChecker.FolderStatus folderStatus = worldNameChecker.checkFolder(mvWorld.getName());
if (!folderStatus.isLoadable()) {
return worldActionResult(LoadFailureReason.WORLD_FOLDER_INVALID, mvWorld.getName());
}
if (folderStatus == WorldNameChecker.FolderStatus.REQUIRES_MIGRATION) {
Logging.info("World '%s' will be automatically migrated by PaperMC to the new dimension " +
"location. If you face any issue with migration, please contact PaperMC support!",
mvWorld.getName());
}
}
WorldCreator worldCreator = WorldCreator.name(mvWorld.getName())
@@ -5,8 +5,9 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import com.dumptruckman.minecraft.util.Logging;
import io.vavr.control.Option;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Service;
@@ -95,7 +96,7 @@ public final class WorldNameChecker {
* @return True if check result is valid, else false.
*/
public boolean isValidWorldFolder(@Nullable String worldName) {
return checkFolder(worldName) == FolderStatus.VALID;
return checkFolder(worldName).loadable;
}
/**
@@ -105,7 +106,7 @@ public final class WorldNameChecker {
* @return True if check result is valid, else false.
*/
public boolean isValidWorldFolder(@Nullable File worldFolder) {
return checkFolder(worldFolder) == FolderStatus.VALID;
return checkFolder(worldFolder).loadable;
}
/**
@@ -119,8 +120,16 @@ public final class WorldNameChecker {
if (worldName == null) {
return FolderStatus.DOES_NOT_EXIST;
}
if (BukkitCompatibility.isUsingNewDimensionStorage()) {
File oldWorldFolder = Bukkit.getWorldContainer().toPath().resolve(worldName).toFile();
if (checkFolder(oldWorldFolder) == FolderStatus.VALID) {
return FolderStatus.REQUIRES_MIGRATION;
}
worldName = worldName.toLowerCase(Locale.ENGLISH); // namespace is case-insensitive and stored as lowercase
}
File worldFolder = BukkitCompatibility.getWorldFoldersDirectory().resolve(worldName).toFile();
Logging.finer("Checking valid folder for world '%s' at: '%s'", worldName, worldFolder.getPath());
return checkFolder(worldFolder);
}
@@ -230,16 +239,39 @@ public final class WorldNameChecker {
/**
* Folder is valid.
*/
VALID,
VALID(true),
/**
* This folder will cause PaperMC to migrate to new dimension world folder in 26.1+
*/
REQUIRES_MIGRATION(true),
/**
* Folder exist, but contents in it doesnt look like a world.
*/
NOT_A_WORLD,
NOT_A_WORLD(false),
/**
* Folder does not exist.
*/
DOES_NOT_EXIST
DOES_NOT_EXIST(false),
;
private final boolean loadable;
FolderStatus(boolean loadable) {
this.loadable = loadable;
}
/**
* Whether this folder status is loadable, meaning it has the basic world data and can be loaded as a world.
* Note that this does not guarantee the server will definitely load the world with no errors.
*
* @return True if folder probably is loadable by the server, else false.
*/
@ApiStatus.AvailableSince("5.6")
public boolean isLoadable() {
return loadable;
}
}
}