diff --git a/build.gradle.kts b/build.gradle.kts index 3094d704e..d3cfd5fee 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -36,8 +36,11 @@ plugins { id("io.sentry.jvm.gradle") version "5.7.0" } +group = "com.volmit" version = "3.6.11-1.20.1-1.21.5" +apply() + // ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED // ======================== WINDOWS ============================= registerCustomOutputTask("Cyberpwn", "C://Users/cyberpwn/Documents/development/server/plugins") @@ -63,6 +66,7 @@ val color = "truecolor" val errorReporting = false val nmsBindings = mapOf( + "v1_21_R5" to "1.21.7-R0.1-SNAPSHOT", "v1_21_R4" to "1.21.5-R0.1-SNAPSHOT", "v1_21_R3" to "1.21.4-R0.1-SNAPSHOT", "v1_21_R2" to "1.21.3-R0.1-SNAPSHOT", @@ -90,6 +94,7 @@ nmsBindings.forEach { key, value -> dependencies { compileOnly(project(":core")) compileOnly("org.jetbrains:annotations:26.0.2") + compileOnly("net.bytebuddy:byte-buddy:1.17.5") } } @@ -104,7 +109,8 @@ nmsBindings.forEach { key, value -> systemProperty("disable.watchdog", "") systemProperty("net.kyori.ansi.colorLevel", color) systemProperty("com.mojang.eula.agree", true) - systemProperty("iris.errorReporting", errorReporting) + systemProperty("iris.suppressReporting", !errorReporting) + jvmArgs("-javaagent:${project(":core:agent").tasks.jar.flatMap { it.archiveFile }.get().asFile.absolutePath}") } } @@ -115,6 +121,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 +175,6 @@ fun exec(vararg command: Any) { p.waitFor() } -dependencies { - implementation(project(":core")) -} - configurations.configureEach { resolutionStrategy.cacheChangingModulesFor(60, "minutes") resolutionStrategy.cacheDynamicVersionsFor(60, "minutes") @@ -193,6 +196,8 @@ 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/") + maven("https://repo.onarandombox.com/content/groups/public/") } dependencies { @@ -275,4 +280,4 @@ fun registerCustomOutputTaskUnix(name: String, path: String) { into(file(path)) rename { "Iris.jar" } } -} +} \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 000000000..c94238170 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + kotlin("jvm") version "2.0.20" +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.ow2.asm:asm:9.8") +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/ApiGenerator.kt b/buildSrc/src/main/kotlin/ApiGenerator.kt new file mode 100644 index 000000000..530d4c6eb --- /dev/null +++ b/buildSrc/src/main/kotlin/ApiGenerator.kt @@ -0,0 +1,121 @@ +import org.gradle.api.DefaultTask +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import org.gradle.jvm.tasks.Jar +import org.objectweb.asm.* +import java.io.File +import java.util.jar.JarFile +import java.util.jar.JarOutputStream + +class ApiGenerator : Plugin { + override fun apply(target: Project): Unit = with(target) { + plugins.apply("maven-publish") + val task = tasks.register("irisApi", GenerateApiTask::class.java) + extensions.findByType(PublishingExtension::class.java)!!.apply { + repositories.maven { + it.name = "deployDir" + it.url = targetDirectory.toURI() + } + + publications.create("maven", MavenPublication::class.java) { + it.groupId = name + it.version = version.toString() + it.artifact(task) + } + } + } +} + +abstract class GenerateApiTask : DefaultTask() { + init { + group = "iris" + dependsOn("jar") + finalizedBy("publishMavenPublicationToDeployDirRepository") + doLast { + logger.lifecycle("The API is located at ${outputFile.absolutePath}") + } + } + + @InputFile + val inputFile: File = project.tasks + .named("jar", Jar::class.java) + .get() + .archiveFile + .get() + .asFile + + @OutputFile + val outputFile: File = project.targetDirectory.resolve(inputFile.name) + + @TaskAction + fun generate() { + JarFile(inputFile).use { jar -> + JarOutputStream(outputFile.apply { parentFile?.mkdirs() }.outputStream()).use { out -> + jar.stream() + .parallel() + .filter { !it.isDirectory } + .filter { it.name.endsWith(".class") } + .forEach { + val bytes = jar.getInputStream(it).use { input -> + val writer = ClassWriter(ClassWriter.COMPUTE_MAXS) + val visitor = MethodClearingVisitor(writer) + ClassReader(input).accept(visitor, 0) + writer.toByteArray() + } + + synchronized(out) { + out.putNextEntry(it) + out.write(bytes) + out.closeEntry() + } + } + } + } + } +} + +val Project.targetDirectory: File get() { + val dir = System.getenv("DEPLOY_DIR") ?: return project.layout.buildDirectory.dir("api").get().asFile + return File(dir) +} + +private class MethodClearingVisitor( + cv: ClassVisitor +) : ClassVisitor(Opcodes.ASM9, cv) { + + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array? + ) = ExceptionThrowingMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions)) +} + +private class ExceptionThrowingMethodVisitor( + mv: MethodVisitor +) : MethodVisitor(Opcodes.ASM9, mv) { + + override fun visitCode() { + if (mv == null) return + mv.visitCode() + + mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException") + mv.visitInsn(Opcodes.DUP) + mv.visitLdcInsn("Only API") + mv.visitMethodInsn( + Opcodes.INVOKESPECIAL, + "java/lang/IllegalStateException", + "", "(Ljava/lang/String;)V", false + ) + mv.visitInsn(Opcodes.ATHROW) + + mv.visitMaxs(0, 0) + mv.visitEnd() + } +} \ 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 80f43d3e7..912dfbaba 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,9 +54,14 @@ 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 + } + compileOnly("org.mvplugins.multiverse.core:multiverse-core:5.1.0") //implementation files("libs/CustomItems.jar") - // Shaded implementation("com.dfsek:paralithic:0.8.1") implementation("io.papermc:paperlib:1.0.5") @@ -71,23 +70,21 @@ 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("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") - compileOnly("org.dom4j:dom4j:2.1.4") + 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("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") + dynamic("org.dom4j:dom4j:2.1.4") } java { @@ -119,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 7785cb722..7b02c80b8 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -36,11 +36,9 @@ 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; -import com.volmit.iris.engine.platform.DummyChunkGenerator; import com.volmit.iris.core.safeguard.IrisSafeguard; import com.volmit.iris.core.safeguard.UtilsSFG; import com.volmit.iris.engine.platform.PlatformChunkGenerator; @@ -72,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; @@ -466,15 +465,13 @@ 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(); ExecutionEnvironment.createSimple(); IrisSafeguard.IrisSafeguardSystem(); getSender().setTag(getTag()); - IrisSafeguard.earlySplash(); + IrisSafeguard.splash(true); linkMultiverseCore = new MultiverseCoreLink(); linkMythicMobs = new MythicMobsLink(); configWatcher = new FileWatcher(getDataFile("settings.json")); @@ -489,8 +486,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(); @@ -761,37 +757,11 @@ public class Iris extends VolmitPlugin implements Listener { @Override public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { Iris.debug("Default World Generator Called for " + worldName + " using ID: " + id); - if (worldName.equals("test")) { - try { - throw new RuntimeException(); - } catch (Throwable e) { - Iris.info(e.getStackTrace()[1].getClassName()); - if (e.getStackTrace()[1].getClassName().contains("com.onarandombox.MultiverseCore")) { - Iris.debug("MVC Test detected, Quick! Send them the dummy!"); - return new DummyChunkGenerator(); - } - } - } - - 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, true); - 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()); @@ -816,6 +786,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; @@ -958,7 +946,7 @@ public class Iris extends VolmitPlugin implements Listener { private static void setupSentry() { var settings = IrisSettings.get().getSentry(); - if (settings.disableAutoReporting || Sentry.isEnabled() || !Boolean.getBoolean("iris.errorReporting")) return; + if (settings.disableAutoReporting || Sentry.isEnabled() || Boolean.getBoolean("iris.suppressReporting")) return; Iris.info("Enabling Sentry for anonymous error reporting. You can disable this in the settings."); Iris.info("Your server ID is: " + ServerID.ID); Sentry.init(options -> { @@ -977,7 +965,7 @@ public class Iris extends VolmitPlugin implements Listener { event.setTag("iris.nms", INMS.get().getClass().getCanonicalName()); var context = IrisContext.get(); if (context != null) event.getContexts().set("engine", context.asContext()); - event.getContexts().set("safeguard", ServerBootSFG.allIncompatibilities); + event.getContexts().set("safeguard", IrisSafeguard.asContext()); return event; }); }); diff --git a/core/src/main/java/com/volmit/iris/core/IrisSettings.java b/core/src/main/java/com/volmit/iris/core/IrisSettings.java index c409c1f9d..30216b29e 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -150,7 +150,7 @@ public class IrisSettings { public boolean useCacheByDefault = true; public boolean useHighPriority = false; public boolean useVirtualThreads = false; - public boolean useTicketQueue = false; + public boolean useTicketQueue = true; public int maxConcurrency = 256; } 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..ca8b242d2 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(); @@ -110,19 +107,25 @@ public class ServerConfigurator { } public static void installDataPacks(IDataFixer fixer, boolean fullInstall) { + if (fixer == null) { + Iris.error("Unable to install datapacks, fixer is null!"); + return; + } Iris.info("Checking Data Packs..."); DimensionHeight height = new DimensionHeight(fixer); 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 +133,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) { @@ -216,6 +221,11 @@ public class ServerConfigurator { } } + if (INMS.get().missingDimensionTypes(dimension.getDimensionTypeKey())) { + Iris.warn("The Dimension Type for " + dimension.getLoadFile() + " is not registered on the server."); + warn = true; + } + if (warn) { Iris.error("The Pack " + key + " is INCAPABLE of generating custom biomes"); Iris.error("If not done automatically, restart your server before generating with this pack!"); @@ -225,9 +235,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 +256,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 +284,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/link/KGeneratorsDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/KGeneratorsDataProvider.java new file mode 100644 index 000000000..618fe4ccc --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/link/KGeneratorsDataProvider.java @@ -0,0 +1,88 @@ +package com.volmit.iris.core.link; + +import com.volmit.iris.core.service.ExternalDataSVC; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.data.B; +import com.volmit.iris.util.data.IrisCustomData; +import me.kryniowesegryderiusz.kgenerators.Main; +import me.kryniowesegryderiusz.kgenerators.api.KGeneratorsAPI; +import me.kryniowesegryderiusz.kgenerators.generators.locations.objects.GeneratorLocation; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.MissingResourceException; + +public class KGeneratorsDataProvider extends ExternalDataProvider { + public KGeneratorsDataProvider() { + super("KGenerators"); + } + + @Override + public void init() { + + } + + @Override + public @NotNull BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { + if (Main.getGenerators().get(blockId.key()) == null) throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); + return new IrisCustomData(Material.STRUCTURE_VOID.createBlockData(), ExternalDataSVC.buildState(blockId, state)); + } + + @Override + public @NotNull ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException { + var gen = Main.getGenerators().get(itemId.key()); + if (gen == null) throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key()); + return gen.getGeneratorItem(); + } + + @Override + public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) { + if (block.getType() != Material.STRUCTURE_VOID) return; + var existing = KGeneratorsAPI.getLoadedGeneratorLocation(block.getLocation()); + if (existing != null) return; + block.setBlockData(B.getAir(), false); + var gen = Main.getGenerators().get(blockId.key()); + if (gen == null) return; + var loc = new GeneratorLocation(-1, gen, block.getLocation(), Main.getPlacedGenerators().getChunkInfo(block.getChunk()), null, null); + Main.getDatabases().getDb().saveGenerator(loc); + Main.getPlacedGenerators().addLoaded(loc); + Main.getSchedules().schedule(loc, true); + } + + @Override + public @NotNull Identifier[] getBlockTypes() { + return Main.getGenerators().getAll().stream() + .map(gen -> new Identifier("kgenerators", gen.getId())) + .filter(i -> { + try { + return getBlockData(i) != null; + } catch (MissingResourceException e) { + return false; + } + }) + .toArray(Identifier[]::new); + } + + @Override + public @NotNull Identifier[] getItemTypes() { + return Main.getGenerators().getAll().stream() + .map(gen -> new Identifier("kgenerators", gen.getId())) + .filter(i -> { + try { + return getItemStack(i) != null; + } catch (MissingResourceException e) { + return false; + } + }) + .toArray(Identifier[]::new); + } + + @Override + public boolean isValidProvider(@NotNull Identifier id, boolean isItem) { + return "kgenerators".equalsIgnoreCase(id.namespace()); + } +} diff --git a/core/src/main/java/com/volmit/iris/core/link/MultiverseCoreLink.java b/core/src/main/java/com/volmit/iris/core/link/MultiverseCoreLink.java index c483ce756..920abeb5c 100644 --- a/core/src/main/java/com/volmit/iris/core/link/MultiverseCoreLink.java +++ b/core/src/main/java/com/volmit/iris/core/link/MultiverseCoreLink.java @@ -18,126 +18,60 @@ package com.volmit.iris.core.link; -import com.volmit.iris.Iris; -import com.volmit.iris.engine.object.IrisDimension; -import com.volmit.iris.util.collection.KMap; +import lombok.SneakyThrows; import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.WorldType; -import org.bukkit.plugin.Plugin; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Map; +import org.mvplugins.multiverse.core.MultiverseCoreApi; +import org.mvplugins.multiverse.core.world.MultiverseWorld; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.core.world.options.ImportWorldOptions; public class MultiverseCoreLink { - private final KMap worldNameTypes = new KMap<>(); + private final boolean active; public MultiverseCoreLink() { - - } - - public boolean addWorld(String worldName, IrisDimension dim, String seed) { - if (!isSupported()) { - return false; - } - - try { - Plugin p = getMultiverse(); - Object mvWorldManager = p.getClass().getDeclaredMethod("getMVWorldManager").invoke(p); - Method m = mvWorldManager.getClass().getDeclaredMethod("addWorld", - - String.class, World.Environment.class, String.class, WorldType.class, Boolean.class, String.class, boolean.class); - boolean b = (boolean) m.invoke(mvWorldManager, worldName, dim.getEnvironment(), seed, WorldType.NORMAL, false, "Iris", false); - saveConfig(); - return b; - } catch (Throwable e) { - Iris.reportError(e); - e.printStackTrace(); - } - - return false; - } - - @SuppressWarnings("unchecked") - public Map getList() { - try { - Plugin p = getMultiverse(); - Object mvWorldManager = p.getClass().getDeclaredMethod("getMVWorldManager").invoke(p); - Field f = mvWorldManager.getClass().getDeclaredField("worldsFromTheConfig"); - f.setAccessible(true); - return (Map) f.get(mvWorldManager); - } catch (Throwable e) { - Iris.reportError(e); - e.printStackTrace(); - } - - return null; + active = Bukkit.getPluginManager().getPlugin("Multiverse-Core") != null; } public void removeFromConfig(World world) { - if (!isSupported()) { - return; - } - - getList().remove(world.getName()); - saveConfig(); + removeFromConfig(world.getName()); } public void removeFromConfig(String world) { - if (!isSupported()) { - return; + if (!active) return; + var manager = worldManager(); + manager.removeWorld(world).onSuccess(manager::saveWorldsConfig); + } + + @SneakyThrows + public void updateWorld(World bukkitWorld, String pack) { + if (!active) return; + var generator = "Iris:" + pack; + var manager = worldManager(); + var world = manager.getWorld(bukkitWorld).getOrElse(() -> { + var options = ImportWorldOptions.worldName(bukkitWorld.getName()) + .generator(generator) + .environment(bukkitWorld.getEnvironment()) + .useSpawnAdjust(false); + return manager.importWorld(options).get(); + }); + + world.setAutoLoad(false); + if (!generator.equals(world.getGenerator())) { + var field = MultiverseWorld.class.getDeclaredField("worldConfig"); + field.setAccessible(true); + + var config = field.get(world); + config.getClass() + .getDeclaredMethod("setGenerator", String.class) + .invoke(config, generator); } - getList().remove(world); - saveConfig(); + manager.saveWorldsConfig(); } - public void saveConfig() { - try { - Plugin p = getMultiverse(); - Object mvWorldManager = p.getClass().getDeclaredMethod("getMVWorldManager").invoke(p); - mvWorldManager.getClass().getDeclaredMethod("saveWorldsConfig").invoke(mvWorldManager); - } catch (Throwable e) { - Iris.reportError(e); - e.printStackTrace(); - } - } - - public void assignWorldType(String worldName, String type) { - worldNameTypes.put(worldName, type); - } - - public String getWorldNameType(String worldName, String defaultType) { - try { - String t = worldNameTypes.get(worldName); - return t == null ? defaultType : t; - } catch (Throwable e) { - Iris.reportError(e); - return defaultType; - } - } - - public boolean isSupported() { - return getMultiverse() != null; - } - - public Plugin getMultiverse() { - - return Bukkit.getPluginManager().getPlugin("Multiverse-Core"); - } - - public String envName(World.Environment environment) { - if (environment == null) { - return "normal"; - } - - return switch (environment) { - case NORMAL -> "normal"; - case NETHER -> "nether"; - case THE_END -> "end"; - default -> environment.toString().toLowerCase(); - }; - + private WorldManager worldManager() { + var api = MultiverseCoreApi.get(); + return api.getWorldManager(); } } diff --git a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java index 87a45076b..34d67ec83 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java +++ b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java @@ -44,6 +44,8 @@ import lombok.Data; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Objects; import java.util.function.Function; @@ -369,6 +371,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { for (ResourceLoader i : loaders.values()) { i.clearList(); } + possibleSnippets.clear(); } public String toLoadKey(File f) { @@ -435,8 +438,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { File f = new File(getDataFolder(), r + ".json"); if (f.exists()) { - try { - JsonReader snippetReader = new JsonReader(new FileReader(f)); + try (JsonReader snippetReader = new JsonReader(new FileReader(f))){ return adapter.read(snippetReader); } catch (Throwable e) { Iris.error("Couldn't read snippet " + r + " in " + reader.getPath() + " (" + e.getMessage() + ")"); @@ -470,11 +472,20 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { KList l = new KList<>(); File snippetFolder = new File(getDataFolder(), "snippet/" + f); + if (!snippetFolder.exists()) return l; - if (snippetFolder.exists() && snippetFolder.isDirectory()) { - for (File i : snippetFolder.listFiles()) { - l.add("snippet/" + f + "/" + i.getName().split("\\Q.\\E")[0]); - } + String absPath = snippetFolder.getAbsolutePath(); + try (var stream = Files.walk(snippetFolder.toPath())) { + stream.filter(Files::isRegularFile) + .map(Path::toAbsolutePath) + .map(Path::toString) + .filter(s -> s.endsWith(".json")) + .map(s -> s.substring(absPath.length() + 1)) + .map(s -> s.replace("\\", "/")) + .map(s -> s.split("\\Q.\\E")[0]) + .forEach(s -> l.add("snippet/" + f + "/" + s)); + } catch (Throwable e) { + e.printStackTrace(); } return l; diff --git a/core/src/main/java/com/volmit/iris/core/nms/INMS.java b/core/src/main/java/com/volmit/iris/core/nms/INMS.java index 99ef8514f..848b08879 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/INMS.java +++ b/core/src/main/java/com/volmit/iris/core/nms/INMS.java @@ -35,7 +35,9 @@ public class INMS { "1.21.2", "v1_21_R2", "1.21.3", "v1_21_R2", "1.21.4", "v1_21_R3", - "1.21.5", "v1_21_R4" + "1.21.5", "v1_21_R4", + "1.21.6", "v1_21_R5", + "1.21.7", "v1_21_R5" ); private static final List PACKS = List.of( new Version(21, 4, "31020"), 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/DataVersion.java b/core/src/main/java/com/volmit/iris/core/nms/datapack/DataVersion.java index f73850b0c..1314d8ecf 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/datapack/DataVersion.java +++ b/core/src/main/java/com/volmit/iris/core/nms/datapack/DataVersion.java @@ -13,6 +13,7 @@ import java.util.function.Supplier; //https://minecraft.wiki/w/Pack_format @Getter public enum DataVersion { + UNSUPPORTED("0.0.0", 0, () -> null), V1192("1.19.2", 10, DataFixerV1192::new), V1205("1.20.6", 41, DataFixerV1206::new), V1213("1.21.3", 57, DataFixerV1213::new); 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..e180ab8d2 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,8 @@ 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.core.nms.datapack.DataVersion; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; @@ -121,25 +120,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; @@ -224,6 +208,11 @@ public class NMSBinding1X implements INMSBinding { return true; } + @Override + public DataVersion getDataVersion() { + return DataVersion.UNSUPPORTED; + } + @Override public int getBiomeId(Biome biome) { return biome.ordinal(); diff --git a/core/src/main/java/com/volmit/iris/core/project/IrisProject.java b/core/src/main/java/com/volmit/iris/core/project/IrisProject.java index 803057318..2ba5e1ee4 100644 --- a/core/src/main/java/com/volmit/iris/core/project/IrisProject.java +++ b/core/src/main/java/com/volmit/iris/core/project/IrisProject.java @@ -447,17 +447,17 @@ public class IrisProject { KSet loot = new KSet<>(); KSet blocks = new KSet<>(); - for (String i : dm.getDimensionLoader().getPossibleKeys()) { + for (String i : dm.getBlockLoader().getPossibleKeys()) { blocks.add(dm.getBlockLoader().load(i)); } dimension.getRegions().forEach((i) -> regions.add(dm.getRegionLoader().load(i))); dimension.getLoot().getTables().forEach((i) -> loot.add(dm.getLootLoader().load(i))); - regions.forEach((i) -> biomes.addAll(i.getAllBiomes(null))); + regions.forEach((i) -> biomes.addAll(i.getAllBiomes(() -> dm))); regions.forEach((r) -> r.getLoot().getTables().forEach((i) -> loot.add(dm.getLootLoader().load(i)))); regions.forEach((r) -> r.getEntitySpawners().forEach((sp) -> spawners.add(dm.getSpawnerLoader().load(sp)))); dimension.getEntitySpawners().forEach((sp) -> spawners.add(dm.getSpawnerLoader().load(sp))); - biomes.forEach((i) -> i.getGenerators().forEach((j) -> generators.add(j.getCachedGenerator(null)))); + biomes.forEach((i) -> i.getGenerators().forEach((j) -> generators.add(j.getCachedGenerator(() -> dm)))); biomes.forEach((r) -> r.getLoot().getTables().forEach((i) -> loot.add(dm.getLootLoader().load(i)))); biomes.forEach((r) -> r.getEntitySpawners().forEach((sp) -> spawners.add(dm.getSpawnerLoader().load(sp)))); spawners.forEach((i) -> i.getSpawns().forEach((j) -> entities.add(dm.getEntityLoader().load(j.getEntity())))); diff --git a/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java b/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java index 6f69bf223..a96cf34c7 100644 --- a/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java +++ b/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java @@ -142,6 +142,8 @@ public class SchemaBuilder { JSONObject property = buildProperty(k, c); + if (property.getBoolean("!required")) + required.put(k.getName()); property.remove("!required"); properties.put(k.getName(), property); } @@ -153,19 +155,7 @@ public class SchemaBuilder { o.put("properties", properties); - if (c.isAnnotationPresent(Snippet.class)) { - JSONObject anyOf = new JSONObject(); - JSONArray arr = new JSONArray(); - JSONObject str = new JSONObject(); - str.put("type", "string"); - arr.put(o); - arr.put(str); - anyOf.put("anyOf", arr); - - return anyOf; - } - - return o; + return buildSnippet(o, c); } private JSONObject buildProperty(Field k, Class cl) { @@ -515,8 +505,16 @@ public class SchemaBuilder { boolean present = !typeDesc.isBlank(); if (present) d.add(typeDesc); - if (k.getType().isAnnotationPresent(Snippet.class)) { - String sm = k.getType().getDeclaredAnnotation(Snippet.class).value(); + Snippet snippet = k.getType().getDeclaredAnnotation(Snippet.class); + if (snippet == null) { + ArrayType array = k.getType().getDeclaredAnnotation(ArrayType.class); + if (array != null) { + snippet = array.type().getDeclaredAnnotation(Snippet.class); + } + } + + if (snippet != null) { + String sm = snippet.value(); if (present) d.add(" "); d.add("You can instead specify \"snippet/" + sm + "/some-name.json\" to use a snippet file instead of specifying it here."); present = false; @@ -547,40 +545,40 @@ public class SchemaBuilder { .replace("", ""); String hDesc = d.toString("
"); prop.put("type", type); - prop.put("description", desc); + prop.put("description", d.toString("\n")); prop.put("x-intellij-html-description", hDesc); + return buildSnippet(prop, k.getType()); + } - if (k.getType().isAnnotationPresent(Snippet.class)) { - JSONObject anyOf = new JSONObject(); - JSONArray arr = new JSONArray(); - JSONObject str = new JSONObject(); - str.put("type", "string"); - String key = "enum-snippet-" + k.getType().getDeclaredAnnotation(Snippet.class).value(); - str.put("$ref", "#/definitions/" + key); + private JSONObject buildSnippet(JSONObject prop, Class type) { + Snippet snippet = type.getDeclaredAnnotation(Snippet.class); + if (snippet == null) return prop; - if (!definitions.containsKey(key)) { - JSONObject j = new JSONObject(); - JSONArray snl = new JSONArray(); - data.getPossibleSnippets(k.getType().getDeclaredAnnotation(Snippet.class).value()).forEach(snl::put); - j.put("enum", snl); - definitions.put(key, j); - } + JSONObject anyOf = new JSONObject(); + JSONArray arr = new JSONArray(); + JSONObject str = new JSONObject(); + str.put("type", "string"); + String key = "enum-snippet-" + snippet.value(); + str.put("$ref", "#/definitions/" + key); - arr.put(prop); - arr.put(str); - prop.put("description", desc); - prop.put("x-intellij-html-description", hDesc); - str.put("description", desc); - str.put("x-intellij-html-description", hDesc); - anyOf.put("anyOf", arr); - anyOf.put("description", desc); - anyOf.put("x-intellij-html-description", hDesc); - anyOf.put("!required", k.isAnnotationPresent(Required.class)); - - return anyOf; + if (!definitions.containsKey(key)) { + JSONObject j = new JSONObject(); + JSONArray snl = new JSONArray(); + data.getPossibleSnippets(snippet.value()).forEach(snl::put); + j.put("enum", snl); + definitions.put(key, j); } - return prop; + arr.put(prop); + arr.put(str); + str.put("description", prop.getString("description")); + str.put("x-intellij-html-description", prop.getString("x-intellij-html-description")); + anyOf.put("anyOf", arr); + anyOf.put("description", prop.getString("description")); + anyOf.put("x-intellij-html-description", prop.getString("x-intellij-html-description")); + anyOf.put("!required", type.isAnnotationPresent(Required.class)); + + return anyOf; } @NotNull 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..7a4ca9b5c 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 @@ -2,8 +2,13 @@ package com.volmit.iris.core.safeguard; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; + +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 +18,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() { @@ -30,5 +37,23 @@ public class IrisSafeguard { return "stable"; } } + + public static KMap asContext() { + KMap m = new KMap<>(); + m.put("diskSpace", !ServerBootSFG.hasEnoughDiskSpace); + m.put("javaVersion", !ServerBootSFG.isCorrectJDK); + m.put("jre", ServerBootSFG.isJRE); + m.put("missingAgent", ServerBootSFG.missingAgent); + m.put("missingDimensionTypes", ServerBootSFG.missingDimensionTypes); + m.put("failedInjection", ServerBootSFG.failedInjection); + m.put("unsupportedVersion", ServerBootSFG.unsuportedversion); + m.put("serverSoftware", !ServerBootSFG.passedserversoftware); + KList incompatiblePlugins = new KList<>(); + ServerBootSFG.incompatibilities.forEach((plugin, present) -> { + if (present) incompatiblePlugins.add(plugin); + }); + m.put("plugins", incompatiblePlugins); + return m; + } } 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..8ec35899f 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; @@ -45,9 +49,7 @@ public class ServerBootSFG { Plugin[] plugins = pluginManager.getPlugins(); incompatibilities.clear(); - incompatibilities.put("Multiverse-Core", false); incompatibilities.put("dynmap", false); - incompatibilities.put("TerraformGenerator", false); incompatibilities.put("Stratos", false); String pluginName; @@ -112,10 +114,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 +182,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..064f48db0 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 { @@ -9,7 +10,9 @@ public class UtilsSFG { } public static void printIncompatibleWarnings() { - // String SupportedIrisVersion = getDescription().getVersion(); //todo Automatic version + String[] parts = Iris.instance.getDescription().getVersion().split("-"); + String minVersion = parts[1]; + String maxVersion = parts[2]; if (ServerBootSFG.safeguardPassed) { Iris.safeguard(C.BLUE + "0 Conflicts found"); @@ -21,29 +24,29 @@ public class UtilsSFG { Iris.safeguard(C.YELLOW + "" + ServerBootSFG.count + " Conflicts found"); } - if (ServerBootSFG.incompatibilities.get("Multiverse-Core")) { - Iris.safeguard(C.RED + "Multiverse"); - Iris.safeguard(C.RED + "- The plugin Multiverse is not compatible with the server."); - Iris.safeguard(C.RED + "- If you want to have a world manager, consider using PhantomWorlds or MyWorlds instead."); - } if (ServerBootSFG.incompatibilities.get("dynmap")) { Iris.safeguard(C.RED + "Dynmap"); Iris.safeguard(C.RED + "- The plugin Dynmap is not compatible with the server."); Iris.safeguard(C.RED + "- If you want to have a map plugin like Dynmap, consider Bluemap."); } - if (ServerBootSFG.incompatibilities.get("TerraformGenerator") || ServerBootSFG.incompatibilities.get("Stratos")) { - Iris.safeguard(C.YELLOW + "Terraform Generator / Stratos"); + if (ServerBootSFG.incompatibilities.get("Stratos")) { + Iris.safeguard(C.YELLOW + "Stratos"); Iris.safeguard(C.YELLOW + "- Iris is not compatible with other worldgen plugins."); } if (ServerBootSFG.unsuportedversion) { Iris.safeguard(C.RED + "Server Version"); - Iris.safeguard(C.RED + "- Iris only supports 1.20.1 > 1.21.4"); + Iris.safeguard(C.RED + "- Iris only supports " + minVersion + " > " + maxVersion); } if (ServerBootSFG.missingDimensionTypes) { Iris.safeguard(C.RED + "Dimension Types"); Iris.safeguard(C.RED + "- Required Iris dimension types were not loaded."); Iris.safeguard(C.RED + "- If this still happens after a restart please contact support."); } + if (ServerBootSFG.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/core/service/ExternalDataSVC.java b/core/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java index c71224a99..4d9596278 100644 --- a/core/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java @@ -75,6 +75,10 @@ public class ExternalDataSVC implements IrisService { if (Bukkit.getPluginManager().getPlugin("EcoItems") != null) { Iris.info("EcoItems found, loading EcoItemsDataProvider..."); } + providers.add(new KGeneratorsDataProvider()); + if (Bukkit.getPluginManager().getPlugin("KGenerators") != null) { + Iris.info("KGenerators found, loading KGeneratorsDataProvider..."); + } for (ExternalDataProvider p : providers) { if (p.isReady()) { diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java b/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java index ddcd5ff85..fac42b051 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java @@ -199,8 +199,10 @@ public class IrisCreator { world.get().setTime(6000); } }); - } else + } else { addToBukkitYml(); + J.s(() -> Iris.linkMultiverseCore.updateWorld(world.get(), dimension)); + } if (pregen != null) { CompletableFuture ff = new CompletableFuture<>(); diff --git a/core/src/main/java/com/volmit/iris/engine/IrisComplex.java b/core/src/main/java/com/volmit/iris/engine/IrisComplex.java index e92e6da84..652b728ca 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisComplex.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisComplex.java @@ -108,10 +108,17 @@ public class IrisComplex implements DataProvider { } //@builder - engine.getDimension().getRegions().forEach((i) -> data.getRegionLoader().load(i) - .getAllBiomes(this).forEach((b) -> b - .getGenerators() - .forEach((c) -> registerGenerator(c.getCachedGenerator(this))))); + if (focusRegion != null) { + focusRegion.getAllBiomes(this).forEach(this::registerGenerators); + } else if (focusBiome != null) { + registerGenerators(focusBiome); + } else { + engine.getDimension() + .getRegions() + .forEach(i -> data.getRegionLoader().load(i) + .getAllBiomes(this) + .forEach(this::registerGenerators)); + } overlayStream = ProceduralStream.ofDouble((x, z) -> 0.0D).waste("Overlay Stream"); engine.getDimension().getOverlayNoise().forEach(i -> overlayStream = overlayStream.add((x, z) -> i.get(rng, getData(), x, z))); rockStream = engine.getDimension().getRockPalette().getLayerGenerator(rng.nextParallelRNG(45), data).stream() @@ -360,6 +367,10 @@ public class IrisComplex implements DataProvider { return Math.max(Math.min(getInterpolatedHeight(engine, x, z, seed) + fluidHeight + overlayStream.get(x, z), engine.getHeight()), 0); } + private void registerGenerators(IrisBiome biome) { + biome.getGenerators().forEach(c -> registerGenerator(c.getCachedGenerator(this))); + } + private void registerGenerator(IrisGenerator cachedGenerator) { generators.computeIfAbsent(cachedGenerator.getInterpolator(), (k) -> new KSet<>()).add(cachedGenerator); } diff --git a/core/src/main/java/com/volmit/iris/engine/actuator/IrisTerrainNormalActuator.java b/core/src/main/java/com/volmit/iris/engine/actuator/IrisTerrainNormalActuator.java index 7d602021e..646ca0773 100644 --- a/core/src/main/java/com/volmit/iris/engine/actuator/IrisTerrainNormalActuator.java +++ b/core/src/main/java/com/volmit/iris/engine/actuator/IrisTerrainNormalActuator.java @@ -106,6 +106,14 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator } } + BlockData ore = biome.generateOres(realX, i, realZ, rng, getData(), true); + ore = ore == null ? region.generateOres(realX, i, realZ, rng, getData(), true) : ore; + ore = ore == null ? getDimension().generateOres(realX, i, realZ, rng, getData(), true) : ore; + if (ore != null) { + h.set(xf, i, zf, ore); + continue; + } + if (i > he && i <= hf) { fdepth = hf - i; @@ -138,9 +146,9 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator continue; } - BlockData ore = biome.generateOres(realX, i, realZ, rng, getData()); - ore = ore == null ? region.generateOres(realX, i, realZ, rng, getData()) : ore; - ore = ore == null ? getDimension().generateOres(realX, i, realZ, rng, getData()) : ore; + ore = biome.generateOres(realX, i, realZ, rng, getData(), false); + ore = ore == null ? region.generateOres(realX, i, realZ, rng, getData(), false) : ore; + ore = ore == null ? getDimension().generateOres(realX, i, realZ, rng, getData(), false) : ore; if (ore != null) { h.set(xf, i, zf, ore); diff --git a/core/src/main/java/com/volmit/iris/engine/modifier/IrisDepositModifier.java b/core/src/main/java/com/volmit/iris/engine/modifier/IrisDepositModifier.java index 037f77d8d..d998088b2 100644 --- a/core/src/main/java/com/volmit/iris/engine/modifier/IrisDepositModifier.java +++ b/core/src/main/java/com/volmit/iris/engine/modifier/IrisDepositModifier.java @@ -52,16 +52,20 @@ public class IrisDepositModifier extends EngineAssignedModifier { BurstExecutor burst = burst().burst(multicore); long seed = x * 341873128712L + z * 132897987541L; + long mask = 0; for (IrisDepositGenerator k : getDimension().getDeposits()) { - burst.queue(() -> generate(k, terrain, rng.nextParallelRNG(seed), x, z, false, context)); + long finalSeed = seed * ++mask; + burst.queue(() -> generate(k, terrain, rng.nextParallelRNG(finalSeed), x, z, false, context)); } for (IrisDepositGenerator k : region.getDeposits()) { - burst.queue(() -> generate(k, terrain, rng.nextParallelRNG(seed), x, z, false, context)); + long finalSeed = seed * ++mask; + burst.queue(() -> generate(k, terrain, rng.nextParallelRNG(finalSeed), x, z, false, context)); } for (IrisDepositGenerator k : biome.getDeposits()) { - burst.queue(() -> generate(k, terrain, rng.nextParallelRNG(seed), x, z, false, context)); + long finalSeed = seed * ++mask; + burst.queue(() -> generate(k, terrain, rng.nextParallelRNG(finalSeed), x, z, false, context)); } burst.complete(); } @@ -78,7 +82,7 @@ public class IrisDepositModifier extends EngineAssignedModifier { if (k.getPerClumpSpawnChance() < rng.d()) continue; - IrisObject clump = k.getClump(rng, getData()); + IrisObject clump = k.getClump(getEngine(), rng, getData()); int dim = clump.getW(); int min = dim / 2; diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisBiome.java b/core/src/main/java/com/volmit/iris/engine/object/IrisBiome.java index 0b4eb11c3..ac1390d83 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisBiome.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisBiome.java @@ -171,12 +171,14 @@ public class IrisBiome extends IrisRegistrant implements IRare { @ArrayType(type = IrisOreGenerator.class, min = 1) private KList ores = new KList<>(); - public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data) { + public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data, boolean surface) { if (ores.isEmpty()) { return null; } BlockData b = null; for (IrisOreGenerator i : ores) { + if (i.isGenerateSurface() != surface) + continue; b = i.generate(x, y, z, rng, data); if (b != null) { diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisCave.java b/core/src/main/java/com/volmit/iris/engine/object/IrisCave.java index e0e818403..22a45ccc4 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisCave.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisCave.java @@ -66,14 +66,12 @@ public class IrisCave extends IrisRegistrant { } public void generate(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z) { - generate(writer, rng, engine, x, y, z, 0, -1); + generate(writer, rng, engine, x, y, z, 0, -1, true); } - public void generate(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z, int recursion, int waterHint) { - + public void generate(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z, int recursion, int waterHint, boolean breakSurface) { double girth = getWorm().getGirth().get(rng, x, z, engine.getData()); - KList points = getWorm().generate(rng, engine.getData(), writer, verticalRange, x, y, z, (at) -> { - }); + KList points = getWorm().generate(rng, engine.getData(), writer, verticalRange, x, y, z, breakSurface, girth + 9); int highestWater = Math.max(waterHint, -1); if (highestWater == -1) { diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisCavePlacer.java b/core/src/main/java/com/volmit/iris/engine/object/IrisCavePlacer.java index 1a95493a1..c56a53d69 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisCavePlacer.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisCavePlacer.java @@ -86,17 +86,13 @@ public class IrisCavePlacer implements IRare { } if (y == -1) { - if(!breakSurface) { - int eH = engine.getHeight(x, z); - if (caveStartHeight.getMax() > eH) { - caveStartHeight.setMax(eH); - } - } - y = (int) caveStartHeight.get(rng, x, z, data); + int h = (int) caveStartHeight.get(rng, x, z, data); + int ma = breakSurface ? h : (int) (engine.getComplex().getHeightStream().get(x, z) - 9); + y = Math.min(h, ma); } try { - cave.generate(mantle, rng, engine, x + rng.nextInt(15), y, z + rng.nextInt(15), recursion, waterHint); + cave.generate(mantle, rng, engine, x + rng.nextInt(15), y, z + rng.nextInt(15), recursion, waterHint, breakSurface); } catch (Throwable e) { e.printStackTrace(); fail.set(true); 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/IrisDepositGenerator.java b/core/src/main/java/com/volmit/iris/engine/object/IrisDepositGenerator.java index 271b54ded..19623ca39 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisDepositGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisDepositGenerator.java @@ -20,6 +20,7 @@ package com.volmit.iris.engine.object; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.annotations.*; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KSet; @@ -87,10 +88,10 @@ public class IrisDepositGenerator { @Desc("Ore varience is how many different objects clumps iris will create") private int varience = 3; - public IrisObject getClump(RNG rng, IrisData rdata) { + public IrisObject getClump(Engine engine, RNG rng, IrisData rdata) { KList objects = this.objects.aquire(() -> { - RNG rngv = rng.nextParallelRNG(3957778); + RNG rngv = new RNG(engine.getSeedManager().getDeposit() + hashCode()); KList objectsf = new KList<>(); for (int i = 0; i < varience; i++) { 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 e2ac178b9..c82a175ea 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; @@ -46,8 +47,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 @@ -76,10 +76,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; @@ -168,10 +164,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 = ""; @@ -263,12 +257,14 @@ public class IrisDimension extends IrisRegistrant { return (int) getDimensionHeight().getMin(); } - public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data) { + public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data, boolean surface) { if (ores.isEmpty()) { return null; } BlockData b = null; for (IrisOreGenerator i : ores) { + if (i.isGenerateSurface() != surface) + continue; b = i.generate(x, y, z, rng, data); if (b != null) { @@ -431,6 +427,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"; @@ -447,11 +476,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 = """ @@ -476,17 +506,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/object/IrisRavine.java b/core/src/main/java/com/volmit/iris/engine/object/IrisRavine.java index e2d9a79e4..3edd37c29 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisRavine.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisRavine.java @@ -97,8 +97,7 @@ public class IrisRavine extends IrisRegistrant { } public void generate(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z, int recursion, int waterHint) { - KList pos = getWorm().generate(rng, engine.getData(), writer, null, x, y, z, (at) -> { - }); + KList pos = getWorm().generate(rng, engine.getData(), writer, null, x, y, z, true, 0); CNG dg = depthStyle.getGenerator().createNoCache(rng, engine.getData()); CNG bw = baseWidthStyle.getGenerator().createNoCache(rng, engine.getData()); int highestWater = Math.max(waterHint, -1); diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisRegion.java b/core/src/main/java/com/volmit/iris/engine/object/IrisRegion.java index 158f04f3e..a2712786a 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisRegion.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisRegion.java @@ -151,12 +151,14 @@ public class IrisRegion extends IrisRegistrant implements IRare { @ArrayType(type = IrisOreGenerator.class, min = 1) private KList ores = new KList<>(); - public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data) { + public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data, boolean surface) { if (ores.isEmpty()) { return null; } BlockData b = null; for (IrisOreGenerator i : ores) { + if (i.isGenerateSurface() != surface) + continue; b = i.generate(x, y, z, rng, data); if (b != null) { diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisWorm.java b/core/src/main/java/com/volmit/iris/engine/object/IrisWorm.java index d2f7e499c..b50f9d3be 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisWorm.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisWorm.java @@ -62,7 +62,7 @@ public class IrisWorm { private IrisStyledRange girth = new IrisStyledRange().setMin(3).setMax(5) .setStyle(new IrisGeneratorStyle(NoiseStyle.PERLIN)); - public KList generate(RNG rng, IrisData data, MantleWriter writer, IrisRange verticalRange, int x, int y, int z, Consumer fork) { + public KList generate(RNG rng, IrisData data, MantleWriter writer, IrisRange verticalRange, int x, int y, int z, boolean breakSurface, double distance) { int itr = maxIterations; double jx, jy, jz; double cx = x; @@ -72,12 +72,11 @@ public class IrisWorm { KList pos = new KList<>(); KSet check = allowLoops ? null : new KSet<>(); CNG gx = xStyle.getGenerator().createNoCache(new RNG(rng.lmax()), data); - CNG gy = xStyle.getGenerator().createNoCache(new RNG(rng.lmax()), data); - CNG gz = xStyle.getGenerator().createNoCache(new RNG(rng.lmax()), data); + CNG gy = yStyle.getGenerator().createNoCache(new RNG(rng.lmax()), data); + CNG gz = zStyle.getGenerator().createNoCache(new RNG(rng.lmax()), data); while (itr-- > 0) { IrisPosition current = new IrisPosition(Math.round(cx), Math.round(cy), Math.round(cz)); - fork.accept(current); pos.add(current); if (check != null) { @@ -92,6 +91,10 @@ public class IrisWorm { cz += jz; IrisPosition next = new IrisPosition(Math.round(cx), Math.round(cy), Math.round(cz)); + if (!breakSurface && writer.getEngineMantle().getHighest(next.getX(), next.getZ(), true) <= next.getY() + distance) { + break; + } + if (verticalRange != null && !verticalRange.contains(next.getY())) { break; } 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/data/B.java b/core/src/main/java/com/volmit/iris/util/data/B.java index 02e0e89cc..1c7a521cb 100644 --- a/core/src/main/java/com/volmit/iris/util/data/B.java +++ b/core/src/main/java/com/volmit/iris/util/data/B.java @@ -105,7 +105,15 @@ public class B { DEEPSLATE_TILES, DEEPSLATE_TILE_STAIRS, DEEPSLATE_TILE_WALL, - CRACKED_DEEPSLATE_TILES + CRACKED_DEEPSLATE_TILES, + DEEPSLATE_COAL_ORE, + DEEPSLATE_IRON_ORE, + DEEPSLATE_COPPER_ORE, + DEEPSLATE_DIAMOND_ORE, + DEEPSLATE_EMERALD_ORE, + DEEPSLATE_GOLD_ORE, + DEEPSLATE_LAPIS_ORE, + DEEPSLATE_REDSTONE_ORE, }).forEach((i) -> b.add(i.ordinal())); return IntSets.unmodifiable(b); diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/DataVersionHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/DataVersionHandler.java index 489590496..53dff488e 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/DataVersionHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/DataVersionHandler.java @@ -8,7 +8,7 @@ import com.volmit.iris.util.decree.exceptions.DecreeParsingException; public class DataVersionHandler implements DecreeParameterHandler { @Override public KList getPossibilities() { - return new KList<>(DataVersion.values()); + return new KList<>(DataVersion.values()).qdel(DataVersion.UNSUPPORTED); } @Override 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 84db720ea..94b6f9e85 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 @@ -1667,7 +1667,7 @@ public class IO { return doc; } - 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 847672c10..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 - - org.lz4:lz4-java:1.8.0 - - com.github.oshi:oshi-core:6.6.5 - - org.dom4j:dom4j:2.1.4 - - jaxen:jaxen:1.1.6 +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/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/CustomBiomeSource.java b/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/CustomBiomeSource.java new file mode 100644 index 000000000..c8fa95353 --- /dev/null +++ b/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/CustomBiomeSource.java @@ -0,0 +1,169 @@ +package com.volmit.iris.core.nms.v1_21_R5; + +import com.mojang.serialization.MapCodec; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.IrisBiome; +import com.volmit.iris.engine.object.IrisBiomeCustom; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.math.RNG; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.biome.Climate; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_21_R5.CraftServer; +import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +public class CustomBiomeSource extends BiomeSource { + + private final long seed; + private final Engine engine; + private final Registry biomeCustomRegistry; + private final Registry biomeRegistry; + private final AtomicCache registryAccess = new AtomicCache<>(); + private final RNG rng; + private final KMap> customBiomes; + + public CustomBiomeSource(long seed, Engine engine, World world) { + this.engine = engine; + this.seed = seed; + this.biomeCustomRegistry = registry().lookup(Registries.BIOME).orElse(null); + this.biomeRegistry = ((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())).lookup(Registries.BIOME).orElse(null); + this.rng = new RNG(engine.getSeedManager().getBiome()); + this.customBiomes = fillCustomBiomes(biomeCustomRegistry, engine); + } + + private static List> getAllBiomes(Registry customRegistry, Registry registry, Engine engine) { + List> b = new ArrayList<>(); + + for (IrisBiome i : engine.getAllBiomes()) { + if (i.isCustom()) { + for (IrisBiomeCustom j : i.getCustomDerivitives()) { + b.add(customRegistry.get(customRegistry.getResourceKey(customRegistry + .getValue(ResourceLocation.fromNamespaceAndPath(engine.getDimension().getLoadKey(), j.getId()))).get()).get()); + } + } else { + b.add(NMSBinding.biomeToBiomeBase(registry, i.getVanillaDerivative())); + } + } + + return b; + } + + private static Object getFor(Class type, Object source) { + Object o = fieldFor(type, source); + + if (o != null) { + return o; + } + + return invokeFor(type, source); + } + + private static Object fieldFor(Class returns, Object in) { + return fieldForClass(returns, in.getClass(), in); + } + + private static Object invokeFor(Class returns, Object in) { + for (Method i : in.getClass().getMethods()) { + if (i.getReturnType().equals(returns)) { + i.setAccessible(true); + try { + Iris.debug("[NMS] Found " + returns.getSimpleName() + " in " + in.getClass().getSimpleName() + "." + i.getName() + "()"); + return i.invoke(in); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + return null; + } + + @SuppressWarnings("unchecked") + private static T fieldForClass(Class returnType, Class sourceType, Object in) { + for (Field i : sourceType.getDeclaredFields()) { + if (i.getType().equals(returnType)) { + i.setAccessible(true); + try { + Iris.debug("[NMS] Found " + returnType.getSimpleName() + " in " + sourceType.getSimpleName() + "." + i.getName()); + return (T) i.get(in); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + return null; + } + + @Override + protected Stream> collectPossibleBiomes() { + return getAllBiomes( + ((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())) + .lookup(Registries.BIOME).orElse(null), + ((CraftWorld) engine.getWorld().realWorld()).getHandle().registryAccess().lookup(Registries.BIOME).orElse(null), + engine).stream(); + } + private KMap> fillCustomBiomes(Registry customRegistry, Engine engine) { + KMap> m = new KMap<>(); + + for (IrisBiome i : engine.getAllBiomes()) { + if (i.isCustom()) { + for (IrisBiomeCustom j : i.getCustomDerivitives()) { + ResourceLocation resourceLocation = ResourceLocation.fromNamespaceAndPath(engine.getDimension().getLoadKey(), j.getId()); + Biome biome = customRegistry.getValue(resourceLocation); + Optional> optionalBiomeKey = customRegistry.getResourceKey(biome); + if (optionalBiomeKey.isEmpty()) { + Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); + continue; + } + ResourceKey biomeKey = optionalBiomeKey.get(); + Optional> optionalReferenceHolder = customRegistry.get(biomeKey); + if (optionalReferenceHolder.isEmpty()) { + Iris.error("Cannot find reference to biome " + biomeKey + " for engine " + engine.getName()); + continue; + } + m.put(j.getId(), optionalReferenceHolder.get()); + } + } + } + + return m; + } + + private RegistryAccess registry() { + return registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())); + } + + @Override + protected MapCodec codec() { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public Holder getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) { + int m = (y - engine.getMinHeight()) << 2; + IrisBiome ib = engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2); + if (ib.isCustom()) { + return customBiomes.get(ib.getCustomBiome(rng, x << 2, m, z << 2).getId()); + } else { + org.bukkit.block.Biome v = ib.getSkyBiome(rng, x << 2, m, z << 2); + return NMSBinding.biomeToBiomeBase(biomeRegistry, v); + } + } +} \ No newline at end of file diff --git a/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/IrisChunkGenerator.java b/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/IrisChunkGenerator.java new file mode 100644 index 000000000..c156c09df --- /dev/null +++ b/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/IrisChunkGenerator.java @@ -0,0 +1,311 @@ +package com.volmit.iris.core.nms.v1_21_R5; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.MapCodec; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.framework.ResultLocator; +import com.volmit.iris.engine.framework.WrongEngineBroException; +import com.volmit.iris.engine.object.IrisJigsawStructure; +import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +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.mantle.MantleFlag; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.reflect.WrappedField; +import net.minecraft.core.*; +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.server.level.WorldGenRegion; +import net.minecraft.tags.StructureTags; +import net.minecraft.tags.TagKey; +import net.minecraft.util.random.WeightedList; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.level.*; +import net.minecraft.world.level.biome.*; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureSet; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R5.generator.CustomChunkGenerator; +import org.spigotmc.SpigotWorldConfig; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class IrisChunkGenerator extends CustomChunkGenerator { + private static final WrappedField BIOME_SOURCE; + private final ChunkGenerator delegate; + private final Engine engine; + private final KMap, KSet> structures = new KMap<>(); + + public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) { + super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null); + this.delegate = delegate; + this.engine = engine; + var dimension = engine.getDimension(); + + KSet placements = new KSet<>(); + addAll(dimension.getJigsawStructures(), placements); + for (var region : dimension.getAllRegions(engine)) { + addAll(region.getJigsawStructures(), placements); + for (var biome : region.getAllBiomes(engine)) + addAll(biome.getJigsawStructures(), placements); + } + var stronghold = dimension.getStronghold(); + if (stronghold != null) + placements.add(engine.getData().getJigsawStructureLoader().load(stronghold)); + placements.removeIf(Objects::isNull); + + var registry = ((CraftWorld) world).getHandle().registryAccess().lookup(Registries.STRUCTURE).orElseThrow(); + for (var s : placements) { + try { + String raw = s.getStructureKey(); + if (raw == null) continue; + boolean tag = raw.startsWith("#"); + if (tag) raw = raw.substring(1); + + var location = ResourceLocation.parse(raw); + if (!tag) { + structures.computeIfAbsent(ResourceKey.create(Registries.STRUCTURE, location), k -> new KSet<>()).add(s.getLoadKey()); + continue; + } + + var key = TagKey.create(Registries.STRUCTURE, location); + var set = registry.get(key).orElse(null); + if (set == null) { + Iris.error("Could not find structure tag: " + raw); + continue; + } + for (var holder : set) { + var resourceKey = holder.unwrapKey().orElse(null); + if (resourceKey == null) continue; + structures.computeIfAbsent(resourceKey, k -> new KSet<>()).add(s.getLoadKey()); + } + } catch (Throwable e) { + Iris.error("Failed to load structure: " + s.getLoadKey()); + e.printStackTrace(); + } + } + } + + private void addAll(KList placements, KSet structures) { + if (placements == null) return; + placements.stream() + .map(IrisJigsawStructurePlacement::getStructure) + .map(engine.getData().getJigsawStructureLoader()::load) + .filter(Objects::nonNull) + .forEach(structures::add); + } + + @Override + public @Nullable Pair> findNearestMapStructure(ServerLevel level, HolderSet holders, BlockPos pos, int radius, boolean findUnexplored) { + if (holders.size() == 0) return null; + if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) { + var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ())); + return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0)); + } + if (engine.getDimension().isDisableExplorerMaps()) + return null; + + KMap> structures = new KMap<>(); + for (var holder : holders) { + if (holder == null) continue; + var key = holder.unwrapKey().orElse(null); + var set = this.structures.get(key); + if (set == null) continue; + for (var structure : set) { + structures.put(structure, holder); + } + } + if (structures.isEmpty()) + return null; + + var locator = ResultLocator.locateStructure(structures.keySet()) + .then((e, p , s) -> structures.get(s.getLoadKey())); + if (findUnexplored) + locator = locator.then((e, p, s) -> e.getMantle().getMantle().getChunk(p.getX(), p.getZ()).isFlagged(MantleFlag.DISCOVERED) ? null : s); + + try { + var result = locator.find(engine, new Position2(pos.getX() >> 4, pos.getZ() >> 4), radius * 10L, i -> {}, false).get(); + if (result == null) return null; + var blockPos = new BlockPos(result.getBlockX(), 0, result.getBlockZ()); + return Pair.of(blockPos, result.obj()); + } catch (WrongEngineBroException | ExecutionException | InterruptedException e) { + return null; + } + } + + @Override + protected MapCodec codec() { + return MapCodec.unit(null); + } + + @Override + public ChunkGenerator getDelegate() { + if (delegate instanceof CustomChunkGenerator chunkGenerator) + return chunkGenerator.getDelegate(); + return delegate; + } + + @Override + public int getMinY() { + return delegate.getMinY(); + } + + @Override + public int getSeaLevel() { + return delegate.getSeaLevel(); + } + + @Override + public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager, ResourceKey resourcekey) { + delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager, resourcekey); + } + + @Override + public ChunkGeneratorStructureState createState(HolderLookup holderlookup, RandomState randomstate, long i, SpigotWorldConfig conf) { + return delegate.createState(holderlookup, randomstate, i, conf); + } + + @Override + public void createReferences(WorldGenLevel generatoraccessseed, StructureManager structuremanager, ChunkAccess ichunkaccess) { + delegate.createReferences(generatoraccessseed, structuremanager, ichunkaccess); + } + + @Override + public CompletableFuture createBiomes(RandomState randomstate, Blender blender, StructureManager structuremanager, ChunkAccess ichunkaccess) { + return delegate.createBiomes(randomstate, blender, structuremanager, ichunkaccess); + } + + @Override + public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) { + delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess); + } + + @Override + public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess) { + delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess); + } + + @Override + public CompletableFuture fillFromNoise(Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) { + return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess); + } + + @Override + public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate); + } + + @Override + public WeightedList getMobsAt(Holder holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) { + return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + } + + @Override + public void addDebugScreenInfo(List list, RandomState randomstate, BlockPos blockposition) { + delegate.addDebugScreenInfo(list, randomstate, blockposition); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) { + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, vanilla); + } + + @Override + public int getFirstFreeHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return delegate.getFirstFreeHeight(i, j, heightmap_type, levelheightaccessor, randomstate); + } + + @Override + public int getFirstOccupiedHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return delegate.getFirstOccupiedHeight(i, j, heightmap_type, levelheightaccessor, randomstate); + } + + @Override + public void addVanillaDecorations(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { + delegate.addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); + } + + @Override + public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) { + delegate.spawnOriginalMobs(regionlimitedworldaccess); + } + + @Override + public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) { + return delegate.getSpawnHeight(levelheightaccessor); + } + + @Override + public int getGenDepth() { + return delegate.getGenDepth(); + } + + @Override + public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + } + + @Override + public Optional>> getTypeNameForDataFixer() { + return delegate.getTypeNameForDataFixer(); + } + + @Override + public void validate() { + delegate.validate(); + } + + @Override + @SuppressWarnings("deprecation") + public BiomeGenerationSettings getBiomeGenerationSettings(Holder holder) { + return delegate.getBiomeGenerationSettings(holder); + } + + static { + Field biomeSource = null; + for (Field field : ChunkGenerator.class.getDeclaredFields()) { + if (!field.getType().equals(BiomeSource.class)) + continue; + biomeSource = field; + break; + } + if (biomeSource == null) + throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + } + + private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { + try { + BIOME_SOURCE.set(generator, source); + if (generator instanceof CustomChunkGenerator custom) + BIOME_SOURCE.set(custom.getDelegate(), source); + + return generator; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/NMSBinding.java b/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/NMSBinding.java new file mode 100644 index 000000000..425dd25a7 --- /dev/null +++ b/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/NMSBinding.java @@ -0,0 +1,762 @@ +package com.volmit.iris.core.nms.v1_21_R5; + +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.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 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.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.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_21_R5.CraftChunk; +import org.bukkit.craftbukkit.v1_21_R5.CraftServer; +import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R5.block.CraftBlockState; +import org.bukkit.craftbukkit.v1_21_R5.block.CraftBlockStates; +import org.bukkit.craftbukkit.v1_21_R5.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R5.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 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 AtomicBoolean injected = new AtomicBoolean(); + private final AtomicCache> registryCache = new AtomicCache<>(); + private final AtomicCache> globalCache = new AtomicCache<>(); + private final AtomicCache registryAccess = new AtomicCache<>(); + private final AtomicCache byIdRef = new AtomicCache<>(); + private Field biomeStorageCache = null; + + private static Object getFor(Class type, Object source) { + Object o = fieldFor(type, source); + + if (o != null) { + return o; + } + + return invokeFor(type, source); + } + + private static Object invokeFor(Class returns, Object in) { + for (Method i : in.getClass().getMethods()) { + if (i.getReturnType().equals(returns)) { + i.setAccessible(true); + try { + Iris.debug("[NMS] Found " + returns.getSimpleName() + " in " + in.getClass().getSimpleName() + "." + i.getName() + "()"); + return i.invoke(in); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + return null; + } + + private static Object fieldFor(Class returns, Object in) { + return fieldForClass(returns, in.getClass(), in); + } + + @SuppressWarnings("unchecked") + private static T fieldForClass(Class returnType, Class sourceType, Object in) { + for (Field i : sourceType.getDeclaredFields()) { + if (i.getType().equals(returnType)) { + i.setAccessible(true); + try { + Iris.debug("[NMS] Found " + returnType.getSimpleName() + " in " + sourceType.getSimpleName() + "." + i.getName()); + return (T) i.get(in); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + return null; + } + + private static Class getClassType(Class type, int ordinal) { + return type.getDeclaredClasses()[ordinal]; + } + + @Override + public boolean hasTile(Material material) { + return !CraftBlockState.class.equals(CraftBlockStates.getBlockStateType(material)); + } + + @Override + public boolean hasTile(Location l) { + return ((CraftWorld) l.getWorld()).getHandle().getBlockEntity(new BlockPos(l.getBlockX(), l.getBlockY(), l.getBlockZ()), false) != null; + } + + @Override + @SuppressWarnings("unchecked") + public KMap serializeTile(Location location) { + BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), false); + + if (e == null) { + return null; + } + + net.minecraft.nbt.CompoundTag tag = e.saveWithoutMetadata(registry()); + return (KMap) convertFromTag(tag, 0, 64); + } + + @Contract(value = "null, _, _ -> null", pure = true) + private Object convertFromTag(Tag tag, int depth, int maxDepth) { + if (tag == null || depth > maxDepth) return null; + return switch (tag) { + case CollectionTag collection -> { + KList list = new KList<>(); + + for (Object i : collection) { + if (i instanceof Tag t) + list.add(convertFromTag(t, depth + 1, maxDepth)); + else list.add(i); + } + yield list; + } + case net.minecraft.nbt.CompoundTag compound -> { + KMap map = new KMap<>(); + + for (String key : compound.keySet()) { + var child = compound.get(key); + if (child == null) continue; + var value = convertFromTag(child, depth + 1, maxDepth); + if (value == null) continue; + map.put(key, value); + } + yield map; + } + case NumericTag numeric -> numeric.box(); + default -> tag.asString().orElse(null); + }; + } + + @Override + public void deserializeTile(KMap map, Location pos) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64); + var level = ((CraftWorld) pos.getWorld()).getHandle(); + var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + J.s(() -> merge(level, blockPos, tag)); + } + + private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) { + var blockEntity = level.getBlockEntity(blockPos); + if (blockEntity == null) { + Iris.warn("[NMS] BlockEntity not found at " + blockPos); + var state = level.getBlockState(blockPos); + if (!state.hasBlockEntity()) + return; + + blockEntity = ((EntityBlock) state.getBlock()) + .newBlockEntity(blockPos, state); + } + var accessor = new BlockDataAccessor(blockEntity, blockPos); + accessor.setData(accessor.getData().merge(tag)); + } + + private Tag convertToTag(Object object, int depth, int maxDepth) { + if (object == null || depth > maxDepth) return EndTag.INSTANCE; + return switch (object) { + case Map map -> { + var tag = new net.minecraft.nbt.CompoundTag(); + for (var i : map.entrySet()) { + tag.put(i.getKey().toString(), convertToTag(i.getValue(), depth + 1, maxDepth)); + } + yield tag; + } + case List list -> { + var tag = new ListTag(); + for (var i : list) { + tag.add(convertToTag(i, depth + 1, maxDepth)); + } + yield tag; + } + case Byte number -> ByteTag.valueOf(number); + case Short number -> ShortTag.valueOf(number); + case Integer number -> IntTag.valueOf(number); + case Long number -> LongTag.valueOf(number); + case Float number -> FloatTag.valueOf(number); + case Double number -> DoubleTag.valueOf(number); + case String string -> StringTag.valueOf(string); + default -> EndTag.INSTANCE; + }; + } + + @Override + public CompoundTag serializeEntity(Entity location) { + return null;// TODO: + } + + @Override + public Entity deserializeEntity(CompoundTag s, Location newPosition) { + return null;// TODO: + } + + @Override + public boolean supportsCustomHeight() { + return true; + } + + private RegistryAccess registry() { + return registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())); + } + + private Registry getCustomBiomeRegistry() { + return registry().lookup(Registries.BIOME).orElse(null); + } + + private Registry getBlockRegistry() { + return registry().lookup(Registries.BLOCK).orElse(null); + } + + @Override + public Object getBiomeBaseFromId(int id) { + return getCustomBiomeRegistry().get(id); + } + + @Override + public int getMinHeight(World world) { + return world.getMinHeight(); + } + + @Override + public boolean supportsCustomBiomes() { + return true; + } + + @Override + public int getTrueBiomeBaseId(Object biomeBase) { + return getCustomBiomeRegistry().getId(((Holder) biomeBase).value()); + } + + @Override + public Object getTrueBiomeBase(Location location) { + return ((CraftWorld) location.getWorld()).getHandle().getBiome(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ())); + } + + @Override + public String getTrueBiomeBaseKey(Location location) { + return getKeyForBiomeBase(getTrueBiomeBase(location)); + } + + @Override + public Object getCustomBiomeBaseFor(String mckey) { + return getCustomBiomeRegistry().getValue(ResourceLocation.parse(mckey)); + } + + @Override + public Object getCustomBiomeBaseHolderFor(String mckey) { + return getCustomBiomeRegistry().get(getTrueBiomeBaseId(getCustomBiomeRegistry().get(ResourceLocation.parse(mckey)))).orElse(null); + } + + public int getBiomeBaseIdForKey(String key) { + return getCustomBiomeRegistry().getId(getCustomBiomeRegistry().get(ResourceLocation.parse(key)).map(Holder::value).orElse(null)); + } + + @Override + public String getKeyForBiomeBase(Object biomeBase) { + return getCustomBiomeRegistry().getKey((net.minecraft.world.level.biome.Biome) biomeBase).getPath(); // something, not something:something + } + + @Override + public Object getBiomeBase(World world, Biome biome) { + return biomeToBiomeBase(((CraftWorld) world).getHandle() + .registryAccess().lookup(Registries.BIOME).orElse(null), biome); + } + + @Override + public Object getBiomeBase(Object registry, Biome biome) { + Object v = baseBiomeCache.get(biome); + + if (v != null) { + return v; + } + //noinspection unchecked + v = biomeToBiomeBase((Registry) registry, biome); + if (v == null) { + // Ok so there is this new biome name called "CUSTOM" in Paper's new releases. + // But, this does NOT exist within CraftBukkit which makes it return an error. + // So, we will just return the ID that the plains biome returns instead. + //noinspection unchecked + return biomeToBiomeBase((Registry) registry, Biome.PLAINS); + } + baseBiomeCache.put(biome, v); + return v; + } + + @Override + public KList getBiomes() { + return new KList<>(Biome.values()).qadd(Biome.CHERRY_GROVE).qdel(Biome.CUSTOM); + } + + @Override + public boolean isBukkit() { + return true; + } + + @Override + public int getBiomeId(Biome biome) { + for (World i : Bukkit.getWorlds()) { + if (i.getEnvironment().equals(World.Environment.NORMAL)) { + Registry registry = ((CraftWorld) i).getHandle().registryAccess().lookup(Registries.BIOME).orElse(null); + return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome)); + } + } + + return biome.ordinal(); + } + + private MCAIdMap getBiomeMapping() { + return biomeMapCache.aquire(() -> new MCAIdMap<>() { + @NotNull + @Override + public Iterator iterator() { + return getCustomBiomeRegistry().iterator(); + } + + @Override + public int getId(net.minecraft.world.level.biome.Biome paramT) { + return getCustomBiomeRegistry().getId(paramT); + } + + @Override + public net.minecraft.world.level.biome.Biome byId(int paramInt) { + return (net.minecraft.world.level.biome.Biome) getBiomeBaseFromId(paramInt); + } + }); + } + + @NotNull + private MCABiomeContainer getBiomeContainerInterface(MCAIdMap biomeMapping, MCAChunkBiomeContainer base) { + return new MCABiomeContainer() { + @Override + public int[] getData() { + return base.writeBiomes(); + } + + @Override + public void setBiome(int x, int y, int z, int id) { + base.setBiome(x, y, z, biomeMapping.byId(id)); + } + + @Override + public int getBiome(int x, int y, int z) { + return biomeMapping.getId(base.getBiome(x, y, z)); + } + }; + } + + @Override + public MCABiomeContainer newBiomeContainer(int min, int max) { + MCAChunkBiomeContainer base = new MCAChunkBiomeContainer<>(getBiomeMapping(), min, max); + return getBiomeContainerInterface(getBiomeMapping(), base); + } + + @Override + public MCABiomeContainer newBiomeContainer(int min, int max, int[] data) { + MCAChunkBiomeContainer base = new MCAChunkBiomeContainer<>(getBiomeMapping(), min, max, data); + return getBiomeContainerInterface(getBiomeMapping(), base); + } + + @Override + public int countCustomBiomes() { + AtomicInteger a = new AtomicInteger(0); + + getCustomBiomeRegistry().keySet().forEach((i) -> { + if (i.getNamespace().equals("minecraft")) { + return; + } + + a.incrementAndGet(); + Iris.debug("Custom Biome: " + i); + }); + + return a.get(); + } + + public boolean supportsDataPacks() { + return true; + } + + public void setBiomes(int cx, int cz, World world, Hunk biomes) { + LevelChunk c = ((CraftWorld) world).getHandle().getChunk(cx, cz); + biomes.iterateSync((x, y, z, b) -> c.setBiome(x, y, z, (Holder) b)); + c.markUnsaved(); + } + + @Override + public void forceBiomeInto(int x, int y, int z, Object somethingVeryDirty, ChunkGenerator.BiomeGrid chunk) { + try { + ChunkAccess s = (ChunkAccess) getFieldForBiomeStorage(chunk).get(chunk); + Holder biome = (Holder) somethingVeryDirty; + s.setBiome(x, y, z, biome); + } catch (IllegalAccessException e) { + Iris.reportError(e); + e.printStackTrace(); + } + } + + private Field getFieldForBiomeStorage(Object storage) { + Field f = biomeStorageCache; + + if (f != null) { + return f; + } + try { + f = storage.getClass().getDeclaredField("biome"); + f.setAccessible(true); + return f; + } catch (Throwable e) { + Iris.reportError(e); + e.printStackTrace(); + Iris.error(storage.getClass().getCanonicalName()); + } + + biomeStorageCache = f; + return null; + } + + @Override + public MCAPaletteAccess createPalette() { + MCAIdMapper registry = registryCache.aquireNasty(() -> { + Field cf = IdMapper.class.getDeclaredField("tToId"); + Field df = IdMapper.class.getDeclaredField("idToT"); + Field bf = IdMapper.class.getDeclaredField("nextId"); + cf.setAccessible(true); + df.setAccessible(true); + bf.setAccessible(true); + IdMapper blockData = Block.BLOCK_STATE_REGISTRY; + int b = bf.getInt(blockData); + Object2IntMap c = (Object2IntMap) cf.get(blockData); + List d = (List) df.get(blockData); + return new MCAIdMapper(c, d, b); + }); + MCAPalette global = globalCache.aquireNasty(() -> new MCAGlobalPalette<>(registry, ((CraftBlockData) AIR).getState())); + MCAPalettedContainer container = new MCAPalettedContainer<>(global, registry, + i -> ((CraftBlockData) NBTWorld.getBlockData(i)).getState(), + i -> NBTWorld.getCompound(CraftBlockData.fromData(i)), + ((CraftBlockData) AIR).getState()); + return new MCAWrappedPalettedContainer<>(container, + i -> NBTWorld.getCompound(CraftBlockData.fromData(i)), + i -> ((CraftBlockData) NBTWorld.getBlockData(i)).getState()); + } + + @Override + public void injectBiomesFromMantle(Chunk e, Mantle mantle) { + ChunkAccess chunk = ((CraftChunk) e).getHandle(ChunkStatus.FULL); + AtomicInteger c = new AtomicInteger(); + AtomicInteger r = new AtomicInteger(); + mantle.iterateChunk(e.getX(), e.getZ(), MatterBiomeInject.class, (x, y, z, b) -> { + if (b != null) { + if (b.isCustom()) { + chunk.setBiome(x, y, z, getCustomBiomeRegistry().get(b.getBiomeId()).get()); + c.getAndIncrement(); + } else { + chunk.setBiome(x, y, z, (Holder) getBiomeBase(e.getWorld(), b.getBiome())); + r.getAndIncrement(); + } + } + }); + } + + public ItemStack applyCustomNbt(ItemStack itemStack, KMap customNbt) throws IllegalArgumentException { + if (customNbt != null && !customNbt.isEmpty()) { + net.minecraft.world.item.ItemStack s = CraftItemStack.asNMSCopy(itemStack); + + try { + net.minecraft.nbt.CompoundTag tag = TagParser.parseCompoundFully((new JSONObject(customNbt)).toString()); + tag.merge(s.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).getUnsafe()); + s.set(DataComponents.CUSTOM_DATA, CustomData.of(tag)); + } catch (CommandSyntaxException var5) { + throw new IllegalArgumentException(var5); + } + + return CraftItemStack.asBukkitCopy(s); + } else { + return itemStack; + } + } + + public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException { + var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; + var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class); + worldGenContextField.setAccessible(true); + var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap); + var dimensionType = chunkMap.level.dimensionTypeRegistration().unwrapKey().orElse(null); + if (dimensionType != null && !dimensionType.location().getNamespace().equals("iris")) + Iris.error("Loaded world %s with invalid dimension type! (%s)", world.getName(), dimensionType.location().toString()); + + var newContext = new WorldGenContext( + worldGenContext.level(), new IrisChunkGenerator(worldGenContext.generator(), seed, engine, world), + worldGenContext.structureManager(), worldGenContext.lightEngine(), worldGenContext.mainThreadExecutor(), worldGenContext.unsavedListener()); + + worldGenContextField.set(chunkMap, newContext); + } + + public Vector3d getBoundingbox(org.bukkit.entity.EntityType entity) { + Field[] fields = EntityType.class.getDeclaredFields(); + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers()) && field.getType().equals(EntityType.class)) { + try { + EntityType entityType = (EntityType) field.get(null); + if (entityType.getDescriptionId().equals("entity.minecraft." + entity.name().toLowerCase())) { + Vector v1 = new Vector<>(); + v1.add(entityType.getHeight()); + entityType.getDimensions(); + Vector3d box = new Vector3d( entityType.getWidth(), entityType.getHeight(), entityType.getWidth()); + //System.out.println("Entity Type: " + entityType.getDescriptionId() + ", " + "Height: " + height + ", Width: " + width); + return box; + } + } catch (IllegalAccessException e) { + Iris.error("Unable to get entity dimensions!"); + e.printStackTrace(); + } + } + } + return null; + } + + + @Override + public Entity spawnEntity(Location location, org.bukkit.entity.EntityType type, CreatureSpawnEvent.SpawnReason reason) { + return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason); + } + + @Override + public Color getBiomeColor(Location location, BiomeColor type) { + LevelReader reader = ((CraftWorld) location.getWorld()).getHandle(); + var holder = reader.getBiome(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ())); + var biome = holder.value(); + if (biome == null) throw new IllegalArgumentException("Invalid biome: " + holder.unwrapKey().orElse(null)); + + int rgba = switch (type) { + case FOG -> biome.getFogColor(); + case WATER -> biome.getWaterColor(); + case WATER_FOG -> biome.getWaterFogColor(); + case SKY -> biome.getSkyColor(); + case FOLIAGE -> biome.getFoliageColor(); + case GRASS -> biome.getGrassColor(location.getBlockX(), location.getBlockZ()); + }; + if (rgba == 0) { + if (BiomeColor.FOLIAGE == type && biome.getSpecialEffects().getFoliageColorOverride().isEmpty()) + return null; + if (BiomeColor.GRASS == type && biome.getSpecialEffects().getGrassColorOverride().isEmpty()) + return null; + } + return new Color(rgba, true); + } + + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { + try { + for (Field f : clazz.getDeclaredFields()) { + if (f.getType().equals(fieldType)) + return f; + } + throw new NoSuchFieldException(fieldType.getName()); + } catch (NoSuchFieldException var4) { + Class superClass = clazz.getSuperclass(); + if (superClass == null) { + throw var4; + } else { + return getField(superClass, fieldType); + } + } + } + + public static Holder biomeToBiomeBase(Registry registry, Biome biome) { + return registry.getOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey()))); + } + + @Override + public DataVersion getDataVersion() { + return DataVersion.V1213; + } + + @Override + public int getSpawnChunkCount(World world) { + var radius = Optional.ofNullable(world.getGameRuleValue(GameRule.SPAWN_CHUNK_RADIUS)) + .orElseGet(() -> world.getGameRuleDefault(GameRule.SPAWN_CHUNK_RADIUS)); + if (radius == null) throw new IllegalStateException("GameRule.SPAWN_CHUNK_RADIUS is null!"); + return (int) Math.pow(2 * radius + 1, 2); + } + + @Override + public KList getStructureKeys() { + KList keys = new KList<>(); + + var registry = registry().lookup(Registries.STRUCTURE).orElse(null); + if (registry == null) return keys; + registry.keySet().stream().map(ResourceLocation::toString).forEach(keys::add); + registry.getTags() + .map(HolderSet.Named::key) + .map(TagKey::location) + .map(ResourceLocation::toString) + .map(s -> "#" + s) + .forEach(keys::add); + + return keys; + } + + @Override + 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 + 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()); + } + + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; + } + + 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)); + } + + 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(); + return new FlatLevelSource(settings); + } + + 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 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; + + 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..18cbee3fe 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,21 +15,15 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -pluginManagement { - repositories { - mavenLocal() - mavenCentral() - gradlePluginPortal() - } -} plugins { id ("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "Iris" -include(":core") +include(":core", ":core:agent") include( + ":nms:v1_21_R5", ":nms:v1_21_R4", ":nms:v1_21_R3", ":nms:v1_21_R2",