Resolve world folders through a WorldStorage seam

Minecraft 26.1 moved every world into its level's dimensions folder, so
Iris can no longer assume <world container>/<world name>. World folders,
data pack folders and moving Iris data out of an old layout now go
through WorldStorage, which the NMS binding provides. Every binding so
far uses LegacyWorldStorage, so behaviour is unchanged.

Code that already holds a loaded World uses World#getWorldFolder, which
is correct for both layouts.

Model used: Claude Opus 5.5
This commit is contained in:
piexel committed 2026-10-11 15:58:50 +02:00
1 parent f1c1869e43
commit e1514503f6
17 files changed
+137 -49

No files matched your search

+17 -2
View File
@@ -31,6 +31,7 @@ import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.core.pregenerator.LazyPregenerator;
import com.volmit.iris.core.service.StudioSVC;
import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.core.world.WorldStorage;
import com.volmit.iris.engine.EnginePanic;
import com.volmit.iris.engine.object.IrisCompat;
import com.volmit.iris.engine.object.IrisDimension;
@@ -438,6 +439,7 @@ public class Iris extends VolmitPlugin implements Listener {
initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class<? extends IrisService>) i.getClass(), (IrisService) i));
IO.delete(new File("iris"));
compat = IrisCompat.configured(getDataFile("compat.json"));
adoptLegacyWorldData();
ServerConfigurator.configure();
IrisSafeguard.execute();
getSender().setTag(getTag());
@@ -464,6 +466,19 @@ public class Iris extends VolmitPlugin implements Listener {
});
}
private void adoptLegacyWorldData() {
WorldStorage storage = WorldStorage.get();
for (String worldName : IrisWorlds.readKnownWorldNames()) {
try {
storage.adoptLegacyData(worldName);
} catch (Throwable e) {
Iris.error("Failed to move the Iris data of " + worldName + " to its new world folder!");
Iris.reportError(e);
e.printStackTrace();
}
}
}
public void addShutdownHook() {
if (shutdownHook != null) {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
@@ -652,7 +667,7 @@ public class Iris extends VolmitPlugin implements Listener {
.name(worldName)
.seed(1337)
.environment(dim.getEnvironment())
.worldFolder(new File(Bukkit.getWorldContainer(), worldName))
.worldFolder(WorldStorage.get().worldFolder(worldName))
.minHeight(dim.getMinHeight())
.maxHeight(dim.getMaxHeight())
.build();
@@ -674,7 +689,7 @@ public class Iris extends VolmitPlugin implements Listener {
@Nullable
public static IrisDimension loadDimension(@NonNull String worldName, @NonNull String id) {
File pack = new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pack"));
File pack = new File(WorldStorage.get().worldFolder(worldName), "iris/pack");
var dimension = pack.isDirectory() ? IrisData.get(pack).getDimensionLoader().load(id) : null;
if (dimension == null) dimension = IrisData.loadAnyDimension(id, null);
if (dimension == null) {
@@ -5,6 +5,7 @@ import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.volmit.iris.Iris;
import com.volmit.iris.core.loader.IrisData;
import com.volmit.iris.core.world.WorldStorage;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.collection.KMap;
@@ -17,7 +18,9 @@ import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
public class IrisWorlds {
@@ -34,24 +37,32 @@ public class IrisWorlds {
}
public static IrisWorlds get() {
return cache.aquire(() -> {
File file = Iris.instance.getDataFile("worlds.json");
if (!file.exists()) {
return new IrisWorlds(new KMap<>());
}
return cache.aquire(() -> new IrisWorlds(readWorldsFile()));
}
try {
String json = IO.readAll(file);
KMap<String, String> worlds = GSON.fromJson(json, TYPE);
return new IrisWorlds(Objects.requireNonNullElseGet(worlds, KMap::new));
} catch (Throwable e) {
Iris.error("Failed to load worlds.json!");
e.printStackTrace();
Iris.reportError(e);
}
public static Set<String> readKnownWorldNames() {
Set<String> names = new HashSet<>(readWorldsFile().keySet());
names.addAll(readBukkitWorlds().keySet());
return names;
}
return new IrisWorlds(new KMap<>());
});
private static KMap<String, String> readWorldsFile() {
File file = Iris.instance.getDataFile("worlds.json");
if (!file.exists()) {
return new KMap<>();
}
try {
String json = IO.readAll(file);
KMap<String, String> worlds = GSON.fromJson(json, TYPE);
return Objects.requireNonNullElseGet(worlds, KMap::new);
} catch (Throwable e) {
Iris.error("Failed to load worlds.json!");
e.printStackTrace();
Iris.reportError(e);
}
return new KMap<>();
}
public void put(String name, String type) {
@@ -85,7 +96,7 @@ public class IrisWorlds {
}
public void clean() {
dirty = worlds.entrySet().removeIf(entry -> !new File(Bukkit.getWorldContainer(), entry.getKey() + "/iris/pack/dimensions/" + entry.getValue() + ".json").exists());
dirty = worlds.entrySet().removeIf(entry -> !new File(WorldStorage.get().worldFolder(entry.getKey()), "iris/pack/dimensions/" + entry.getValue() + ".json").exists());
}
public synchronized void save() {
@@ -23,6 +23,7 @@ import com.volmit.iris.core.loader.IrisData;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.core.nms.datapack.IDataFixer;
import com.volmit.iris.core.world.WorldStorage;
import com.volmit.iris.engine.object.*;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
@@ -97,10 +98,7 @@ public class ServerConfigurator {
if (!IrisSettings.get().getGeneral().forceMainWorld.isEmpty()) {
return new KList<File>().qadd(new File(Bukkit.getWorldContainer(), IrisSettings.get().getGeneral().forceMainWorld + "/datapacks"));
}
KList<File> worlds = new KList<>();
Bukkit.getServer().getWorlds().forEach(w -> worlds.add(new File(w.getWorldFolder(), "datapacks")));
if (worlds.isEmpty()) worlds.add(new File(Bukkit.getWorldContainer(), ServerProperties.LEVEL_NAME + "/datapacks"));
return worlds;
return WorldStorage.get().datapackFolders();
}
public static boolean installDataPacks(boolean fullInstall) {
@@ -198,7 +196,7 @@ public class ServerConfigurator {
}
}
}
String key = getWorld(dimension.getLoader());
String key = worldFolderNameOf(dimension.getLoader());
if (key == null) key = dimension.getLoadKey();
else key += "/" + dimension.getLoadKey();
@@ -247,14 +245,13 @@ public class ServerConfigurator {
}
@Nullable
public static String getWorld(@NonNull IrisData data) {
String worldContainer = Bukkit.getWorldContainer().getAbsolutePath();
if (!worldContainer.endsWith(File.separator)) worldContainer += File.separator;
String path = data.getDataFolder().getAbsolutePath();
if (!path.startsWith(worldContainer)) return null;
int l = path.endsWith(File.separator) ? 11 : 10;
return path.substring(worldContainer.length(), path.length() - l);
private static String worldFolderNameOf(IrisData data) {
File pack = data.getDataFolder();
File iris = pack.getParentFile();
if (iris == null || iris.getParentFile() == null || !pack.getName().equals("pack") || !iris.getName().equals("iris")) {
return null;
}
return iris.getParentFile().getName();
}
@SneakyThrows
@@ -23,6 +23,7 @@ import com.volmit.iris.core.IrisSettings;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.core.service.StudioSVC;
import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.core.world.WorldStorage;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.collection.KList;
@@ -93,7 +94,7 @@ public class CommandIris implements DecreeExecutor {
return;
}
if (new File(Bukkit.getWorldContainer(), name).exists()) {
if (WorldStorage.get().worldFolder(name).exists()) {
sender().sendMessage(C.RED + "That folder already exists!");
return;
}
@@ -470,6 +471,7 @@ public class CommandIris implements DecreeExecutor {
String world
) {
World worldloaded = Bukkit.getWorld(world);
WorldStorage.get().adoptLegacyData(world);
worldNameToCheck = world;
boolean worldExists = doesWorldExist(worldNameToCheck);
WorldEngine = world;
@@ -479,8 +481,7 @@ public class CommandIris implements DecreeExecutor {
return;
}
String pathtodim = world + File.separator +"iris"+File.separator +"pack"+File.separator +"dimensions"+File.separator;
File directory = new File(Bukkit.getWorldContainer(), pathtodim);
File directory = new File(WorldStorage.get().worldFolder(world), "iris/pack/dimensions");
String dimension = null;
if (directory.exists() && directory.isDirectory()) {
@@ -533,8 +534,7 @@ public class CommandIris implements DecreeExecutor {
}
boolean doesWorldExist(String worldName) {
File worldContainer = Bukkit.getWorldContainer();
File worldDirectory = new File(worldContainer, worldName);
File worldDirectory = WorldStorage.get().worldFolder(worldName);
return worldDirectory.exists() && worldDirectory.isDirectory();
}
}
@@ -54,7 +54,7 @@ public class CommandLazyPregen implements DecreeExecutor {
) {
worldName = world.getName();
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
File worldDirectory = world.getWorldFolder();
File lazyFile = new File(worldDirectory, "lazygen.json");
if (lazyFile.exists()) {
sender().sendMessage(C.BLUE + "Lazy pregen is already in progress");
@@ -47,7 +47,7 @@ public class CommandTurboPregen implements DecreeExecutor {
) {
worldName = world.getName();
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
File worldDirectory = world.getWorldFolder();
File TurboFile = new File(worldDirectory, "turbogen.json");
if (TurboFile.exists()) {
if (TurboPregenerator.getInstance() != null) {
@@ -93,7 +93,7 @@ public class CommandTurboPregen implements DecreeExecutor {
@Decree(description = "Stop the active pregeneration task", aliases = "x")
public void stop(@Param(aliases = "world", description = "The world to pause") World world) throws IOException {
TurboPregenerator turboPregenInstance = TurboPregenerator.getInstance();
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
File worldDirectory = world.getWorldFolder();
File turboFile = new File(worldDirectory, "turbogen.json");
if (turboPregenInstance != null) {
@@ -117,7 +117,7 @@ public class CommandTurboPregen implements DecreeExecutor {
TurboPregenerator.setPausedTurbo(world);
sender().sendMessage(C.GREEN + "Paused/unpaused Turbo Pregen, now: " + (TurboPregenerator.isPausedTurbo(world) ? "Paused" : "Running") + ".");
} else {
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
File worldDirectory = world.getWorldFolder();
File TurboFile = new File(worldDirectory, "turbogen.json");
if (TurboFile.exists()){
TurboPregenerator.loadTurboGenerator(world.getName());
@@ -23,6 +23,8 @@ import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.container.BlockProperty;
import com.volmit.iris.core.nms.container.StructurePlacement;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.core.world.LegacyWorldStorage;
import com.volmit.iris.core.world.WorldStorage;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
import com.volmit.iris.util.collection.KList;
@@ -121,6 +123,10 @@ public interface INMSBinding {
Color getBiomeColor(Location location, BiomeColor type);
default WorldStorage getWorldStorage() {
return LegacyWorldStorage.INSTANCE;
}
default DataVersion getDataVersion() {
return DataVersion.V1_19_2;
}
@@ -221,7 +221,7 @@ public class DeepSearchPregenerator extends Thread implements Listener {
public void shutdownInstance(World world) throws IOException {
Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " Shutting down..");
DeepSearchJob job = jobs.get(world.getName());
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
File worldDirectory = world.getWorldFolder();
File deepFile = new File(worldDirectory, "DeepSearch.json");
if (job == null) {
@@ -215,7 +215,7 @@ public class LazyPregenerator extends Thread implements Listener {
public void shutdownInstance(World world) throws IOException {
Iris.info("LazyGen: " + C.IRIS + world.getName() + C.BLUE + " Shutting down..");
LazyPregenJob job = jobs.get(world.getName());
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
File worldDirectory = world.getWorldFolder();
File lazyFile = new File(worldDirectory, "lazygen.json");
if (job == null) {
@@ -287,7 +287,7 @@ public class TurboPregenerator extends Thread implements Listener {
public void shutdownInstance(World world) throws IOException {
Iris.info("turboGen: " + C.IRIS + world.getName() + C.BLUE + " Shutting down..");
TurboPregenJob job = jobs.get(world.getName());
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
File worldDirectory = world.getWorldFolder();
File turboFile = new File(worldDirectory, "turbogen.json");
if (job == null) {
@@ -3,6 +3,7 @@ package com.volmit.iris.core.service;
import com.volmit.iris.core.IrisSettings;
import com.volmit.iris.core.pregenerator.cache.PregenCache;
import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.core.world.WorldStorage;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.plugin.IrisService;
import com.volmit.iris.util.scheduling.Looper;
@@ -131,6 +132,6 @@ public class GlobalCacheSVC implements IrisService {
private static PregenCache createDefault0(String worldName) {
if (disabled) return PregenCache.EMPTY;
return PregenCache.create(new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pregen"))).sync();
return PregenCache.create(new File(WorldStorage.get().worldFolder(worldName), "iris/pregen")).sync();
}
}
@@ -25,6 +25,7 @@ import com.volmit.iris.core.ServerConfigurator;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.core.pregenerator.PregenTask;
import com.volmit.iris.core.service.StudioSVC;
import com.volmit.iris.core.world.WorldStorage;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
import com.volmit.iris.util.exceptions.IrisException;
@@ -124,7 +125,7 @@ public class IrisCreator {
sender = Iris.getSender();
if (!studio() || benchmark) {
Iris.service(StudioSVC.class).installIntoWorld(sender, d.getLoadKey(), new File(Bukkit.getWorldContainer(), name()));
Iris.service(StudioSVC.class).installIntoWorld(sender, d.getLoadKey(), WorldStorage.get().worldFolder(name()));
}
AtomicDouble pp = new AtomicDouble(0);
@@ -3,6 +3,7 @@ package com.volmit.iris.core.tools;
import com.volmit.iris.Iris;
import com.volmit.iris.core.pregenerator.PregenTask;
import com.volmit.iris.core.world.WorldStorage;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.collection.KList;
@@ -45,7 +46,7 @@ public class IrisPackBenchmarking {
.name("PackBenchmarking")
.start(() -> {
Iris.info("Setting up benchmark environment ");
IO.delete(new File(Bukkit.getWorldContainer(), "benchmark"));
IO.delete(WorldStorage.get().worldFolder("benchmark"));
createBenchmark();
while (!IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) {
J.sleep(1000);
@@ -19,6 +19,7 @@
package com.volmit.iris.core.tools;
import com.volmit.iris.core.loader.IrisData;
import com.volmit.iris.core.world.WorldStorage;
import com.volmit.iris.engine.object.*;
import com.volmit.iris.engine.platform.BukkitChunkGenerator;
import org.bukkit.Bukkit;
@@ -71,7 +72,7 @@ public class IrisWorldCreator {
.minHeight(dim.getMinHeight())
.maxHeight(dim.getMaxHeight())
.seed(seed)
.worldFolder(new File(Bukkit.getWorldContainer(), name))
.worldFolder(WorldStorage.get().worldFolder(name))
.environment(findEnvironment())
.build();
ChunkGenerator g = new BukkitChunkGenerator(w, studio, studio
@@ -0,0 +1,29 @@
package com.volmit.iris.core.world;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.misc.ServerProperties;
import org.bukkit.Bukkit;
import org.bukkit.World;
import java.io.File;
public class LegacyWorldStorage implements WorldStorage {
public static final LegacyWorldStorage INSTANCE = new LegacyWorldStorage();
@Override
public File worldFolder(String worldName) {
return new File(Bukkit.getWorldContainer(), worldName);
}
@Override
public KList<File> datapackFolders() {
KList<File> folders = new KList<>();
for (World world : Bukkit.getWorlds()) {
folders.add(new File(world.getWorldFolder(), "datapacks"));
}
if (folders.isEmpty()) {
folders.add(new File(worldFolder(ServerProperties.LEVEL_NAME), "datapacks"));
}
return folders;
}
}
@@ -0,0 +1,25 @@
package com.volmit.iris.core.world;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.util.collection.KList;
import java.io.File;
public interface WorldStorage {
static WorldStorage get() {
return INMS.get().getWorldStorage();
}
/**
* Also resolves worlds that are not loaded.
*/
File worldFolder(String worldName);
KList<File> datapackFolders();
/**
* Has to run before the server loads the world, because the server deletes the old folder while migrating it.
*/
default void adoptLegacyData(String worldName) {
}
}
@@ -20,6 +20,7 @@ package com.volmit.iris.engine.object;
import com.volmit.iris.Iris;
import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.core.world.WorldStorage;
import com.volmit.iris.util.collection.KList;
import lombok.*;
import lombok.experimental.Accessors;
@@ -108,7 +109,7 @@ public class IrisWorld {
public void bind(WorldInfo worldInfo) {
name(worldInfo.getName())
.worldFolder(new File(Bukkit.getWorldContainer(), worldInfo.getName()))
.worldFolder(WorldStorage.get().worldFolder(worldInfo.getName()))
.minHeight(worldInfo.getMinHeight())
.maxHeight(worldInfo.getMaxHeight())
.environment(worldInfo.getEnvironment());