diff --git a/build.gradle b/build.gradle index 4a0d19784..6d02651b2 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,7 @@ plugins { } -version '3.6.3-1.20.1-1.21.4' +version '3.6.5-1.20.1-1.21.4' // ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED // ======================== WINDOWS ============================= diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index 45e7a4765..07e01c6e4 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; @@ -458,9 +459,12 @@ public class Iris extends VolmitPlugin implements Listener { initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class) i.getClass(), (IrisService) i)); INMS.get(); IO.delete(new File("iris")); + compat = IrisCompat.configured(getDataFile("compat.json")); + ServerConfigurator.configure(); + new IrisContextInjector(); IrisSafeguard.IrisSafeguardSystem(); getSender().setTag(getTag()); - compat = IrisCompat.configured(getDataFile("compat.json")); + IrisSafeguard.earlySplash(); linkMultiverseCore = new MultiverseCoreLink(); linkMythicMobs = new MythicMobsLink(); configWatcher = new FileWatcher(getDataFile("settings.json")); @@ -515,11 +519,10 @@ public class Iris extends VolmitPlugin implements Listener { Iris.info("Loading World: %s | Generator: %s", s, generator); Iris.info(C.LIGHT_PURPLE + "Preparing Spawn for " + s + "' using Iris:" + generator + "..."); - new WorldCreator(s) - .type(IrisWorldCreator.IRIS) + WorldCreator c = new WorldCreator(s) .generator(getDefaultWorldGenerator(s, generator)) - .environment(IrisData.loadAnyDimension(generator).getEnvironment()) - .createWorld(); + .environment(IrisData.loadAnyDimension(generator).getEnvironment()); + INMS.get().createWorld(c); Iris.info(C.LIGHT_PURPLE + "Loaded " + s + "!"); } } catch (Throwable e) { diff --git a/core/src/main/java/com/volmit/iris/core/IrisSettings.java b/core/src/main/java/com/volmit/iris/core/IrisSettings.java index 8a687b564..5381a7e39 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -181,6 +181,7 @@ public class IrisSettings { public boolean splashLogoStartup = true; public boolean useConsoleCustomColors = true; public boolean useCustomColorsIngame = true; + public boolean adjustVanillaHeight = false; public String forceMainWorld = ""; public int spinh = -20; public int spins = 7; diff --git a/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java b/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java index 9a641f1d3..ffeae3115 100644 --- a/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java +++ b/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java @@ -28,21 +28,24 @@ import com.volmit.iris.engine.object.IrisBiomeCustom; import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.engine.object.IrisRange; import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.format.C; +import com.volmit.iris.util.misc.ServerProperties; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.J; import lombok.Data; +import lombok.NonNull; import org.bukkit.Bukkit; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.util.Arrays; -import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; @@ -89,12 +92,13 @@ public class ServerConfigurator { } } - private static List getDatapacksFolder() { + private static KList getDatapacksFolder() { if (!IrisSettings.get().getGeneral().forceMainWorld.isEmpty()) { return new KList().qadd(new File(Bukkit.getWorldContainer(), IrisSettings.get().getGeneral().forceMainWorld + "/datapacks")); } KList 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; } @@ -105,14 +109,16 @@ public class ServerConfigurator { public static void installDataPacks(IDataFixer fixer, boolean fullInstall) { Iris.info("Checking Data Packs..."); DimensionHeight height = new DimensionHeight(fixer); + KList folders = getDatapacksFolder(); + KMap> biomes = new KMap<>(); allPacks().flatMap(height::merge) + .parallel() .forEach(dim -> { - for (File dpack : getDatapacksFolder()) { - Iris.verbose(" Checking Dimension " + dim.getLoadFile().getPath()); - dim.installDataPack(fixer, dim::getLoader, dpack, height); - } + Iris.verbose(" Checking Dimension " + dim.getLoadFile().getPath()); + dim.installBiomes(fixer, dim::getLoader, folders, biomes.computeIfAbsent(dim.getLoadKey(), k -> new KSet<>())); }); + IrisDimension.writeShared(folders, height); Iris.info("Data Packs Setup!"); @@ -164,7 +170,7 @@ public class ServerConfigurator { Iris.warn("This will only happen when your pack changes (updates/first time setup)"); Iris.warn("(You can disable this auto restart in iris settings)"); J.s(() -> { - Iris.warn("Looks like the restart command diddn't work. Stopping the server instead!"); + Iris.warn("Looks like the restart command didn't work. Stopping the server instead!"); Bukkit.shutdown(); }, 100); Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "restart"); @@ -172,22 +178,24 @@ public class ServerConfigurator { } public static boolean verifyDataPackInstalled(IrisDimension dimension) { - IrisData idm = IrisData.get(Iris.instance.getDataFolder("packs", dimension.getLoadKey())); KSet keys = new KSet<>(); boolean warn = false; - for (IrisBiome i : dimension.getAllBiomes(() -> idm)) { + for (IrisBiome i : dimension.getAllBiomes(dimension::getLoader)) { if (i.isCustom()) { for (IrisBiomeCustom j : i.getCustomDerivitives()) { keys.add(dimension.getLoadKey() + ":" + j.getId()); } } } + String key = getWorld(dimension.getLoader()); + if (key == null) key = dimension.getLoadKey(); + else key += "/" + dimension.getLoadKey(); if (!INMS.get().supportsDataPacks()) { if (!keys.isEmpty()) { Iris.warn("==================================================================================="); - Iris.warn("Pack " + dimension.getLoadKey() + " has " + keys.size() + " custom biome(s). "); + Iris.warn("Pack " + key + " has " + keys.size() + " custom biome(s). "); Iris.warn("Your server version does not yet support datapacks for iris."); Iris.warn("The world will generate these biomes as backup biomes."); Iris.warn("===================================================================================="); @@ -206,7 +214,7 @@ public class ServerConfigurator { } if (warn) { - Iris.error("The Pack " + dimension.getLoadKey() + " is INCAPABLE of generating custom biomes"); + Iris.error("The Pack " + key + " is INCAPABLE of generating custom biomes"); Iris.error("If not done automatically, restart your server before generating with this pack!"); } @@ -220,6 +228,17 @@ public class ServerConfigurator { .map(IrisData::get); } + @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 Stream listFiles(File parent) { var files = parent.listFiles(); return files == null ? Stream.empty() : Arrays.stream(files); diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java index 53f3788fe..d1279666c 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java @@ -145,7 +145,7 @@ public class CommandDeveloper implements DecreeExecutor { public void packBenchmark( @Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld") IrisDimension dimension, - @Param(description = "Diameter in regions", defaultValue = "5") + @Param(description = "Diameter in regions", defaultValue = "2048") int diameter, @Param(description = "Headless", defaultValue = "true") boolean headless, diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java index 4411ccd67..8be356675 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java @@ -602,10 +602,10 @@ public class CommandIris implements DecreeExecutor { continue; } Iris.info(C.LIGHT_PURPLE + "Preparing Spawn for " + s + "' using Iris:" + generator + "..."); - new WorldCreator(s) + WorldCreator c = new WorldCreator(s) .generator(getDefaultWorldGenerator(s, generator)) - .environment(IrisData.loadAnyDimension(generator).getEnvironment()) - .createWorld(); + .environment(IrisData.loadAnyDimension(generator).getEnvironment()); + INMS.get().createWorld(c); Iris.info(C.LIGHT_PURPLE + "Loaded " + s + "!"); } } catch (Throwable e) { diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandPregen.java b/core/src/main/java/com/volmit/iris/core/commands/CommandPregen.java index e3ed1c844..6d7bc42a6 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandPregen.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandPregen.java @@ -19,9 +19,7 @@ package com.volmit.iris.core.commands; import com.volmit.iris.Iris; -import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.gui.PregeneratorJob; -import com.volmit.iris.core.pregenerator.LazyPregenerator; import com.volmit.iris.core.pregenerator.PregenTask; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.util.decree.DecreeExecutor; @@ -29,12 +27,9 @@ import com.volmit.iris.util.decree.annotations.Decree; import com.volmit.iris.util.decree.annotations.Param; import com.volmit.iris.util.format.C; import com.volmit.iris.util.math.Position2; -import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.util.Vector; -import java.io.File; - @Decree(name = "pregen", aliases = "pregenerate", description = "Pregenerate your Iris worlds!") public class CommandPregen implements DecreeExecutor { @Decree(description = "Pregenerate a world") @@ -52,13 +47,12 @@ public class CommandPregen implements DecreeExecutor { sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example."); } radius = Math.max(radius, 1024); - int w = (radius >> 9 + 1) * 2; IrisToolbelt.pregenerate(PregenTask .builder() - .center(new Position2(center.getBlockX() >> 9, center.getBlockZ() >> 9)) + .center(new Position2(center.getBlockX(), center.getBlockZ())) .gui(true) - .width(w) - .height(w) + .radiusX(radius) + .radiusZ(radius) .build(), world); String msg = C.GREEN + "Pregen started in " + C.GOLD + world.getName() + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ(); sender().sendMessage(msg); diff --git a/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java b/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java index 35569f6f6..63b8919db 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java +++ b/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java @@ -24,7 +24,6 @@ import com.volmit.iris.core.pregenerator.IrisPregenerator; import com.volmit.iris.core.pregenerator.PregenListener; import com.volmit.iris.core.pregenerator.PregenTask; import com.volmit.iris.core.pregenerator.PregeneratorMethod; -import com.volmit.iris.core.tools.IrisPackBenchmarking; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.format.Form; @@ -45,8 +44,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; -import static com.volmit.iris.core.tools.IrisPackBenchmarking.benchmarkInProgress; - public class PregeneratorJob implements PregenListener { private static final Color COLOR_EXISTS = parseColor("#4d7d5b"); private static final Color COLOR_BLACK = parseColor("#4d7d5b"); @@ -81,12 +78,12 @@ public class PregeneratorJob implements PregenListener { this.task = task; this.pregenerator = new IrisPregenerator(task, method, this); max = new Position2(0, 0); - min = new Position2(0, 0); - task.iterateRegions((xx, zz) -> { - min.setX(Math.min(xx << 5, min.getX())); - min.setZ(Math.min(zz << 5, min.getZ())); - max.setX(Math.max((xx << 5) + 31, max.getX())); - max.setZ(Math.max((zz << 5) + 31, max.getZ())); + min = new Position2(Integer.MAX_VALUE, Integer.MAX_VALUE); + task.iterateAllChunks((xx, zz) -> { + min.setX(Math.min(xx, min.getX())); + min.setZ(Math.min(zz, min.getZ())); + max.setX(Math.max(xx, max.getX())); + max.setZ(Math.max(zz, max.getZ())); }); if (IrisSettings.get().getGui().isUseServerLaunchedGuis() && task.isGui()) { @@ -162,7 +159,7 @@ public class PregeneratorJob implements PregenListener { } public void drawRegion(int x, int z, Color color) { - J.a(() -> PregenTask.iterateRegion(x, z, (xx, zz) -> { + J.a(() -> task.iterateChunks(x, z, (xx, zz) -> { draw(xx, zz, color); J.sleep(3); })); diff --git a/core/src/main/java/com/volmit/iris/core/nms/INMS.java b/core/src/main/java/com/volmit/iris/core/nms/INMS.java index d9a960341..3daefa900 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/INMS.java +++ b/core/src/main/java/com/volmit/iris/core/nms/INMS.java @@ -37,7 +37,7 @@ public class INMS { "1.21.4", "v1_21_R3" ); private static final List PACKS = List.of( - new Version(21, 4, "31010"), + new Version(21, 4, "31020"), new Version(21, 2, "31000"), new Version(20, 1, "3910") ); 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 dd5b383b9..d9beaa9e1 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.core.nms.headless.IRegionStorage; import com.volmit.iris.engine.framework.Engine; @@ -89,7 +91,12 @@ public interface INMSBinding { MCABiomeContainer newBiomeContainer(int min, int max); default World createWorld(WorldCreator c) { - return c.createWorld(); + if (missingDimensionTypes(true, true, true)) + throw new IllegalStateException("Missing dimenstion types to create world"); + + try (var ignored = injectLevelStems()) { + return c.createWorld(); + } } int countCustomBiomes(); @@ -125,4 +132,10 @@ public interface INMSBinding { IRegionStorage createRegionStorage(Engine engine); KList getStructureKeys(); + + AutoClosing injectLevelStems(); + + Pair injectUncached(boolean overworld, boolean nether, boolean end); + + boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end); } 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/core/nms/datapack/IDataFixer.java b/core/src/main/java/com/volmit/iris/core/nms/datapack/IDataFixer.java index 7ea4e1ded..4d972128e 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/datapack/IDataFixer.java +++ b/core/src/main/java/com/volmit/iris/core/nms/datapack/IDataFixer.java @@ -20,46 +20,6 @@ public interface IDataFixer { return obj; } - default JSONObject createPreset() { - return new JSONObject(""" -{ - "dimensions": { - "minecraft:overworld": { - "type": "iris:overworld", - "generator": { - "type": "minecraft:noise", - "biome_source": { - "type": "minecraft:multi_noise", - "preset": "minecraft:overworld" - }, - "settings": "minecraft:overworld" - } - }, - "minecraft:the_end": { - "type": "iris:the_end", - "generator": { - "type": "minecraft:noise", - "biome_source": { - "type": "minecraft:the_end" - }, - "settings": "minecraft:end" - } - }, - "minecraft:the_nether": { - "type": "iris:the_nether", - "generator": { - "type": "minecraft:noise", - "biome_source": { - "type": "minecraft:multi_noise", - "preset": "minecraft:nether" - }, - "settings": "minecraft:nether" - } - } - } -}"""); - } - enum Dimension { OVERRWORLD, NETHER, diff --git a/core/src/main/java/com/volmit/iris/core/nms/v1X/NMSBinding1X.java b/core/src/main/java/com/volmit/iris/core/nms/v1X/NMSBinding1X.java index 742b01b58..69cd4a8f8 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/v1X/NMSBinding1X.java +++ b/core/src/main/java/com/volmit/iris/core/nms/v1X/NMSBinding1X.java @@ -20,8 +20,10 @@ package com.volmit.iris.core.nms.v1X; 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.headless.IRegionStorage; +import com.volmit.iris.core.nms.container.Pair; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; @@ -124,6 +126,21 @@ public class NMSBinding1X implements INMSBinding { return new KList<>(list); } + @Override + public AutoClosing injectLevelStems() { + return new AutoClosing(() -> {}); + } + + @Override + public Pair injectUncached(boolean overworld, boolean nether, boolean end) { + return new Pair<>(0, new AutoClosing(() -> {})); + } + + @Override + public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { + return false; + } + @Override public CompoundTag serializeEntity(Entity location) { return null; diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java index e8c00d3df..cb8407272 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java @@ -165,8 +165,11 @@ public class ChunkUpdater { if (rX < dimensions.min.getX() || rX > dimensions.max.getX() || rZ < dimensions.min.getZ() || rZ > dimensions.max.getZ()) { return; } + if (!new File(world.getWorldFolder(), "region" + File.separator + rX + "." + rZ + ".mca").exists()) { + return; + } - PregenTask.iterateRegion(rX, rZ, (x, z) -> { + task.iterateChunks(rX, rZ, (x, z) -> { while (paused.get() && !cancelled.get()) { J.sleep(50); } @@ -348,8 +351,8 @@ public class ChunkUpdater { int width = maxZ - minZ + 1; return new Dimensions(new Position2(minX, minZ), new Position2(maxX, maxZ), height * width, PregenTask.builder() - .width((int) Math.ceil(width / 2d)) - .height((int) Math.ceil(height / 2d)) + .radiusZ((int) Math.ceil(width / 2d * 512)) + .radiusX((int) Math.ceil(height / 2d * 512)) .center(new Position2(oX, oZ)) .build()); } diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java b/core/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java index 56ee44b05..57aa8b0eb 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java @@ -19,7 +19,6 @@ package com.volmit.iris.core.pregenerator; import com.volmit.iris.Iris; -import com.volmit.iris.core.pack.IrisPack; import com.volmit.iris.core.tools.IrisPackBenchmarking; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KSet; @@ -83,7 +82,7 @@ public class IrisPregenerator { generatedLast = new AtomicInteger(0); generatedLastMinute = new AtomicInteger(0); totalChunks = new AtomicInteger(0); - task.iterateRegions((_a, _b) -> totalChunks.addAndGet(1024)); + task.iterateAllChunks((_a, _b) -> totalChunks.incrementAndGet()); startTime = new AtomicLong(M.ms()); ticker = new Looper() { @Override @@ -194,7 +193,7 @@ public class IrisPregenerator { } else if (!regions) { hit = true; listener.onRegionGenerating(x, z); - PregenTask.iterateRegion(x, z, (xx, zz) -> { + task.iterateChunks(x, z, (xx, zz) -> { while (paused.get() && !shutdown.get()) { J.sleep(50); } diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/PregenTask.java b/core/src/main/java/com/volmit/iris/core/pregenerator/PregenTask.java index 015a418a1..db8d76e7a 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/PregenTask.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/PregenTask.java @@ -32,17 +32,26 @@ import java.util.Comparator; @Data public class PregenTask { private static final Position2 ZERO = new Position2(0, 0); - private static final KList ORDER_CENTER = computeChunkOrder(); private static final KMap> ORDERS = new KMap<>(); @Builder.Default - private boolean gui = false; + private final boolean gui = false; @Builder.Default - private Position2 center = new Position2(0, 0); + private final Position2 center = new Position2(0, 0); @Builder.Default - private int width = 1; + private final int radiusX = 1; @Builder.Default - private int height = 1; + private final int radiusZ = 1; + + private final Bounds bounds = new Bounds(); + + protected PregenTask(boolean gui, Position2 center, int radiusX, int radiusZ) { + this.gui = gui; + this.center = new ProxiedPos(center); + this.radiusX = radiusX; + this.radiusZ = radiusZ; + bounds.update(); + } public static void iterateRegion(int xr, int zr, Spiraled s, Position2 pull) { for (Position2 i : ORDERS.computeIfAbsent(pull, PregenTask::computeOrder)) { @@ -70,29 +79,72 @@ public class PregenTask { return p; } - private static KList computeChunkOrder() { - Position2 center = new Position2(15, 15); - KList p = new KList<>(); - new Spiraler(33, 33, (x, z) -> { - int xx = x + 15; - int zz = z + 15; - if (xx < 0 || xx > 31 || zz < 0 || zz > 31) { - return; - } - - p.add(new Position2(xx, zz)); - }).drain(); - p.sort(Comparator.comparing((i) -> i.distance(center))); - return p; + public void iterateRegions(Spiraled s) { + var bound = bounds.region(); + new Spiraler(bound.sizeX, bound.sizeZ, ((x, z) -> { + if (bound.check(x, z)) s.on(x, z); + })).setOffset(center.getX() >> 9, center.getZ() >> 9).drain(); } - public void iterateRegions(Spiraled s) { - new Spiraler(getWidth() * 2, getHeight() * 2, s) - .setOffset(center.getX(), center.getZ()).drain(); + public void iterateChunks(int rX, int rZ, Spiraled s) { + var bound = bounds.chunk(); + iterateRegion(rX, rZ, ((x, z) -> { + if (bound.check(x, z)) s.on(x, z); + })); } public void iterateAllChunks(Spiraled s) { - new Spiraler(getWidth() * 2, getHeight() * 2, (x, z) -> iterateRegion(x, z, s)) - .setOffset(center.getX(), center.getZ()).drain(); + iterateRegions(((rX, rZ) -> iterateChunks(rX, rZ, s))); + } + + private class Bounds { + private Bound chunk = null; + private Bound region = null; + + public void update() { + int maxX = center.getX() + radiusX; + int maxZ = center.getZ() + radiusZ; + int minX = center.getX() - radiusX; + int minZ = center.getZ() - radiusZ; + + chunk = new Bound(minX >> 4, minZ >> 4, Math.ceilDiv(maxX, 16), Math.ceilDiv(maxZ, 16)); + region = new Bound(minX >> 9, minZ >> 9, Math.ceilDiv(maxX, 512), Math.ceilDiv(maxZ, 512)); + } + + public Bound chunk() { + if (chunk == null) update(); + return chunk; + } + + public Bound region() { + if (region == null) update(); + return region; + } + } + + private record Bound(int minX, int maxX, int minZ, int maxZ, int sizeX, int sizeZ) { + private Bound(int minX, int minZ, int maxX, int maxZ) { + this(minX, maxX, minZ, maxZ, maxZ - minZ + 1, maxZ - minZ + 1); + } + + boolean check(int x, int z) { + return x >= minX && x <= maxX && z >= minZ && z <= maxZ; + } + } + + private static class ProxiedPos extends Position2 { + public ProxiedPos(Position2 p) { + super(p.getX(), p.getZ()); + } + + @Override + public void setX(int x) { + throw new IllegalStateException("This Position2 may not be modified"); + } + + @Override + public void setZ(int z) { + throw new IllegalStateException("This Position2 may not be modified"); + } } } diff --git a/core/src/main/java/com/volmit/iris/core/safeguard/IrisSafeguard.java b/core/src/main/java/com/volmit/iris/core/safeguard/IrisSafeguard.java index 4fb25371a..5217be9f2 100644 --- a/core/src/main/java/com/volmit/iris/core/safeguard/IrisSafeguard.java +++ b/core/src/main/java/com/volmit/iris/core/safeguard/IrisSafeguard.java @@ -1,6 +1,7 @@ package com.volmit.iris.core.safeguard; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; public class IrisSafeguard { public static boolean unstablemode = false; @@ -11,5 +12,13 @@ public class IrisSafeguard { Iris.info("Enabled Iris SafeGuard"); ServerBootSFG.BootCheck(); } + + public static void earlySplash() { + if (ServerBootSFG.safeguardPassed || IrisSettings.get().getGeneral().DoomsdayAnnihilationSelfDestructMode) + return; + + Iris.instance.splash(); + UtilsSFG.splash(); + } } diff --git a/core/src/main/java/com/volmit/iris/core/safeguard/ServerBootSFG.java b/core/src/main/java/com/volmit/iris/core/safeguard/ServerBootSFG.java index a1de18768..2c59c2ae9 100644 --- a/core/src/main/java/com/volmit/iris/core/safeguard/ServerBootSFG.java +++ b/core/src/main/java/com/volmit/iris/core/safeguard/ServerBootSFG.java @@ -3,6 +3,7 @@ package com.volmit.iris.core.safeguard; import com.volmit.iris.Iris; import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.nms.v1X.NMSBinding1X; +import com.volmit.iris.engine.object.IrisContextInjector; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; @@ -29,6 +30,7 @@ public class ServerBootSFG { public static boolean isJRE = false; public static boolean hasPrivileges = true; public static boolean unsuportedversion = false; + public static boolean missingDimensionTypes = false; protected static boolean safeguardPassed; public static boolean passedserversoftware = true; protected static int count; @@ -110,6 +112,12 @@ public class ServerBootSFG { severityMedium++; } + if (IrisContextInjector.isMissingDimensionTypes()) { + missingDimensionTypes = true; + joiner.add("Missing Dimension Types"); + severityHigh++; + } + allIncompatibilities = joiner.toString(); safeguardPassed = (severityHigh == 0 && severityMedium == 0 && severityLow == 0); diff --git a/core/src/main/java/com/volmit/iris/core/safeguard/UtilsSFG.java b/core/src/main/java/com/volmit/iris/core/safeguard/UtilsSFG.java index cfff91b62..c45cfc7bb 100644 --- a/core/src/main/java/com/volmit/iris/core/safeguard/UtilsSFG.java +++ b/core/src/main/java/com/volmit/iris/core/safeguard/UtilsSFG.java @@ -39,6 +39,11 @@ public class UtilsSFG { Iris.safeguard(C.RED + "Server Version"); Iris.safeguard(C.RED + "- Iris only supports 1.20.1 > 1.21.4"); } + if (ServerBootSFG.missingDimensionTypes) { + Iris.safeguard(C.RED + "Dimension Types"); + Iris.safeguard(C.RED + "- Required Iris dimension types were not loaded."); + Iris.safeguard(C.RED + "- If this still happens after a restart please contact support."); + } if (!ServerBootSFG.passedserversoftware) { Iris.safeguard(C.YELLOW + "Unsupported Server Software"); Iris.safeguard(C.YELLOW + "- Please consider using Paper or Purpur instead."); diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java b/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java index 3ea66ae4b..c5acd2812 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.AtomicDouble; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; 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.engine.object.IrisDimension; @@ -200,7 +201,7 @@ public class IrisCreator { try { J.sfut(() -> { - world.set(wc.createWorld()); + world.set(INMS.get().createWorld(wc)); }).get(); } catch (Throwable e) { e.printStackTrace(); diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java b/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java index 2cb353b3f..50892516e 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java @@ -174,8 +174,8 @@ public class IrisPackBenchmarking { IrisToolbelt.pregenerate(PregenTask .builder() .gui(gui) - .width(diameter) - .height(diameter) + .radiusX(diameter) + .radiusZ(diameter) .build(), headless ? new HeadlessPregenMethod(engine) : new HybridPregenMethod( diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java index c1799c07d..97ec2eceb 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java @@ -32,8 +32,6 @@ import sun.misc.Unsafe; import java.io.File; public class IrisWorldCreator { - public static final WorldType IRIS; - private String name; private boolean studio = false; private String dimensionName = null; @@ -85,7 +83,6 @@ public class IrisWorldCreator { return new WorldCreator(name) - .type(IRIS) .environment(findEnvironment()) .generateStructures(true) .generator(g).seed(seed); @@ -104,17 +101,4 @@ public class IrisWorldCreator { this.studio = studio; return this; } - - static { - try { - var unsafe = new WrappedField(Unsafe.class, "theUnsafe").get(); - var iris = (WorldType) unsafe.allocateInstance(WorldType.class); - unsafe.putIntVolatile(iris, unsafe.objectFieldOffset(Enum.class.getDeclaredField("ordinal")), 0); - unsafe.putObjectVolatile(iris, unsafe.objectFieldOffset(Enum.class.getDeclaredField("name")), "IRIS"); - - IRIS = iris; - } catch (Throwable e) { - throw new ExceptionInInitializerError(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..3ecafab8a --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisContextInjector.java @@ -0,0 +1,77 @@ +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 lombok.Getter; +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 { + @Getter + private static boolean missingDimensionTypes = false; + 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 (INMS.get().missingDimensionTypes(overworld, nether, end)) { + missingDimensionTypes = true; + totalWorlds = 0; + return; + } + + if (overworld || nether || end) { + var pair = INMS.get().injectUncached(overworld, nether, end); + i += pair.getA() - 3; + autoClosing = pair.getB(); + } + + totalWorlds = i; + instance.registerListener(this); + } + + @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/engine/object/IrisDimension.java b/core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java index eb0493f52..f2c5b724c 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java @@ -19,6 +19,7 @@ package com.volmit.iris.engine.object; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.ServerConfigurator.DimensionHeight; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.loader.IrisRegistrant; @@ -27,6 +28,7 @@ import com.volmit.iris.core.nms.datapack.IDataFixer; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.object.annotations.*; import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.data.DataProvider; import com.volmit.iris.util.io.IO; import com.volmit.iris.util.json.JSONObject; @@ -377,60 +379,35 @@ public class IrisDimension extends IrisRegistrant { return landBiomeStyle; } - public boolean installDataPack(IDataFixer fixer, DataProvider data, File datapacks, DimensionHeight height) { - boolean write = false; - boolean changed = false; - - IO.delete(new File(datapacks, "iris/data/" + getLoadKey().toLowerCase())); - - for (IrisBiome i : getAllBiomes(data)) { - if (i.isCustom()) { - write = true; - - for (IrisBiomeCustom j : i.getCustomDerivitives()) { - File output = new File(datapacks, "iris/data/" + getLoadKey().toLowerCase() + "/worldgen/biome/" + j.getId() + ".json"); - - if (!output.exists()) { - changed = true; - } - - Iris.verbose(" Installing Data Pack Biome: " + output.getPath()); - output.getParentFile().mkdirs(); - try { - IO.writeAll(output, j.generateJson(fixer)); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - } - } - } - - Iris.verbose(" Installing Data Pack Dimension Types: \"iris:overworld\", \"iris:the_nether\", \"iris:the_end\""); - changed = writeDimensionType(changed, datapacks, height); - - Iris.verbose(" Installing Data Pack World Preset: \"minecraft:iris\""); - changed = writeWorldPreset(changed, datapacks, fixer); - - if (write) { - File mcm = new File(datapacks, "iris/pack.mcmeta"); - try { - IO.writeAll(mcm, """ - { - "pack": { - "description": "Iris Data Pack. This pack contains all installed Iris Packs' resources.", - "pack_format": {} - } + public void installBiomes(IDataFixer fixer, DataProvider data, KList folders, KSet biomes) { + getAllBiomes(data) + .stream() + .filter(IrisBiome::isCustom) + .map(IrisBiome::getCustomDerivitives) + .flatMap(KList::stream) + .parallel() + .forEach(j -> { + String json = j.generateJson(fixer); + synchronized (biomes) { + if (!biomes.add(j.getId())) { + Iris.verbose("Duplicate Data Pack Biome: " + getLoadKey() + "/" + j.getId()); + return; } - """.replace("{}", INMS.get().getDataVersion().getPackFormat() + "")); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - Iris.verbose(" Installing Data Pack MCMeta: " + mcm.getPath()); - } + } - return changed; + for (File datapacks : folders) { + File output = new File(datapacks, "iris/data/" + getLoadKey().toLowerCase() + "/worldgen/biome/" + j.getId() + ".json"); + + Iris.verbose(" Installing Data Pack Biome: " + output.getPath()); + output.getParentFile().mkdirs(); + try { + IO.writeAll(output, json); + } catch (IOException e) { + Iris.reportError(e); + e.printStackTrace(); + } + } + }); } @Override @@ -448,56 +425,55 @@ public class IrisDimension extends IrisRegistrant { } - public boolean writeDimensionType(boolean changed, File datapacks, DimensionHeight height) { - File dimTypeOverworld = new File(datapacks, "iris/data/iris/dimension_type/overworld.json"); - if (!dimTypeOverworld.exists()) - changed = true; - dimTypeOverworld.getParentFile().mkdirs(); - try { - IO.writeAll(dimTypeOverworld, height.overworldType()); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); + public static void writeShared(KList folders, DimensionHeight height) { + Iris.verbose(" Installing Data Pack Dimension Types: \"iris:overworld\", \"iris:the_nether\", \"iris:the_end\""); + for (File datapacks : folders) { + write(datapacks, "overworld", height.overworldType()); + write(datapacks, "the_nether", height.netherType()); + write(datapacks, "the_end", height.endType()); } + String raw = """ + { + "pack": { + "description": "Iris Data Pack. This pack contains all installed Iris Packs' resources.", + "pack_format": {} + } + } + """.replace("{}", INMS.get().getDataVersion().getPackFormat() + ""); - File dimTypeNether = new File(datapacks, "iris/data/iris/dimension_type/the_nether.json"); - if (!dimTypeNether.exists()) - changed = true; - dimTypeNether.getParentFile().mkdirs(); - try { - IO.writeAll(dimTypeNether, height.netherType()); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); + for (File datapacks : folders) { + File mcm = new File(datapacks, "iris/pack.mcmeta"); + try { + IO.writeAll(mcm, raw); + } catch (IOException e) { + Iris.reportError(e); + e.printStackTrace(); + } + Iris.verbose(" Installing Data Pack MCMeta: " + mcm.getPath()); } - - - File dimTypeEnd = new File(datapacks, "iris/data/iris/dimension_type/the_end.json"); - if (!dimTypeEnd.exists()) - changed = true; - dimTypeEnd.getParentFile().mkdirs(); - try { - IO.writeAll(dimTypeEnd, height.endType()); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - - return changed; } - public boolean writeWorldPreset(boolean changed, File datapacks, IDataFixer fixer) { - File worldPreset = new File(datapacks, "iris/data/minecraft/worldgen/world_preset/iris.json"); - if (!worldPreset.exists()) - changed = true; + private static void write(File datapacks, String type, String json) { + File dimType = new File(datapacks, "iris/data/iris/dimension_type/" + type + ".json"); + File dimTypeVanilla = new File(datapacks, "iris/data/minecraft/dimension_type/" + type + ".json"); + + dimType.getParentFile().mkdirs(); try { - IO.writeAll(worldPreset, fixer.createPreset()); + IO.writeAll(dimType, json); } catch (IOException e) { Iris.reportError(e); e.printStackTrace(); } - return changed; + if (IrisSettings.get().getGeneral().adjustVanillaHeight || dimTypeVanilla.exists()) { + dimTypeVanilla.getParentFile().mkdirs(); + try { + IO.writeAll(dimTypeVanilla, json); + } catch (IOException e) { + Iris.reportError(e); + e.printStackTrace(); + } + } } } diff --git a/core/src/main/java/com/volmit/iris/util/math/Spiraler.java b/core/src/main/java/com/volmit/iris/util/math/Spiraler.java index fc8716bb7..f6f52f2bd 100644 --- a/core/src/main/java/com/volmit/iris/util/math/Spiraler.java +++ b/core/src/main/java/com/volmit/iris/util/math/Spiraler.java @@ -63,7 +63,7 @@ public class Spiraler { public void next() { if ((-sizeX / 2 <= x) && (x <= sizeX / 2) && (-sizeZ / 2 <= z) && (z <= sizeZ / 2)) { - spiraled.on(x + ox, z + ox); + spiraled.on(x + ox, z + oz); } if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z))) { 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..ecfc4f518 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/misc/ServerProperties.java @@ -0,0 +1,44 @@ +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; + + static { + String[] args = ProcessHandle.current() + .info() + .arguments() + .orElse(new String[0]); + + String propertiesPath = "server.properties"; + String bukkitYml = "bukkit.yml"; + String levelName = null; + + 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]; + case "-w", "--level-name", "--world" -> levelName = 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); + } + + if (levelName != null) LEVEL_NAME = levelName; + else LEVEL_NAME = DATA.getProperty("level-name", "world"); + } +} 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 bd36e4bba..c0923ace1 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 @@ -2,8 +2,10 @@ package com.volmit.iris.core.nms.v1_20_R1; import com.mojang.brigadier.exceptions.CommandSyntaxException; 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.core.nms.headless.IRegionStorage; import com.volmit.iris.core.nms.v1_20_R1.headless.RegionStorage; @@ -22,27 +24,36 @@ import com.volmit.iris.util.nbt.mca.palette.*; import com.volmit.iris.util.nbt.tag.CompoundTag; import com.volmit.iris.util.scheduling.J; import it.unimi.dsi.fastutil.objects.Object2IntMap; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; +import lombok.SneakyThrows; +import net.minecraft.core.*; import net.minecraft.core.Registry; -import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.*; import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WorldLoader; import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.TagKey; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.biome.Biomes; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.FlatLevelSource; +import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -75,11 +86,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 { @@ -87,9 +97,11 @@ public class NMSBinding implements INMSBinding { private final KMap baseBiomeCache = new KMap<>(); private final BlockData AIR = Material.AIR.createBlockData(); private final AtomicCache> biomeMapCache = new AtomicCache<>(); + private final AtomicCache dataLoadContext = new AtomicCache<>(); 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; @@ -570,6 +582,9 @@ public class NMSBinding implements INMSBinding { public void inject(long seed, Engine engine, World world) { var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; + 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); } @@ -635,4 +650,120 @@ public class NMSBinding implements INMSBinding { public IRegionStorage createRegionStorage(Engine engine) { return new RegionStorage(engine); } + + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + + @Override + @SneakyThrows + 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); + })); + } + + @Override + public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { + var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE); + if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); + if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); + if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); + return overworld || nether || end; + } + + 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, transformer.apply(old)); + + return new AutoClosing(() -> { + field.set(nmsServer, old); + dataContextLock.unlock(); + }); + } + + 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 settings = new FlatLevelGeneratorSettings( + Optional.empty(), + access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), + List.of() + ); + settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR)); + settings.updateLayers(); + + var source = new FlatLevelSource(settings); + var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); + if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD); + if (nether) register(fake, dimensions, source, LevelStem.NETHER); + if (end) register(fake, dimensions, source, LevelStem.END); + copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM)); + + return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); + } + + private void register(MappedRegistry target, Registry dimensions, FlatLevelSource source, ResourceKey key) { + var loc = createIrisKey(key); + target.register(key, new LevelStem( + dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())), + source + ), 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); + }); + } + + private ResourceLocation createIrisKey(ResourceKey key) { + return new ResourceLocation("iris", key.location().getPath()); + } } 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 08dcb5f77..c1c6ad8b3 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,23 +8,36 @@ 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.core.nms.headless.IRegionStorage; import com.volmit.iris.core.nms.v1_20_R2.headless.RegionStorage; import com.volmit.iris.util.scheduling.J; +import lombok.SneakyThrows; +import net.minecraft.core.*; +import net.minecraft.core.Registry; import net.minecraft.nbt.*; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WorldLoader; import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.tags.TagKey; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.FlatLevelSource; +import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -63,10 +76,6 @@ import com.volmit.iris.util.nbt.mca.palette.*; import com.volmit.iris.util.nbt.tag.CompoundTag; import it.unimi.dsi.fastutil.objects.Object2IntMap; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; -import net.minecraft.core.Registry; -import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; @@ -85,9 +94,11 @@ public class NMSBinding implements INMSBinding { private final KMap baseBiomeCache = new KMap<>(); private final BlockData AIR = Material.AIR.createBlockData(); private final AtomicCache> biomeMapCache = new AtomicCache<>(); + private final AtomicCache dataLoadContext = new AtomicCache<>(); 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,6 +551,9 @@ public class NMSBinding implements INMSBinding { public void inject(long seed, Engine engine, World world) { var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; + 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,4 +651,120 @@ public class NMSBinding implements INMSBinding { public IRegionStorage createRegionStorage(Engine engine) { return new RegionStorage(engine); } + + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + + @Override + @SneakyThrows + 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); + })); + } + + @Override + public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { + var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE); + if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); + if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); + if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); + return overworld || nether || end; + } + + 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, transformer.apply(old)); + + return new AutoClosing(() -> { + field.set(nmsServer, old); + dataContextLock.unlock(); + }); + } + + 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 settings = new FlatLevelGeneratorSettings( + Optional.empty(), + access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), + List.of() + ); + settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR)); + settings.updateLayers(); + + var source = new FlatLevelSource(settings); + var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); + if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD); + if (nether) register(fake, dimensions, source, LevelStem.NETHER); + if (end) register(fake, dimensions, source, LevelStem.END); + copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM)); + + return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); + } + + private void register(MappedRegistry target, Registry dimensions, FlatLevelSource source, ResourceKey key) { + var loc = createIrisKey(key); + target.register(key, new LevelStem( + dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())), + source + ), 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); + }); + } + + private ResourceLocation createIrisKey(ResourceKey key) { + return new ResourceLocation("iris", key.location().getPath()); + } } 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 5d10e5466..8de11fab3 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,23 +8,36 @@ 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.core.nms.headless.IRegionStorage; import com.volmit.iris.core.nms.v1_20_R3.headless.RegionStorage; import com.volmit.iris.util.scheduling.J; +import lombok.SneakyThrows; +import net.minecraft.core.*; +import net.minecraft.core.Registry; import net.minecraft.nbt.*; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WorldLoader; import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.tags.TagKey; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.FlatLevelSource; +import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -63,10 +76,6 @@ import com.volmit.iris.util.nbt.mca.palette.*; import com.volmit.iris.util.nbt.tag.CompoundTag; import it.unimi.dsi.fastutil.objects.Object2IntMap; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; -import net.minecraft.core.Registry; -import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; @@ -85,9 +94,11 @@ public class NMSBinding implements INMSBinding { private final KMap baseBiomeCache = new KMap<>(); private final BlockData AIR = Material.AIR.createBlockData(); private final AtomicCache> biomeMapCache = new AtomicCache<>(); + private final AtomicCache dataLoadContext = new AtomicCache<>(); 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,6 +551,9 @@ public class NMSBinding implements INMSBinding { public void inject(long seed, Engine engine, World world) { var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; + 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,4 +652,120 @@ public class NMSBinding implements INMSBinding { public IRegionStorage createRegionStorage(Engine engine) { return new RegionStorage(engine); } + + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + + @Override + @SneakyThrows + 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); + })); + } + + @Override + public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { + var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE); + if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); + if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); + if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); + return overworld || nether || end; + } + + 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, transformer.apply(old)); + + return new AutoClosing(() -> { + field.set(nmsServer, old); + dataContextLock.unlock(); + }); + } + + 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 settings = new FlatLevelGeneratorSettings( + Optional.empty(), + access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), + List.of() + ); + settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR)); + settings.updateLayers(); + + var source = new FlatLevelSource(settings); + var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); + if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD); + if (nether) register(fake, dimensions, source, LevelStem.NETHER); + if (end) register(fake, dimensions, source, LevelStem.END); + copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM)); + + return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); + } + + private void register(MappedRegistry target, Registry dimensions, FlatLevelSource source, ResourceKey key) { + var loc = createIrisKey(key); + target.register(key, new LevelStem( + dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())), + source + ), 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); + }); + } + + private ResourceLocation createIrisKey(ResourceKey key) { + return new ResourceLocation("iris", key.location().getPath()); + } } 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 c8d9bc390..b3443ee70 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,14 +6,19 @@ 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.core.nms.headless.IRegionStorage; import com.volmit.iris.core.nms.v1_20_R4.headless.RegionStorage; import com.volmit.iris.util.nbt.tag.CompoundTag; import com.volmit.iris.util.scheduling.J; +import lombok.SneakyThrows; import net.minecraft.core.*; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponents; @@ -27,15 +32,24 @@ import net.minecraft.nbt.LongTag; import net.minecraft.nbt.ShortTag; import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WorldLoader; import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.server.commands.data.DataCommands; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.TagKey; import net.minecraft.world.item.component.CustomData; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.FlatLevelSource; +import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -88,9 +102,11 @@ public class NMSBinding implements INMSBinding { private final KMap baseBiomeCache = new KMap<>(); private final BlockData AIR = Material.AIR.createBlockData(); private final AtomicCache> biomeMapCache = new AtomicCache<>(); + private final AtomicCache dataLoadContext = new AtomicCache<>(); 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; @@ -546,6 +562,10 @@ public class NMSBinding implements INMSBinding { public void inject(long seed, Engine engine, World world) { var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; + 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); } @@ -657,4 +677,120 @@ public class NMSBinding implements INMSBinding { public IRegionStorage createRegionStorage(Engine engine) { return new RegionStorage(engine); } + + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + + @Override + @SneakyThrows + 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); + })); + } + + @Override + public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { + var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE); + if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); + if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); + if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); + return overworld || nether || end; + } + + 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, transformer.apply(old)); + + return new AutoClosing(() -> { + field.set(nmsServer, old); + dataContextLock.unlock(); + }); + } + + 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 settings = new FlatLevelGeneratorSettings( + Optional.empty(), + access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), + List.of() + ); + settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR)); + settings.updateLayers(); + + var source = new FlatLevelSource(settings); + var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); + if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD); + if (nether) register(fake, dimensions, source, LevelStem.NETHER); + if (end) register(fake, dimensions, source, LevelStem.END); + copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM)); + + return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); + } + + private void register(MappedRegistry target, Registry dimensions, FlatLevelSource source, ResourceKey key) { + var loc = createIrisKey(key); + target.register(key, new LevelStem( + dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())), + source + ), 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); + }); + } + + private ResourceLocation createIrisKey(ResourceKey key) { + return new ResourceLocation("iris", key.location().getPath()); + } } 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 85e349550..7a5483b5e 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,24 +10,40 @@ 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.core.nms.headless.IRegionStorage; import com.volmit.iris.core.nms.v1_21_R1.headless.RegionStorage; import com.volmit.iris.util.scheduling.J; +import lombok.SneakyThrows; +import net.minecraft.core.*; +import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.*; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WorldLoader; import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.server.level.ChunkMap; import net.minecraft.tags.TagKey; import net.minecraft.world.item.component.CustomData; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.chunk.status.WorldGenContext; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.FlatLevelSource; +import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -66,10 +82,6 @@ import com.volmit.iris.util.nbt.mca.palette.*; import com.volmit.iris.util.nbt.tag.CompoundTag; import it.unimi.dsi.fastutil.objects.Object2IntMap; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; -import net.minecraft.core.Registry; -import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; @@ -87,9 +99,11 @@ public class NMSBinding implements INMSBinding { private final KMap baseBiomeCache = new KMap<>(); private final BlockData AIR = Material.AIR.createBlockData(); private final AtomicCache> biomeMapCache = new AtomicCache<>(); + private final AtomicCache dataLoadContext = new AtomicCache<>(); 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; @@ -548,6 +562,9 @@ public class NMSBinding implements INMSBinding { var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class); worldGenContextField.setAccessible(true); var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap); + 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), @@ -664,4 +681,120 @@ public class NMSBinding implements INMSBinding { public IRegionStorage createRegionStorage(Engine engine) { return new RegionStorage(engine); } + + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + + @Override + @SneakyThrows + 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); + })); + } + + @Override + public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { + var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE); + if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); + if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); + if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); + return overworld || nether || end; + } + + 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, transformer.apply(old)); + + return new AutoClosing(() -> { + field.set(nmsServer, old); + dataContextLock.unlock(); + }); + } + + 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 settings = new FlatLevelGeneratorSettings( + Optional.empty(), + access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), + List.of() + ); + settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR)); + settings.updateLayers(); + + var source = new FlatLevelSource(settings); + var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); + if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD); + if (nether) register(fake, dimensions, source, LevelStem.NETHER); + if (end) register(fake, dimensions, source, LevelStem.END); + copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM)); + + return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); + } + + private void register(MappedRegistry target, Registry dimensions, FlatLevelSource source, ResourceKey key) { + var loc = createIrisKey(key); + target.register(key, new LevelStem( + dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())), + source + ), 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); + }); + } + + private ResourceLocation createIrisKey(ResourceKey key) { + return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath()); + } } 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 d7bd95e7a..35943a9f2 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,24 +6,39 @@ 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.core.nms.headless.IRegionStorage; import com.volmit.iris.core.nms.v1_21_R2.headless.RegionStorage; import com.volmit.iris.util.scheduling.J; +import lombok.SneakyThrows; import net.minecraft.core.*; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.*; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WorldLoader; import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.tags.TagKey; import net.minecraft.world.item.component.CustomData; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.chunk.status.WorldGenContext; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.FlatLevelSource; +import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -74,9 +89,11 @@ public class NMSBinding implements INMSBinding { private final KMap baseBiomeCache = new KMap<>(); private final BlockData AIR = Material.AIR.createBlockData(); private final AtomicCache> biomeMapCache = new AtomicCache<>(); + private final AtomicCache dataLoadContext = new AtomicCache<>(); 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; @@ -535,6 +552,9 @@ public class NMSBinding implements INMSBinding { var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class); worldGenContextField.setAccessible(true); var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap); + 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), @@ -651,4 +671,120 @@ public class NMSBinding implements INMSBinding { public IRegionStorage createRegionStorage(Engine engine) { return new RegionStorage(engine); } + + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + + @Override + @SneakyThrows + 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); + })); + } + + @Override + public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { + var registry = registry().lookupOrThrow(Registries.DIMENSION_TYPE); + if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); + if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); + if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); + return overworld || nether || end; + } + + 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, transformer.apply(old)); + + return new AutoClosing(() -> { + field.set(nmsServer, old); + dataContextLock.unlock(); + }); + } + + 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 settings = new FlatLevelGeneratorSettings( + Optional.empty(), + access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), + List.of() + ); + settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR)); + settings.updateLayers(); + + var source = new FlatLevelSource(settings); + var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); + if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD); + if (nether) register(fake, dimensions, source, LevelStem.NETHER); + if (end) register(fake, dimensions, source, LevelStem.END); + copy(fake, datapack.lookup(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, access.lookupOrThrow(Registries.LEVEL_STEM)); + + return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); + } + + private void register(MappedRegistry target, Registry dimensions, FlatLevelSource source, ResourceKey key) { + var loc = createIrisKey(key); + target.register(key, new LevelStem( + dimensions.get(loc).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())), + source + ), 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); + }); + } + + private ResourceLocation createIrisKey(ResourceKey key) { + return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath()); + } } 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 c787d7a7b..27552784b 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 @@ -1,9 +1,12 @@ package com.volmit.iris.core.nms.v1_21_R3; 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.core.nms.headless.IRegionStorage; import com.volmit.iris.core.nms.v1_21_R3.headless.RegionStorage; @@ -21,6 +24,7 @@ import com.volmit.iris.util.nbt.mca.palette.*; import com.volmit.iris.util.nbt.tag.CompoundTag; import com.volmit.iris.util.scheduling.J; import it.unimi.dsi.fastutil.objects.Object2IntMap; +import lombok.SneakyThrows; import net.minecraft.core.Registry; import net.minecraft.core.*; import net.minecraft.core.component.DataComponents; @@ -29,13 +33,17 @@ import net.minecraft.nbt.Tag; import net.minecraft.nbt.*; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WorldLoader; import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.TagKey; import net.minecraft.world.entity.EntityType; import net.minecraft.world.item.component.CustomData; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.biome.Biomes; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; @@ -43,6 +51,11 @@ import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.chunk.status.WorldGenContext; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.FlatLevelSource; +import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -68,14 +81,18 @@ 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<>(); private final BlockData AIR = Material.AIR.createBlockData(); private final AtomicCache> biomeMapCache = new AtomicCache<>(); + private final AtomicCache dataLoadContext = new AtomicCache<>(); 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; @@ -534,6 +551,9 @@ public class NMSBinding implements INMSBinding { var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class); worldGenContextField.setAccessible(true); var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap); + 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), @@ -650,4 +670,120 @@ public class NMSBinding implements INMSBinding { public IRegionStorage createRegionStorage(Engine engine) { return new RegionStorage(engine); } + + @Override + public AutoClosing injectLevelStems() { + return inject(this::supplier); + } + + @Override + @SneakyThrows + 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); + })); + } + + @Override + public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { + var registry = registry().lookupOrThrow(Registries.DIMENSION_TYPE); + if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); + if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); + if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); + return overworld || nether || end; + } + + 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, transformer.apply(old)); + + return new AutoClosing(() -> { + field.set(nmsServer, old); + dataContextLock.unlock(); + }); + } + + 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 settings = new FlatLevelGeneratorSettings( + Optional.empty(), + access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), + List.of() + ); + settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR)); + settings.updateLayers(); + + var source = new FlatLevelSource(settings); + var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental()); + if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD); + if (nether) register(fake, dimensions, source, LevelStem.NETHER); + if (end) register(fake, dimensions, source, LevelStem.END); + copy(fake, datapack.lookup(Registries.LEVEL_STEM).orElse(null)); + + if (copy) copy(fake, access.lookupOrThrow(Registries.LEVEL_STEM)); + + return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze(); + } + + private void register(MappedRegistry target, Registry dimensions, FlatLevelSource source, ResourceKey key) { + var loc = createIrisKey(key); + target.register(key, new LevelStem( + dimensions.get(loc).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())), + source + ), 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); + }); + } + + private ResourceLocation createIrisKey(ResourceKey key) { + return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath()); + } }