diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index d848cdd9e..db0d69377 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -35,6 +35,7 @@ import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.core.tools.IrisWorldCreator; import com.volmit.iris.engine.EnginePanic; import com.volmit.iris.engine.object.IrisCompat; +import com.volmit.iris.engine.object.IrisContextInjector; import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.engine.object.IrisWorld; import com.volmit.iris.engine.platform.BukkitChunkGenerator; @@ -466,6 +467,7 @@ public class Iris extends VolmitPlugin implements Listener { configWatcher = new FileWatcher(getDataFile("settings.json")); services.values().forEach(IrisService::onEnable); services.values().forEach(this::registerListener); + registerListener(new IrisContextInjector()); J.s(() -> { J.a(() -> PaperLib.suggestPaper(this)); J.a(() -> IO.delete(getTemp())); diff --git a/core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java b/core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java index d75c87672..ac3894f65 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java +++ b/core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java @@ -18,7 +18,9 @@ package com.volmit.iris.core.nms; +import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.core.nms.container.Pair; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.collection.KList; @@ -91,7 +93,9 @@ public interface INMSBinding { MCABiomeContainer newBiomeContainer(int min, int max); default World createWorld(WorldCreator c) { - return c.createWorld(); + try (var ignored = injectLevelStems()) { + return c.createWorld(); + } } int countCustomBiomes(); @@ -125,4 +129,12 @@ public interface INMSBinding { } KList getStructureKeys(); + + default AutoClosing injectLevelStems() { + return new AutoClosing(() -> {}); + } + + default Pair injectUncached(boolean overworld, boolean nether, boolean end) { + return new Pair<>(0, injectLevelStems()); + } } diff --git a/core/src/main/java/com/volmit/iris/core/nms/container/AutoClosing.java b/core/src/main/java/com/volmit/iris/core/nms/container/AutoClosing.java new file mode 100644 index 000000000..afa2ba9dc --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/nms/container/AutoClosing.java @@ -0,0 +1,22 @@ +package com.volmit.iris.core.nms.container; + +import com.volmit.iris.util.function.NastyRunnable; +import lombok.AllArgsConstructor; + +import java.util.concurrent.atomic.AtomicBoolean; + +@AllArgsConstructor +public class AutoClosing implements AutoCloseable { + private final AtomicBoolean closed = new AtomicBoolean(); + private final NastyRunnable action; + + @Override + public void close() { + if (closed.getAndSet(true)) return; + try { + action.run(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisContextInjector.java b/core/src/main/java/com/volmit/iris/engine/object/IrisContextInjector.java new file mode 100644 index 000000000..772a4c560 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisContextInjector.java @@ -0,0 +1,67 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.nms.container.AutoClosing; +import com.volmit.iris.util.misc.ServerProperties; +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldInitEvent; + +import java.util.List; + +import static com.volmit.iris.Iris.instance; + +public class IrisContextInjector implements Listener { + private AutoClosing autoClosing = null; + private final int totalWorlds; + private int worldCounter = 0; + + public IrisContextInjector() { + if (!Bukkit.getWorlds().isEmpty()) { + totalWorlds = 0; + return; + } + + String levelName = ServerProperties.LEVEL_NAME; + List irisWorlds = irisWorlds(); + boolean overworld = irisWorlds.contains(levelName); + boolean nether = irisWorlds.contains(levelName + "_nether"); + boolean end = irisWorlds.contains(levelName + "_end"); + + int i = 1; + if (Bukkit.getAllowNether()) i++; + if (Bukkit.getAllowEnd()) i++; + + if (overworld || nether || end) { + var pair = INMS.get().injectUncached(overworld, nether, end); + i += pair.getA() - 3; + autoClosing = pair.getB(); + } + + totalWorlds = i; + } + + @EventHandler + public void on(WorldInitEvent event) { + if (++worldCounter < totalWorlds) return; + if (autoClosing != null) { + autoClosing.close(); + autoClosing = null; + } + instance.unregisterListener(this); + } + + private List irisWorlds() { + var config = YamlConfiguration.loadConfiguration(ServerProperties.BUKKIT_YML); + ConfigurationSection section = config.getConfigurationSection("worlds"); + if (section == null) return List.of(); + + return section.getKeys(false) + .stream() + .filter(k -> section.getString(k + ".generator", "").startsWith("Iris")) + .toList(); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/misc/ServerProperties.java b/core/src/main/java/com/volmit/iris/util/misc/ServerProperties.java new file mode 100644 index 000000000..b79e844f1 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/misc/ServerProperties.java @@ -0,0 +1,39 @@ +package com.volmit.iris.util.misc; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +public class ServerProperties { + public static final Properties DATA = new Properties(); + public static final File SERVER_PROPERTIES; + public static final File BUKKIT_YML; + + public static final String LEVEL_NAME = DATA.getProperty("level-name", "world"); + + static { + String[] args = ProcessHandle.current() + .info() + .arguments() + .orElse(new String[0]); + + String propertiesPath = "server.properties"; + String bukkitYml = "bukkit.yml"; + + for (int i = 0; i < args.length - 1; i++) { + switch (args[i]) { + case "-c", "--config" -> propertiesPath = args[i + 1]; + case "-b", "--bukkit-settings" -> bukkitYml = args[i + 1]; + } + } + + SERVER_PROPERTIES = new File(propertiesPath); + BUKKIT_YML = new File(bukkitYml); + try (FileInputStream in = new FileInputStream(SERVER_PROPERTIES)){ + DATA.load(in); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/NMSBinding.java b/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/NMSBinding.java index 32964212e..0a0e51a6e 100644 --- a/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/NMSBinding.java +++ b/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/NMSBinding.java @@ -5,6 +5,7 @@ import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Lifecycle; import com.volmit.iris.Iris; import com.volmit.iris.core.nms.INMSBinding; +import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; @@ -78,11 +79,10 @@ import java.io.DataOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Vector; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; public class NMSBinding implements INMSBinding { @@ -94,6 +94,7 @@ public class NMSBinding implements INMSBinding { private final AtomicCache> registryCache = new AtomicCache<>(); private final AtomicCache> globalCache = new AtomicCache<>(); private final AtomicCache registryAccess = new AtomicCache<>(); + private final ReentrantLock dataContextLock = new ReentrantLock(true); private final AtomicCache byIdRef = new AtomicCache<>(); private Field biomeStorageCache = null; @@ -574,8 +575,9 @@ public class NMSBinding implements INMSBinding { public void inject(long seed, Engine engine, World world) { var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; - if (!chunkMap.level.dimension().location().getPath().startsWith("iris")) - Iris.error("Loaded world %s with invalid dimension type!", world.getName()); + var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null); + if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris")) + Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString()); chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world); } @@ -637,38 +639,79 @@ public class NMSBinding implements INMSBinding { } } + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + @Override @SneakyThrows - public World createWorld(WorldCreator creator) { + public com.volmit.iris.core.nms.container.Pair injectUncached(boolean overworld, boolean nether, boolean end) { + var reg = registry(); + var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); + field.setAccessible(true); + + AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) + ) + ); + + var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM); + var old = (Map>, Registry>) field.get(reg); + var fake = new HashMap<>(old); + fake.put(Registries.LEVEL_STEM, injected); + field.set(reg, fake); + + return new com.volmit.iris.core.nms.container.Pair<>( + injected.size(), + new AutoClosing(() -> { + closing.close(); + field.set(reg, old); + })); + } + + private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { + return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), false, true, true, true) + )); + } + + @SneakyThrows + private AutoClosing inject(Function transformer) { + if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); + var server = ((CraftServer) Bukkit.getServer()); var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); var nmsServer = server.getServer(); var old = nmsServer.worldLoader; field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess() - ))); + field.set(nmsServer, transformer.apply(old)); - try { - return server.createWorld(creator); - } finally { + return new AutoClosing(() -> { field.set(nmsServer, old); - } + dataContextLock.unlock(); + }); } - private RegistryAccess.Frozen createRegistryAccess() { + private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { var access = registry(); var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE); var levelStems = access.registryOrThrow(Registries.LEVEL_STEM); var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); - register(fake, levelStems, dimensions, LevelStem.OVERWORLD); - register(fake, levelStems, dimensions, LevelStem.NETHER); - register(fake, levelStems, dimensions, LevelStem.END); + if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD); + if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER); + if (end) register(fake, levelStems, dimensions, LevelStem.END); + copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, levelStems); return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); } @@ -679,4 +722,14 @@ public class NMSBinding implements INMSBinding { levelStems.getOrThrow(key).generator() ), Lifecycle.stable()); } + + private void copy(MappedRegistry target, Registry source) { + if (source == null) return; + source.registryKeySet().forEach(key -> { + var value = source.get(key); + var info = source.lifecycle(value); + if (value != null && info != null && !target.containsKey(key)) + target.register(key, value, info); + }); + } } diff --git a/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java b/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java index f37090480..924e04791 100644 --- a/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java +++ b/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java @@ -8,14 +8,14 @@ import java.io.DataOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Vector; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Lifecycle; +import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.util.scheduling.J; import lombok.SneakyThrows; @@ -91,6 +91,7 @@ public class NMSBinding implements INMSBinding { private final AtomicCache> registryCache = new AtomicCache<>(); private final AtomicCache> globalCache = new AtomicCache<>(); private final AtomicCache registryAccess = new AtomicCache<>(); + private final ReentrantLock dataContextLock = new ReentrantLock(true); private final AtomicCache byIdRef = new AtomicCache<>(); private Field biomeStorageCache = null; @@ -543,8 +544,9 @@ public class NMSBinding implements INMSBinding { public void inject(long seed, Engine engine, World world) { var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; - if (!chunkMap.level.dimension().location().getPath().startsWith("iris")) - Iris.error("Loaded world %s with invalid dimension type!", world.getName()); + var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null); + if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris")) + Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString()); chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world); } @@ -638,38 +640,79 @@ public class NMSBinding implements INMSBinding { return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey()))); } + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + @Override @SneakyThrows - public World createWorld(WorldCreator creator) { + public com.volmit.iris.core.nms.container.Pair injectUncached(boolean overworld, boolean nether, boolean end) { + var reg = registry(); + var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); + field.setAccessible(true); + + AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) + ) + ); + + var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM); + var old = (Map>, Registry>) field.get(reg); + var fake = new HashMap<>(old); + fake.put(Registries.LEVEL_STEM, injected); + field.set(reg, fake); + + return new com.volmit.iris.core.nms.container.Pair<>( + injected.size(), + new AutoClosing(() -> { + closing.close(); + field.set(reg, old); + })); + } + + private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { + return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), false, true, true, true) + )); + } + + @SneakyThrows + private AutoClosing inject(Function transformer) { + if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); + var server = ((CraftServer) Bukkit.getServer()); var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); var nmsServer = server.getServer(); var old = nmsServer.worldLoader; field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess() - ))); + field.set(nmsServer, transformer.apply(old)); - try { - return server.createWorld(creator); - } finally { + return new AutoClosing(() -> { field.set(nmsServer, old); - } + dataContextLock.unlock(); + }); } - private RegistryAccess.Frozen createRegistryAccess() { + private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { var access = registry(); var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE); var levelStems = access.registryOrThrow(Registries.LEVEL_STEM); var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); - register(fake, levelStems, dimensions, LevelStem.OVERWORLD); - register(fake, levelStems, dimensions, LevelStem.NETHER); - register(fake, levelStems, dimensions, LevelStem.END); + if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD); + if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER); + if (end) register(fake, levelStems, dimensions, LevelStem.END); + copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, levelStems); return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); } @@ -680,4 +723,14 @@ public class NMSBinding implements INMSBinding { levelStems.getOrThrow(key).generator() ), Lifecycle.stable()); } + + private void copy(MappedRegistry target, Registry source) { + if (source == null) return; + source.registryKeySet().forEach(key -> { + var value = source.get(key); + var info = source.lifecycle(value); + if (value != null && info != null && !target.containsKey(key)) + target.register(key, value, info); + }); + } } diff --git a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java index 06c84620b..283e4a594 100644 --- a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java @@ -8,14 +8,14 @@ import java.io.DataOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Vector; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Lifecycle; +import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.util.scheduling.J; import lombok.SneakyThrows; @@ -91,6 +91,7 @@ public class NMSBinding implements INMSBinding { private final AtomicCache> registryCache = new AtomicCache<>(); private final AtomicCache> globalCache = new AtomicCache<>(); private final AtomicCache registryAccess = new AtomicCache<>(); + private final ReentrantLock dataContextLock = new ReentrantLock(true); private final AtomicCache byIdRef = new AtomicCache<>(); private Field biomeStorageCache = null; @@ -543,8 +544,9 @@ public class NMSBinding implements INMSBinding { public void inject(long seed, Engine engine, World world) { var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; - if (!chunkMap.level.dimension().location().getPath().startsWith("iris")) - Iris.error("Loaded world %s with invalid dimension type!", world.getName()); + var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null); + if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris")) + Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString()); chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world); } @@ -639,38 +641,79 @@ public class NMSBinding implements INMSBinding { return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey()))); } + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + @Override @SneakyThrows - public World createWorld(WorldCreator creator) { + public com.volmit.iris.core.nms.container.Pair injectUncached(boolean overworld, boolean nether, boolean end) { + var reg = registry(); + var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); + field.setAccessible(true); + + AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) + ) + ); + + var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM); + var old = (Map>, Registry>) field.get(reg); + var fake = new HashMap<>(old); + fake.put(Registries.LEVEL_STEM, injected); + field.set(reg, fake); + + return new com.volmit.iris.core.nms.container.Pair<>( + injected.size(), + new AutoClosing(() -> { + closing.close(); + field.set(reg, old); + })); + } + + private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { + return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), false, true, true, true) + )); + } + + @SneakyThrows + private AutoClosing inject(Function transformer) { + if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); + var server = ((CraftServer) Bukkit.getServer()); var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); var nmsServer = server.getServer(); var old = nmsServer.worldLoader; field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess() - ))); + field.set(nmsServer, transformer.apply(old)); - try { - return server.createWorld(creator); - } finally { + return new AutoClosing(() -> { field.set(nmsServer, old); - } + dataContextLock.unlock(); + }); } - private RegistryAccess.Frozen createRegistryAccess() { + private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { var access = registry(); var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE); var levelStems = access.registryOrThrow(Registries.LEVEL_STEM); var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); - register(fake, levelStems, dimensions, LevelStem.OVERWORLD); - register(fake, levelStems, dimensions, LevelStem.NETHER); - register(fake, levelStems, dimensions, LevelStem.END); + if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD); + if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER); + if (end) register(fake, levelStems, dimensions, LevelStem.END); + copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, levelStems); return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); } @@ -681,4 +724,14 @@ public class NMSBinding implements INMSBinding { levelStems.getOrThrow(key).generator() ), Lifecycle.stable()); } + + private void copy(MappedRegistry target, Registry source) { + if (source == null) return; + source.registryKeySet().forEach(key -> { + var value = source.get(key); + var info = source.lifecycle(value); + if (value != null && info != null && !target.containsKey(key)) + target.register(key, value, info); + }); + } } diff --git a/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/NMSBinding.java b/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/NMSBinding.java index be7a8c0be..14e103eb5 100644 --- a/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/NMSBinding.java +++ b/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/NMSBinding.java @@ -6,9 +6,12 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Lifecycle; +import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.util.nbt.tag.CompoundTag; @@ -96,6 +99,7 @@ public class NMSBinding implements INMSBinding { private final AtomicCache> registryCache = new AtomicCache<>(); private final AtomicCache> globalCache = new AtomicCache<>(); private final AtomicCache registryAccess = new AtomicCache<>(); + private final ReentrantLock dataContextLock = new ReentrantLock(true); private final AtomicCache byIdRef = new AtomicCache<>(); private Field biomeStorageCache = null; @@ -551,8 +555,9 @@ public class NMSBinding implements INMSBinding { public void inject(long seed, Engine engine, World world) { var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; - if (!chunkMap.level.dimension().location().getPath().startsWith("iris")) - Iris.error("Loaded world %s with invalid dimension type!", world.getName()); + var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null); + if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris")) + Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString()); chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world); } @@ -661,38 +666,79 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + @Override @SneakyThrows - public World createWorld(WorldCreator creator) { + public com.volmit.iris.core.nms.container.Pair injectUncached(boolean overworld, boolean nether, boolean end) { + var reg = registry(); + var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); + field.setAccessible(true); + + AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) + ) + ); + + var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM); + var old = (Map>, Registry>) field.get(reg); + var fake = new HashMap<>(old); + fake.put(Registries.LEVEL_STEM, injected); + field.set(reg, fake); + + return new com.volmit.iris.core.nms.container.Pair<>( + injected.size(), + new AutoClosing(() -> { + closing.close(); + field.set(reg, old); + })); + } + + private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { + return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), false, true, true, true) + )); + } + + @SneakyThrows + private AutoClosing inject(Function transformer) { + if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); + var server = ((CraftServer) Bukkit.getServer()); var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); var nmsServer = server.getServer(); var old = nmsServer.worldLoader; field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess() - ))); + field.set(nmsServer, transformer.apply(old)); - try { - return server.createWorld(creator); - } finally { + return new AutoClosing(() -> { field.set(nmsServer, old); - } + dataContextLock.unlock(); + }); } - private RegistryAccess.Frozen createRegistryAccess() { + private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { var access = registry(); var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE); var levelStems = access.registryOrThrow(Registries.LEVEL_STEM); var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); - register(fake, levelStems, dimensions, LevelStem.OVERWORLD); - register(fake, levelStems, dimensions, LevelStem.NETHER); - register(fake, levelStems, dimensions, LevelStem.END); + if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD); + if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER); + if (end) register(fake, levelStems, dimensions, LevelStem.END); + copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, levelStems); return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); } @@ -703,4 +749,14 @@ public class NMSBinding implements INMSBinding { levelStems.getOrThrow(key).generator() ), RegistrationInfo.BUILT_IN); } + + private void copy(MappedRegistry target, Registry source) { + if (source == null) return; + source.registryKeySet().forEach(key -> { + var value = source.get(key); + var info = source.registrationInfo(key).orElse(null); + if (value != null && info != null && !target.containsKey(key)) + target.register(key, value, info); + }); + } } diff --git a/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java b/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java index 6e540ca26..da85e16de 100644 --- a/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java +++ b/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java @@ -10,9 +10,12 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Lifecycle; +import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.util.scheduling.J; @@ -93,6 +96,7 @@ public class NMSBinding implements INMSBinding { private final AtomicCache> registryCache = new AtomicCache<>(); private final AtomicCache> globalCache = new AtomicCache<>(); private final AtomicCache registryAccess = new AtomicCache<>(); + private final ReentrantLock dataContextLock = new ReentrantLock(true); private final AtomicCache byIdRef = new AtomicCache<>(); private Field biomeStorageCache = null; @@ -551,8 +555,9 @@ public class NMSBinding implements INMSBinding { var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class); worldGenContextField.setAccessible(true); var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap); - if (!chunkMap.level.dimension().location().getPath().startsWith("iris")) - Iris.error("Loaded world %s with invalid dimension type!", world.getName()); + var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null); + if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris")) + Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString()); var newContext = new WorldGenContext( worldGenContext.level(), new IrisChunkGenerator(worldGenContext.generator(), seed, engine, world), @@ -665,38 +670,79 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + @Override @SneakyThrows - public World createWorld(WorldCreator creator) { + public com.volmit.iris.core.nms.container.Pair injectUncached(boolean overworld, boolean nether, boolean end) { + var reg = registry(); + var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); + field.setAccessible(true); + + AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) + ) + ); + + var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM); + var old = (Map>, Registry>) field.get(reg); + var fake = new HashMap<>(old); + fake.put(Registries.LEVEL_STEM, injected); + field.set(reg, fake); + + return new com.volmit.iris.core.nms.container.Pair<>( + injected.size(), + new AutoClosing(() -> { + closing.close(); + field.set(reg, old); + })); + } + + private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { + return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), false, true, true, true) + )); + } + + @SneakyThrows + private AutoClosing inject(Function transformer) { + if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); + var server = ((CraftServer) Bukkit.getServer()); var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); var nmsServer = server.getServer(); var old = nmsServer.worldLoader; field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess() - ))); + field.set(nmsServer, transformer.apply(old)); - try { - return server.createWorld(creator); - } finally { + return new AutoClosing(() -> { field.set(nmsServer, old); - } + dataContextLock.unlock(); + }); } - private RegistryAccess.Frozen createRegistryAccess() { + private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { var access = registry(); var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE); var levelStems = access.registryOrThrow(Registries.LEVEL_STEM); var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); - register(fake, levelStems, dimensions, LevelStem.OVERWORLD); - register(fake, levelStems, dimensions, LevelStem.NETHER); - register(fake, levelStems, dimensions, LevelStem.END); + if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD); + if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER); + if (end) register(fake, levelStems, dimensions, LevelStem.END); + copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, levelStems); return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); } @@ -707,4 +753,14 @@ public class NMSBinding implements INMSBinding { levelStems.getOrThrow(key).generator() ), RegistrationInfo.BUILT_IN); } + + private void copy(MappedRegistry target, Registry source) { + if (source == null) return; + source.registryKeySet().forEach(key -> { + var value = source.get(key); + var info = source.registrationInfo(key).orElse(null); + if (value != null && info != null && !target.containsKey(key)) + target.register(key, value, info); + }); + } } diff --git a/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/NMSBinding.java b/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/NMSBinding.java index 3170bbb00..5773392ae 100644 --- a/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/NMSBinding.java +++ b/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/NMSBinding.java @@ -6,9 +6,13 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; import com.mojang.serialization.Lifecycle; +import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.core.nms.container.Pair; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.util.scheduling.J; import lombok.SneakyThrows; @@ -82,6 +86,7 @@ public class NMSBinding implements INMSBinding { private final AtomicCache> registryCache = new AtomicCache<>(); private final AtomicCache> globalCache = new AtomicCache<>(); private final AtomicCache registryAccess = new AtomicCache<>(); + private final ReentrantLock dataContextLock = new ReentrantLock(true); private final AtomicCache byIdRef = new AtomicCache<>(); private Field biomeStorageCache = null; @@ -540,8 +545,9 @@ public class NMSBinding implements INMSBinding { var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class); worldGenContextField.setAccessible(true); var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap); - if (!chunkMap.level.dimension().location().getPath().startsWith("iris")) - Iris.error("Loaded world %s with invalid dimension type!", world.getName()); + var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null); + if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris")) + Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString()); var newContext = new WorldGenContext( worldGenContext.level(), new IrisChunkGenerator(worldGenContext.generator(), seed, engine, world), @@ -654,38 +660,79 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + @Override @SneakyThrows - public World createWorld(WorldCreator creator) { + public Pair injectUncached(boolean overworld, boolean nether, boolean end) { + var reg = registry(); + var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); + field.setAccessible(true); + + AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) + ) + ); + + var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM); + var old = (Map>, Registry>) field.get(reg); + var fake = new HashMap<>(old); + fake.put(Registries.LEVEL_STEM, injected); + field.set(reg, fake); + + return new Pair<>( + injected.size(), + new AutoClosing(() -> { + closing.close(); + field.set(reg, old); + })); + } + + private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { + return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), false, true, true, true) + )); + } + + @SneakyThrows + private AutoClosing inject(Function transformer) { + if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); + var server = ((CraftServer) Bukkit.getServer()); var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); var nmsServer = server.getServer(); var old = nmsServer.worldLoader; field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess() - ))); + field.set(nmsServer, transformer.apply(old)); - try { - return server.createWorld(creator); - } finally { + return new AutoClosing(() -> { field.set(nmsServer, old); - } + dataContextLock.unlock(); + }); } - private RegistryAccess.Frozen createRegistryAccess() { + private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { var access = registry(); var dimensions = access.lookupOrThrow(Registries.DIMENSION_TYPE); var levelStems = access.lookupOrThrow(Registries.LEVEL_STEM); var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); - register(fake, levelStems, dimensions, LevelStem.OVERWORLD); - register(fake, levelStems, dimensions, LevelStem.NETHER); - register(fake, levelStems, dimensions, LevelStem.END); + if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD); + if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER); + if (end) register(fake, levelStems, dimensions, LevelStem.END); + copy(fake, datapack.lookup(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, levelStems); return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); } @@ -696,4 +743,14 @@ public class NMSBinding implements INMSBinding { levelStems.getValueOrThrow(key).generator() ), RegistrationInfo.BUILT_IN); } + + private void copy(MappedRegistry target, Registry source) { + if (source == null) return; + source.listElementIds().forEach(key -> { + var value = source.getValue(key); + var info = source.registrationInfo(key).orElse(null); + if (value != null && info != null && !target.containsKey(key)) + target.register(key, value, info); + }); + } } diff --git a/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/NMSBinding.java b/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/NMSBinding.java index 2a45ca360..e31895550 100644 --- a/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/NMSBinding.java +++ b/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/NMSBinding.java @@ -4,7 +4,9 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.serialization.Lifecycle; import com.volmit.iris.Iris; import com.volmit.iris.core.nms.INMSBinding; +import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.core.nms.container.Pair; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; @@ -72,6 +74,8 @@ import java.lang.reflect.Modifier; import java.util.List; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; public class NMSBinding implements INMSBinding { private final KMap baseBiomeCache = new KMap<>(); @@ -81,6 +85,7 @@ public class NMSBinding implements INMSBinding { private final AtomicCache> registryCache = new AtomicCache<>(); private final AtomicCache> globalCache = new AtomicCache<>(); private final AtomicCache registryAccess = new AtomicCache<>(); + private final ReentrantLock dataContextLock = new ReentrantLock(true); private final AtomicCache byIdRef = new AtomicCache<>(); private Field biomeStorageCache = null; @@ -539,8 +544,9 @@ public class NMSBinding implements INMSBinding { var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class); worldGenContextField.setAccessible(true); var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap); - if (!chunkMap.level.dimension().location().getPath().startsWith("iris")) - Iris.error("Loaded world %s with invalid dimension type!", world.getName()); + var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null); + if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris")) + Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString()); var newContext = new WorldGenContext( worldGenContext.level(), new IrisChunkGenerator(worldGenContext.generator(), seed, engine, world), @@ -653,38 +659,79 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + @Override @SneakyThrows - public World createWorld(WorldCreator creator) { + public Pair injectUncached(boolean overworld, boolean nether, boolean end) { + var reg = registry(); + var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); + field.setAccessible(true); + + AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) + ) + ); + + var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM); + var old = (Map>, Registry>) field.get(reg); + var fake = new HashMap<>(old); + fake.put(Registries.LEVEL_STEM, injected); + field.set(reg, fake); + + return new Pair<>( + injected.size(), + new AutoClosing(() -> { + closing.close(); + field.set(reg, old); + })); + } + + private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { + return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( + old.resources(), + old.dataConfiguration(), + old.datapackWorldgen(), + createRegistryAccess(old.datapackDimensions(), false, true, true, true) + )); + } + + @SneakyThrows + private AutoClosing inject(Function transformer) { + if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); + var server = ((CraftServer) Bukkit.getServer()); var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); var nmsServer = server.getServer(); var old = nmsServer.worldLoader; field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess() - ))); + field.set(nmsServer, transformer.apply(old)); - try { - return server.createWorld(creator); - } finally { + return new AutoClosing(() -> { field.set(nmsServer, old); - } + dataContextLock.unlock(); + }); } - private RegistryAccess.Frozen createRegistryAccess() { + private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { var access = registry(); var dimensions = access.lookupOrThrow(Registries.DIMENSION_TYPE); var levelStems = access.lookupOrThrow(Registries.LEVEL_STEM); var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); - register(fake, levelStems, dimensions, LevelStem.OVERWORLD); - register(fake, levelStems, dimensions, LevelStem.NETHER); - register(fake, levelStems, dimensions, LevelStem.END); + if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD); + if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER); + if (end) register(fake, levelStems, dimensions, LevelStem.END); + copy(fake, datapack.lookup(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, levelStems); return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); } @@ -695,4 +742,14 @@ public class NMSBinding implements INMSBinding { levelStems.getValueOrThrow(key).generator() ), RegistrationInfo.BUILT_IN); } + + private void copy(MappedRegistry target, Registry source) { + if (source == null) return; + source.listElementIds().forEach(key -> { + var value = source.getValue(key); + var info = source.registrationInfo(key).orElse(null); + if (value != null && info != null && !target.containsKey(key)) + target.register(key, value, info); + }); + } }