diff --git a/build.gradle.kts b/build.gradle.kts index 8fc40d64c..98ee16ae3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -90,6 +90,7 @@ nmsBindings.forEach { key, value -> dependencies { compileOnly(project(":core")) compileOnly("org.jetbrains:annotations:26.0.2") + compileOnly("net.bytebuddy:byte-buddy:1.17.5") } } @@ -105,6 +106,7 @@ nmsBindings.forEach { key, value -> systemProperty("net.kyori.ansi.colorLevel", color) systemProperty("com.mojang.eula.agree", true) systemProperty("iris.suppressReporting", !errorReporting) + jvmArgs("-javaagent:${project(":core:agent").tasks.jar.flatMap { it.archiveFile }.get().asFile.absolutePath}") } } @@ -115,6 +117,7 @@ tasks { from(project(":nms:$key").tasks.named("remap").map { zipTree(it.outputs.files.singleFile) }) } from(project(":core").tasks.shadowJar.flatMap { it.archiveFile }.map { zipTree(it) }) + from(project(":core:agent").tasks.jar.flatMap { it.archiveFile }) archiveFileName.set("Iris-${project.version}.jar") } @@ -168,10 +171,6 @@ fun exec(vararg command: Any) { p.waitFor() } -dependencies { - implementation(project(":core")) -} - configurations.configureEach { resolutionStrategy.cacheChangingModulesFor(60, "minutes") resolutionStrategy.cacheDynamicVersionsFor(60, "minutes") @@ -193,6 +192,7 @@ allprojects { maven("https://repo.mineinabyss.com/releases") maven("https://hub.jeff-media.com/nexus/repository/jeff-media-public/") maven("https://repo.nexomc.com/releases/") + maven("https://nexus.phoenixdevt.fr/repository/maven-public/") } dependencies { @@ -275,4 +275,4 @@ fun registerCustomOutputTaskUnix(name: String, path: String) { into(file(path)) rename { "Iris.jar" } } -} +} \ No newline at end of file diff --git a/core/agent/build.gradle.kts b/core/agent/build.gradle.kts new file mode 100644 index 000000000..a0d8024df --- /dev/null +++ b/core/agent/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + java +} + +tasks.jar { + manifest.attributes( + "Agent-Class" to "com.volmit.iris.util.agent.Installer", + "Premain-Class" to "com.volmit.iris.util.agent.Installer", + "Can-Redefine-Classes" to true, + "Can-Retransform-Classes" to true + ) +} \ No newline at end of file diff --git a/core/agent/src/main/java/com/volmit/iris/util/agent/Installer.java b/core/agent/src/main/java/com/volmit/iris/util/agent/Installer.java new file mode 100644 index 000000000..3c68fd579 --- /dev/null +++ b/core/agent/src/main/java/com/volmit/iris/util/agent/Installer.java @@ -0,0 +1,29 @@ +package com.volmit.iris.util.agent; + +import java.lang.instrument.Instrumentation; + +public class Installer { + private static volatile Instrumentation instrumentation; + + public static Instrumentation getInstrumentation() { + Instrumentation instrumentation = Installer.instrumentation; + if (instrumentation == null) { + throw new IllegalStateException("The agent is not loaded or this method is not called via the system class loader"); + } + return instrumentation; + } + + public static void premain(String arguments, Instrumentation instrumentation) { + doMain(instrumentation); + } + + public static void agentmain(String arguments, Instrumentation instrumentation) { + doMain(instrumentation); + } + + private static synchronized void doMain(Instrumentation instrumentation) { + if (Installer.instrumentation != null) + return; + Installer.instrumentation = instrumentation; + } +} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 50ff813f0..e2facc0dc 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -26,10 +26,8 @@ plugins { val apiVersion = "1.19" val main = "com.volmit.iris.Iris" -repositories { - maven("https://nexus.phoenixdevt.fr/repository/maven-public/") - maven("https://repo.auxilor.io/repository/maven-public/") -} +val dynamic: Configuration by configurations.creating +configurations.compileOnly { extendsFrom(dynamic) } /** * Dependencies. @@ -48,10 +46,6 @@ dependencies { compileOnly("org.spigotmc:spigot-api:1.20.1-R0.1-SNAPSHOT") compileOnly("org.apache.logging.log4j:log4j-api:2.19.0") compileOnly("org.apache.logging.log4j:log4j-core:2.19.0") - compileOnly("commons-io:commons-io:2.13.0") - compileOnly("commons-lang:commons-lang:2.6") - compileOnly("com.github.oshi:oshi-core:5.8.5") - compileOnly("org.lz4:lz4-java:1.8.0") // Third Party Integrations compileOnly("com.nexomc:nexo:1.6.0") @@ -60,12 +54,13 @@ dependencies { compileOnly("com.github.Ssomar-Developement:SCore:4.23.10.8") compileOnly("net.Indyuce:MMOItems-API:6.9.5-SNAPSHOT") compileOnly("com.willfp:EcoItems:5.44.0") + compileOnly("io.lumine:Mythic-Dist:5.2.1") + compileOnly("io.lumine:MythicCrucible-Dist:2.0.0") compileOnly("me.kryniowesegryderiusz:kgenerators-core:7.3") { isTransitive = false } //implementation files("libs/CustomItems.jar") - // Shaded implementation("com.dfsek:paralithic:0.8.1") implementation("io.papermc:paperlib:1.0.5") @@ -74,24 +69,22 @@ dependencies { implementation("net.kyori:adventure-api:4.17.0") implementation("org.bstats:bstats-bukkit:3.1.0") - //implementation("org.bytedeco:javacpp:1.5.10") - //implementation("org.bytedeco:cuda-platform:12.3-8.9-1.5.10") - compileOnly("io.lumine:Mythic-Dist:5.2.1") - compileOnly("io.lumine:MythicCrucible-Dist:2.0.0") - // Dynamically Loaded - compileOnly("io.timeandspace:smoothie-map:2.0.2") - compileOnly("it.unimi.dsi:fastutil:8.5.8") - compileOnly("com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2") - compileOnly("org.zeroturnaround:zt-zip:1.14") - compileOnly("com.google.code.gson:gson:2.10.1") - compileOnly("org.ow2.asm:asm:9.2") - compileOnly("com.google.guava:guava:33.0.0-jre") - compileOnly("bsf:bsf:2.4.0") - compileOnly("rhino:js:1.7R2") - compileOnly("com.github.ben-manes.caffeine:caffeine:3.0.6") - compileOnly("org.apache.commons:commons-lang3:3.12.0") - compileOnly("com.github.oshi:oshi-core:6.6.5") + dynamic("commons-io:commons-io:2.13.0") + dynamic("commons-lang:commons-lang:2.6") + dynamic("com.github.oshi:oshi-core:6.6.5") + dynamic("org.lz4:lz4-java:1.8.0") + dynamic("it.unimi.dsi:fastutil:8.5.8") + dynamic("com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2") + dynamic("org.zeroturnaround:zt-zip:1.14") + dynamic("com.google.code.gson:gson:2.10.1") + dynamic("org.ow2.asm:asm:9.8") + dynamic("bsf:bsf:2.4.0") + dynamic("rhino:js:1.7R2") + dynamic("com.github.ben-manes.caffeine:caffeine:3.0.6") + dynamic("org.apache.commons:commons-lang3:3.12.0") + dynamic("net.bytebuddy:byte-buddy:1.17.5") + dynamic("net.bytebuddy:byte-buddy-agent:1.17.5") } java { @@ -123,7 +116,8 @@ tasks { "name" to rootProject.name, "version" to rootProject.version, "apiVersion" to apiVersion, - "main" to main + "main" to main, + "libraries" to dynamic.allDependencies.map { "\n - $it" }.sorted().joinToString("") ) filesMatching("**/plugin.yml") { expand(inputs.properties) diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index 37ce7e686..9f39f70ad 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -35,7 +35,6 @@ import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.core.tools.IrisToolbelt; 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; @@ -71,6 +70,7 @@ import com.volmit.iris.util.sentry.IrisLogger; import com.volmit.iris.util.sentry.ServerID; import io.papermc.lib.PaperLib; import io.sentry.Sentry; +import lombok.NonNull; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.serializer.ComponentSerializer; import org.bstats.bukkit.Metrics; @@ -465,14 +465,12 @@ public class Iris extends VolmitPlugin implements Listener { setupAudience(); setupSentry(); 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()); - IrisSafeguard.earlySplash(); + IrisSafeguard.splash(true); linkMultiverseCore = new MultiverseCoreLink(); linkMythicMobs = new MythicMobsLink(); configWatcher = new FileWatcher(getDataFile("settings.json")); @@ -487,8 +485,7 @@ public class Iris extends VolmitPlugin implements Listener { J.sr(this::tickQueue, 0); J.s(this::setupPapi); J.a(ServerConfigurator::configure, 20); - splash(); - UtilsSFG.splash(); + IrisSafeguard.splash(false); autoStartStudio(); checkForBukkitWorlds(); @@ -771,25 +768,11 @@ public class Iris extends VolmitPlugin implements Listener { } } - IrisDimension dim; - if (id == null || id.isEmpty()) { - dim = IrisData.loadAnyDimension(IrisSettings.get().getGenerator().getDefaultWorldType()); - } else { - dim = IrisData.loadAnyDimension(id); - } + if (id == null || id.isEmpty()) id = IrisSettings.get().getGenerator().getDefaultWorldType(); Iris.debug("Generator ID: " + id + " requested by bukkit/plugin"); - + IrisDimension dim = loadDimension(worldName, id); if (dim == null) { - Iris.warn("Unable to find dimension type " + id + " Looking for online packs..."); - - service(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, false); - dim = IrisData.loadAnyDimension(id); - - if (dim == null) { - throw new RuntimeException("Can't find dimension " + id + "!"); - } else { - Iris.info("Resolved missing dimension, proceeding with generation."); - } + throw new RuntimeException("Can't find dimension " + id + "!"); } Iris.debug("Assuming IrisDimension: " + dim.getName()); @@ -814,6 +797,24 @@ public class Iris extends VolmitPlugin implements Listener { return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey()); } + @Nullable + public static IrisDimension loadDimension(@NonNull String worldName, @NonNull String id) { + var data = IrisData.get(new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pack"))); + var dimension = data.getDimensionLoader().load(id); + if (dimension == null) dimension = IrisData.loadAnyDimension(id); + if (dimension == null) { + Iris.warn("Unable to find dimension type " + id + " Looking for online packs..."); + Iris.service(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, false); + dimension = IrisData.loadAnyDimension(id); + + if (dimension != null) { + Iris.info("Resolved missing dimension, proceeding."); + } + } + + return dimension; + } + public void splash() { if (!IrisSettings.get().getGeneral().isSplashLogoStartup()) { return; diff --git a/core/src/main/java/com/volmit/iris/core/IrisWorlds.java b/core/src/main/java/com/volmit/iris/core/IrisWorlds.java new file mode 100644 index 000000000..9c8302102 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/IrisWorlds.java @@ -0,0 +1,79 @@ +package com.volmit.iris.core; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.io.IO; +import org.bukkit.Bukkit; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.lang.reflect.Type; +import java.util.Objects; +import java.util.stream.Stream; + +public class IrisWorlds { + private static final AtomicCache cache = new AtomicCache<>(); + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + private static final Type TYPE = TypeToken.getParameterized(KMap.class, String.class, String.class).getType(); + private final KMap worlds; + private volatile boolean dirty = false; + + private IrisWorlds(KMap worlds) { + this.worlds = worlds; + save(); + } + + public static IrisWorlds get() { + return cache.aquire(() -> { + File file = Iris.instance.getDataFile("worlds.json"); + if (!file.exists()) { + return new IrisWorlds(new KMap<>()); + } + + try { + String json = IO.readAll(file); + KMap worlds = GSON.fromJson(json, TYPE); + return new IrisWorlds(Objects.requireNonNullElseGet(worlds, KMap::new)); + } catch (Throwable e) { + Iris.error("Failed to load worlds.json!"); + e.printStackTrace(); + Iris.reportError(e); + } + + return new IrisWorlds(new KMap<>()); + }); + } + + public void put(String name, String type) { + String old = worlds.put(name, type); + if (!type.equals(old)) + dirty = true; + save(); + } + + public Stream getFolders() { + return worlds.keySet().stream().map(k -> new File(Bukkit.getWorldContainer(), k)); + } + + public void clean() { + dirty = worlds.entrySet().removeIf(entry -> !new File(Bukkit.getWorldContainer(), entry.getKey() + "/iris/pack/dimensions/" + entry.getValue() + ".json").exists()); + } + + public synchronized void save() { + clean(); + if (!dirty) return; + try { + IO.write(Iris.instance.getDataFile("worlds.json"), OutputStreamWriter::new, writer -> GSON.toJson(worlds, TYPE, writer)); + dirty = false; + } catch (IOException e) { + Iris.error("Failed to save worlds.json!"); + e.printStackTrace(); + Iris.reportError(e); + } + } +} 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 b3709e43f..79261d63d 100644 --- a/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java +++ b/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java @@ -23,10 +23,7 @@ import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.core.nms.datapack.IDataFixer; -import com.volmit.iris.engine.object.IrisBiome; -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.engine.object.*; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; @@ -34,8 +31,8 @@ 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 lombok.SneakyThrows; import org.bukkit.Bukkit; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.FileConfiguration; @@ -45,12 +42,12 @@ import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; -import java.util.Arrays; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.stream.Stream; -import static com.volmit.iris.core.nms.datapack.IDataFixer.Dimension.*; - public class ServerConfigurator { public static void configure() { IrisSettings.IrisSettingsAutoconfiguration s = IrisSettings.get().getAutoConfiguration(); @@ -115,14 +112,16 @@ public class ServerConfigurator { KList folders = getDatapacksFolder(); KMap> biomes = new KMap<>(); - allPacks().flatMap(height::merge) - .parallel() - .forEach(dim -> { - Iris.verbose(" Checking Dimension " + dim.getLoadFile().getPath()); - dim.installBiomes(fixer, dim::getLoader, folders, biomes.computeIfAbsent(dim.getLoadKey(), k -> new KSet<>())); - }); + try (Stream stream = allPacks()) { + stream.flatMap(height::merge) + .parallel() + .forEach(dim -> { + Iris.verbose(" Checking Dimension " + dim.getLoadFile().getPath()); + dim.installBiomes(fixer, dim::getLoader, folders, biomes.computeIfAbsent(dim.getLoadKey(), k -> new KSet<>())); + dim.installDimensionType(fixer, folders); + }); + } IrisDimension.writeShared(folders, height); - Iris.info("Data Packs Setup!"); if (fullInstall) @@ -130,19 +129,21 @@ public class ServerConfigurator { } private static void verifyDataPacksPost(boolean allowRestarting) { - boolean bad = allPacks() - .map(data -> { - Iris.verbose("Checking Pack: " + data.getDataFolder().getPath()); - var loader = data.getDimensionLoader(); - return loader.loadAll(loader.getPossibleKeys()) - .stream() - .map(ServerConfigurator::verifyDataPackInstalled) - .toList() - .contains(false); - }) - .toList() - .contains(true); - if (!bad) return; + try (Stream stream = allPacks()) { + boolean bad = stream + .map(data -> { + Iris.verbose("Checking Pack: " + data.getDataFolder().getPath()); + var loader = data.getDimensionLoader(); + return loader.loadAll(loader.getPossibleKeys()) + .stream() + .map(ServerConfigurator::verifyDataPackInstalled) + .toList() + .contains(false); + }) + .toList() + .contains(true); + if (!bad) return; + } if (allowRestarting) { @@ -225,9 +226,13 @@ public class ServerConfigurator { } public static Stream allPacks() { - return Stream.concat(listFiles(new File("plugins/Iris/packs")), - listFiles(Bukkit.getWorldContainer()).map(w -> new File(w, "iris/pack"))) + return Stream.concat(listFiles(Iris.instance.getDataFolder("packs")), + IrisWorlds.get().getFolders().map(w -> new File(w, "iris/pack"))) .filter(File::isDirectory) + .filter( base -> { + var content = new File(base, "dimensions").listFiles(); + return content != null && content.length > 0; + }) .map(IrisData::get); } @@ -242,20 +247,24 @@ public class ServerConfigurator { return path.substring(worldContainer.length(), path.length() - l); } + @SneakyThrows private static Stream listFiles(File parent) { - var files = parent.listFiles(); - return files == null ? Stream.empty() : Arrays.stream(files); + if (!parent.isDirectory()) return Stream.empty(); + return Files.walk(parent.toPath()).map(Path::toFile); } - @Data public static class DimensionHeight { private final IDataFixer fixer; - private IrisRange overworld = new IrisRange(); - private IrisRange nether = new IrisRange(); - private IrisRange end = new IrisRange(); - private int logicalOverworld = 0; - private int logicalNether = 0; - private int logicalEnd = 0; + private final AtomicIntegerArray[] dimensions = new AtomicIntegerArray[3]; + + public DimensionHeight(IDataFixer fixer) { + this.fixer = fixer; + for (int i = 0; i < 3; i++) { + dimensions[i] = new AtomicIntegerArray(new int[]{ + Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE + }); + } + } public Stream merge(IrisData data) { Iris.verbose("Checking Pack: " + data.getDataFolder().getPath()); @@ -266,25 +275,29 @@ public class ServerConfigurator { } public void merge(IrisDimension dimension) { - overworld.merge(dimension.getDimensionHeight()); - nether.merge(dimension.getDimensionHeight()); - end.merge(dimension.getDimensionHeight()); - - logicalOverworld = Math.max(logicalOverworld, dimension.getLogicalHeight()); - logicalNether = Math.max(logicalNether, dimension.getLogicalHeightNether()); - logicalEnd = Math.max(logicalEnd, dimension.getLogicalHeightEnd()); + AtomicIntegerArray array = dimensions[dimension.getBaseDimension().ordinal()]; + array.updateAndGet(0, min -> Math.min(min, dimension.getMinHeight())); + array.updateAndGet(1, max -> Math.max(max, dimension.getMaxHeight())); + array.updateAndGet(2, logical -> Math.max(logical, dimension.getLogicalHeight())); } - public String overworldType() { - return fixer.createDimension(OVERRWORLD, overworld, logicalOverworld).toString(4); + public String[] jsonStrings() { + var dims = IDataFixer.Dimension.values(); + var arr = new String[3]; + for (int i = 0; i < 3; i++) { + arr[i] = jsonString(dims[i]); + } + return arr; } - public String netherType() { - return fixer.createDimension(NETHER, nether, logicalNether).toString(4); - } - - public String endType() { - return fixer.createDimension(THE_END, end, logicalEnd).toString(4); + public String jsonString(IDataFixer.Dimension dimension) { + var data = dimensions[dimension.ordinal()]; + int minY = data.get(0); + int maxY = data.get(1); + int logicalHeight = data.get(2); + if (minY == Integer.MAX_VALUE || maxY == Integer.MIN_VALUE || Integer.MIN_VALUE == logicalHeight) + return null; + return fixer.createDimension(dimension, minY, maxY - minY, logicalHeight, null).toString(4); } } } 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 1b881e6fe..29fa4e37d 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,10 +18,10 @@ 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.datapack.DataVersion; import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.platform.PlatformChunkGenerator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.mantle.Mantle; @@ -36,6 +36,7 @@ import org.bukkit.entity.EntityType; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; import java.awt.Color; @@ -89,13 +90,10 @@ public interface INMSBinding { MCABiomeContainer newBiomeContainer(int min, int max); default World createWorld(WorldCreator c) { - if (missingDimensionTypes(true, true, true)) - throw new IllegalStateException("Missing dimenstion types to create world"); - - try (var ignored = injectLevelStems()) { - ignored.storeContext(); - return c.createWorld(); - } + if (c.generator() instanceof PlatformChunkGenerator gen + && missingDimensionTypes(gen.getTarget().getDimension().getDimensionTypeKey())) + throw new IllegalStateException("Missing dimension types to create world"); + return c.createWorld(); } int countCustomBiomes(); @@ -130,13 +128,9 @@ public interface INMSBinding { KList getStructureKeys(); - AutoClosing injectLevelStems(); + boolean missingDimensionTypes(String... keys); - default AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) { - return null; + default boolean injectBukkit() { + return true; } - - boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end); - - void removeCustomDimensions(World world); } 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 4d972128e..0e8a706a3 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 @@ -1,28 +1,31 @@ package com.volmit.iris.core.nms.datapack; import com.volmit.iris.engine.object.IrisBiomeCustom; -import com.volmit.iris.engine.object.IrisRange; +import com.volmit.iris.engine.object.IrisDimensionTypeOptions; import com.volmit.iris.util.json.JSONObject; +import org.jetbrains.annotations.Nullable; public interface IDataFixer { - default JSONObject fixCustomBiome(IrisBiomeCustom biome, JSONObject json) { return json; } - JSONObject rawDimension(Dimension dimension); + JSONObject resolve(Dimension dimension, @Nullable IrisDimensionTypeOptions options); - default JSONObject createDimension(Dimension dimension, IrisRange height, int logicalHeight) { - JSONObject obj = rawDimension(dimension); - obj.put("min_y", height.getMin()); - obj.put("height", height.getMax() - height.getMin()); + void fixDimension(Dimension dimension, JSONObject json); + + default JSONObject createDimension(Dimension base, int minY, int height, int logicalHeight, @Nullable IrisDimensionTypeOptions options) { + JSONObject obj = resolve(base, options); + obj.put("min_y", minY); + obj.put("height", height); obj.put("logical_height", logicalHeight); + fixDimension(base, obj); return obj; } enum Dimension { - OVERRWORLD, + OVERWORLD, NETHER, - THE_END + END } } diff --git a/core/src/main/java/com/volmit/iris/core/nms/datapack/v1192/DataFixerV1192.java b/core/src/main/java/com/volmit/iris/core/nms/datapack/v1192/DataFixerV1192.java index a0a854868..a9bb59e0e 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/datapack/v1192/DataFixerV1192.java +++ b/core/src/main/java/com/volmit/iris/core/nms/datapack/v1192/DataFixerV1192.java @@ -1,81 +1,104 @@ package com.volmit.iris.core.nms.datapack.v1192; import com.volmit.iris.core.nms.datapack.IDataFixer; +import com.volmit.iris.engine.object.IrisDimensionTypeOptions; import com.volmit.iris.util.json.JSONObject; +import org.jetbrains.annotations.Nullable; + import java.util.Map; +import static com.volmit.iris.engine.object.IrisDimensionTypeOptions.TriState.*; + public class DataFixerV1192 implements IDataFixer { + private static final Map OPTIONS = Map.of( + Dimension.OVERWORLD, new IrisDimensionTypeOptions( + FALSE, + TRUE, + FALSE, + FALSE, + TRUE, + TRUE, + TRUE, + FALSE, + 1d, + 0f, + null, + 192, + 0), + Dimension.NETHER, new IrisDimensionTypeOptions( + TRUE, + FALSE, + TRUE, + TRUE, + FALSE, + FALSE, + FALSE, + TRUE, + 8d, + 0.1f, + 18000L, + null, + 15), + Dimension.END, new IrisDimensionTypeOptions( + FALSE, + FALSE, + FALSE, + FALSE, + FALSE, + TRUE, + FALSE, + FALSE, + 1d, + 0f, + 6000L, + null, + 0) + ); private static final Map DIMENSIONS = Map.of( - Dimension.OVERRWORLD, """ + Dimension.OVERWORLD, """ { - "ambient_light": 0.0, - "bed_works": true, - "coordinate_scale": 1.0, "effects": "minecraft:overworld", - "has_ceiling": false, - "has_raids": true, - "has_skylight": true, "infiniburn": "#minecraft:infiniburn_overworld", - "monster_spawn_block_light_limit": 0, "monster_spawn_light_level": { "type": "minecraft:uniform", "value": { "max_inclusive": 7, "min_inclusive": 0 } - }, - "natural": true, - "piglin_safe": false, - "respawn_anchor_works": false, - "ultrawarm": false + } }""", Dimension.NETHER, """ { - "ambient_light": 0.1, - "bed_works": false, - "coordinate_scale": 8.0, "effects": "minecraft:the_nether", - "fixed_time": 18000, - "has_ceiling": true, - "has_raids": false, - "has_skylight": false, "infiniburn": "#minecraft:infiniburn_nether", - "monster_spawn_block_light_limit": 15, "monster_spawn_light_level": 7, - "natural": false, - "piglin_safe": true, - "respawn_anchor_works": true, - "ultrawarm": true }""", - Dimension.THE_END, """ + Dimension.END, """ { - "ambient_light": 0.0, - "bed_works": false, - "coordinate_scale": 1.0, "effects": "minecraft:the_end", - "fixed_time": 6000, - "has_ceiling": false, - "has_raids": true, - "has_skylight": false, "infiniburn": "#minecraft:infiniburn_end", - "monster_spawn_block_light_limit": 0, "monster_spawn_light_level": { "type": "minecraft:uniform", "value": { "max_inclusive": 7, "min_inclusive": 0 } - }, - "natural": false, - "piglin_safe": false, - "respawn_anchor_works": false, - "ultrawarm": false + } }""" ); @Override - public JSONObject rawDimension(Dimension dimension) { - return new JSONObject(DIMENSIONS.get(dimension)); + public JSONObject resolve(Dimension dimension, @Nullable IrisDimensionTypeOptions options) { + return options == null ? OPTIONS.get(dimension).toJson() : options.resolve(OPTIONS.get(dimension)).toJson(); + } + + @Override + public void fixDimension(Dimension dimension, JSONObject json) { + var missing = new JSONObject(DIMENSIONS.get(dimension)); + for (String key : missing.keySet()) { + if (json.has(key)) continue; + json.put(key, missing.get(key)); + } } } diff --git a/core/src/main/java/com/volmit/iris/core/nms/datapack/v1206/DataFixerV1206.java b/core/src/main/java/com/volmit/iris/core/nms/datapack/v1206/DataFixerV1206.java index 638f043b1..eb8b59c28 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/datapack/v1206/DataFixerV1206.java +++ b/core/src/main/java/com/volmit/iris/core/nms/datapack/v1206/DataFixerV1206.java @@ -45,13 +45,12 @@ public class DataFixerV1206 extends DataFixerV1192 { } @Override - public JSONObject rawDimension(Dimension dimension) { - JSONObject json = super.rawDimension(dimension); + public void fixDimension(Dimension dimension, JSONObject json) { + super.fixDimension(dimension, json); if (!(json.get("monster_spawn_light_level") instanceof JSONObject lightLevel)) - return json; + return; var value = (JSONObject) lightLevel.remove("value"); lightLevel.put("max_inclusive", value.get("max_inclusive")); lightLevel.put("min_inclusive", value.get("min_inclusive")); - return json; } } 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 a5ace2486..642a00e38 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,9 +20,7 @@ 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.container.Pair; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; @@ -121,25 +119,10 @@ public class NMSBinding1X implements INMSBinding { } @Override - public AutoClosing injectLevelStems() { - return new AutoClosing(() -> {}); - } - - @Override - public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) { - return injectLevelStems(); - } - - @Override - public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { + public boolean missingDimensionTypes(String... keys) { return false; } - @Override - public void removeCustomDimensions(World world) { - - } - @Override public CompoundTag serializeEntity(Entity location) { return null; 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 2c9eb9be1..241bdd01f 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 @@ -3,7 +3,10 @@ package com.volmit.iris.core.safeguard; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; +import java.util.concurrent.atomic.AtomicBoolean; + public class IrisSafeguard { + private static final AtomicBoolean sfg = new AtomicBoolean(false); public static boolean unstablemode = false; public static boolean warningmode = false; public static boolean stablemode = false; @@ -13,12 +16,14 @@ public class IrisSafeguard { ServerBootSFG.BootCheck(); } - public static void earlySplash() { - if (ServerBootSFG.safeguardPassed || IrisSettings.get().getGeneral().DoomsdayAnnihilationSelfDestructMode) + public static void splash(boolean early) { + if (early && (ServerBootSFG.safeguardPassed || IrisSettings.get().getGeneral().DoomsdayAnnihilationSelfDestructMode)) return; - Iris.instance.splash(); - UtilsSFG.splash(); + if (!sfg.getAndSet(true)) { + Iris.instance.splash(); + UtilsSFG.splash(); + } } public static String mode() { 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 b94d7e89e..f3fbbd9dc 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 @@ -1,10 +1,15 @@ package com.volmit.iris.core.safeguard; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.nms.v1X.NMSBinding1X; -import com.volmit.iris.engine.object.IrisContextInjector; +import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.util.agent.Agent; +import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.misc.ServerProperties; import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; import javax.tools.JavaCompiler; @@ -15,10 +20,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.StringJoiner; +import java.util.*; import static com.volmit.iris.Iris.getJavaVersion; import static com.volmit.iris.core.safeguard.IrisSafeguard.*; @@ -31,6 +33,8 @@ public class ServerBootSFG { public static boolean hasPrivileges = true; public static boolean unsuportedversion = false; public static boolean missingDimensionTypes = false; + public static boolean missingAgent = false; + public static boolean failedInjection = false; protected static boolean safeguardPassed; public static boolean passedserversoftware = true; protected static int count; @@ -112,10 +116,21 @@ public class ServerBootSFG { severityMedium++; } - if (IrisContextInjector.isMissingDimensionTypes()) { - missingDimensionTypes = true; - joiner.add("Missing Dimension Types"); + if (!Agent.install()) { + missingAgent = true; + joiner.add("Missing Java Agent"); severityHigh++; + } else { + if (missingDimensionTypes()) { + missingDimensionTypes = true; + joiner.add("Missing Dimension Types"); + severityHigh++; + } + if (!INMS.get().injectBukkit()) { + failedInjection = true; + joiner.add("Failed Bukkit Injection"); + severityHigh++; + } } allIncompatibilities = joiner.toString(); @@ -169,4 +184,32 @@ public class ServerBootSFG { return !path.isEmpty() && (new File(path, "javac").exists() || new File(path, "javac.exe").exists()); } + private static boolean missingDimensionTypes() { + return INMS.get().missingDimensionTypes(getDimensionTypes().toArray(String[]::new)); + } + + private static KSet getDimensionTypes() { + var bukkit = YamlConfiguration.loadConfiguration(ServerProperties.BUKKIT_YML); + var worlds = bukkit.getConfigurationSection("worlds"); + if (worlds == null) return new KSet<>(); + + var types = new KSet(); + for (String world : worlds.getKeys(false)) { + var gen = worlds.getString(world + ".generator"); + if (gen == null) continue; + + String loadKey; + if (gen.equalsIgnoreCase("iris")) { + loadKey = IrisSettings.get().getGenerator().getDefaultWorldType(); + } else if (gen.startsWith("Iris:")) { + loadKey = gen.substring(5); + } else continue; + + IrisDimension dimension = Iris.loadDimension(world, loadKey); + if (dimension == null) continue; + types.add(dimension.getDimensionTypeKey()); + } + + return types; + } } 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 c45cfc7bb..b59a24585 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 @@ -1,6 +1,7 @@ package com.volmit.iris.core.safeguard; import com.volmit.iris.Iris; +import com.volmit.iris.util.agent.Agent; import com.volmit.iris.util.format.C; public class UtilsSFG { @@ -44,6 +45,11 @@ public class UtilsSFG { 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.missingAgent) { + Iris.safeguard(C.RED + "Java Agent"); + Iris.safeguard(C.RED + "- Please enable dynamic agent loading by adding -XX:+EnableDynamicAgentLoading to your jvm arguments."); + Iris.safeguard(C.RED + "- or add the jvm argument -javaagent:" + Agent.AGENT_JAR.getPath()); + } 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/engine/object/IrisContextInjector.java b/core/src/main/java/com/volmit/iris/engine/object/IrisContextInjector.java deleted file mode 100644 index 415dd7ec8..000000000 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisContextInjector.java +++ /dev/null @@ -1,66 +0,0 @@ -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.collection.KList; -import com.volmit.iris.util.misc.ServerProperties; -import lombok.Getter; -import org.bukkit.Bukkit; -import org.bukkit.NamespacedKey; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -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; - - public IrisContextInjector() { - if (!Bukkit.getWorlds().isEmpty()) 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"); - - if (INMS.get().missingDimensionTypes(overworld, nether, end)) { - missingDimensionTypes = true; - return; - } - - if (overworld || nether || end) { - autoClosing = INMS.get().injectUncached(overworld, nether, end); - } - - instance.registerListener(this); - } - - @EventHandler(priority = EventPriority.LOWEST) - public void on(WorldInitEvent event) { - 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 0459e1d06..6ff0fa44f 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 @@ -25,6 +25,7 @@ import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.loader.IrisRegistrant; import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.nms.datapack.IDataFixer; +import com.volmit.iris.core.nms.datapack.IDataFixer.Dimension; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.object.annotations.*; import com.volmit.iris.util.collection.KList; @@ -45,8 +46,7 @@ import org.bukkit.Material; import org.bukkit.World.Environment; import org.bukkit.block.data.BlockData; -import java.io.File; -import java.io.IOException; +import java.io.*; @Accessors(chain = true) @AllArgsConstructor @@ -74,10 +74,6 @@ public class IrisDimension extends IrisRegistrant { @MaxNumber(2032) @Desc("Maximum height at which players can be teleported to through gameplay.") private int logicalHeight = 256; - @Desc("Maximum height at which players can be teleported to through gameplay.") - private int logicalHeightEnd = 256; - @Desc("Maximum height at which players can be teleported to through gameplay.") - private int logicalHeightNether = 256; @RegistryListResource(IrisJigsawStructure.class) @Desc("If defined, Iris will place the given jigsaw structure where minecraft should place the overworld stronghold.") private String stronghold; @@ -166,10 +162,8 @@ public class IrisDimension extends IrisRegistrant { private int fluidHeight = 63; @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") private IrisRange dimensionHeight = new IrisRange(-64, 320); - @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") - private IrisRange dimensionHeightEnd = new IrisRange(-64, 320); - @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") - private IrisRange dimensionHeightNether = new IrisRange(-64, 320); + @Desc("Define options for this dimension") + private IrisDimensionTypeOptions dimensionOptions = new IrisDimensionTypeOptions(); @RegistryListResource(IrisBiome.class) @Desc("Keep this either undefined or empty. Setting any biome name into this will force iris to only generate the specified biome. Great for testing.") private String focus = ""; @@ -412,6 +406,39 @@ public class IrisDimension extends IrisRegistrant { }); } + public Dimension getBaseDimension() { + return switch (getEnvironment()) { + case NETHER -> Dimension.NETHER; + case THE_END -> Dimension.END; + default -> Dimension.OVERWORLD; + }; + } + + public String getDimensionTypeKey() { + return getDimensionType().key(); + } + + public IrisDimensionType getDimensionType() { + return new IrisDimensionType(getBaseDimension(), getDimensionOptions(), getLogicalHeight(), getMaxHeight() - getMinHeight(), getMinHeight()); + } + + public void installDimensionType(IDataFixer fixer, KList folders) { + IrisDimensionType type = getDimensionType(); + String json = type.toJson(fixer); + + Iris.verbose(" Installing Data Pack Dimension Type: \"iris:" + type.key() + '"'); + for (File datapacks : folders) { + File output = new File(datapacks, "iris/data/iris/dimension_type/" + type.key() + ".json"); + output.getParentFile().mkdirs(); + try { + IO.writeAll(output, json); + } catch (IOException e) { + Iris.reportError(e); + e.printStackTrace(); + } + } + } + @Override public String getFolderName() { return "dimensions"; @@ -428,11 +455,12 @@ public class IrisDimension extends IrisRegistrant { } public static void writeShared(KList folders, DimensionHeight height) { - Iris.verbose(" Installing Data Pack Dimension Types: \"iris:overworld\", \"iris:the_nether\", \"iris:the_end\""); + Iris.verbose(" Installing Data Pack Vanilla Dimension Types"); + String[] jsonStrings = height.jsonStrings(); for (File datapacks : folders) { - write(datapacks, "overworld", height.overworldType()); - write(datapacks, "the_nether", height.netherType()); - write(datapacks, "the_end", height.endType()); + write(datapacks, "overworld", jsonStrings[0]); + write(datapacks, "the_nether", jsonStrings[1]); + write(datapacks, "the_end", jsonStrings[2]); } String raw = """ @@ -457,17 +485,9 @@ public class IrisDimension extends IrisRegistrant { } private static void write(File datapacks, String type, String json) { - File dimType = new File(datapacks, "iris/data/iris/dimension_type/" + type + ".json"); + if (json == null) return; File dimTypeVanilla = new File(datapacks, "iris/data/minecraft/dimension_type/" + type + ".json"); - dimType.getParentFile().mkdirs(); - try { - IO.writeAll(dimType, json); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - if (IrisSettings.get().getGeneral().adjustVanillaHeight || dimTypeVanilla.exists()) { dimTypeVanilla.getParentFile().mkdirs(); try { diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisDimensionType.java b/core/src/main/java/com/volmit/iris/engine/object/IrisDimensionType.java new file mode 100644 index 000000000..756e4c538 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisDimensionType.java @@ -0,0 +1,91 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.core.nms.datapack.IDataFixer; +import com.volmit.iris.util.data.Varint; +import com.volmit.iris.util.io.IO; +import lombok.*; +import lombok.experimental.Accessors; + +import java.io.*; + +@Getter +@ToString +@Accessors(fluent = true, chain = true) +@EqualsAndHashCode +public final class IrisDimensionType { + @NonNull + private final String key; + @NonNull + private final IDataFixer.Dimension base; + @NonNull + private final IrisDimensionTypeOptions options; + private final int logicalHeight; + private final int height; + private final int minY; + + public IrisDimensionType( + @NonNull IDataFixer.Dimension base, + @NonNull IrisDimensionTypeOptions options, + int logicalHeight, + int height, + int minY + ) { + if (logicalHeight > height) throw new IllegalArgumentException("Logical height cannot be greater than height"); + if (logicalHeight < 0) throw new IllegalArgumentException("Logical height cannot be less than zero"); + if (height < 16 || height > 4064 ) throw new IllegalArgumentException("Height must be between 16 and 4064"); + if ((height & 15) != 0) throw new IllegalArgumentException("Height must be a multiple of 16"); + if (minY < -2032 || minY > 2031) throw new IllegalArgumentException("Min Y must be between -2032 and 2031"); + if ((minY & 15) != 0) throw new IllegalArgumentException("Min Y must be a multiple of 16"); + + this.base = base; + this.options = options; + this.logicalHeight = logicalHeight; + this.height = height; + this.minY = minY; + this.key = createKey(); + } + + public static IrisDimensionType fromKey(String key) { + var stream = new ByteArrayInputStream(IO.decode(key.replace(".", "=").toUpperCase())); + try (var din = new DataInputStream(stream)) { + return new IrisDimensionType( + IDataFixer.Dimension.values()[din.readUnsignedByte()], + new IrisDimensionTypeOptions().read(din), + Varint.readUnsignedVarInt(din), + Varint.readUnsignedVarInt(din), + Varint.readSignedVarInt(din) + ); + } catch (IOException e) { + throw new RuntimeException("This is impossible", e); + } + } + + public String toJson(IDataFixer fixer) { + return fixer.createDimension( + base, + minY, + height, + logicalHeight, + options.copy() + ).toString(4); + } + + private String createKey() { + var stream = new ByteArrayOutputStream(41); + try (var dos = new DataOutputStream(stream)) { + dos.writeByte(base.ordinal()); + options.write(dos); + Varint.writeUnsignedVarInt(logicalHeight, dos); + Varint.writeUnsignedVarInt(height, dos); + Varint.writeSignedVarInt(minY, dos); + } catch (IOException e) { + throw new RuntimeException("This is impossible", e); + } + + return IO.encode(stream.toByteArray()).replace("=", ".").toLowerCase(); + } + + public IrisDimensionTypeOptions options() { + return options.copy(); + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisDimensionTypeOptions.java b/core/src/main/java/com/volmit/iris/engine/object/IrisDimensionTypeOptions.java new file mode 100644 index 000000000..139d6296d --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisDimensionTypeOptions.java @@ -0,0 +1,320 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.engine.object.annotations.Desc; +import com.volmit.iris.engine.object.annotations.MaxNumber; +import com.volmit.iris.engine.object.annotations.MinNumber; +import com.volmit.iris.util.data.Varint; +import com.volmit.iris.util.json.JSONObject; +import lombok.*; +import lombok.experimental.Accessors; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Arrays; + +import static com.volmit.iris.engine.object.IrisDimensionTypeOptions.TriState.*; + +@Data +@NoArgsConstructor +@Accessors(fluent = true, chain = true) +@Desc("Optional options for the dimension type") +public class IrisDimensionTypeOptions { + @MinNumber(0.00001) + @MaxNumber(30000000) + @Desc("The multiplier applied to coordinates when leaving the dimension. Value between 0.00001 and 30000000.0 (both inclusive).") + private double coordinateScale = -1; + @MinNumber(0) + @MaxNumber(1) + @Desc("How much light the dimension has. When set to 0, it completely follows the light level; when set to 1, there is no ambient lighting.") + private float ambientLight = -1; + @Nullable + @MinNumber(0) + @MaxNumber(Long.MAX_VALUE) + @Desc("If this is set to an int, the time of the day is the specified value. To ensure a normal time cycle, set it to null.") + private Long fixedTime = -1L; + @Nullable + @MinNumber(-2032) + @MaxNumber(2031) + @Desc("Optional value between -2032 and 2031. If set, determines the lower edge of the clouds. If set to null, clouds are disabled in the dimension.") + private Integer cloudHeight = -1; + @MinNumber(0) + @MaxNumber(15) + @Desc("Value between 0 and 15 (both inclusive). Maximum block light required when the monster spawns.") + private int monsterSpawnBlockLightLimit = -1; + + @NonNull + @Desc("Whether the dimensions behaves like the nether (water evaporates and sponges dry) or not. Also lets stalactites drip lava and causes lava to spread faster and thinner.") + private TriState ultrawarm = DEFAULT; + @NonNull + @Desc("When false, compasses spin randomly, and using a bed to set the respawn point or sleep, is disabled. When true, nether portals can spawn zombified piglins, and creaking hearts can spawn creakings.") + private TriState natural = DEFAULT; + @NonNull + @Desc("When false, Piglins and hoglins shake and transform to zombified entities.") + private TriState piglinSafe = DEFAULT; + @NonNull + @Desc("When false, the respawn anchor blows up when trying to set spawn point.") + private TriState respawnAnchorWorks = DEFAULT; + @NonNull + @Desc("When false, the bed blows up when trying to sleep.") + private TriState bedWorks = DEFAULT; + @NonNull + @Desc("Whether players with the Bad Omen effect can cause a raid.") + private TriState raids = DEFAULT; + @NonNull + @Desc("Whether the dimension has skylight or not.") + private TriState skylight = DEFAULT; + @NonNull + @Desc("Whether the dimension has a bedrock ceiling. Note that this is only a logical ceiling. It is unrelated with whether the dimension really has a block ceiling.") + private TriState ceiling = DEFAULT; + + public IrisDimensionTypeOptions( + @NonNull TriState ultrawarm, + @NonNull TriState natural, + @NonNull TriState piglinSafe, + @NonNull TriState respawnAnchorWorks, + @NonNull TriState bedWorks, + @NonNull TriState raids, + @NonNull TriState skylight, + @NonNull TriState ceiling, + double coordinateScale, + float ambientLight, + @Nullable Long fixedTime, + @Nullable Integer cloudHeight, + int monsterSpawnBlockLightLimit + ) { + if (coordinateScale != -1 && (coordinateScale < 0.00001 || coordinateScale > 30000000)) + throw new IllegalArgumentException("Coordinate scale must be between 0.00001 and 30000000"); + if (ambientLight != -1 && (ambientLight < 0 || ambientLight > 1)) + throw new IllegalArgumentException("Ambient light must be between 0 and 1"); + if (cloudHeight != null && cloudHeight != -1 && (cloudHeight < -2032 || cloudHeight > 2031)) + throw new IllegalArgumentException("Cloud height must be between -2032 and 2031"); + if (monsterSpawnBlockLightLimit != -1 && (monsterSpawnBlockLightLimit < 0 || monsterSpawnBlockLightLimit > 15)) + throw new IllegalArgumentException("Monster spawn block light limit must be between 0 and 15"); + + this.ultrawarm = ultrawarm; + this.natural = natural; + this.piglinSafe = piglinSafe; + this.respawnAnchorWorks = respawnAnchorWorks; + this.bedWorks = bedWorks; + this.raids = raids; + this.skylight = skylight; + this.ceiling = ceiling; + this.coordinateScale = coordinateScale; + this.ambientLight = ambientLight; + this.fixedTime = fixedTime; + this.cloudHeight = cloudHeight; + this.monsterSpawnBlockLightLimit = monsterSpawnBlockLightLimit; + } + + public IrisDimensionTypeOptions coordinateScale(double coordinateScale) { + if (coordinateScale != -1 && (coordinateScale < 0.00001 || coordinateScale > 30000000)) + throw new IllegalArgumentException("Coordinate scale must be between 0.00001 and 30000000"); + this.coordinateScale = coordinateScale; + return this; + } + + public IrisDimensionTypeOptions ambientLight(float ambientLight) { + if (ambientLight != -1 && (ambientLight < 0 || ambientLight > 1)) + throw new IllegalArgumentException("Ambient light must be between 0 and 1"); + this.ambientLight = ambientLight; + return this; + } + + public IrisDimensionTypeOptions cloudHeight(@Nullable Integer cloudHeight) { + if (cloudHeight != null && cloudHeight != -1 && (cloudHeight < -2032 || cloudHeight > 2031)) + throw new IllegalArgumentException("Cloud height must be between -2032 and 2031"); + this.cloudHeight = cloudHeight; + return this; + } + + public IrisDimensionTypeOptions monsterSpawnBlockLightLimit(int monsterSpawnBlockLightLimit) { + if (monsterSpawnBlockLightLimit != -1 && (monsterSpawnBlockLightLimit < 0 || monsterSpawnBlockLightLimit > 15)) + throw new IllegalArgumentException("Monster spawn block light limit must be between 0 and 15"); + this.monsterSpawnBlockLightLimit = monsterSpawnBlockLightLimit; + return this; + } + + public void write(DataOutput dos) throws IOException { + int bits = 0; + int index = 0; + + for (TriState state : new TriState[]{ + ultrawarm, + natural, + skylight, + ceiling, + piglinSafe, + bedWorks, + respawnAnchorWorks, + raids + }) { + if (state == DEFAULT) { + index++; + continue; + } + + bits |= (short) (1 << index++); + if (state == TRUE) + bits |= (short) (1 << index++); + } + + if (coordinateScale != -1) + bits |= (1 << index++); + if (ambientLight != -1) + bits |= (1 << index++); + if (monsterSpawnBlockLightLimit != -1) + bits |= (1 << index++); + if (fixedTime != null) { + bits |= (1 << index++); + if (fixedTime != -1L) + bits |= (1 << index++); + } + if (cloudHeight != null) { + bits |= (1 << index++); + if (cloudHeight != -1) + bits |= (1 << index); + } + + Varint.writeSignedVarInt(bits, dos); + + if (coordinateScale != -1) + Varint.writeUnsignedVarLong(Double.doubleToLongBits(coordinateScale), dos); + if (ambientLight != -1) + Varint.writeUnsignedVarInt(Float.floatToIntBits(ambientLight), dos); + if (monsterSpawnBlockLightLimit != -1) + Varint.writeSignedVarInt(monsterSpawnBlockLightLimit, dos); + if (fixedTime != null && fixedTime != -1L) + Varint.writeSignedVarLong(fixedTime, dos); + if (cloudHeight != null && cloudHeight != -1) + Varint.writeSignedVarInt(cloudHeight, dos); + } + + public IrisDimensionTypeOptions read(DataInput dis) throws IOException { + TriState[] states = new TriState[8]; + Arrays.fill(states, DEFAULT); + + int bits = Varint.readSignedVarInt(dis); + int index = 0; + + for (int i = 0; i < 8; i++) { + if ((bits & (1 << index++)) == 0) + continue; + states[i] = (bits & (1 << index++)) == 0 ? FALSE : TRUE; + } + ultrawarm = states[0]; + natural = states[1]; + skylight = states[2]; + ceiling = states[3]; + piglinSafe = states[4]; + bedWorks = states[5]; + respawnAnchorWorks = states[6]; + raids = states[7]; + + coordinateScale = (bits & (1 << index++)) != 0 ? Double.longBitsToDouble(Varint.readUnsignedVarLong(dis)) : -1; + ambientLight = (bits & (1 << index++)) != 0 ? Float.intBitsToFloat(Varint.readUnsignedVarInt(dis)) : -1; + monsterSpawnBlockLightLimit = (bits & (1 << index++)) != 0 ? Varint.readSignedVarInt(dis) : -1; + fixedTime = (bits & (1 << index++)) != 0 ? (bits & (1 << index)) != 0 ? Varint.readSignedVarLong(dis) : -1L : null; + cloudHeight = (bits & (1 << index++)) != 0 ? (bits & (1 << index)) != 0 ? Varint.readSignedVarInt(dis) : -1 : null; + + return this; + } + + public IrisDimensionTypeOptions resolve(IrisDimensionTypeOptions other) { + if (ultrawarm == DEFAULT) + ultrawarm = other.ultrawarm; + if (natural == DEFAULT) + natural = other.natural; + if (piglinSafe == DEFAULT) + piglinSafe = other.piglinSafe; + if (respawnAnchorWorks == DEFAULT) + respawnAnchorWorks = other.respawnAnchorWorks; + if (bedWorks == DEFAULT) + bedWorks = other.bedWorks; + if (raids == DEFAULT) + raids = other.raids; + if (skylight == DEFAULT) + skylight = other.skylight; + if (ceiling == DEFAULT) + ceiling = other.ceiling; + if (coordinateScale == -1) + coordinateScale = other.coordinateScale; + if (ambientLight == -1) + ambientLight = other.ambientLight; + if (fixedTime != null && fixedTime == -1L) + fixedTime = other.fixedTime; + if (cloudHeight != null && cloudHeight == -1) + cloudHeight = other.cloudHeight; + if (monsterSpawnBlockLightLimit == -1) + monsterSpawnBlockLightLimit = other.monsterSpawnBlockLightLimit; + return this; + } + + public JSONObject toJson() { + if (!isComplete()) throw new IllegalStateException("Cannot serialize incomplete options"); + JSONObject json = new JSONObject(); + json.put("ultrawarm", ultrawarm.bool()); + json.put("natural", natural.bool()); + json.put("piglin_safe", piglinSafe.bool()); + json.put("respawn_anchor_works", respawnAnchorWorks.bool()); + json.put("bed_works", bedWorks.bool()); + json.put("has_raids", raids.bool()); + json.put("has_skylight", skylight.bool()); + json.put("has_ceiling", ceiling.bool()); + json.put("coordinate_scale", coordinateScale); + json.put("ambient_light", ambientLight); + json.put("monster_spawn_block_light_limit", monsterSpawnBlockLightLimit); + if (fixedTime != null) json.put("fixed_time", fixedTime); + if (cloudHeight != null) json.put("cloud_height", cloudHeight); + return json; + } + + public IrisDimensionTypeOptions copy() { + return new IrisDimensionTypeOptions( + ultrawarm, + natural, + piglinSafe, + respawnAnchorWorks, + bedWorks, + raids, + skylight, + ceiling, + coordinateScale, + ambientLight, + fixedTime, + cloudHeight, + monsterSpawnBlockLightLimit + ); + } + + public boolean isComplete() { + return ultrawarm != DEFAULT + && natural != DEFAULT + && piglinSafe != DEFAULT + && respawnAnchorWorks != DEFAULT + && bedWorks != DEFAULT + && raids != DEFAULT + && skylight != DEFAULT + && ceiling != DEFAULT + && coordinateScale != -1 + && ambientLight != -1 + && monsterSpawnBlockLightLimit != -1 + && (fixedTime == null || fixedTime != -1L) + && (cloudHeight == null || cloudHeight != -1); + } + + @Desc("Allows reusing the behavior of the base dimension") + public enum TriState { + @Desc("Follow the behavior of the base dimension") + DEFAULT, + @Desc("True") + TRUE, + @Desc("False") + FALSE; + + public boolean bool() { + return this == TRUE; + } + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java index ab5d67066..2e6035534 100644 --- a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java @@ -19,11 +19,12 @@ package com.volmit.iris.engine.platform; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisWorlds; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.nms.INMS; -import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.engine.IrisEngine; +import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.data.chunk.TerrainChunk; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.framework.EngineTarget; @@ -61,7 +62,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; -import java.lang.reflect.Field; import java.util.List; import java.util.Random; import java.util.concurrent.CompletableFuture; @@ -87,6 +87,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun private final boolean studio; private final AtomicInteger a = new AtomicInteger(0); private final CompletableFuture spawnChunks = new CompletableFuture<>(); + private final AtomicCache targetCache = new AtomicCache<>(); private volatile Engine engine; private volatile Looper hotloader; private volatile StudioMode lastMode; @@ -113,36 +114,35 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun @EventHandler(priority = EventPriority.LOWEST) public void onWorldInit(WorldInitEvent event) { - try { - if (initialized || !world.name().equals(event.getWorld().getName())) - return; - AutoClosing.closeContext(); - INMS.get().removeCustomDimensions(event.getWorld()); - world.setRawWorldSeed(event.getWorld().getSeed()); - Engine engine = getEngine(event.getWorld()); - if (engine == null) { - Iris.warn("Failed to get Engine!"); - J.s(() -> { - Engine engine1 = getEngine(event.getWorld()); - if (engine1 != null) { - try { - INMS.get().inject(event.getWorld().getSeed(), engine1, event.getWorld()); - Iris.info("Injected Iris Biome Source into " + event.getWorld().getName()); - initialized = true; - } catch (Throwable e) { - e.printStackTrace(); - } - } - }, 10); - } else { - INMS.get().inject(event.getWorld().getSeed(), engine, event.getWorld()); - Iris.info("Injected Iris Biome Source into " + event.getWorld().getName()); - spawnChunks.complete(INMS.get().getSpawnChunkCount(event.getWorld())); - initialized = true; + if (initialized || !world.name().equals(event.getWorld().getName())) + return; + world.setRawWorldSeed(event.getWorld().getSeed()); + if (initialize(event.getWorld())) return; + + Iris.warn("Failed to get Engine for " + event.getWorld().getName() + " re-trying..."); + J.s(() -> { + if (!initialize(event.getWorld())) { + Iris.error("Failed to get Engine for " + event.getWorld().getName() + "!"); } + }, 10); + } + + private boolean initialize(World world) { + Engine engine = getEngine(world); + if (engine == null) return false; + try { + INMS.get().inject(world.getSeed(), engine, world); + Iris.info("Injected Iris Biome Source into " + world.getName()); } catch (Throwable e) { + Iris.reportError(e); + Iris.error("Failed to inject biome source into " + world.getName()); e.printStackTrace(); } + spawnChunks.complete(INMS.get().getSpawnChunkCount(world)); + Iris.instance.unregisterListener(this); + initialized = true; + IrisWorlds.get().put(world.getName(), dimensionKey); + return true; } @Nullable @@ -160,37 +160,48 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun } private void setupEngine() { - IrisData data = IrisData.get(dataLocation); - IrisDimension dimension = data.getDimensionLoader().load(dimensionKey); + lastMode = StudioMode.NORMAL; + engine = new IrisEngine(getTarget(), studio); + populators.clear(); + targetCache.reset(); + } - if (dimension == null) { - Iris.error("Oh No! There's no pack in " + data.getDataFolder().getPath() + " or... there's no dimension for the key " + dimensionKey); - IrisDimension test = IrisData.loadAnyDimension(dimensionKey); + @NotNull + @Override + public EngineTarget getTarget() { + if (engine != null) return engine.getTarget(); - if (test != null) { - Iris.warn("Looks like " + dimensionKey + " exists in " + test.getLoadFile().getPath() + " "); - Iris.service(StudioSVC.class).installIntoWorld(Iris.getSender(), dimensionKey, dataLocation.getParentFile().getParentFile()); - Iris.warn("Attempted to install into " + data.getDataFolder().getPath()); - data.dump(); - data.clearLists(); - test = data.getDimensionLoader().load(dimensionKey); + return targetCache.aquire(() -> { + IrisData data = IrisData.get(dataLocation); + IrisDimension dimension = data.getDimensionLoader().load(dimensionKey); + + if (dimension == null) { + Iris.error("Oh No! There's no pack in " + data.getDataFolder().getPath() + " or... there's no dimension for the key " + dimensionKey); + IrisDimension test = IrisData.loadAnyDimension(dimensionKey); if (test != null) { - Iris.success("Woo! Patched the Engine!"); - dimension = test; + Iris.warn("Looks like " + dimensionKey + " exists in " + test.getLoadFile().getPath() + " "); + Iris.service(StudioSVC.class).installIntoWorld(Iris.getSender(), dimensionKey, dataLocation.getParentFile().getParentFile()); + Iris.warn("Attempted to install into " + data.getDataFolder().getPath()); + data.dump(); + data.clearLists(); + test = data.getDimensionLoader().load(dimensionKey); + + if (test != null) { + Iris.success("Woo! Patched the Engine!"); + dimension = test; + } else { + Iris.error("Failed to patch dimension!"); + throw new RuntimeException("Missing Dimension: " + dimensionKey); + } } else { - Iris.error("Failed to patch dimension!"); + Iris.error("Nope, you don't have an installation containing " + dimensionKey + " try downloading it?"); throw new RuntimeException("Missing Dimension: " + dimensionKey); } - } else { - Iris.error("Nope, you don't have an installation containing " + dimensionKey + " try downloading it?"); - throw new RuntimeException("Missing Dimension: " + dimensionKey); } - } - lastMode = StudioMode.NORMAL; - engine = new IrisEngine(new EngineTarget(world, dimension, data), studio); - populators.clear(); + return new EngineTarget(world, dimension, data); + }); } @Override diff --git a/core/src/main/java/com/volmit/iris/engine/platform/PlatformChunkGenerator.java b/core/src/main/java/com/volmit/iris/engine/platform/PlatformChunkGenerator.java index e79c3dd6f..687788527 100644 --- a/core/src/main/java/com/volmit/iris/engine/platform/PlatformChunkGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/platform/PlatformChunkGenerator.java @@ -24,21 +24,23 @@ import com.volmit.iris.engine.framework.EngineTarget; import com.volmit.iris.engine.framework.Hotloadable; import com.volmit.iris.util.data.DataProvider; import org.bukkit.World; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; public interface PlatformChunkGenerator extends Hotloadable, DataProvider { + @Nullable Engine getEngine(); @Override default IrisData getData() { - return getEngine().getData(); + return getTarget().getData(); } - default EngineTarget getTarget() { - return getEngine().getTarget(); - } + @NotNull + EngineTarget getTarget(); void injectChunkReplacement(World world, int x, int z, Consumer jobs); diff --git a/core/src/main/java/com/volmit/iris/util/agent/Agent.java b/core/src/main/java/com/volmit/iris/util/agent/Agent.java new file mode 100644 index 000000000..5f823fc30 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/agent/Agent.java @@ -0,0 +1,46 @@ +package com.volmit.iris.util.agent; + +import com.volmit.iris.Iris; +import net.bytebuddy.agent.ByteBuddyAgent; +import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; + +import java.io.File; +import java.lang.instrument.Instrumentation; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; + +public class Agent { + private static final String NAME = "com.volmit.iris.util.agent.Installer"; + public static final File AGENT_JAR = new File(Iris.instance.getDataFolder(), "agent.jar"); + + public static ClassReloadingStrategy installed() { + return ClassReloadingStrategy.of(getInstrumentation()); + } + + public static Instrumentation getInstrumentation() { + Instrumentation instrumentation = doGetInstrumentation(); + if (instrumentation == null) throw new IllegalStateException("The agent is not initialized or unavailable"); + return instrumentation; + } + + public static boolean install() { + if (doGetInstrumentation() != null) + return true; + try { + Files.copy(Iris.instance.getResource("agent.jar"), AGENT_JAR.toPath(), StandardCopyOption.REPLACE_EXISTING); + Iris.info("Installing Java Agent..."); + ByteBuddyAgent.attach(AGENT_JAR, ByteBuddyAgent.ProcessProvider.ForCurrentVm.INSTANCE); + } catch (Throwable e) { + e.printStackTrace(); + } + return doGetInstrumentation() != null; + } + + private static Instrumentation doGetInstrumentation() { + try { + return (Instrumentation) Class.forName(NAME, true, ClassLoader.getSystemClassLoader()).getMethod("getInstrumentation").invoke(null); + } catch (Exception ex) { + return null; + } + } +} diff --git a/core/src/main/java/com/volmit/iris/util/io/IO.java b/core/src/main/java/com/volmit/iris/util/io/IO.java index 281f08096..61deb3670 100644 --- a/core/src/main/java/com/volmit/iris/util/io/IO.java +++ b/core/src/main/java/com/volmit/iris/util/io/IO.java @@ -1643,7 +1643,7 @@ public class IO { return (ch2 == -1); } - public static void write(File file, IOFunction builder, IOConsumer action) throws IOException { + public static void write(File file, IOFunction builder, IOConsumer action) throws IOException { File dir = new File(file.getParentFile(), ".tmp"); dir.mkdirs(); dir.deleteOnExit(); diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index 0a58f5d00..89fbfb449 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -5,21 +5,7 @@ load: STARTUP authors: [ cyberpwn, NextdoorPsycho, Vatuu ] website: volmit.com description: More than a Dimension! -libraries: - - com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2 - - com.github.ben-manes.caffeine:caffeine:3.0.5 - - org.apache.commons:commons-lang3:3.12.0 - - commons-io:commons-io:2.13.0 - - io.timeandspace:smoothie-map:2.0.2 - - com.google.guava:guava:31.0.1-jre - - com.google.code.gson:gson:2.10.1 - - org.zeroturnaround:zt-zip:1.14 - - it.unimi.dsi:fastutil:8.5.6 - - org.ow2.asm:asm:9.2 - - rhino:js:1.7R2 - - bsf:bsf:2.4.0 - - org.lz4:lz4-java:1.8.0 - - com.github.oshi:oshi-core:6.6.5 +libraries: ${libraries} commands: iris: aliases: [ ir, irs ] 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 8f974008b..9b28f5456 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,42 +2,45 @@ 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.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.platform.PlatformChunkGenerator; +import com.volmit.iris.util.agent.Agent; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.format.C; import com.volmit.iris.util.hunk.Hunk; import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.mantle.Mantle; import com.volmit.iris.util.math.Vector3d; import com.volmit.iris.util.matter.MatterBiomeInject; -import com.volmit.iris.util.nbt.io.NBTUtil; import com.volmit.iris.util.nbt.mca.NBTWorld; 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.*; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.matcher.ElementMatchers; +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.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.server.level.progress.ChunkProgressListener; import net.minecraft.tags.TagKey; -import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.RandomSequences; 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; @@ -47,15 +50,16 @@ 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.chunk.ProtoChunk; 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 net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; - import org.bukkit.craftbukkit.v1_20_R1.CraftChunk; import org.bukkit.craftbukkit.v1_20_R1.CraftServer; import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; @@ -63,31 +67,26 @@ import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlock; import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlockState; import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftDolphin; import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; -import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; import org.bukkit.event.entity.CreatureSpawnEvent; -import org.bukkit.entity.EntityType; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import sun.misc.Unsafe; +import java.awt.*; import java.awt.Color; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Function; public class NMSBinding implements INMSBinding { @@ -95,11 +94,10 @@ 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 AtomicBoolean injected = new AtomicBoolean(); 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; @@ -645,103 +643,91 @@ public class NMSBinding implements INMSBinding { } @Override - @SneakyThrows - public AutoClosing injectLevelStems() { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - ))); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); + public boolean missingDimensionTypes(String... keys) { + var type = registry().registryOrThrow(Registries.DIMENSION_TYPE); + return !Arrays.stream(keys) + .map(key -> new ResourceLocation("iris", key)) + .allMatch(type::containsKey); } @Override - @SneakyThrows - public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) { - var reg = registry(); - var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); - field.setAccessible(true); + public boolean injectBukkit() { + if (injected.getAndSet(true)) + return true; + try { + Iris.info("Injecting Bukkit"); + var buddy = new ByteBuddy(); + buddy.redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments( + MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, PrimaryLevelData.class, + ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, List.class, + boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), Agent.installed()); + for (Class clazz : List.of(ChunkAccess.class, ProtoChunk.class)) { + buddy.redefine(clazz) + .visit(Advice.to(ChunkAccessAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(short.class, int.class)))) + .make() + .load(clazz.getClassLoader(), Agent.installed()); + } - var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end); - var injected = access.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 AutoClosing(() -> field.set(reg, old)); + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } - @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; + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { + if (!(raw instanceof PlatformChunkGenerator gen)) + throw new IllegalStateException("Generator is not platform chunk generator!"); + + var dimensionKey = new ResourceLocation("iris", gen.getTarget().getDimension().getDimensionTypeKey()); + var dimensionType = access.lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, dimensionKey)); + return new LevelStem(dimensionType, chunkGenerator(access)); } - @Override - public void removeCustomDimensions(World world) { - ((CraftWorld) world).getHandle().K.customDimensions = null; - } - - 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() - ); + private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) { + var settings = new FlatLevelGeneratorSettings(Optional.empty(), access.registryOrThrow(Registries.BIOME).getHolderOrThrow(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(); + return new FlatLevelSource(settings); } - 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 static class ChunkAccessAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This ChunkAccess access, @Advice.Argument(1) int index) { + return index >= access.getPostProcessing().length; + } } - 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 static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter( + @Advice.Argument(0) MinecraftServer server, + @Advice.Argument(3) PrimaryLevelData levelData, + @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem, + @Advice.Argument(12) World.Environment env, + @Advice.Argument(value = 13) ChunkGenerator gen + ) { + if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris")) + return; - private ResourceLocation createIrisKey(ResourceKey key) { - return new ResourceLocation("iris", key.location().getPath()); + try { + Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris") + .getClass() + .getClassLoader()) + .getDeclaredMethod("get") + .invoke(null); + levelStem = (LevelStem) bindings.getClass() + .getDeclaredMethod("levelStem", RegistryAccess.class, ChunkGenerator.class) + .invoke(bindings, server.registryAccess(), gen); + + levelData.customDimensions = null; + } catch (Throwable e) { + throw new RuntimeException("Iris failed to replace the levelStem", e instanceof InvocationTargetException ex ? ex.getCause() : e); + } + } } } 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 c68a8a19e..99187723f 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 @@ -1,41 +1,63 @@ package com.volmit.iris.core.nms.v1_20_R2; -import java.awt.Color; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.lang.reflect.Field; -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.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Pair; -import com.mojang.serialization.Lifecycle; -import com.volmit.iris.core.nms.container.AutoClosing; +import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.platform.PlatformChunkGenerator; +import com.volmit.iris.util.agent.Agent; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.hunk.Hunk; +import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.mantle.Mantle; +import com.volmit.iris.util.math.Vector3d; +import com.volmit.iris.util.matter.MatterBiomeInject; +import com.volmit.iris.util.nbt.mca.NBTWorld; +import com.volmit.iris.util.nbt.mca.palette.*; +import com.volmit.iris.util.nbt.tag.CompoundTag; import com.volmit.iris.util.scheduling.J; -import lombok.SneakyThrows; -import net.minecraft.core.*; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.matcher.ElementMatchers; +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.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.server.level.progress.ChunkProgressListener; import net.minecraft.tags.TagKey; +import net.minecraft.world.RandomSequences; +import net.minecraft.world.entity.EntityType; 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.dimension.DimensionType; +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.chunk.ProtoChunk; 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 net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -45,58 +67,36 @@ import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlockState; import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData; -import org.bukkit.craftbukkit.v1_20_R2.entity.CraftDolphin; import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R2.util.CraftNamespacedKey; -import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.volmit.iris.Iris; -import com.volmit.iris.core.nms.INMSBinding; -import com.volmit.iris.engine.data.cache.AtomicCache; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.hunk.Hunk; -import com.volmit.iris.util.json.JSONObject; -import com.volmit.iris.util.mantle.Mantle; -import com.volmit.iris.util.math.Vector3d; -import com.volmit.iris.util.matter.MatterBiomeInject; -import com.volmit.iris.util.nbt.io.NBTUtil; -import com.volmit.iris.util.nbt.mca.NBTWorld; -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.registries.Registries; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.level.biome.BiomeSource; -import net.minecraft.world.level.block.Block; -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 sun.misc.Unsafe; +import java.awt.*; +import java.awt.Color; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; 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 AtomicBoolean injected = new AtomicBoolean(); 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; @@ -624,6 +624,14 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public boolean missingDimensionTypes(String... keys) { + var type = registry().registryOrThrow(Registries.DIMENSION_TYPE); + return !Arrays.stream(keys) + .map(key -> new ResourceLocation("iris", key)) + .allMatch(type::containsKey); + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { @@ -646,103 +654,83 @@ public class NMSBinding implements INMSBinding { } @Override - @SneakyThrows - public AutoClosing injectLevelStems() { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); + public boolean injectBukkit() { + if (injected.getAndSet(true)) + return true; + try { + Iris.info("Injecting Bukkit"); + var buddy = new ByteBuddy(); + buddy.redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments( + MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, PrimaryLevelData.class, + ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, List.class, + boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), Agent.installed()); + for (Class clazz : List.of(ChunkAccess.class, ProtoChunk.class)) { + buddy.redefine(clazz) + .visit(Advice.to(ChunkAccessAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(short.class, int.class)))) + .make() + .load(clazz.getClassLoader(), Agent.installed()); + } - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - ))); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } - @Override - @SneakyThrows - public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) { - var reg = registry(); - var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); - field.setAccessible(true); + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { + if (!(raw instanceof PlatformChunkGenerator gen)) + throw new IllegalStateException("Generator is not platform chunk generator!"); - var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end); - var injected = access.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 AutoClosing(() -> field.set(reg, old)); + var dimensionKey = new ResourceLocation("iris", gen.getTarget().getDimension().getDimensionTypeKey()); + var dimensionType = access.lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, dimensionKey)); + return new LevelStem(dimensionType, chunkGenerator(access)); } - @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; - } - - @Override - public void removeCustomDimensions(World world) { - ((CraftWorld) world).getHandle().K.customDimensions = null; - } - - 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() - ); + private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) { + var settings = new FlatLevelGeneratorSettings(Optional.empty(), access.registryOrThrow(Registries.BIOME).getHolderOrThrow(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(); + return new FlatLevelSource(settings); } - 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 static class ChunkAccessAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This ChunkAccess access, @Advice.Argument(1) int index) { + return index >= access.getPostProcessing().length; + } } - 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 static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter( + @Advice.Argument(0) MinecraftServer server, + @Advice.Argument(3) PrimaryLevelData levelData, + @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem, + @Advice.Argument(12) World.Environment env, + @Advice.Argument(value = 13) ChunkGenerator gen + ) { + if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris")) + return; - private ResourceLocation createIrisKey(ResourceKey key) { - return new ResourceLocation("iris", key.location().getPath()); + try { + Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris") + .getClass() + .getClassLoader()) + .getDeclaredMethod("get") + .invoke(null); + levelStem = (LevelStem) bindings.getClass() + .getDeclaredMethod("levelStem", RegistryAccess.class, ChunkGenerator.class) + .invoke(bindings, server.registryAccess(), gen); + + levelData.customDimensions = null; + } catch (Throwable e) { + throw new RuntimeException("Iris failed to replace the levelStem", e instanceof InvocationTargetException ex ? ex.getCause() : e); + } + } } } 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 2d0e08931..c1680fb93 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 @@ -1,41 +1,63 @@ package com.volmit.iris.core.nms.v1_20_R3; -import java.awt.Color; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.lang.reflect.Field; -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.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Pair; -import com.mojang.serialization.Lifecycle; -import com.volmit.iris.core.nms.container.AutoClosing; +import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.platform.PlatformChunkGenerator; +import com.volmit.iris.util.agent.Agent; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.hunk.Hunk; +import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.mantle.Mantle; +import com.volmit.iris.util.math.Vector3d; +import com.volmit.iris.util.matter.MatterBiomeInject; +import com.volmit.iris.util.nbt.mca.NBTWorld; +import com.volmit.iris.util.nbt.mca.palette.*; +import com.volmit.iris.util.nbt.tag.CompoundTag; import com.volmit.iris.util.scheduling.J; -import lombok.SneakyThrows; -import net.minecraft.core.*; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.matcher.ElementMatchers; +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.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.server.level.progress.ChunkProgressListener; import net.minecraft.tags.TagKey; +import net.minecraft.world.RandomSequences; +import net.minecraft.world.entity.EntityType; 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.dimension.DimensionType; +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.chunk.ProtoChunk; 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 net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -45,58 +67,36 @@ import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockState; import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftDolphin; import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey; -import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.volmit.iris.Iris; -import com.volmit.iris.core.nms.INMSBinding; -import com.volmit.iris.engine.data.cache.AtomicCache; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.hunk.Hunk; -import com.volmit.iris.util.json.JSONObject; -import com.volmit.iris.util.mantle.Mantle; -import com.volmit.iris.util.math.Vector3d; -import com.volmit.iris.util.matter.MatterBiomeInject; -import com.volmit.iris.util.nbt.io.NBTUtil; -import com.volmit.iris.util.nbt.mca.NBTWorld; -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.registries.Registries; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.level.biome.BiomeSource; -import net.minecraft.world.level.block.Block; -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 sun.misc.Unsafe; +import java.awt.*; +import java.awt.Color; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; 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 AtomicBoolean injected = new AtomicBoolean(); 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; @@ -647,103 +647,91 @@ public class NMSBinding implements INMSBinding { } @Override - @SneakyThrows - public AutoClosing injectLevelStems() { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - ))); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); + public boolean missingDimensionTypes(String... keys) { + var type = registry().registryOrThrow(Registries.DIMENSION_TYPE); + return !Arrays.stream(keys) + .map(key -> new ResourceLocation("iris", key)) + .allMatch(type::containsKey); } @Override - @SneakyThrows - public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) { - var reg = registry(); - var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); - field.setAccessible(true); + public boolean injectBukkit() { + if (injected.getAndSet(true)) + return true; + try { + Iris.info("Injecting Bukkit"); + var buddy = new ByteBuddy(); + buddy.redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments( + MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, PrimaryLevelData.class, + ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, List.class, + boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), Agent.installed()); + for (Class clazz : List.of(ChunkAccess.class, ProtoChunk.class)) { + buddy.redefine(clazz) + .visit(Advice.to(ChunkAccessAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(short.class, int.class)))) + .make() + .load(clazz.getClassLoader(), Agent.installed()); + } - var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end); - var injected = access.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 AutoClosing(() -> field.set(reg, old)); + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } - @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; + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { + if (!(raw instanceof PlatformChunkGenerator gen)) + throw new IllegalStateException("Generator is not platform chunk generator!"); + + var dimensionKey = new ResourceLocation("iris", gen.getTarget().getDimension().getDimensionTypeKey()); + var dimensionType = access.lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, dimensionKey)); + return new LevelStem(dimensionType, chunkGenerator(access)); } - @Override - public void removeCustomDimensions(World world) { - ((CraftWorld) world).getHandle().K.customDimensions = null; - } - - 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() - ); + private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) { + var settings = new FlatLevelGeneratorSettings(Optional.empty(), access.registryOrThrow(Registries.BIOME).getHolderOrThrow(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(); + return new FlatLevelSource(settings); } - 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 static class ChunkAccessAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This ChunkAccess access, @Advice.Argument(1) int index) { + return index >= access.getPostProcessing().length; + } } - 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 static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter( + @Advice.Argument(0) MinecraftServer server, + @Advice.Argument(3) PrimaryLevelData levelData, + @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem, + @Advice.Argument(12) World.Environment env, + @Advice.Argument(value = 13) ChunkGenerator gen + ) { + if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris")) + return; - private ResourceLocation createIrisKey(ResourceKey key) { - return new ResourceLocation("iris", key.location().getPath()); + try { + Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris") + .getClass() + .getClassLoader()) + .getDeclaredMethod("get") + .invoke(null); + levelStem = (LevelStem) bindings.getClass() + .getDeclaredMethod("levelStem", RegistryAccess.class, ChunkGenerator.class) + .invoke(bindings, server.registryAccess(), gen); + + levelData.customDimensions = null; + } catch (Throwable e) { + throw new RuntimeException("Iris failed to replace the levelStem", e instanceof InvocationTargetException ex ? ex.getCause() : e); + } + } } } 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 ec076e7df..1ce8b16b4 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 @@ -1,53 +1,64 @@ package com.volmit.iris.core.nms.v1_20_R4; -import java.awt.Color; -import java.lang.reflect.Field; -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.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Pair; -import com.mojang.serialization.Lifecycle; -import com.volmit.iris.core.nms.container.AutoClosing; +import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.nms.datapack.DataVersion; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.platform.PlatformChunkGenerator; +import com.volmit.iris.util.agent.Agent; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.hunk.Hunk; +import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.mantle.Mantle; +import com.volmit.iris.util.math.Vector3d; +import com.volmit.iris.util.matter.MatterBiomeInject; +import com.volmit.iris.util.nbt.mca.NBTWorld; +import com.volmit.iris.util.nbt.mca.palette.*; import com.volmit.iris.util.nbt.tag.CompoundTag; import com.volmit.iris.util.scheduling.J; -import lombok.SneakyThrows; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.matcher.ElementMatchers; import net.minecraft.core.*; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.Registries; import net.minecraft.nbt.*; -import net.minecraft.nbt.ByteTag; -import net.minecraft.nbt.DoubleTag; -import net.minecraft.nbt.EndTag; -import net.minecraft.nbt.FloatTag; -import net.minecraft.nbt.IntTag; -import net.minecraft.nbt.LongTag; -import net.minecraft.nbt.ShortTag; -import net.minecraft.nbt.StringTag; 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.commands.data.DataCommands; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.tags.TagKey; +import net.minecraft.world.RandomSequences; +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.BlockEntityType; +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.LevelChunk; +import net.minecraft.world.level.chunk.ProtoChunk; 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 net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -56,55 +67,37 @@ import org.bukkit.craftbukkit.v1_20_R4.CraftServer; import org.bukkit.craftbukkit.v1_20_R4.CraftWorld; import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockState; import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockStates; -import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockType; import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData; -import org.bukkit.craftbukkit.v1_20_R4.entity.CraftDolphin; -import org.bukkit.craftbukkit.v1_20_R4.generator.CustomChunkGenerator; import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R4.util.CraftNamespacedKey; -import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.volmit.iris.Iris; -import com.volmit.iris.core.nms.INMSBinding; -import com.volmit.iris.engine.data.cache.AtomicCache; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.hunk.Hunk; -import com.volmit.iris.util.json.JSONObject; -import com.volmit.iris.util.mantle.Mantle; -import com.volmit.iris.util.math.Vector3d; -import com.volmit.iris.util.matter.MatterBiomeInject; -import com.volmit.iris.util.nbt.mca.NBTWorld; -import com.volmit.iris.util.nbt.mca.palette.*; - -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.level.block.Block; -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.LevelChunk; +import java.awt.*; +import java.awt.Color; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; 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 AtomicBoolean injected = new AtomicBoolean(); 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; @@ -672,103 +665,91 @@ public class NMSBinding implements INMSBinding { } @Override - @SneakyThrows - public AutoClosing injectLevelStems() { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - ))); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); + public boolean missingDimensionTypes(String... keys) { + var type = registry().registryOrThrow(Registries.DIMENSION_TYPE); + return !Arrays.stream(keys) + .map(key -> new ResourceLocation("iris", key)) + .allMatch(type::containsKey); } @Override - @SneakyThrows - public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) { - var reg = registry(); - var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); - field.setAccessible(true); + public boolean injectBukkit() { + if (injected.getAndSet(true)) + return true; + try { + Iris.info("Injecting Bukkit"); + var buddy = new ByteBuddy(); + buddy.redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments( + MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, PrimaryLevelData.class, + ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, List.class, + boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), Agent.installed()); + for (Class clazz : List.of(ChunkAccess.class, ProtoChunk.class)) { + buddy.redefine(clazz) + .visit(Advice.to(ChunkAccessAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(short.class, int.class)))) + .make() + .load(clazz.getClassLoader(), Agent.installed()); + } - var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end); - var injected = access.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 AutoClosing(() -> field.set(reg, old)); + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } - @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; + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { + if (!(raw instanceof PlatformChunkGenerator gen)) + throw new IllegalStateException("Generator is not platform chunk generator!"); + + var dimensionKey = new ResourceLocation("iris", gen.getTarget().getDimension().getDimensionTypeKey()); + var dimensionType = access.lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, dimensionKey)); + return new LevelStem(dimensionType, chunkGenerator(access)); } - @Override - public void removeCustomDimensions(World world) { - ((CraftWorld) world).getHandle().K.customDimensions = null; - } - - 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() - ); + private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) { + var settings = new FlatLevelGeneratorSettings(Optional.empty(), access.registryOrThrow(Registries.BIOME).getHolderOrThrow(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(); + return new FlatLevelSource(settings); } - 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 static class ChunkAccessAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This ChunkAccess access, @Advice.Argument(1) int index) { + return index >= access.getPostProcessing().length; + } } - 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 static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter( + @Advice.Argument(0) MinecraftServer server, + @Advice.Argument(3) PrimaryLevelData levelData, + @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem, + @Advice.Argument(12) World.Environment env, + @Advice.Argument(value = 13) ChunkGenerator gen + ) { + if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris")) + return; - private ResourceLocation createIrisKey(ResourceKey key) { - return new ResourceLocation("iris", key.location().getPath()); + try { + Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris") + .getClass() + .getClassLoader()) + .getDeclaredMethod("get") + .invoke(null); + levelStem = (LevelStem) bindings.getClass() + .getDeclaredMethod("levelStem", RegistryAccess.class, ChunkGenerator.class) + .invoke(bindings, server.registryAccess(), gen); + + levelData.customDimensions = null; + } catch (Throwable e) { + throw new RuntimeException("Iris failed to replace the levelStem", e instanceof InvocationTargetException ex ? ex.getCause() : e); + } + } } } 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 7613bf71f..485b44e0c 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 @@ -1,47 +1,68 @@ package com.volmit.iris.core.nms.v1_21_R1; -import java.awt.Color; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.lang.reflect.Field; -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.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Pair; -import com.mojang.serialization.Lifecycle; -import com.volmit.iris.core.nms.container.AutoClosing; +import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.nms.datapack.DataVersion; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.platform.PlatformChunkGenerator; +import com.volmit.iris.util.agent.Agent; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.hunk.Hunk; +import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.mantle.Mantle; +import com.volmit.iris.util.math.Vector3d; +import com.volmit.iris.util.matter.MatterBiomeInject; +import com.volmit.iris.util.nbt.mca.NBTWorld; +import com.volmit.iris.util.nbt.mca.palette.*; +import com.volmit.iris.util.nbt.tag.CompoundTag; import com.volmit.iris.util.scheduling.J; -import lombok.SneakyThrows; -import net.minecraft.core.*; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.shorts.ShortList; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.matcher.ElementMatchers; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; import net.minecraft.core.component.DataComponents; +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.ChunkMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.tags.TagKey; +import net.minecraft.world.RandomSequences; +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; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.ProtoChunk; 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 net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -51,57 +72,36 @@ import org.bukkit.craftbukkit.v1_21_R1.CraftWorld; import org.bukkit.craftbukkit.v1_21_R1.block.CraftBlockState; import org.bukkit.craftbukkit.v1_21_R1.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_21_R1.block.data.CraftBlockData; -import org.bukkit.craftbukkit.v1_21_R1.entity.CraftDolphin; import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_21_R1.util.CraftNamespacedKey; -import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.volmit.iris.Iris; -import com.volmit.iris.core.nms.INMSBinding; -import com.volmit.iris.engine.data.cache.AtomicCache; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.hunk.Hunk; -import com.volmit.iris.util.json.JSONObject; -import com.volmit.iris.util.mantle.Mantle; -import com.volmit.iris.util.math.Vector3d; -import com.volmit.iris.util.matter.MatterBiomeInject; -import com.volmit.iris.util.nbt.io.NBTUtil; -import com.volmit.iris.util.nbt.mca.NBTWorld; -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.registries.Registries; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.level.biome.BiomeSource; -import net.minecraft.world.level.block.Block; -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.LevelChunk; -import sun.misc.Unsafe; +import java.awt.*; +import java.awt.Color; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; 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 AtomicBoolean injected = new AtomicBoolean(); 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; @@ -676,103 +676,91 @@ public class NMSBinding implements INMSBinding { } @Override - @SneakyThrows - public AutoClosing injectLevelStems() { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - ))); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); + public boolean missingDimensionTypes(String... keys) { + var type = registry().registryOrThrow(Registries.DIMENSION_TYPE); + return !Arrays.stream(keys) + .map(key -> ResourceLocation.fromNamespaceAndPath("iris", key)) + .allMatch(type::containsKey); } @Override - @SneakyThrows - public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) { - var reg = registry(); - var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); - field.setAccessible(true); + public boolean injectBukkit() { + if (injected.getAndSet(true)) + return true; + try { + Iris.info("Injecting Bukkit"); + var buddy = new ByteBuddy(); + buddy.redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments( + MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, PrimaryLevelData.class, + ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, List.class, + boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), Agent.installed()); + for (Class clazz : List.of(ChunkAccess.class, ProtoChunk.class)) { + buddy.redefine(clazz) + .visit(Advice.to(ChunkAccessAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(short.class, int.class)))) + .make() + .load(clazz.getClassLoader(), Agent.installed()); + } - var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end); - var injected = access.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 AutoClosing(() -> field.set(reg, old)); + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } - @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; + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { + if (!(raw instanceof PlatformChunkGenerator gen)) + throw new IllegalStateException("Generator is not platform chunk generator!"); + + var dimensionKey = ResourceLocation.fromNamespaceAndPath("iris", gen.getTarget().getDimension().getDimensionTypeKey()); + var dimensionType = access.lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, dimensionKey)); + return new LevelStem(dimensionType, chunkGenerator(access)); } - @Override - public void removeCustomDimensions(World world) { - ((CraftWorld) world).getHandle().K.customDimensions = null; - } - - 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() - ); + private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) { + var settings = new FlatLevelGeneratorSettings(Optional.empty(), access.registryOrThrow(Registries.BIOME).getHolderOrThrow(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(); + return new FlatLevelSource(settings); } - 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 static class ChunkAccessAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This ChunkAccess access, @Advice.Argument(1) int index) { + return index >= access.getPostProcessing().length; + } } - 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 static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter( + @Advice.Argument(0) MinecraftServer server, + @Advice.Argument(3) PrimaryLevelData levelData, + @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem, + @Advice.Argument(12) World.Environment env, + @Advice.Argument(value = 13) ChunkGenerator gen + ) { + if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris")) + return; - private ResourceLocation createIrisKey(ResourceKey key) { - return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath()); + try { + Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris") + .getClass() + .getClassLoader()) + .getDeclaredMethod("get") + .invoke(null); + levelStem = (LevelStem) bindings.getClass() + .getDeclaredMethod("levelStem", RegistryAccess.class, ChunkGenerator.class) + .invoke(bindings, server.registryAccess(), gen); + + levelData.customDimensions = null; + } catch (Throwable e) { + throw new RuntimeException("Iris failed to replace the levelStem", e instanceof InvocationTargetException ex ? ex.getCause() : e); + } + } } } 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 41b75953b..dff3db099 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 @@ -1,42 +1,65 @@ package com.volmit.iris.core.nms.v1_21_R2; -import java.awt.Color; -import java.lang.reflect.Field; -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.mojang.brigadier.exceptions.CommandSyntaxException; +import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.container.BiomeColor; -import com.volmit.iris.core.nms.container.Pair; import com.volmit.iris.core.nms.datapack.DataVersion; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.platform.PlatformChunkGenerator; +import com.volmit.iris.util.agent.Agent; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.hunk.Hunk; +import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.mantle.Mantle; +import com.volmit.iris.util.math.Vector3d; +import com.volmit.iris.util.matter.MatterBiomeInject; +import com.volmit.iris.util.nbt.mca.NBTWorld; +import com.volmit.iris.util.nbt.mca.palette.*; +import com.volmit.iris.util.nbt.tag.CompoundTag; import com.volmit.iris.util.scheduling.J; -import lombok.SneakyThrows; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.shorts.ShortList; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.matcher.ElementMatchers; import net.minecraft.core.*; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponents; +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.server.level.progress.ChunkProgressListener; import net.minecraft.tags.TagKey; +import net.minecraft.world.RandomSequences; +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; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.ProtoChunk; 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 net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -50,48 +73,32 @@ import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_21_R2.util.CraftNamespacedKey; import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.volmit.iris.Iris; -import com.volmit.iris.core.nms.INMSBinding; -import com.volmit.iris.engine.data.cache.AtomicCache; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.hunk.Hunk; -import com.volmit.iris.util.json.JSONObject; -import com.volmit.iris.util.mantle.Mantle; -import com.volmit.iris.util.math.Vector3d; -import com.volmit.iris.util.matter.MatterBiomeInject; -import com.volmit.iris.util.nbt.mca.NBTWorld; -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.registries.Registries; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.level.block.Block; -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.LevelChunk; +import java.awt.*; +import java.awt.Color; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; 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 AtomicBoolean injected = new AtomicBoolean(); 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; @@ -666,103 +673,91 @@ public class NMSBinding implements INMSBinding { } @Override - @SneakyThrows - public AutoClosing injectLevelStems() { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - ))); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); + public boolean missingDimensionTypes(String... keys) { + var type = registry().lookupOrThrow(Registries.DIMENSION_TYPE); + return !Arrays.stream(keys) + .map(key -> ResourceLocation.fromNamespaceAndPath("iris", key)) + .allMatch(type::containsKey); } @Override - @SneakyThrows - public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) { - var reg = registry(); - var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); - field.setAccessible(true); + public boolean injectBukkit() { + if (injected.getAndSet(true)) + return true; + try { + Iris.info("Injecting Bukkit"); + var buddy = new ByteBuddy(); + buddy.redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments( + MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, PrimaryLevelData.class, + ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, List.class, + boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), Agent.installed()); + for (Class clazz : List.of(ChunkAccess.class, ProtoChunk.class)) { + buddy.redefine(clazz) + .visit(Advice.to(ChunkAccessAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(ShortList.class, int.class)))) + .make() + .load(clazz.getClassLoader(), Agent.installed()); + } - var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end); - var injected = access.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 AutoClosing(() -> field.set(reg, old)); + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } - @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; + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { + if (!(raw instanceof PlatformChunkGenerator gen)) + throw new IllegalStateException("Generator is not platform chunk generator!"); + + var dimensionKey = ResourceLocation.fromNamespaceAndPath("iris", gen.getTarget().getDimension().getDimensionTypeKey()); + var dimensionType = access.lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, dimensionKey)); + return new LevelStem(dimensionType, chunkGenerator(access)); } - @Override - public void removeCustomDimensions(World world) { - ((CraftWorld) world).getHandle().L.customDimensions = null; - } - - 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() - ); + private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) { + 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(); + return new FlatLevelSource(settings); } - 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 static class ChunkAccessAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This ChunkAccess access, @Advice.Argument(1) int index) { + return index >= access.getPostProcessing().length; + } } - 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 static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter( + @Advice.Argument(0) MinecraftServer server, + @Advice.Argument(3) PrimaryLevelData levelData, + @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem, + @Advice.Argument(12) World.Environment env, + @Advice.Argument(value = 13) ChunkGenerator gen + ) { + if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris")) + return; - private ResourceLocation createIrisKey(ResourceKey key) { - return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath()); + try { + Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris") + .getClass() + .getClassLoader()) + .getDeclaredMethod("get") + .invoke(null); + levelStem = (LevelStem) bindings.getClass() + .getDeclaredMethod("levelStem", RegistryAccess.class, ChunkGenerator.class) + .invoke(bindings, server.registryAccess(), gen); + + levelData.customDimensions = null; + } catch (Throwable e) { + throw new RuntimeException("Iris failed to replace the levelStem", e instanceof InvocationTargetException ex ? ex.getCause() : e); + } + } } } 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 c6ba0e6dd..ca046a955 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,17 +1,18 @@ 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.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.platform.PlatformChunkGenerator; +import com.volmit.iris.util.agent.Agent; 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.hunk.Hunk; import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.mantle.Mantle; @@ -22,20 +23,24 @@ 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 it.unimi.dsi.fastutil.shorts.ShortList; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.matcher.ElementMatchers; import net.minecraft.core.*; +import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.Registries; -import net.minecraft.nbt.Tag; 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.server.level.progress.ChunkProgressListener; import net.minecraft.tags.TagKey; +import net.minecraft.world.RandomSequences; import net.minecraft.world.entity.EntityType; import net.minecraft.world.item.component.CustomData; import net.minecraft.world.level.LevelReader; @@ -47,13 +52,15 @@ 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.LevelChunk; +import net.minecraft.world.level.chunk.ProtoChunk; 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 net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -67,6 +74,7 @@ import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_21_R3.util.CraftNamespacedKey; import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Contract; @@ -74,23 +82,23 @@ import org.jetbrains.annotations.NotNull; import java.awt.Color; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.List; import java.util.*; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; 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 AtomicBoolean injected = new AtomicBoolean(); 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; @@ -665,103 +673,91 @@ public class NMSBinding implements INMSBinding { } @Override - @SneakyThrows - public AutoClosing injectLevelStems() { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - ))); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); + public boolean missingDimensionTypes(String... keys) { + var type = registry().lookupOrThrow(Registries.DIMENSION_TYPE); + return !Arrays.stream(keys) + .map(key -> ResourceLocation.fromNamespaceAndPath("iris", key)) + .allMatch(type::containsKey); } @Override - @SneakyThrows - public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) { - var reg = registry(); - var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); - field.setAccessible(true); + public boolean injectBukkit() { + if (injected.getAndSet(true)) + return true; + try { + Iris.info("Injecting Bukkit"); + var buddy = new ByteBuddy(); + buddy.redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments( + MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, PrimaryLevelData.class, + ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, List.class, + boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), Agent.installed()); + for (Class clazz : List.of(ChunkAccess.class, ProtoChunk.class)) { + buddy.redefine(clazz) + .visit(Advice.to(ChunkAccessAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(ShortList.class, int.class)))) + .make() + .load(clazz.getClassLoader(), Agent.installed()); + } - var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end); - var injected = access.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 AutoClosing(() -> field.set(reg, old)); + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } - @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; + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { + if (!(raw instanceof PlatformChunkGenerator gen)) + throw new IllegalStateException("Generator is not platform chunk generator!"); + + var dimensionKey = ResourceLocation.fromNamespaceAndPath("iris", gen.getTarget().getDimension().getDimensionTypeKey()); + var dimensionType = access.lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, dimensionKey)); + return new LevelStem(dimensionType, chunkGenerator(access)); } - @Override - public void removeCustomDimensions(World world) { - ((CraftWorld) world).getHandle().L.customDimensions = null; - } - - 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() - ); + private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) { + 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(); + return new FlatLevelSource(settings); } - 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 static class ChunkAccessAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This ChunkAccess access, @Advice.Argument(1) int index) { + return index >= access.getPostProcessing().length; + } } - 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 static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter( + @Advice.Argument(0) MinecraftServer server, + @Advice.Argument(3) PrimaryLevelData levelData, + @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem, + @Advice.Argument(12) World.Environment env, + @Advice.Argument(value = 13) ChunkGenerator gen + ) { + if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris")) + return; - private ResourceLocation createIrisKey(ResourceKey key) { - return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath()); + try { + Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris") + .getClass() + .getClassLoader()) + .getDeclaredMethod("get") + .invoke(null); + levelStem = (LevelStem) bindings.getClass() + .getDeclaredMethod("levelStem", RegistryAccess.class, ChunkGenerator.class) + .invoke(bindings, server.registryAccess(), gen); + + levelData.customDimensions = null; + } catch (Throwable e) { + throw new RuntimeException("Iris failed to replace the levelStem", e instanceof InvocationTargetException ex ? ex.getCause() : e); + } + } } } diff --git a/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/NMSBinding.java b/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/NMSBinding.java index 3b11e017b..a376cb83d 100644 --- a/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/NMSBinding.java +++ b/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/NMSBinding.java @@ -1,16 +1,17 @@ package com.volmit.iris.core.nms.v1_21_R4; 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.datapack.DataVersion; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.platform.PlatformChunkGenerator; +import com.volmit.iris.util.agent.Agent; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.format.C; import com.volmit.iris.util.hunk.Hunk; import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.mantle.Mantle; @@ -21,20 +22,24 @@ 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 it.unimi.dsi.fastutil.shorts.ShortList; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.matcher.ElementMatchers; import net.minecraft.core.*; +import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.Registries; -import net.minecraft.nbt.Tag; 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.server.level.progress.ChunkProgressListener; import net.minecraft.tags.TagKey; +import net.minecraft.world.RandomSequences; import net.minecraft.world.entity.EntityType; import net.minecraft.world.item.component.CustomData; import net.minecraft.world.level.LevelReader; @@ -46,13 +51,16 @@ 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.LevelChunk; +import net.minecraft.world.level.chunk.ProtoChunk; 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.chunk.storage.SerializableChunkData; 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 net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -66,6 +74,7 @@ import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_21_R4.util.CraftNamespacedKey; import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Contract; @@ -73,22 +82,23 @@ import org.jetbrains.annotations.NotNull; import java.awt.Color; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.List; import java.util.*; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; 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 AtomicBoolean injected = new AtomicBoolean(); 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; @@ -663,103 +673,91 @@ public class NMSBinding implements INMSBinding { } @Override - @SneakyThrows - public AutoClosing injectLevelStems() { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - ))); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); + public boolean missingDimensionTypes(String... keys) { + var type = registry().lookupOrThrow(Registries.DIMENSION_TYPE); + return !Arrays.stream(keys) + .map(key -> ResourceLocation.fromNamespaceAndPath("iris", key)) + .allMatch(type::containsKey); } @Override - @SneakyThrows - public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) { - var reg = registry(); - var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); - field.setAccessible(true); + public boolean injectBukkit() { + if (injected.getAndSet(true)) + return true; + try { + Iris.info("Injecting Bukkit"); + var buddy = new ByteBuddy(); + buddy.redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments( + MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, PrimaryLevelData.class, + ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, List.class, + boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), Agent.installed()); + for (Class clazz : List.of(ChunkAccess.class, ProtoChunk.class)) { + buddy.redefine(clazz) + .visit(Advice.to(ChunkAccessAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(ShortList.class, int.class)))) + .make() + .load(clazz.getClassLoader(), Agent.installed()); + } - var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end); - var injected = access.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 AutoClosing(() -> field.set(reg, old)); + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } - @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; + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { + if (!(raw instanceof PlatformChunkGenerator gen)) + throw new IllegalStateException("Generator is not platform chunk generator!"); + + var dimensionKey = ResourceLocation.fromNamespaceAndPath("iris", gen.getTarget().getDimension().getDimensionTypeKey()); + var dimensionType = access.lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, dimensionKey)); + return new LevelStem(dimensionType, chunkGenerator(access)); } - @Override - public void removeCustomDimensions(World world) { - ((CraftWorld) world).getHandle().L.customDimensions = null; - } - - 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() - ); + private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) { + 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(); + return new FlatLevelSource(settings); } - 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 static class ChunkAccessAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This ChunkAccess access, @Advice.Argument(1) int index) { + return index >= access.getPostProcessing().length; + } } - 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 static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter( + @Advice.Argument(0) MinecraftServer server, + @Advice.Argument(3) PrimaryLevelData levelData, + @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem, + @Advice.Argument(12) World.Environment env, + @Advice.Argument(value = 13) ChunkGenerator gen + ) { + if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris")) + return; - private ResourceLocation createIrisKey(ResourceKey key) { - return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath()); + try { + Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris") + .getClass() + .getClassLoader()) + .getDeclaredMethod("get") + .invoke(null); + levelStem = (LevelStem) bindings.getClass() + .getDeclaredMethod("levelStem", RegistryAccess.class, ChunkGenerator.class) + .invoke(bindings, server.registryAccess(), gen); + + levelData.customDimensions = null; + } catch (Throwable e) { + throw new RuntimeException("Iris failed to replace the levelStem", e instanceof InvocationTargetException ex ? ex.getCause() : e); + } + } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index c45462895..862732b5a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,7 +28,7 @@ plugins { rootProject.name = "Iris" -include(":core") +include(":core", ":core:agent") include( ":nms:v1_21_R4", ":nms:v1_21_R3",