diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 46037f9ca..000000000 --- a/build.gradle +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -buildscript() { - repositories { - maven { url 'https://jitpack.io'} - } - dependencies { - classpath 'com.github.VolmitSoftware:NMSTools:1.0.1' - } -} - -plugins { - id 'java' - id 'java-library' - id "io.github.goooler.shadow" version "8.1.7" - id "de.undercouch.download" version "5.0.1" -} - - -version '3.6.5-1.20.1-1.21.4' - -// ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED -// ======================== WINDOWS ============================= -registerCustomOutputTask('Cyberpwn', 'C://Users/cyberpwn/Documents/development/server/plugins') -registerCustomOutputTask('Psycho', 'C://Dan/MinecraftDevelopment/Server/plugins') -registerCustomOutputTask('ArcaneArts', 'C://Users/arcane/Documents/development/server/plugins') -registerCustomOutputTask('Coco', 'D://mcsm/plugins') -registerCustomOutputTask('Strange', 'D://Servers/1.17 Test Server/plugins') -registerCustomOutputTask('Vatuu', 'D://Minecraft/Servers/1.19.4/plugins') -registerCustomOutputTask('CrazyDev22', 'C://Users/Julian/Desktop/server/plugins') -registerCustomOutputTask('PixelFury', 'C://Users/repix/workplace/Iris/1.21.3 - Development-Public-v3/plugins') -registerCustomOutputTask('PixelFuryDev', 'C://Users/repix/workplace/Iris/1.21 - Development-v3/plugins') -// ========================== UNIX ============================== -registerCustomOutputTaskUnix('CyberpwnLT', '/Users/danielmills/development/server/plugins') -registerCustomOutputTaskUnix('PsychoLT', '/Users/brianfopiano/Developer/RemoteGit/Server/plugins') -registerCustomOutputTaskUnix('PixelMac', '/Users/test/Desktop/mcserver/plugins') -registerCustomOutputTaskUnix('CrazyDev22LT', '/home/julian/Desktop/server/plugins') -// ============================================================== - -def NMS_BINDINGS = Map.of( - "v1_21_R3", "1.21.4-R0.1-SNAPSHOT", - "v1_21_R2", "1.21.3-R0.1-SNAPSHOT", - "v1_21_R1", "1.21.1-R0.1-SNAPSHOT", - "v1_20_R4", "1.20.6-R0.1-SNAPSHOT", - "v1_20_R3", "1.20.4-R0.1-SNAPSHOT", - "v1_20_R2", "1.20.2-R0.1-SNAPSHOT", - "v1_20_R1", "1.20.1-R0.1-SNAPSHOT", -) -def JVM_VERSION = Map.of() -NMS_BINDINGS.each { nms -> - project(":nms:${nms.key}") { - apply plugin: 'java' - apply plugin: 'com.volmit.nmstools' - - nmsTools { - it.jvm = JVM_VERSION.getOrDefault(nms.key, 21) - it.version = nms.value - } - - dependencies { - implementation project(":core") - } - } -} - -shadowJar { - NMS_BINDINGS.each { - dependsOn(":nms:${it.key}:remap") - from("${project(":nms:${it.key}").layout.buildDirectory.asFile.get()}/libs/${it.key}-mapped.jar") - } - - //minimize() - append("plugin.yml") - relocate 'com.dfsek.paralithic', 'com.volmit.iris.util.paralithic' - relocate 'io.papermc.lib', 'com.volmit.iris.util.paper' - relocate 'net.kyori', 'com.volmit.iris.util.kyori' - relocate 'org.bstats', 'com.volmit.util.metrics' - archiveFileName.set("Iris-${project.version}.jar") -} - -dependencies { - implementation project(':core') -} - -configurations.configureEach { - resolutionStrategy.cacheChangingModulesFor 60, 'minutes' - resolutionStrategy.cacheDynamicVersionsFor 60, 'minutes' -} - -allprojects { - apply plugin: 'java' - - repositories { - mavenCentral() - maven { url "https://repo.papermc.io/repository/maven-public/" } - maven { url "https://repo.codemc.org/repository/maven-public" } - maven { url "https://mvn.lumine.io/repository/maven-public/" } - maven { url "https://jitpack.io" } - - maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots" } - maven { url "https://mvn.lumine.io/repository/maven/" } - maven { url "https://repo.triumphteam.dev/snapshots" } - maven { url "https://repo.mineinabyss.com/releases" } - maven { url 'https://hub.jeff-media.com/nexus/repository/jeff-media-public/' } - maven { url "https://repo.nexomc.com/snapshots/" } - maven { url "https://libraries.minecraft.net" } - } - - dependencies { - // Provided or Classpath - compileOnly 'org.projectlombok:lombok:1.18.36' - annotationProcessor 'org.projectlombok:lombok:1.18.36' - - // Shaded - implementation 'com.dfsek:paralithic:0.8.1' - implementation 'io.papermc:paperlib:1.0.5' - implementation "net.kyori:adventure-text-minimessage:4.17.0" - implementation 'net.kyori:adventure-platform-bukkit:4.3.4' - implementation 'net.kyori:adventure-api:4.17.0' - implementation 'org.bstats:bstats-bukkit:3.1.0' - //implementation 'org.bytedeco:javacpp:1.5.10' - //implementation 'org.bytedeco:cuda-platform:12.3-8.9-1.5.10' - compileOnly 'io.lumine:Mythic-Dist:5.2.1' - compileOnly 'io.lumine:MythicCrucible-Dist:2.0.0' - - // Dynamically Loaded - compileOnly 'io.timeandspace:smoothie-map:2.0.2' - compileOnly 'it.unimi.dsi:fastutil:8.5.8' - compileOnly 'com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2' - compileOnly 'org.zeroturnaround:zt-zip:1.14' - compileOnly 'com.google.code.gson:gson:2.10.1' - compileOnly 'org.ow2.asm:asm:9.2' - compileOnly 'com.google.guava:guava:33.0.0-jre' - compileOnly 'bsf:bsf:2.4.0' - compileOnly 'rhino:js:1.7R2' - compileOnly 'com.github.ben-manes.caffeine:caffeine:3.0.6' - compileOnly 'org.apache.commons:commons-lang3:3.12.0' - compileOnly 'com.github.oshi:oshi-core:6.6.5' - compileOnly 'org.apache.commons:commons-math3:3.6.1' - } - - /** - * We need parameter meta for the decree command system - */ - compileJava { - options.compilerArgs << '-parameters' - options.encoding = "UTF-8" - } - - javadoc { - options.encoding = "UTF-8" - options.addStringOption('Xdoclint:none', '-quiet') - } - - task sourcesJar(type: Jar, dependsOn: classes) { - archiveClassifier.set('sources') - from sourceSets.main.allSource - } - - task javadocJar(type: Jar, dependsOn: javadoc) { - archiveClassifier.set('javadoc') - from javadoc.destinationDir - } -} - -if (JavaVersion.current().toString() != "21") { - System.err.println() - System.err.println("=========================================================================================================") - System.err.println("You must run gradle on Java 21. You are using " + JavaVersion.current()) - System.err.println() - System.err.println("=== For IDEs ===") - System.err.println("1. Configure the project for Java 21") - System.err.println("2. Configure the bundled gradle to use Java 21 in settings") - System.err.println() - System.err.println("=== For Command Line (gradlew) ===") - System.err.println("1. Install JDK 21 from https://www.oracle.com/java/technologies/javase/jdk21-archive-downloads.html") - System.err.println("2. Set JAVA_HOME environment variable to the new jdk installation folder such as C:\\Program Files\\Java\\jdk-21.0.4") - System.err.println("3. Open a new command prompt window to get the new environment variables if need be.") - System.err.println("=========================================================================================================") - System.err.println() - System.exit(69); -} - -task iris(type: Copy) { - group "iris" - from new File(layout.buildDirectory.asFile.get(), "libs/Iris-${version}.jar") - into layout.buildDirectory.asFile.get() - dependsOn(build) -} - -// with classifier: 'javadoc' and 'sources' -task irisDev(type: Copy) { - group "iris" - from("core/build/libs/core-javadoc.jar", "core/build/libs/core-sources.jar") - rename { String fileName -> - fileName.replace("core", "Iris-${version}") - } - into layout.buildDirectory.asFile.get() - dependsOn(iris) - dependsOn("core:sourcesJar") - dependsOn("core:javadocJar") -} - - -def registerCustomOutputTask(name, path) { - if (!System.properties['os.name'].toLowerCase().contains('windows')) { - return; - } - - tasks.register('build' + name, Copy) { - group('development') - outputs.upToDateWhen { false } - dependsOn(iris) - from(new File(buildDir, "Iris-" + version + ".jar")) - into(file(path)) - rename { String fileName -> - fileName.replace("Iris-" + version + ".jar", "Iris.jar") - } - } -} - -def registerCustomOutputTaskUnix(name, path) { - if (System.properties['os.name'].toLowerCase().contains('windows')) { - return; - } - - tasks.register('build' + name, Copy) { - group('development') - outputs.upToDateWhen { false } - dependsOn(iris) - from(new File(buildDir, "Iris-" + version + ".jar")) - into(file(path)) - rename { String fileName -> - fileName.replace("Iris-" + version + ".jar", "Iris.jar") - } - } -} - -tasks.build.dependsOn(shadowJar) diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 000000000..462d697a2 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,280 @@ +import com.volmit.nmstools.NMSToolsExtension +import com.volmit.nmstools.NMSToolsPlugin +import de.undercouch.gradle.tasks.download.Download +import xyz.jpenilla.runpaper.task.RunServer +import kotlin.system.exitProcess + +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +buildscript { + repositories.maven("https://jitpack.io") + dependencies.classpath("com.github.VolmitSoftware:NMSTools:c5cbc46ce6") +} + +plugins { + java + `java-library` + alias(libs.plugins.shadow) + alias(libs.plugins.sentry) + alias(libs.plugins.download) + alias(libs.plugins.runPaper) +} + +group = "com.volmit" +version = "3.7.0-1.20.1-1.21.7" + +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") +registerCustomOutputTask("Psycho", "C://Dan/MinecraftDevelopment/Server/plugins") +registerCustomOutputTask("ArcaneArts", "C://Users/arcane/Documents/development/server/plugins") +registerCustomOutputTask("Coco", "D://mcsm/plugins") +registerCustomOutputTask("Strange", "D://Servers/1.17 Test Server/plugins") +registerCustomOutputTask("Vatuu", "D://Minecraft/Servers/1.19.4/plugins") +registerCustomOutputTask("CrazyDev22", "C://Users/Julian/Desktop/server/plugins") +registerCustomOutputTask("PixelFury", "C://Users/repix/workplace/Iris/1.21.3 - Development-Public-v3/plugins") +registerCustomOutputTask("PixelFuryDev", "C://Users/repix/workplace/Iris/1.21 - Development-v3/plugins") +// ========================== UNIX ============================== +registerCustomOutputTaskUnix("CyberpwnLT", "/Users/danielmills/development/server/plugins") +registerCustomOutputTaskUnix("PsychoLT", "/Users/brianfopiano/Developer/RemoteGit/Server/plugins") +registerCustomOutputTaskUnix("PixelMac", "/Users/test/Desktop/mcserver/plugins") +registerCustomOutputTaskUnix("CrazyDev22LT", "/home/julian/Desktop/server/plugins") +// ============================================================== + +val serverMinHeap = "2G" +val serverMaxHeap = "8G" +//Valid values are: none, truecolor, indexed256, indexed16, indexed8 +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", + "v1_21_R1" to "1.21.1-R0.1-SNAPSHOT", + "v1_20_R4" to "1.20.6-R0.1-SNAPSHOT", + "v1_20_R3" to "1.20.4-R0.1-SNAPSHOT", + "v1_20_R2" to "1.20.2-R0.1-SNAPSHOT", + "v1_20_R1" to "1.20.1-R0.1-SNAPSHOT", +) +val jvmVersion = mapOf() +nmsBindings.forEach { key, value -> + project(":nms:$key") { + apply() + apply() + + repositories { + maven("https://libraries.minecraft.net") + } + + extensions.configure(NMSToolsExtension::class) { + jvm = jvmVersion.getOrDefault(key, 21) + version = value + } + + dependencies { + compileOnly(project(":core")) + compileOnly(rootProject.libs.annotations) + compileOnly(rootProject.libs.byteBuddy.core) + } + } + + tasks.register("runServer-$key") { + group = "servers" + minecraftVersion(value.split("-")[0]) + minHeapSize = serverMinHeap + maxHeapSize = serverMaxHeap + pluginJars(tasks.jar.flatMap { it.archiveFile }) + javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(jvmVersion.getOrDefault(key, 21))} + runDirectory.convention(layout.buildDirectory.dir("run/$key")) + systemProperty("disable.watchdog", "") + systemProperty("net.kyori.ansi.colorLevel", color) + systemProperty("com.mojang.eula.agree", true) + systemProperty("iris.suppressReporting", !errorReporting) + jvmArgs("-javaagent:${project(":core:agent").tasks.jar.flatMap { it.archiveFile }.get().asFile.absolutePath}") + } +} + +tasks { + jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + nmsBindings.forEach { key, _ -> + 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") + } + + register("iris") { + group = "iris" + dependsOn("jar") + from(layout.buildDirectory.file("libs/Iris-${project.version}.jar")) + into(layout.buildDirectory) + } + + register("irisDev") { + group = "iris" + from(project(":core").layout.buildDirectory.files("libs/core-javadoc.jar", "libs/core-sources.jar")) + rename { it.replace("core", "Iris-${project.version}") } + into(layout.buildDirectory) + dependsOn(":core:sourcesJar") + dependsOn(":core:javadocJar") + } + + val cli = file("sentry-cli.exe") + register("downloadCli") { + group = "io.sentry" + src("https://release-registry.services.sentry.io/apps/sentry-cli/latest?response=download&arch=x86_64&platform=${System.getProperty("os.name")}&package=sentry-cli") + dest(cli) + + doLast { + cli.setExecutable(true) + } + } + + register("release") { + group = "io.sentry" + dependsOn("downloadCli") + doLast { + val authToken = project.findProperty("sentry.auth.token") ?: System.getenv("SENTRY_AUTH_TOKEN") + val org = "volmit-software" + val projectName = "iris" + exec(cli, "releases", "new", "--auth-token", authToken, "-o", org, "-p", projectName, version) + exec(cli, "releases", "set-commits", "--auth-token", authToken, "-o", org, "-p", projectName, version, "--auto", "--ignore-missing") + exec(cli, "releases", "finalize", "--auth-token", authToken, "-o", org, "-p", projectName, version) + cli.delete() + } + } +} + +fun exec(vararg command: Any) { + val p = ProcessBuilder(command.map { it.toString() }) + .start() + p.inputStream.reader().useLines { it.forEach(::println) } + p.errorStream.reader().useLines { it.forEach(::println) } + p.waitFor() +} + +configurations.configureEach { + resolutionStrategy.cacheChangingModulesFor(60, "minutes") + resolutionStrategy.cacheDynamicVersionsFor(60, "minutes") +} + +allprojects { + apply() + + repositories { + mavenCentral() + maven("https://repo.papermc.io/repository/maven-public/") + maven("https://repo.codemc.org/repository/maven-public/") + + maven("https://jitpack.io") // EcoItems, score + maven("https://repo.nexomc.com/releases/") // nexo + maven("https://maven.devs.beer/") // itemsadder + maven("https://repo.extendedclip.com/releases/") // placeholderapi + maven("https://mvn.lumine.io/repository/maven-public/") // mythic + maven("https://nexus.phoenixdevt.fr/repository/maven-public/") //MMOItems + maven("https://repo.onarandombox.com/content/groups/public/") //Multiverse Core + } + + dependencies { + // Provided or Classpath + compileOnly(rootProject.libs.lombok) + annotationProcessor(rootProject.libs.lombok) + } + + /** + * We need parameter meta for the decree command system + */ + tasks { + compileJava { + options.compilerArgs.add("-parameters") + options.encoding = "UTF-8" + } + + javadoc { + options.encoding = "UTF-8" + options.quiet() + //options.addStringOption("Xdoclint:none") // TODO: Re-enable this + } + + register("sourcesJar") { + archiveClassifier.set("sources") + from(sourceSets.main.map { it.allSource }) + } + + register("javadocJar") { + archiveClassifier.set("javadoc") + from(javadoc.map { it.destinationDir!! }) + } + } +} + +if (JavaVersion.current().toString() != "21") { + System.err.println() + System.err.println("=========================================================================================================") + System.err.println("You must run gradle on Java 21. You are using " + JavaVersion.current()) + System.err.println() + System.err.println("=== For IDEs ===") + System.err.println("1. Configure the project for Java 21") + System.err.println("2. Configure the bundled gradle to use Java 21 in settings") + System.err.println() + System.err.println("=== For Command Line (gradlew) ===") + System.err.println("1. Install JDK 21 from https://www.oracle.com/java/technologies/javase/jdk21-archive-downloads.html") + System.err.println("2. Set JAVA_HOME environment variable to the new jdk installation folder such as C:\\Program Files\\Java\\jdk-21.0.4") + System.err.println("3. Open a new command prompt window to get the new environment variables if need be.") + System.err.println("=========================================================================================================") + System.err.println() + exitProcess(69) +} + + +fun registerCustomOutputTask(name: String, path: String) { + if (!System.getProperty("os.name").lowercase().contains("windows")) { + return + } + + tasks.register("build$name") { + group = "development" + outputs.upToDateWhen { false } + dependsOn("iris") + from(layout.buildDirectory.file("Iris-${project.version}.jar")) + into(file(path)) + rename { "Iris.jar" } + } +} + +fun registerCustomOutputTaskUnix(name: String, path: String) { + if (System.getProperty("os.name").lowercase().contains("windows")) { + return + } + + tasks.register("build$name") { + group = "development" + outputs.upToDateWhen { false } + dependsOn("iris") + from(layout.buildDirectory.file("Iris-${project.version}.jar")) + 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 b/core/build.gradle deleted file mode 100644 index 2c1e96fc2..000000000 --- a/core/build.gradle +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -plugins { - id 'java' - id 'java-library' - id "io.freefair.lombok" version "8.6" -} - -def apiVersion = '1.19' -def main = 'com.volmit.iris.Iris' - -/** - * We need parameter meta for the decree command system - */ -compileJava { - options.compilerArgs << '-parameters' - options.encoding = "UTF-8" -} - -repositories { - maven { url 'https://nexus.phoenixdevt.fr/repository/maven-public/'} - maven { url 'https://repo.auxilor.io/repository/maven-public/' } -} - -/** - * Dependencies. - * - * Provided or classpath dependencies are not shaded and are available on the runtime classpath - * - * Shaded dependencies are not available at runtime, nor are they available on mvn central so they - * need to be shaded into the jar (increasing binary size) - * - * Dynamically loaded dependencies are defined in the plugin.yml (updating these must be updated in the - * plugin.yml also, otherwise they wont be available). These do not increase binary size). Only declare - * these dependencies if they are available on mvn central. - */ -dependencies { - // Provided or Classpath - 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.ticxo.playeranimator:PlayerAnimator:R1.2.7' - compileOnly 'com.nexomc:nexo:1.0.0-dev.38' - compileOnly 'com.github.LoneDev6:api-itemsadder:3.4.1-r4' - compileOnly 'com.github.PlaceholderAPI:placeholderapi:2.11.3' - 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' - //implementation files('libs/CustomItems.jar') -} - -java { - disableAutoTargetJvm() -} - -/** - * Gradle is weird sometimes, we need to delete the plugin yml from the build folder to actually filter properly. - */ -file(jar.archiveFile.get().getAsFile().getParentFile().getParentFile().getParentFile().getAbsolutePath() + '/build/resources/main/plugin.yml').delete() - -/** - * Expand properties into plugin yml - */ -processResources { - filesMatching('**/plugin.yml') { - expand( - 'name': rootProject.name.toString(), - 'version': rootProject.version.toString(), - 'main': main.toString(), - 'apiversion': apiVersion.toString() - ) - } -} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 000000000..b40ab5d16 --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,159 @@ +import io.github.slimjar.func.slimjar +import io.github.slimjar.resolver.data.Mirror +import java.net.URI + +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +plugins { + java + `java-library` + alias(libs.plugins.shadow) + alias(libs.plugins.sentry) + alias(libs.plugins.slimjar) +} + +val apiVersion = "1.19" +val main = "com.volmit.iris.Iris" +val lib = "com.volmit.iris.util" + +/** + * Dependencies. + * + * Provided or classpath dependencies are not shaded and are available on the runtime classpath + * + * Shaded dependencies are not available at runtime, nor are they available on mvn central so they + * need to be shaded into the jar (increasing binary size) + * + * Dynamically loaded dependencies are defined in the plugin.yml (updating these must be updated in the + * plugin.yml also, otherwise they wont be available). These do not increase binary size). Only declare + * these dependencies if they are available on mvn central. + */ +dependencies { + // Provided or Classpath + compileOnly(libs.spigot) + compileOnly(libs.log4j.api) + compileOnly(libs.log4j.core) + + // Third Party Integrations + compileOnly(libs.nexo) + compileOnly(libs.itemsadder) + compileOnly(libs.placeholderApi) + compileOnly(libs.score) + compileOnly(libs.mmoitems) + compileOnly(libs.ecoitems) + compileOnly(libs.mythic) + compileOnly(libs.mythicChrucible) + compileOnly(libs.kgenerators) { + isTransitive = false + } + compileOnly(libs.multiverseCore) + + // Shaded + implementation(slimjar()) + + // Dynamically Loaded + slim(libs.paralithic) + slim(libs.paperlib) + slim(libs.adventure.api) + slim(libs.adventure.minimessage) + slim(libs.adventure.platform) + slim(libs.bstats) + slim(libs.sentry) + + slim(libs.commons.io) + slim(libs.commons.lang) + slim(libs.commons.lang3) + slim(libs.commons.math3) + slim(libs.oshi) + slim(libs.lz4) + slim(libs.fastutil) + slim(libs.lru) + slim(libs.zip) + slim(libs.gson) + slim(libs.asm) + slim(libs.bsf) + slim(libs.rhino) + slim(libs.caffeine) + slim(libs.byteBuddy.core) + slim(libs.byteBuddy.agent) +} + +java { + disableAutoTargetJvm() +} + +sentry { + autoInstallation.enabled = false + includeSourceContext = true + + org = "volmit-software" + projectName = "iris" + authToken = findProperty("sentry.auth.token") as String? ?: System.getenv("SENTRY_AUTH_TOKEN") +} + +slimJar { + mirrors = listOf(Mirror( + URI.create("https://maven-central.storage-download.googleapis.com/maven2").toURL(), + URI.create("https://repo.maven.apache.org/maven2/").toURL() + )) + + relocate("com.dfsek.paralithic", "$lib.paralithic") + relocate("io.papermc.lib", "$lib.paper") + relocate("net.kyori", "$lib.kyori") + relocate("org.bstats", "$lib.metrics") + relocate("io.sentry", "$lib.sentry") +} + +tasks { + /** + * We need parameter meta for the decree command system + */ + compileJava { + options.compilerArgs.add("-parameters") + options.encoding = "UTF-8" + } + + /** + * Expand properties into plugin yml + */ + processResources { + inputs.properties( + "name" to rootProject.name, + "version" to rootProject.version, + "apiVersion" to apiVersion, + "main" to main, + ) + filesMatching("**/plugin.yml") { + expand(inputs.properties) + } + } + + shadowJar { + mergeServiceFiles() + //minimize() + relocate("io.github.slimjar", "$lib.slimjar") + } +} + +/** + * Gradle is weird sometimes, we need to delete the plugin yml from the build folder to actually filter properly. + */ +afterEvaluate { + layout.buildDirectory.file("resources/main/plugin.yml").get().asFile.delete() +} \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index 07e01c6e4..f299e4409 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -1,930 +1,877 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonParser; -import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.ServerConfigurator; -import com.volmit.iris.core.link.IrisPapiExpansion; -import com.volmit.iris.core.link.MultiverseCoreLink; -import com.volmit.iris.core.link.MythicMobsLink; -import com.volmit.iris.core.loader.IrisData; -import com.volmit.iris.core.nms.INMS; -import com.volmit.iris.core.nms.v1X.NMSBinding1X; -import com.volmit.iris.core.pregenerator.LazyPregenerator; -import com.volmit.iris.core.service.StudioSVC; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.core.tools.IrisWorldCreator; -import com.volmit.iris.engine.EnginePanic; -import com.volmit.iris.engine.object.IrisCompat; -import com.volmit.iris.engine.object.IrisContextInjector; -import com.volmit.iris.engine.object.IrisDimension; -import com.volmit.iris.engine.object.IrisWorld; -import com.volmit.iris.engine.platform.BukkitChunkGenerator; -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; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.exceptions.IrisException; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.function.NastyRunnable; -import com.volmit.iris.util.io.FileWatcher; -import com.volmit.iris.util.io.IO; -import com.volmit.iris.util.io.InstanceState; -import com.volmit.iris.util.io.JarScanner; -import com.volmit.iris.util.math.M; -import com.volmit.iris.util.math.RNG; -import com.volmit.iris.util.misc.getHardware; -import com.volmit.iris.util.parallel.MultiBurst; -import com.volmit.iris.util.plugin.IrisService; -import com.volmit.iris.util.plugin.VolmitPlugin; -import com.volmit.iris.util.plugin.VolmitSender; -import com.volmit.iris.util.reflect.ShadeFix; -import com.volmit.iris.util.scheduling.J; -import com.volmit.iris.util.scheduling.Queue; -import com.volmit.iris.util.scheduling.ShurikenQueue; -import io.papermc.lib.PaperLib; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; -import net.kyori.adventure.text.serializer.ComponentSerializer; -import org.bstats.bukkit.Metrics; -import org.bstats.charts.DrilldownPie; -import org.bstats.charts.SimplePie; -import org.bstats.charts.SingleLineChart; -import org.bukkit.*; -import org.bukkit.block.data.BlockData; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.*; -import org.bukkit.generator.BiomeProvider; -import org.bukkit.generator.ChunkGenerator; -import org.bukkit.plugin.IllegalPluginAccessException; -import org.bukkit.plugin.Plugin; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import oshi.SystemInfo; - -import java.io.*; -import java.lang.annotation.Annotation; -import java.math.RoundingMode; -import java.net.URL; -import java.text.NumberFormat; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import static com.volmit.iris.core.safeguard.IrisSafeguard.*; -import static com.volmit.iris.core.safeguard.ServerBootSFG.passedserversoftware; - -@SuppressWarnings("CanBeFinal") -public class Iris extends VolmitPlugin implements Listener { - private static final Queue syncJobs = new ShurikenQueue<>(); - - public static Iris instance; - public static BukkitAudiences audiences; - public static MultiverseCoreLink linkMultiverseCore; - public static MythicMobsLink linkMythicMobs; - public static IrisCompat compat; - public static FileWatcher configWatcher; - private static VolmitSender sender; - - static { - try { - fixShading(); - InstanceState.updateInstanceId(); - } catch (Throwable ignored) { - - } - } - - private final KList postShutdown = new KList<>(); - private KMap, IrisService> services; - - public static VolmitSender getSender() { - if (sender == null) { - sender = new VolmitSender(Bukkit.getConsoleSender()); - sender.setTag(instance.getTag()); - } - return sender; - } - - @SuppressWarnings("unchecked") - public static T service(Class c) { - return (T) instance.services.get(c); - } - - public static void callEvent(Event e) { - if (!e.isAsynchronous()) { - J.s(() -> Bukkit.getPluginManager().callEvent(e)); - } else { - Bukkit.getPluginManager().callEvent(e); - } - } - - public static KList initialize(String s, Class slicedClass) { - JarScanner js = new JarScanner(instance.getJarFile(), s); - KList v = new KList<>(); - J.attempt(js::scan); - for (Class i : js.getClasses()) { - if (slicedClass == null || i.isAnnotationPresent(slicedClass)) { - try { - v.add(i.getDeclaredConstructor().newInstance()); - } catch (Throwable ignored) { - - } - } - } - - return v; - } - - public static KList> getClasses(String s, Class slicedClass) { - JarScanner js = new JarScanner(instance.getJarFile(), s); - KList> v = new KList<>(); - J.attempt(js::scan); - for (Class i : js.getClasses()) { - if (slicedClass == null || i.isAnnotationPresent(slicedClass)) { - try { - v.add(i); - } catch (Throwable ignored) { - - } - } - } - - return v; - } - - public static KList initialize(String s) { - return initialize(s, null); - } - - public static void sq(Runnable r) { - synchronized (syncJobs) { - syncJobs.queue(r); - } - } - - public static File getTemp() { - return instance.getDataFolder("cache", "temp"); - } - - public static void msg(String string) { - try { - getSender().sendMessage(string); - } catch (Throwable e) { - try { - instance.getLogger().info(instance.getTag() + string.replaceAll("(<([^>]+)>)", "")); - } catch (Throwable ignored1) { - - } - } - } - - public static File getCached(String name, String url) { - String h = IO.hash(name + "@" + url); - File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); - - if (!f.exists()) { - try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { - byte[] dataBuffer = new byte[1024]; - int bytesRead; - while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { - fileOutputStream.write(dataBuffer, 0, bytesRead); - Iris.verbose("Aquiring " + name); - } - } catch (IOException e) { - Iris.reportError(e); - } - } - - return f.exists() ? f : null; - } - - public static String getNonCached(String name, String url) { - String h = IO.hash(name + "*" + url); - File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); - - try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { - byte[] dataBuffer = new byte[1024]; - int bytesRead; - while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { - fileOutputStream.write(dataBuffer, 0, bytesRead); - } - } catch (IOException e) { - Iris.reportError(e); - } - - try { - return IO.readAll(f); - } catch (IOException e) { - Iris.reportError(e); - } - - return ""; - } - - public static File getNonCachedFile(String name, String url) { - String h = IO.hash(name + "*" + url); - File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); - Iris.verbose("Download " + name + " -> " + url); - try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { - byte[] dataBuffer = new byte[1024]; - int bytesRead; - while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { - fileOutputStream.write(dataBuffer, 0, bytesRead); - } - - fileOutputStream.flush(); - } catch (IOException e) { - e.printStackTrace(); - Iris.reportError(e); - } - - return f; - } - - public static void warn(String format, Object... objs) { - msg(C.YELLOW + String.format(format, objs)); - } - - public static void error(String format, Object... objs) { - msg(C.RED + String.format(format, objs)); - } - - public static void debug(String string) { - if (!IrisSettings.get().getGeneral().isDebug()) { - return; - } - - try { - throw new RuntimeException(); - } catch (Throwable e) { - try { - String[] cc = e.getStackTrace()[1].getClassName().split("\\Q.\\E"); - - if (cc.length > 5) { - debug(cc[3] + "/" + cc[4] + "/" + cc[cc.length - 1], e.getStackTrace()[1].getLineNumber(), string); - } else { - debug(cc[3] + "/" + cc[4], e.getStackTrace()[1].getLineNumber(), string); - } - } catch (Throwable ex) { - debug("Origin", -1, string); - } - } - } - - public static void debug(String category, int line, String string) { - if (!IrisSettings.get().getGeneral().isDebug()) { - return; - } - if (IrisSettings.get().getGeneral().isUseConsoleCustomColors()) { - msg("" + category + " <#bf3b76>" + line + " " + C.LIGHT_PURPLE + string.replaceAll("\\Q<\\E", "[").replaceAll("\\Q>\\E", "]")); - } else { - msg(C.BLUE + category + ":" + C.AQUA + line + C.RESET + C.LIGHT_PURPLE + " " + string.replaceAll("\\Q<\\E", "[").replaceAll("\\Q>\\E", "]")); - - } - } - - public static void verbose(String string) { - debug(string); - } - - public static void success(String string) { - msg(C.IRIS + string); - } - - public static void info(String format, Object... args) { - msg(C.WHITE + String.format(format, args)); - } - public static void safeguard(String format, Object... args) { - msg(C.RESET + String.format(format, args)); - } - - @SuppressWarnings("deprecation") - public static void later(NastyRunnable object) { - try { - Bukkit.getScheduler().scheduleAsyncDelayedTask(instance, () -> - { - try { - object.run(); - } catch (Throwable e) { - e.printStackTrace(); - Iris.reportError(e); - } - }, RNG.r.i(100, 1200)); - } catch (IllegalPluginAccessException ignored) { - - } - } - - public static int jobCount() { - return syncJobs.size(); - } - - public static void clearQueues() { - synchronized (syncJobs) { - syncJobs.clear(); - } - } - - public static int getJavaVersion() { - String version = System.getProperty("java.version"); - if (version.startsWith("1.")) { - version = version.substring(2, 3); - } else { - int dot = version.indexOf("."); - if (dot != -1) { - version = version.substring(0, dot); - } - } - return Integer.parseInt(version); - } - - public static String getJava() { - String javaRuntimeName = System.getProperty("java.vm.name"); - String javaRuntimeVendor = System.getProperty("java.vendor"); - String javaRuntimeVersion = System.getProperty("java.vm.version"); - return String.format("%s %s (build %s)", javaRuntimeName, javaRuntimeVendor, javaRuntimeVersion); - } - - public static void reportErrorChunk(int x, int z, Throwable e, String extra) { - if (IrisSettings.get().getGeneral().isDebug()) { - File f = instance.getDataFile("debug", "chunk-errors", "chunk." + x + "." + z + ".txt"); - - if (!f.exists()) { - J.attempt(() -> { - PrintWriter pw = new PrintWriter(f); - pw.println("Thread: " + Thread.currentThread().getName()); - pw.println("First: " + new Date(M.ms())); - e.printStackTrace(pw); - pw.close(); - }); - } - - Iris.debug("Chunk " + x + "," + z + " Exception Logged: " + e.getClass().getSimpleName() + ": " + C.RESET + "" + C.LIGHT_PURPLE + e.getMessage()); - } - } - - public static void reportError(Throwable e) { - if (IrisSettings.get().getGeneral().isDebug()) { - String n = e.getClass().getCanonicalName() + "-" + e.getStackTrace()[0].getClassName() + "-" + e.getStackTrace()[0].getLineNumber(); - - if (e.getCause() != null) { - n += "-" + e.getCause().getStackTrace()[0].getClassName() + "-" + e.getCause().getStackTrace()[0].getLineNumber(); - } - - File f = instance.getDataFile("debug", "caught-exceptions", n + ".txt"); - - if (!f.exists()) { - J.attempt(() -> { - PrintWriter pw = new PrintWriter(f); - pw.println("Thread: " + Thread.currentThread().getName()); - pw.println("First: " + new Date(M.ms())); - e.printStackTrace(pw); - pw.close(); - }); - } - - Iris.debug("Exception Logged: " + e.getClass().getSimpleName() + ": " + C.RESET + "" + C.LIGHT_PURPLE + e.getMessage()); - } - } - - public static void dump() { - try { - File fi = Iris.instance.getDataFile("dump", "td-" + new java.sql.Date(M.ms()) + ".txt"); - FileOutputStream fos = new FileOutputStream(fi); - Map f = Thread.getAllStackTraces(); - PrintWriter pw = new PrintWriter(fos); - for (Thread i : f.keySet()) { - pw.println("========================================"); - pw.println("Thread: '" + i.getName() + "' ID: " + i.getId() + " STATUS: " + i.getState().name()); - - for (StackTraceElement j : f.get(i)) { - pw.println(" @ " + j.toString()); - } - - pw.println("========================================"); - pw.println(); - pw.println(); - } - - pw.close(); - Iris.info("DUMPED! See " + fi.getAbsolutePath()); - } catch (Throwable e) { - e.printStackTrace(); - } - } - - public static void panic() { - EnginePanic.panic(); - } - - public static void addPanic(String s, String v) { - EnginePanic.add(s, v); - } - - private static void fixShading() { - ShadeFix.fix(ComponentSerializer.class); - } - private void enable() { - instance = this; - services = new KMap<>(); - setupAudience(); - initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class) i.getClass(), (IrisService) i)); - INMS.get(); - IO.delete(new File("iris")); - compat = IrisCompat.configured(getDataFile("compat.json")); - ServerConfigurator.configure(); - new IrisContextInjector(); - IrisSafeguard.IrisSafeguardSystem(); - getSender().setTag(getTag()); - IrisSafeguard.earlySplash(); - linkMultiverseCore = new MultiverseCoreLink(); - linkMythicMobs = new MythicMobsLink(); - configWatcher = new FileWatcher(getDataFile("settings.json")); - services.values().forEach(IrisService::onEnable); - services.values().forEach(this::registerListener); - J.s(() -> { - J.a(() -> PaperLib.suggestPaper(this)); - J.a(() -> IO.delete(getTemp())); - J.a(LazyPregenerator::loadLazyGenerators, 100); - J.a(this::bstats); - J.ar(this::checkConfigHotload, 60); - J.sr(this::tickQueue, 0); - J.s(this::setupPapi); - J.a(ServerConfigurator::configure, 20); - splash(); - UtilsSFG.splash(); - - autoStartStudio(); - checkForBukkitWorlds(); - IrisToolbelt.retainMantleDataForSlice(String.class.getCanonicalName()); - IrisToolbelt.retainMantleDataForSlice(BlockData.class.getCanonicalName()); - }); - } - - private void checkForBukkitWorlds() { - FileConfiguration fc = new YamlConfiguration(); - try { - fc.load(new File("bukkit.yml")); - ConfigurationSection section = fc.getConfigurationSection("worlds"); - if (section == null) { - return; - } - - for (String s : section.getKeys(false)) { - ConfigurationSection entry = section.getConfigurationSection(s); - if (!entry.contains("generator", true)) { - continue; - } - - String generator = entry.getString("generator"); - if (generator.startsWith("Iris:")) { - generator = generator.split("\\Q:\\E")[1]; - } else if (generator.equalsIgnoreCase("Iris")) { - generator = IrisSettings.get().getGenerator().getDefaultWorldType(); - } else { - continue; - } - - if (Bukkit.getWorld(s) != null) - continue; - - Iris.info("Loading World: %s | Generator: %s", s, generator); - - Iris.info(C.LIGHT_PURPLE + "Preparing Spawn for " + s + "' using Iris:" + generator + "..."); - WorldCreator c = new WorldCreator(s) - .generator(getDefaultWorldGenerator(s, generator)) - .environment(IrisData.loadAnyDimension(generator).getEnvironment()); - INMS.get().createWorld(c); - Iris.info(C.LIGHT_PURPLE + "Loaded " + s + "!"); - } - } catch (Throwable e) { - e.printStackTrace(); - } - } - - private void autoStartStudio() { - if (IrisSettings.get().getStudio().isAutoStartDefaultStudio()) { - Iris.info("Starting up auto Studio!"); - try { - Player r = new KList<>(getServer().getOnlinePlayers()).getRandom(); - Iris.service(StudioSVC.class).open(r != null ? new VolmitSender(r) : getSender(), 1337, IrisSettings.get().getGenerator().getDefaultWorldType(), (w) -> { - J.s(() -> { - for (Player i : getServer().getOnlinePlayers()) { - i.setGameMode(GameMode.SPECTATOR); - i.teleport(new Location(w, 0, 200, 0)); - } - }); - }); - } catch (IrisException e) { - e.printStackTrace(); - } - } - } - - private void setupAudience() { - try { - audiences = BukkitAudiences.create(this); - } catch (Throwable e) { - e.printStackTrace(); - IrisSettings.get().getGeneral().setUseConsoleCustomColors(false); - IrisSettings.get().getGeneral().setUseCustomColorsIngame(false); - Iris.error("Failed to setup Adventure API... No custom colors :("); - } - } - - public void postShutdown(Runnable r) { - postShutdown.add(r); - } - - public void onEnable() { - enable(); - super.onEnable(); - Bukkit.getPluginManager().registerEvents(this, this); - setupChecks(); - } - - public void onDisable() { - services.values().forEach(IrisService::onDisable); - Bukkit.getScheduler().cancelTasks(this); - HandlerList.unregisterAll((Plugin) this); - postShutdown.forEach(Runnable::run); - services.clear(); - MultiBurst.burst.close(); - super.onDisable(); - } - - private void setupPapi() { - if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { - new IrisPapiExpansion().register(); - } - } - - @Override - public void start() { - - } - - @Override - public void stop() { - - } - - @Override - public String getTag(String subTag) { - if (unstablemode) { - return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.RED + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; - } - if (warningmode) { - return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.GOLD + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; - } - return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.IRIS + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; - - } - - private boolean setupChecks() { - boolean passed = true; - Iris.info("Version Information: " + instance.getServer().getVersion() + " | " + instance.getServer().getBukkitVersion()); - if (INMS.get() instanceof NMSBinding1X) { - passed = false; - Iris.warn("============================================"); - Iris.warn("="); - Iris.warn("="); - Iris.warn("="); - Iris.warn("Iris is not compatible with this version of Minecraft."); - Iris.warn("="); - Iris.warn("="); - Iris.warn("="); - Iris.warn("============================================"); - } - if (!instance.getServer().getVersion().contains("Purpur")) { - passed = false; - Iris.info("We recommend using Purpur for the best experience with Iris."); - Iris.info("Purpur is a fork of Paper that is optimized for performance and stability."); - Iris.info("Plugins that work on Spigot / Paper work on Purpur."); - Iris.info("You can download it here: https://purpurmc.org"); - } - return passed; - } - - private void checkConfigHotload() { - if (configWatcher.checkModified()) { - IrisSettings.invalidate(); - IrisSettings.get(); - configWatcher.checkModified(); - Iris.info("Hotloaded settings.json "); - } - } - - private void tickQueue() { - synchronized (Iris.syncJobs) { - if (!Iris.syncJobs.hasNext()) { - return; - } - - long ms = M.ms(); - - while (Iris.syncJobs.hasNext() && M.ms() - ms < 25) { - try { - Iris.syncJobs.next().run(); - } catch (Throwable e) { - e.printStackTrace(); - Iris.reportError(e); - } - } - } - } - - private void bstats() { - if (IrisSettings.get().getGeneral().isPluginMetrics()) { - J.s(() -> { - var metrics = new Metrics(Iris.instance, 24220); - metrics.addCustomChart(new SingleLineChart("custom_dimensions", () -> Bukkit.getWorlds() - .stream() - .filter(IrisToolbelt::isIrisWorld) - .mapToInt(w -> 1) - .sum())); - - metrics.addCustomChart(new DrilldownPie("used_packs", () -> Bukkit.getWorlds().stream() - .map(IrisToolbelt::access) - .filter(Objects::nonNull) - .map(PlatformChunkGenerator::getEngine) - .collect(Collectors.toMap(engine -> engine.getDimension().getLoadKey(), engine -> { - var hash32 = engine.getHash32().getNow(null); - if (hash32 == null) return Map.of(); - int version = engine.getDimension().getVersion(); - String checksum = Long.toHexString(hash32); - - return Map.of("v" + version + " (" + checksum + ")", 1); - }, (a, b) -> { - Map merged = new HashMap<>(a); - b.forEach((k, v) -> merged.merge(k, v, Integer::sum)); - return merged; - })))); - - - var info = new SystemInfo().getHardware(); - var cpu = info.getProcessor().getProcessorIdentifier(); - var mem = info.getMemory(); - metrics.addCustomChart(new SimplePie("cpu_model", cpu::getName)); - - var nf = NumberFormat.getInstance(Locale.ENGLISH); - nf.setMinimumFractionDigits(0); - nf.setMaximumFractionDigits(2); - nf.setRoundingMode(RoundingMode.HALF_UP); - - metrics.addCustomChart(new DrilldownPie("memory", () -> { - double total = mem.getTotal() * 1E-9; - double alloc = Math.min(total, Runtime.getRuntime().maxMemory() * 1E-9); - return Map.of(nf.format(alloc), Map.of(nf.format(total), 1)); - })); - - postShutdown.add(metrics::shutdown); - }); - } - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - return super.onCommand(sender, command, label, args); - } - - public void imsg(CommandSender s, String msg) { - s.sendMessage(C.IRIS + "[" + C.DARK_GRAY + "Iris" + C.IRIS + "]" + C.GRAY + ": " + msg); - } - - @Nullable - @Override - public BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) { - Iris.debug("Biome Provider Called for " + worldName + " using ID: " + id); - return super.getDefaultBiomeProvider(worldName, id); - } - - @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); - } - Iris.debug("Generator ID: " + id + " requested by bukkit/plugin"); - - 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."); - } - } - - Iris.debug("Assuming IrisDimension: " + dim.getName()); - - IrisWorld w = IrisWorld.builder() - .name(worldName) - .seed(1337) - .environment(dim.getEnvironment()) - .worldFolder(new File(Bukkit.getWorldContainer(), worldName)) - .minHeight(dim.getMinHeight()) - .maxHeight(dim.getMaxHeight()) - .build(); - - Iris.debug("Generator Config: " + w.toString()); - - File ff = new File(w.worldFolder(), "iris/pack"); - if (!ff.exists() || ff.listFiles().length == 0) { - ff.mkdirs(); - service(StudioSVC.class).installIntoWorld(getSender(), dim.getLoadKey(), w.worldFolder()); - } - - return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey()); - } - - public void splash() { - if (!IrisSettings.get().getGeneral().isSplashLogoStartup()) { - return; - } - - String padd = Form.repeat(" ", 8); - String padd2 = Form.repeat(" ", 4); - String[] info = {"", "", "", "", "", padd2 + C.IRIS + " Iris", padd2 + C.GRAY + " by " + "Volmit Software", padd2 + C.GRAY + " v" + C.IRIS + getDescription().getVersion()}; - if (unstablemode) { - info = new String[]{"", "", "", "", "", padd2 + C.RED + " Iris", padd2 + C.GRAY + " by " + C.DARK_RED + "Volmit Software", padd2 + C.GRAY + " v" + C.RED + getDescription().getVersion()}; - } - if (warningmode) { - info = new String[]{"", "", "", "", "", padd2 + C.GOLD + " Iris", padd2 + C.GRAY + " by " + C.GOLD + "Volmit Software", padd2 + C.GRAY + " v" + C.GOLD + getDescription().getVersion()}; - } - - String[] splashstable = { - padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@", - padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + C.IRIS + " .(((()))). ", - padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + C.IRIS + " .((((((())))))). ", - padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + C.IRIS + " ((((((((())))))))) " + C.GRAY + " @", - padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + C.IRIS + " ((((((((-))))))))) " + C.GRAY + " @@", - padd + C.GRAY + "@@@&&" + C.IRIS + " ((((((({ })))))))) " + C.GRAY + " &&@@@", - padd + C.GRAY + "@@" + C.IRIS + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@", - padd + C.GRAY + "@" + C.IRIS + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@", - padd + C.GRAY + "" + C.IRIS + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@", - padd + C.GRAY + "" + C.IRIS + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@", - padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@" - }; - - String[] splashunstable = { - padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@", - padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + C.RED + " .(((()))). ", - padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + C.RED + " .((((((())))))). ", - padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + C.RED + " ((((((((())))))))) " + C.GRAY + " @", - padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + C.RED + " ((((((((-))))))))) " + C.GRAY + " @@", - padd + C.GRAY + "@@@&&" + C.RED + " ((((((({ })))))))) " + C.GRAY + " &&@@@", - padd + C.GRAY + "@@" + C.RED + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@", - padd + C.GRAY + "@" + C.RED + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@", - padd + C.GRAY + "" + C.RED + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@", - padd + C.GRAY + "" + C.RED + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@", - padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@" - }; - String[] splashwarning = { - padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@", - padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + C.GOLD + " .(((()))). ", - padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + C.GOLD + " .((((((())))))). ", - padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + C.GOLD + " ((((((((())))))))) " + C.GRAY + " @", - padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + C.GOLD + " ((((((((-))))))))) " + C.GRAY + " @@", - padd + C.GRAY + "@@@&&" + C.GOLD + " ((((((({ })))))))) " + C.GRAY + " &&@@@", - padd + C.GRAY + "@@" + C.GOLD + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@", - padd + C.GRAY + "@" + C.GOLD + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@", - padd + C.GRAY + "" + C.GOLD + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@", - padd + C.GRAY + "" + C.GOLD + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@", - padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@" - }; - String[] splash; - File freeSpace = new File(Bukkit.getWorldContainer() + "."); - if (unstablemode) { - splash = splashunstable; - } else if (warningmode) { - splash = splashwarning; - } else { - splash = splashstable; - } - - if (!passedserversoftware) { - Iris.info("Server type & version: " + C.RED + Bukkit.getVersion()); - } else { Iris.info("Server type & version: " + Bukkit.getVersion()); } - Iris.info("Java: " + getJava()); - if (!instance.getServer().getVersion().contains("Purpur")) { - if (instance.getServer().getVersion().contains("Spigot") && instance.getServer().getVersion().contains("Bukkit")) { - Iris.info(C.RED + " Iris requires paper or above to function properly.."); - } else { - Iris.info(C.YELLOW + "Purpur is recommended to use with iris."); - } - } - if (getHardware.getProcessMemory() < 5999) { - Iris.warn("6GB+ Ram is recommended"); - Iris.warn("Process Memory: " + getHardware.getProcessMemory() + " MB"); - } - Iris.info("Bukkit distro: " + Bukkit.getName()); - Iris.info("Custom Biomes: " + INMS.get().countCustomBiomes()); - setupChecks(); - printPacks(); - - for (int i = 0; i < info.length; i++) { - splash[i] += info[i]; - } - - Iris.info("\n\n " + new KList<>(splash).toString("\n") + "\n"); - } - - private void printPacks() { - File packFolder = Iris.service(StudioSVC.class).getWorkspaceFolder(); - File[] packs = packFolder.listFiles(File::isDirectory); - if (packs == null || packs.length == 0) - return; - Iris.info("Custom Dimensions: " + packs.length); - for (File f : packs) - printPack(f); - } - - private void printPack(File pack) { - String dimName = pack.getName(); - String version = "???"; - try (FileReader r = new FileReader(new File(pack, "dimensions/" + dimName + ".json"))) { - JsonObject json = JsonParser.parseReader(r).getAsJsonObject(); - if (json.has("version")) - version = json.get("version").getAsString(); - } catch (IOException | JsonParseException ignored) { - } - Iris.info(" " + dimName + " v" + version); - } - - public int getIrisVersion() { - String input = Iris.instance.getDescription().getVersion(); - int hyphenIndex = input.indexOf('-'); - if (hyphenIndex != -1) { - String result = input.substring(0, hyphenIndex); - result = result.replaceAll("\\.", ""); - return Integer.parseInt(result); - } - return -1; - } - - public int getMCVersion() { - try { - String version = Bukkit.getVersion(); - Matcher matcher = Pattern.compile("\\(MC: ([\\d.]+)\\)").matcher(version); - if (matcher.find()) { - version = matcher.group(1).replaceAll("\\.", ""); - long versionNumber = Long.parseLong(version); - if (versionNumber > Integer.MAX_VALUE) { - return -1; - } - return (int) versionNumber; - } - return -1; - } catch (Exception e) { - return -1; - } - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.ServerConfigurator; +import com.volmit.iris.core.link.IrisPapiExpansion; +import com.volmit.iris.core.link.MultiverseCoreLink; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.nms.v1X.NMSBinding1X; +import com.volmit.iris.core.pregenerator.LazyPregenerator; +import com.volmit.iris.core.service.StudioSVC; +import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.engine.EnginePanic; +import com.volmit.iris.engine.object.IrisCompat; +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.core.safeguard.IrisSafeguard; +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.exceptions.IrisException; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.function.NastyRunnable; +import com.volmit.iris.util.io.FileWatcher; +import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.io.InstanceState; +import com.volmit.iris.util.io.JarScanner; +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.misc.Bindings; +import com.volmit.iris.util.misc.SlimJar; +import com.volmit.iris.util.misc.getHardware; +import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.plugin.IrisService; +import com.volmit.iris.util.plugin.VolmitPlugin; +import com.volmit.iris.util.plugin.VolmitSender; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.Queue; +import com.volmit.iris.util.scheduling.ShurikenQueue; +import lombok.NonNull; +import org.bukkit.*; +import org.bukkit.block.data.BlockData; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.*; +import org.bukkit.generator.BiomeProvider; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.plugin.IllegalPluginAccessException; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.lang.annotation.Annotation; +import java.net.URL; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.volmit.iris.core.safeguard.IrisSafeguard.*; +import static com.volmit.iris.core.safeguard.ServerBootSFG.passedserversoftware; + +@SuppressWarnings("CanBeFinal") +public class Iris extends VolmitPlugin implements Listener { + private static final Queue syncJobs = new ShurikenQueue<>(); + + public static Iris instance; + public static Bindings.Adventure audiences; + public static MultiverseCoreLink linkMultiverseCore; + public static IrisCompat compat; + public static FileWatcher configWatcher; + private static VolmitSender sender; + + static { + try { + InstanceState.updateInstanceId(); + } catch (Throwable ignored) { + + } + } + + private final KList postShutdown = new KList<>(); + private KMap, IrisService> services; + + public static VolmitSender getSender() { + if (sender == null) { + sender = new VolmitSender(Bukkit.getConsoleSender()); + sender.setTag(instance.getTag()); + } + return sender; + } + + @SuppressWarnings("unchecked") + public static T service(Class c) { + return (T) instance.services.get(c); + } + + public static void callEvent(Event e) { + if (!e.isAsynchronous()) { + J.s(() -> Bukkit.getPluginManager().callEvent(e)); + } else { + Bukkit.getPluginManager().callEvent(e); + } + } + + public static KList initialize(String s, Class slicedClass) { + JarScanner js = new JarScanner(instance.getJarFile(), s); + KList v = new KList<>(); + J.attempt(js::scan); + for (Class i : js.getClasses()) { + if (slicedClass == null || i.isAnnotationPresent(slicedClass)) { + try { + v.add(i.getDeclaredConstructor().newInstance()); + } catch (Throwable ignored) { + + } + } + } + + return v; + } + + public static KList> getClasses(String s, Class slicedClass) { + JarScanner js = new JarScanner(instance.getJarFile(), s); + KList> v = new KList<>(); + J.attempt(js::scan); + for (Class i : js.getClasses()) { + if (slicedClass == null || i.isAnnotationPresent(slicedClass)) { + try { + v.add(i); + } catch (Throwable ignored) { + + } + } + } + + return v; + } + + public static KList initialize(String s) { + return initialize(s, null); + } + + public static void sq(Runnable r) { + synchronized (syncJobs) { + syncJobs.queue(r); + } + } + + public static File getTemp() { + return instance.getDataFolder("cache", "temp"); + } + + public static void msg(String string) { + try { + getSender().sendMessage(string); + } catch (Throwable e) { + try { + instance.getLogger().info(instance.getTag() + string.replaceAll("(<([^>]+)>)", "")); + } catch (Throwable ignored1) { + + } + } + } + + public static File getCached(String name, String url) { + String h = IO.hash(name + "@" + url); + File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); + + if (!f.exists()) { + try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { + byte[] dataBuffer = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead); + Iris.verbose("Aquiring " + name); + } + } catch (IOException e) { + Iris.reportError(e); + } + } + + return f.exists() ? f : null; + } + + public static String getNonCached(String name, String url) { + String h = IO.hash(name + "*" + url); + File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); + + try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { + byte[] dataBuffer = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead); + } + } catch (IOException e) { + Iris.reportError(e); + } + + try { + return IO.readAll(f); + } catch (IOException e) { + Iris.reportError(e); + } + + return ""; + } + + public static File getNonCachedFile(String name, String url) { + String h = IO.hash(name + "*" + url); + File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); + Iris.verbose("Download " + name + " -> " + url); + try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { + byte[] dataBuffer = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead); + } + + fileOutputStream.flush(); + } catch (IOException e) { + e.printStackTrace(); + Iris.reportError(e); + } + + return f; + } + + public static void warn(String format, Object... objs) { + msg(C.YELLOW + String.format(format, objs)); + } + + public static void error(String format, Object... objs) { + msg(C.RED + String.format(format, objs)); + } + + public static void debug(String string) { + if (!IrisSettings.get().getGeneral().isDebug()) { + return; + } + + try { + throw new RuntimeException(); + } catch (Throwable e) { + try { + String[] cc = e.getStackTrace()[1].getClassName().split("\\Q.\\E"); + + if (cc.length > 5) { + debug(cc[3] + "/" + cc[4] + "/" + cc[cc.length - 1], e.getStackTrace()[1].getLineNumber(), string); + } else { + debug(cc[3] + "/" + cc[4], e.getStackTrace()[1].getLineNumber(), string); + } + } catch (Throwable ex) { + debug("Origin", -1, string); + } + } + } + + public static void debug(String category, int line, String string) { + if (!IrisSettings.get().getGeneral().isDebug()) { + return; + } + if (IrisSettings.get().getGeneral().isUseConsoleCustomColors()) { + msg("" + category + " <#bf3b76>" + line + " " + C.LIGHT_PURPLE + string.replaceAll("\\Q<\\E", "[").replaceAll("\\Q>\\E", "]")); + } else { + msg(C.BLUE + category + ":" + C.AQUA + line + C.RESET + C.LIGHT_PURPLE + " " + string.replaceAll("\\Q<\\E", "[").replaceAll("\\Q>\\E", "]")); + + } + } + + public static void verbose(String string) { + debug(string); + } + + public static void success(String string) { + msg(C.IRIS + string); + } + + public static void info(String format, Object... args) { + msg(C.WHITE + String.format(format, args)); + } + public static void safeguard(String format, Object... args) { + msg(C.RESET + String.format(format, args)); + } + + @SuppressWarnings("deprecation") + public static void later(NastyRunnable object) { + try { + Bukkit.getScheduler().scheduleAsyncDelayedTask(instance, () -> + { + try { + object.run(); + } catch (Throwable e) { + e.printStackTrace(); + Iris.reportError(e); + } + }, RNG.r.i(100, 1200)); + } catch (IllegalPluginAccessException ignored) { + + } + } + + public static int jobCount() { + return syncJobs.size(); + } + + public static void clearQueues() { + synchronized (syncJobs) { + syncJobs.clear(); + } + } + + public static int getJavaVersion() { + String version = System.getProperty("java.version"); + if (version.startsWith("1.")) { + version = version.substring(2, 3); + } else { + int dot = version.indexOf("."); + if (dot != -1) { + version = version.substring(0, dot); + } + } + return Integer.parseInt(version); + } + + public static String getJava() { + String javaRuntimeName = System.getProperty("java.vm.name"); + String javaRuntimeVendor = System.getProperty("java.vendor"); + String javaRuntimeVersion = System.getProperty("java.vm.version"); + return String.format("%s %s (build %s)", javaRuntimeName, javaRuntimeVendor, javaRuntimeVersion); + } + + public static void reportErrorChunk(int x, int z, Throwable e, String extra) { + if (IrisSettings.get().getGeneral().isDebug()) { + File f = instance.getDataFile("debug", "chunk-errors", "chunk." + x + "." + z + ".txt"); + + if (!f.exists()) { + J.attempt(() -> { + PrintWriter pw = new PrintWriter(f); + pw.println("Thread: " + Thread.currentThread().getName()); + pw.println("First: " + new Date(M.ms())); + e.printStackTrace(pw); + pw.close(); + }); + } + + Iris.debug("Chunk " + x + "," + z + " Exception Logged: " + e.getClass().getSimpleName() + ": " + C.RESET + "" + C.LIGHT_PURPLE + e.getMessage()); + } + } + + public static void reportError(Throwable e) { + Bindings.capture(e); + if (IrisSettings.get().getGeneral().isDebug()) { + String n = e.getClass().getCanonicalName() + "-" + e.getStackTrace()[0].getClassName() + "-" + e.getStackTrace()[0].getLineNumber(); + + if (e.getCause() != null) { + n += "-" + e.getCause().getStackTrace()[0].getClassName() + "-" + e.getCause().getStackTrace()[0].getLineNumber(); + } + + File f = instance.getDataFile("debug", "caught-exceptions", n + ".txt"); + + if (!f.exists()) { + J.attempt(() -> { + PrintWriter pw = new PrintWriter(f); + pw.println("Thread: " + Thread.currentThread().getName()); + pw.println("First: " + new Date(M.ms())); + e.printStackTrace(pw); + pw.close(); + }); + } + + Iris.debug("Exception Logged: " + e.getClass().getSimpleName() + ": " + C.RESET + "" + C.LIGHT_PURPLE + e.getMessage()); + } + } + + public static void dump() { + try { + File fi = Iris.instance.getDataFile("dump", "td-" + new java.sql.Date(M.ms()) + ".txt"); + FileOutputStream fos = new FileOutputStream(fi); + Map f = Thread.getAllStackTraces(); + PrintWriter pw = new PrintWriter(fos); + for (Thread i : f.keySet()) { + pw.println("========================================"); + pw.println("Thread: '" + i.getName() + "' ID: " + i.getId() + " STATUS: " + i.getState().name()); + + for (StackTraceElement j : f.get(i)) { + pw.println(" @ " + j.toString()); + } + + pw.println("========================================"); + pw.println(); + pw.println(); + } + + pw.close(); + Iris.info("DUMPED! See " + fi.getAbsolutePath()); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + public static void panic() { + EnginePanic.panic(); + } + + public static void addPanic(String s, String v) { + EnginePanic.add(s, v); + } + + public Iris() { + instance = this; + SlimJar.debug(IrisSettings.get().getSentry().isDebug()); + SlimJar.load(getDataFolder("cache", "libraries")); + } + + private void enable() { + services = new KMap<>(); + setupAudience(); + Bindings.setupSentry(); + initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class) i.getClass(), (IrisService) i)); + IO.delete(new File("iris")); + compat = IrisCompat.configured(getDataFile("compat.json")); + ServerConfigurator.configure(); + IrisSafeguard.IrisSafeguardSystem(); + getSender().setTag(getTag()); + IrisSafeguard.splash(true); + linkMultiverseCore = new MultiverseCoreLink(); + configWatcher = new FileWatcher(getDataFile("settings.json")); + services.values().forEach(IrisService::onEnable); + services.values().forEach(this::registerListener); + J.s(() -> { + J.a(IrisSafeguard::suggestPaper); + J.a(() -> IO.delete(getTemp())); + J.a(LazyPregenerator::loadLazyGenerators, 100); + J.a(this::bstats); + J.ar(this::checkConfigHotload, 60); + J.sr(this::tickQueue, 0); + J.s(this::setupPapi); + J.a(ServerConfigurator::configure, 20); + IrisSafeguard.splash(false); + + autoStartStudio(); + checkForBukkitWorlds(); + IrisToolbelt.retainMantleDataForSlice(String.class.getCanonicalName()); + IrisToolbelt.retainMantleDataForSlice(BlockData.class.getCanonicalName()); + }); + } + + private void checkForBukkitWorlds() { + FileConfiguration fc = new YamlConfiguration(); + try { + fc.load(new File("bukkit.yml")); + ConfigurationSection section = fc.getConfigurationSection("worlds"); + if (section == null) { + return; + } + + for (String s : section.getKeys(false)) { + ConfigurationSection entry = section.getConfigurationSection(s); + if (!entry.contains("generator", true)) { + continue; + } + + String generator = entry.getString("generator"); + if (generator.startsWith("Iris:")) { + generator = generator.split("\\Q:\\E")[1]; + } else if (generator.equalsIgnoreCase("Iris")) { + generator = IrisSettings.get().getGenerator().getDefaultWorldType(); + } else { + continue; + } + + if (Bukkit.getWorld(s) != null) + continue; + + Iris.info("Loading World: %s | Generator: %s", s, generator); + + Iris.info(C.LIGHT_PURPLE + "Preparing Spawn for " + s + "' using Iris:" + generator + "..."); + WorldCreator c = new WorldCreator(s) + .generator(getDefaultWorldGenerator(s, generator)) + .environment(IrisData.loadAnyDimension(generator).getEnvironment()); + INMS.get().createWorld(c); + Iris.info(C.LIGHT_PURPLE + "Loaded " + s + "!"); + } + } catch (Throwable e) { + e.printStackTrace(); + reportError(e); + } + } + + private void autoStartStudio() { + if (IrisSettings.get().getStudio().isAutoStartDefaultStudio()) { + Iris.info("Starting up auto Studio!"); + try { + Player r = new KList<>(getServer().getOnlinePlayers()).getRandom(); + Iris.service(StudioSVC.class).open(r != null ? new VolmitSender(r) : getSender(), 1337, IrisSettings.get().getGenerator().getDefaultWorldType(), (w) -> { + J.s(() -> { + for (Player i : getServer().getOnlinePlayers()) { + i.setGameMode(GameMode.SPECTATOR); + i.teleport(new Location(w, 0, 200, 0)); + } + }); + }); + } catch (IrisException e) { + reportError(e); + } + } + } + + private void setupAudience() { + try { + audiences = new Bindings.Adventure(this); + } catch (Throwable e) { + e.printStackTrace(); + IrisSettings.get().getGeneral().setUseConsoleCustomColors(false); + IrisSettings.get().getGeneral().setUseCustomColorsIngame(false); + Iris.error("Failed to setup Adventure API... No custom colors :("); + } + } + + public void postShutdown(Runnable r) { + postShutdown.add(r); + } + + public void onEnable() { + enable(); + super.onEnable(); + Bukkit.getPluginManager().registerEvents(this, this); + setupChecks(); + } + + public void onDisable() { + services.values().forEach(IrisService::onDisable); + Bukkit.getScheduler().cancelTasks(this); + HandlerList.unregisterAll((Plugin) this); + postShutdown.forEach(Runnable::run); + super.onDisable(); + + J.attempt(new JarScanner(instance.getJarFile(), "", false)::scan); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + Bukkit.getWorlds() + .stream() + .map(IrisToolbelt::access) + .filter(Objects::nonNull) + .forEach(PlatformChunkGenerator::close); + + MultiBurst.burst.close(); + services.clear(); + })); + } + + private void setupPapi() { + if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { + new IrisPapiExpansion().register(); + } + } + + @Override + public void start() { + + } + + @Override + public void stop() { + + } + + @Override + public String getTag(String subTag) { + if (unstablemode) { + return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.RED + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; + } + if (warningmode) { + return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.GOLD + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; + } + return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.IRIS + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; + + } + + private boolean setupChecks() { + boolean passed = true; + Iris.info("Version Information: " + instance.getServer().getVersion() + " | " + instance.getServer().getBukkitVersion()); + if (INMS.get() instanceof NMSBinding1X) { + passed = false; + Iris.warn("============================================"); + Iris.warn("="); + Iris.warn("="); + Iris.warn("="); + Iris.warn("Iris is not compatible with this version of Minecraft."); + Iris.warn("="); + Iris.warn("="); + Iris.warn("="); + Iris.warn("============================================"); + } + + try { + Class.forName("io.papermc.paper.configuration.PaperConfigurations"); + } catch (ClassNotFoundException e) { + Iris.info(C.RED + "Iris requires paper or above to function properly.."); + return false; + } + + try { + Class.forName("org.purpurmc.purpur.PurpurConfig"); + } catch (ClassNotFoundException e) { + Iris.info("We recommend using Purpur for the best experience with Iris."); + Iris.info("Purpur is a fork of Paper that is optimized for performance and stability."); + Iris.info("Plugins that work on Spigot / Paper work on Purpur."); + Iris.info("You can download it here: https://purpurmc.org"); + return false; + } + return passed; + } + + private void checkConfigHotload() { + if (configWatcher.checkModified()) { + IrisSettings.invalidate(); + IrisSettings.get(); + configWatcher.checkModified(); + Iris.info("Hotloaded settings.json "); + } + } + + private void tickQueue() { + synchronized (Iris.syncJobs) { + if (!Iris.syncJobs.hasNext()) { + return; + } + + long ms = M.ms(); + + while (Iris.syncJobs.hasNext() && M.ms() - ms < 25) { + try { + Iris.syncJobs.next().run(); + } catch (Throwable e) { + e.printStackTrace(); + Iris.reportError(e); + } + } + } + } + + private void bstats() { + if (IrisSettings.get().getGeneral().isPluginMetrics()) { + Bindings.setupBstats(this); + } + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + return super.onCommand(sender, command, label, args); + } + + public void imsg(CommandSender s, String msg) { + s.sendMessage(C.IRIS + "[" + C.DARK_GRAY + "Iris" + C.IRIS + "]" + C.GRAY + ": " + msg); + } + + @Nullable + @Override + public BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) { + Iris.debug("Biome Provider Called for " + worldName + " using ID: " + id); + return super.getDefaultBiomeProvider(worldName, id); + } + + @Override + public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { + Iris.debug("Default World Generator Called for " + worldName + " using ID: " + 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) { + throw new RuntimeException("Can't find dimension " + id + "!"); + } + + Iris.debug("Assuming IrisDimension: " + dim.getName()); + + IrisWorld w = IrisWorld.builder() + .name(worldName) + .seed(1337) + .environment(dim.getEnvironment()) + .worldFolder(new File(Bukkit.getWorldContainer(), worldName)) + .minHeight(dim.getMinHeight()) + .maxHeight(dim.getMaxHeight()) + .build(); + + Iris.debug("Generator Config: " + w.toString()); + + File ff = new File(w.worldFolder(), "iris/pack"); + if (!ff.exists() || ff.listFiles().length == 0) { + ff.mkdirs(); + service(StudioSVC.class).installIntoWorld(getSender(), dim.getLoadKey(), w.worldFolder()); + } + + 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; + } + + String padd = Form.repeat(" ", 8); + String padd2 = Form.repeat(" ", 4); + String[] info = {"", "", "", "", "", padd2 + C.IRIS + " Iris", padd2 + C.GRAY + " by " + "Volmit Software", padd2 + C.GRAY + " v" + C.IRIS + getDescription().getVersion()}; + if (unstablemode) { + info = new String[]{"", "", "", "", "", padd2 + C.RED + " Iris", padd2 + C.GRAY + " by " + C.DARK_RED + "Volmit Software", padd2 + C.GRAY + " v" + C.RED + getDescription().getVersion()}; + } + if (warningmode) { + info = new String[]{"", "", "", "", "", padd2 + C.GOLD + " Iris", padd2 + C.GRAY + " by " + C.GOLD + "Volmit Software", padd2 + C.GRAY + " v" + C.GOLD + getDescription().getVersion()}; + } + + String[] splashstable = { + padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@", + padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + C.IRIS + " .(((()))). ", + padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + C.IRIS + " .((((((())))))). ", + padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + C.IRIS + " ((((((((())))))))) " + C.GRAY + " @", + padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + C.IRIS + " ((((((((-))))))))) " + C.GRAY + " @@", + padd + C.GRAY + "@@@&&" + C.IRIS + " ((((((({ })))))))) " + C.GRAY + " &&@@@", + padd + C.GRAY + "@@" + C.IRIS + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@", + padd + C.GRAY + "@" + C.IRIS + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@", + padd + C.GRAY + "" + C.IRIS + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@", + padd + C.GRAY + "" + C.IRIS + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@", + padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@" + }; + + String[] splashunstable = { + padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@", + padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + C.RED + " .(((()))). ", + padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + C.RED + " .((((((())))))). ", + padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + C.RED + " ((((((((())))))))) " + C.GRAY + " @", + padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + C.RED + " ((((((((-))))))))) " + C.GRAY + " @@", + padd + C.GRAY + "@@@&&" + C.RED + " ((((((({ })))))))) " + C.GRAY + " &&@@@", + padd + C.GRAY + "@@" + C.RED + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@", + padd + C.GRAY + "@" + C.RED + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@", + padd + C.GRAY + "" + C.RED + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@", + padd + C.GRAY + "" + C.RED + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@", + padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@" + }; + String[] splashwarning = { + padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@", + padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + C.GOLD + " .(((()))). ", + padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + C.GOLD + " .((((((())))))). ", + padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + C.GOLD + " ((((((((())))))))) " + C.GRAY + " @", + padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + C.GOLD + " ((((((((-))))))))) " + C.GRAY + " @@", + padd + C.GRAY + "@@@&&" + C.GOLD + " ((((((({ })))))))) " + C.GRAY + " &&@@@", + padd + C.GRAY + "@@" + C.GOLD + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@", + padd + C.GRAY + "@" + C.GOLD + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@", + padd + C.GRAY + "" + C.GOLD + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@", + padd + C.GRAY + "" + C.GOLD + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@", + padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@" + }; + String[] splash; + File freeSpace = new File(Bukkit.getWorldContainer() + "."); + if (unstablemode) { + splash = splashunstable; + } else if (warningmode) { + splash = splashwarning; + } else { + splash = splashstable; + } + + if (!passedserversoftware) { + Iris.info("Server type & version: " + C.RED + Bukkit.getVersion()); + } else { Iris.info("Server type & version: " + Bukkit.getVersion()); } + Iris.info("Java: " + getJava()); + if (getHardware.getProcessMemory() < 5999) { + Iris.warn("6GB+ Ram is recommended"); + Iris.warn("Process Memory: " + getHardware.getProcessMemory() + " MB"); + } + Iris.info("Bukkit distro: " + Bukkit.getName()); + Iris.info("Custom Biomes: " + INMS.get().countCustomBiomes()); + setupChecks(); + printPacks(); + + for (int i = 0; i < info.length; i++) { + splash[i] += info[i]; + } + + Iris.info("\n\n " + new KList<>(splash).toString("\n") + "\n"); + } + + private void printPacks() { + File packFolder = Iris.service(StudioSVC.class).getWorkspaceFolder(); + File[] packs = packFolder.listFiles(File::isDirectory); + if (packs == null || packs.length == 0) + return; + Iris.info("Custom Dimensions: " + packs.length); + for (File f : packs) + printPack(f); + } + + private void printPack(File pack) { + String dimName = pack.getName(); + String version = "???"; + try (FileReader r = new FileReader(new File(pack, "dimensions/" + dimName + ".json"))) { + JsonObject json = JsonParser.parseReader(r).getAsJsonObject(); + if (json.has("version")) + version = json.get("version").getAsString(); + } catch (IOException | JsonParseException ignored) { + } + Iris.info(" " + dimName + " v" + version); + } + + public int getIrisVersion() { + String input = Iris.instance.getDescription().getVersion(); + int hyphenIndex = input.indexOf('-'); + if (hyphenIndex != -1) { + String result = input.substring(0, hyphenIndex); + result = result.replaceAll("\\.", ""); + return Integer.parseInt(result); + } + return -1; + } + + public int getMCVersion() { + try { + String version = Bukkit.getVersion(); + Matcher matcher = Pattern.compile("\\(MC: ([\\d.]+)\\)").matcher(version); + if (matcher.find()) { + version = matcher.group(1).replaceAll("\\.", ""); + long versionNumber = Long.parseLong(version); + if (versionNumber > Integer.MAX_VALUE) { + return -1; + } + return (int) versionNumber; + } + return -1; + } catch (Exception e) { + return -1; + } + } +} 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 5381a7e39..faaecc8f0 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -23,8 +23,9 @@ import com.volmit.iris.Iris; import com.volmit.iris.util.io.IO; import com.volmit.iris.util.json.JSONException; import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.misc.SlimJar; +import com.volmit.iris.util.misc.getHardware; import com.volmit.iris.util.plugin.VolmitSender; -import com.volmit.iris.util.scheduling.ChronoLatch; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -45,6 +46,8 @@ public class IrisSettings { private IrisSettingsStudio studio = new IrisSettingsStudio(); private IrisSettingsPerformance performance = new IrisSettingsPerformance(); private IrisSettingsUpdater updater = new IrisSettingsUpdater(); + private IrisSettingsPregen pregen = new IrisSettingsPregen(); + private IrisSettingsSentry sentry = new IrisSettingsSentry(); public static int getThreadCount(int c) { return switch (c) { @@ -84,6 +87,7 @@ public class IrisSettings { Iris.error("Configuration Error in settings.json! " + ee.getClass().getSimpleName() + ": " + ee.getMessage()); } } + SlimJar.debug(settings.general.debug); return settings; } @@ -130,21 +134,46 @@ public class IrisSettings { public boolean markerEntitySpawningSystem = true; public boolean effectSystem = true; public boolean worldEditWandCUI = true; + public boolean globalPregenCache = false; } @Data public static class IrisSettingsConcurrency { public int parallelism = -1; + public int worldGenParallelism = -1; + + public int getWorldGenThreads() { + return getThreadCount(worldGenParallelism); + } + } + + @Data + public static class IrisSettingsPregen { + public boolean useCacheByDefault = true; + public boolean useHighPriority = false; + public boolean useVirtualThreads = false; + public boolean useTicketQueue = true; + public int maxConcurrency = 256; } @Data public static class IrisSettingsPerformance { + private IrisSettingsEngineSVC engineSVC = new IrisSettingsEngineSVC(); public boolean trimMantleInStudio = false; public int mantleKeepAlive = 30; public int cacheSize = 4_096; public int resourceLoaderCacheSize = 1_024; public int objectLoaderCacheSize = 4_096; public int scriptLoaderCacheSize = 512; + public int tectonicPlateSize = -1; + public int mantleCleanupDelay = 200; + + public int getTectonicPlateSize() { + if (tectonicPlateSize > 0) + return tectonicPlateSize; + + return (int) (getHardware.getProcessMemory() / 200L); + } } @Data @@ -176,6 +205,7 @@ public class IrisSettings { public boolean DoomsdayAnnihilationSelfDestructMode = false; public boolean commandSounds = true; public boolean debug = false; + public boolean dumpMantleOnError = false; public boolean disableNMS = false; public boolean pluginMetrics = true; public boolean splashLogoStartup = true; @@ -195,6 +225,13 @@ public class IrisSettings { } } + @Data + public static class IrisSettingsSentry { + public boolean includeServerId = true; + public boolean disableAutoReporting = false; + public boolean debug = false; + } + @Data public static class IrisSettingsGUI { public boolean useServerLaunchedGuis = true; @@ -216,4 +253,14 @@ public class IrisSettings { public boolean disableTimeAndWeather = true; public boolean autoStartDefaultStudio = false; } + + @Data + public static class IrisSettingsEngineSVC { + public boolean useVirtualThreads = true; + public int priority = Thread.NORM_PRIORITY; + + public int getPriority() { + return Math.max(Math.min(priority, Thread.MAX_PRIORITY), Thread.MIN_PRIORITY); + } + } } 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 ffeae3115..d249bfbcd 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,13 @@ 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.Objects; 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(); @@ -71,10 +69,12 @@ public class ServerConfigurator { f.load(spigotConfig); long tt = f.getLong("settings.timeout-time"); - if (tt < TimeUnit.MINUTES.toSeconds(5)) { - Iris.warn("Updating spigot.yml timeout-time: " + tt + " -> " + TimeUnit.MINUTES.toSeconds(20) + " (5 minutes)"); + long spigotTimeout = TimeUnit.MINUTES.toSeconds(5); + + if (tt < spigotTimeout) { + Iris.warn("Updating spigot.yml timeout-time: " + tt + " -> " + spigotTimeout + " (5 minutes)"); Iris.warn("You can disable this change (autoconfigureServer) in Iris settings, then change back the value."); - f.set("settings.timeout-time", TimeUnit.MINUTES.toSeconds(5)); + f.set("settings.timeout-time", spigotTimeout); f.save(spigotConfig); } } @@ -84,10 +84,11 @@ public class ServerConfigurator { f.load(spigotConfig); long tt = f.getLong("watchdog.early-warning-delay"); - if (tt < TimeUnit.MINUTES.toMillis(3)) { - Iris.warn("Updating paper.yml watchdog early-warning-delay: " + tt + " -> " + TimeUnit.MINUTES.toMillis(15) + " (3 minutes)"); + long watchdog = TimeUnit.MINUTES.toMillis(3); + if (tt < watchdog) { + Iris.warn("Updating paper.yml watchdog early-warning-delay: " + tt + " -> " + watchdog + " (3 minutes)"); Iris.warn("You can disable this change (autoconfigureServer) in Iris settings, then change back the value."); - f.set("watchdog.early-warning-delay", TimeUnit.MINUTES.toMillis(3)); + f.set("watchdog.early-warning-delay", watchdog); f.save(spigotConfig); } } @@ -107,19 +108,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) @@ -127,19 +134,22 @@ 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() + .filter(Objects::nonNull) + .map(ServerConfigurator::verifyDataPackInstalled) + .toList() + .contains(false); + }) + .toList() + .contains(true); + if (!bad) return; + } if (allowRestarting) { @@ -213,6 +223,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!"); @@ -222,9 +237,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); } @@ -239,49 +258,58 @@ 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()); var loader = data.getDimensionLoader(); return loader.loadAll(loader.getPossibleKeys()) .stream() + .filter(Objects::nonNull) .peek(this::merge); } 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/commands/CommandDeepSearch.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeepSearch.java deleted file mode 100644 index d8cd6a260..000000000 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeepSearch.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.core.commands; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.pregenerator.DeepSearchPregenerator; -import com.volmit.iris.core.pregenerator.PregenTask; -import com.volmit.iris.core.pregenerator.TurboPregenerator; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.util.data.Dimension; -import com.volmit.iris.util.decree.DecreeExecutor; -import com.volmit.iris.util.decree.annotations.Decree; -import com.volmit.iris.util.decree.annotations.Param; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.math.Position2; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.util.Vector; - -import java.io.File; -import java.io.IOException; - -@Decree(name = "DeepSearch", aliases = "search", description = "Pregenerate your Iris worlds!") -public class CommandDeepSearch implements DecreeExecutor { - public String worldName; - @Decree(description = "DeepSearch a world") - public void start( - @Param(description = "The radius of the pregen in blocks", aliases = "size") - int radius, - @Param(description = "The world to pregen", contextual = true) - World world, - @Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0") - Vector center - ) { - - worldName = world.getName(); - File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); - File TurboFile = new File(worldDirectory, "DeepSearch.json"); - if (TurboFile.exists()) { - if (DeepSearchPregenerator.getInstance() != null) { - sender().sendMessage(C.BLUE + "DeepSearch is already in progress"); - Iris.info(C.YELLOW + "DeepSearch is already in progress"); - return; - } else { - try { - TurboFile.delete(); - } catch (Exception e){ - Iris.error("Failed to delete the old instance file of DeepSearch!"); - return; - } - } - } - - try { - if (sender().isPlayer() && access() == null) { - sender().sendMessage(C.RED + "The engine access for this world is null!"); - sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example."); - } - - DeepSearchPregenerator.DeepSearchJob DeepSearchJob = DeepSearchPregenerator.DeepSearchJob.builder() - .world(world) - .radiusBlocks(radius) - .position(0) - .build(); - - File SearchGenFile = new File(worldDirectory, "DeepSearch.json"); - DeepSearchPregenerator pregenerator = new DeepSearchPregenerator(DeepSearchJob, SearchGenFile); - pregenerator.start(); - - String msg = C.GREEN + "DeepSearch started in " + C.GOLD + worldName + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ(); - sender().sendMessage(msg); - Iris.info(msg); - } catch (Throwable e) { - sender().sendMessage(C.RED + "Epic fail. See console."); - Iris.reportError(e); - e.printStackTrace(); - } - } - - @Decree(description = "Stop the active DeepSearch task", aliases = "x") - public void stop(@Param(aliases = "world", description = "The world to pause") World world) throws IOException { - DeepSearchPregenerator DeepSearchInstance = DeepSearchPregenerator.getInstance(); - File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); - File turboFile = new File(worldDirectory, "DeepSearch.json"); - - if (DeepSearchInstance != null) { - DeepSearchInstance.shutdownInstance(world); - sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName()); - } else if (turboFile.exists() && turboFile.delete()) { - sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName()); - } else if (turboFile.exists()) { - Iris.error("Failed to delete the old instance file of Turbo Pregen!"); - } else { - sender().sendMessage(C.YELLOW + "No active pregeneration tasks to stop"); - } - } - - @Decree(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"}) - public void pause( - @Param(aliases = "world", description = "The world to pause") - World world - ) { - if (TurboPregenerator.getInstance() != null) { - TurboPregenerator.setPausedTurbo(world); - sender().sendMessage(C.GREEN + "Paused/unpaused Turbo Pregen, now: " + (TurboPregenerator.isPausedTurbo(world) ? "Paused" : "Running") + "."); - } else { - File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); - File TurboFile = new File(worldDirectory, "DeepSearch.json"); - if (TurboFile.exists()){ - TurboPregenerator.loadTurboGenerator(world.getName()); - sender().sendMessage(C.YELLOW + "Started DeepSearch back up!"); - } else { - sender().sendMessage(C.YELLOW + "No active DeepSearch tasks to pause/unpause."); - } - - } - } -} diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java index 3f286ba5b..bd361e7c1 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java @@ -30,6 +30,7 @@ import com.volmit.iris.core.tools.IrisPackBenchmarking; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.util.context.IrisContext; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.decree.DecreeExecutor; @@ -42,6 +43,7 @@ import com.volmit.iris.util.format.Form; import com.volmit.iris.util.io.CountingDataInputStream; import com.volmit.iris.util.io.IO; import com.volmit.iris.util.mantle.TectonicPlate; +import com.volmit.iris.util.math.M; import com.volmit.iris.util.nbt.mca.MCAFile; import com.volmit.iris.util.nbt.mca.MCAUtil; import com.volmit.iris.util.parallel.MultiBurst; @@ -71,57 +73,56 @@ import java.util.zip.GZIPOutputStream; @Decree(name = "Developer", origin = DecreeOrigin.BOTH, description = "Iris World Manager", aliases = {"dev"}) public class CommandDeveloper implements DecreeExecutor { private CommandTurboPregen turboPregen; + private CommandLazyPregen lazyPregen; private CommandUpdater updater; @Decree(description = "Get Loaded TectonicPlates Count", origin = DecreeOrigin.BOTH, sync = true) public void EngineStatus() { - List IrisWorlds = new ArrayList<>(); - int TotalLoadedChunks = 0; - int TotalQueuedTectonicPlates = 0; - int TotalNotQueuedTectonicPlates = 0; - int TotalTectonicPlates = 0; + Iris.service(IrisEngineSVC.class) + .engineStatus(sender()); + } - long lowestUnloadDuration = 0; - long highestUnloadDuration = 0; + @Decree(description = "Send a test exception to sentry") + public void Sentry() { + Engine engine = engine(); + if (engine != null) IrisContext.getOr(engine); + Iris.reportError(new Exception("This is a test")); + } - for (World world : Bukkit.getWorlds()) { - try { - if (IrisToolbelt.access(world).getEngine() != null) { - IrisWorlds.add(world); + @Decree(description = "Test") + public void dumpThreads() { + try { + File fi = Iris.instance.getDataFile("dump", "td-" + new java.sql.Date(M.ms()) + ".txt"); + FileOutputStream fos = new FileOutputStream(fi); + Map f = Thread.getAllStackTraces(); + PrintWriter pw = new PrintWriter(fos); + + pw.println(Thread.activeCount() + "/" + f.size()); + var run = Runtime.getRuntime(); + pw.println("Memory:"); + pw.println("\tMax: " + run.maxMemory()); + pw.println("\tTotal: " + run.totalMemory()); + pw.println("\tFree: " + run.freeMemory()); + pw.println("\tUsed: " + (run.totalMemory() - run.freeMemory())); + + for (Thread i : f.keySet()) { + pw.println("========================================"); + pw.println("Thread: '" + i.getName() + "' ID: " + i.getId() + " STATUS: " + i.getState().name()); + + for (StackTraceElement j : f.get(i)) { + pw.println(" @ " + j.toString()); } - } catch (Exception e) { - // no - } - } - for (World world : IrisWorlds) { - Engine engine = IrisToolbelt.access(world).getEngine(); - TotalQueuedTectonicPlates += (int) engine.getMantle().getToUnload(); - TotalNotQueuedTectonicPlates += (int) engine.getMantle().getNotQueuedLoadedRegions(); - TotalTectonicPlates += engine.getMantle().getLoadedRegionCount(); - if (highestUnloadDuration <= (long) engine.getMantle().getTectonicDuration()) { - highestUnloadDuration = (long) engine.getMantle().getTectonicDuration(); - } - if (lowestUnloadDuration >= (long) engine.getMantle().getTectonicDuration()) { - lowestUnloadDuration = (long) engine.getMantle().getTectonicDuration(); - } - for (Chunk chunk : world.getLoadedChunks()) { - if (chunk.isLoaded()) { - TotalLoadedChunks++; - } + pw.println("========================================"); + pw.println(); + pw.println(); } + + pw.close(); + Iris.info("DUMPED! See " + fi.getAbsolutePath()); + } catch (Throwable e) { + e.printStackTrace(); } - Iris.info("-------------------------"); - Iris.info(C.DARK_PURPLE + "Engine Status"); - Iris.info(C.DARK_PURPLE + "Total Loaded Chunks: " + C.LIGHT_PURPLE + TotalLoadedChunks); - Iris.info(C.DARK_PURPLE + "Tectonic Limit: " + C.LIGHT_PURPLE + IrisEngineSVC.getTectonicLimit()); - Iris.info(C.DARK_PURPLE + "Tectonic Total Plates: " + C.LIGHT_PURPLE + TotalTectonicPlates); - Iris.info(C.DARK_PURPLE + "Tectonic Active Plates: " + C.LIGHT_PURPLE + TotalNotQueuedTectonicPlates); - Iris.info(C.DARK_PURPLE + "Tectonic ToUnload: " + C.LIGHT_PURPLE + TotalQueuedTectonicPlates); - Iris.info(C.DARK_PURPLE + "Lowest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(lowestUnloadDuration)); - Iris.info(C.DARK_PURPLE + "Highest Tectonic Unload Duration: " + C.LIGHT_PURPLE + Form.duration(highestUnloadDuration)); - Iris.info(C.DARK_PURPLE + "Cache Size: " + C.LIGHT_PURPLE + Form.f(IrisData.cacheSize())); - Iris.info("-------------------------"); } @Decree(description = "Test") @@ -137,7 +138,7 @@ public class CommandDeveloper implements DecreeExecutor { File tectonicplates = new File(folder, "mantle"); for (File i : Objects.requireNonNull(tectonicplates.listFiles())) { - TectonicPlate.read(maxHeight, i); + TectonicPlate.read(maxHeight, i, true); c++; Iris.info("Loaded count: " + c ); @@ -345,7 +346,8 @@ public class CommandDeveloper implements DecreeExecutor { @Param(description = "base IrisWorld") World world, @Param(description = "raw TectonicPlate File") String path, @Param(description = "Algorithm to Test") String algorithm, - @Param(description = "Amount of Tests") int amount) { + @Param(description = "Amount of Tests") int amount, + @Param(description = "Is versioned", defaultValue = "false") boolean versioned) { if (!IrisToolbelt.isIrisWorld(world)) { sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", Bukkit.getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList())); return; @@ -362,7 +364,7 @@ public class CommandDeveloper implements DecreeExecutor { service.submit(() -> { try { CountingDataInputStream raw = CountingDataInputStream.wrap(new FileInputStream(file)); - TectonicPlate plate = new TectonicPlate(height, raw); + TectonicPlate plate = new TectonicPlate(height, raw, versioned); raw.close(); double d1 = 0; @@ -381,7 +383,7 @@ public class CommandDeveloper implements DecreeExecutor { size = tmp.length(); start = System.currentTimeMillis(); CountingDataInputStream din = createInput(tmp, algorithm); - new TectonicPlate(height, din); + new TectonicPlate(height, din, true); din.close(); d2 += System.currentTimeMillis() - start; tmp.delete(); diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java index 9279beb71..84f601642 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java @@ -1,662 +1,645 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.core.commands; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.loader.IrisData; -import com.volmit.iris.core.nms.INMS; -import com.volmit.iris.core.pregenerator.ChunkUpdater; -import com.volmit.iris.core.service.StudioSVC; -import com.volmit.iris.core.tools.IrisBenchmarking; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.engine.object.IrisDimension; -import com.volmit.iris.core.safeguard.UtilsSFG; -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.util.collection.KList; -import com.volmit.iris.util.decree.DecreeExecutor; -import com.volmit.iris.util.decree.DecreeOrigin; -import com.volmit.iris.util.decree.annotations.Decree; -import com.volmit.iris.util.decree.annotations.Param; -import com.volmit.iris.util.decree.specialhandlers.NullablePlayerHandler; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.plugin.VolmitSender; -import com.volmit.iris.util.scheduling.J; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.WorldCreator; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.generator.ChunkGenerator; -import org.bukkit.scheduler.BukkitRunnable; - -import java.io.File; -import java.io.IOException; -import java.util.Collections; -import java.util.List; - -import static com.volmit.iris.Iris.service; -import static com.volmit.iris.core.service.EditSVC.deletingWorld; -import static com.volmit.iris.core.tools.IrisBenchmarking.inProgress; -import static com.volmit.iris.core.safeguard.IrisSafeguard.unstablemode; -import static com.volmit.iris.core.safeguard.ServerBootSFG.incompatibilities; -import static org.bukkit.Bukkit.getServer; - -@Decree(name = "iris", aliases = {"ir", "irs"}, description = "Basic Command") -public class CommandIris implements DecreeExecutor { - private CommandStudio studio; - private CommandPregen pregen; - private CommandLazyPregen lazyPregen; - private CommandSettings settings; - private CommandObject object; - private CommandJigsaw jigsaw; - private CommandWhat what; - private CommandEdit edit; - private CommandFind find; - private CommandSupport support; - private CommandDeveloper developer; - public static boolean worldCreation = false; - String WorldEngine; - String worldNameToCheck = "YourWorldName"; - VolmitSender sender = Iris.getSender(); - - @Decree(description = "Create a new world", aliases = {"+", "c"}) - public void create( - @Param(aliases = "world-name", description = "The name of the world to create") - String name, - @Param(aliases = "dimension", description = "The dimension type to create the world with", defaultValue = "default") - IrisDimension type, - @Param(description = "The seed to generate the world with", defaultValue = "1337") - long seed - ) { - if(sender() instanceof Player) { - if (incompatibilities.get("Multiverse-Core")) { - sender().sendMessage(C.RED + "Your server has an incompatibility that may corrupt all worlds on the server if not handled properly."); - sender().sendMessage(C.RED + "it is strongly advised for you to take action. see log for full detail"); - sender().sendMessage(C.RED + "----------------------------------------------------------------"); - sender().sendMessage(C.RED + "Command ran: /iris create"); - sender().sendMessage(C.RED + UtilsSFG.MSGIncompatibleWarnings()); - sender().sendMessage(C.RED + "----------------------------------------------------------------"); - } - if (unstablemode && !incompatibilities.get("Multiverse-Core")) { - sender().sendMessage(C.RED + "Your server is experiencing an incompatibility with the Iris plugin."); - sender().sendMessage(C.RED + "Please rectify this problem to avoid further complications."); - sender().sendMessage(C.RED + "----------------------------------------------------------------"); - sender().sendMessage(C.RED + "Command ran: /iris create"); - sender().sendMessage(C.RED + UtilsSFG.MSGIncompatibleWarnings()); - sender().sendMessage(C.RED + "----------------------------------------------------------------"); - } - } - if (name.equals("iris")) { - sender().sendMessage(C.RED + "You cannot use the world name \"iris\" for creating worlds as Iris uses this directory for studio worlds."); - sender().sendMessage(C.RED + "May we suggest the name \"IrisWorld\" instead?"); - return; - } - if (name.equals("Benchmark")) { - sender().sendMessage(C.RED + "You cannot use the world name \"Benchmark\" for creating worlds as Iris uses this directory for Benchmarking Packs."); - sender().sendMessage(C.RED + "May we suggest the name \"IrisWorld\" instead?"); - return; - } - - if (new File(Bukkit.getWorldContainer(), name).exists()) { - sender().sendMessage(C.RED + "That folder already exists!"); - return; - } - - try { - worldCreation = true; - IrisToolbelt.createWorld() - .dimension(type.getLoadKey()) - .name(name) - .seed(seed) - .sender(sender()) - .studio(false) - .create(); - } catch (Throwable e) { - sender().sendMessage(C.RED + "Exception raised during creation. See the console for more details."); - Iris.error("Exception raised during world creation: " + e.getMessage()); - Iris.reportError(e); - worldCreation = false; - return; - } - worldCreation = false; - sender().sendMessage(C.GREEN + "Successfully created your world!"); - } - - @Decree(description = "Teleport to another world", aliases = {"tp"}, sync = true) - public void teleport( - @Param(description = "World to teleport to") - World world, - @Param(description = "Player to teleport", defaultValue = "---", customHandler = NullablePlayerHandler.class) - Player player - ) { - if (player == null && sender().isPlayer()) - player = sender().player(); - - final Player target = player; - if (target == null) { - sender().sendMessage(C.RED + "The specified player does not exist."); - return; - } - - new BukkitRunnable() { - @Override - public void run() { - target.teleport(world.getSpawnLocation()); - new VolmitSender(target).sendMessage(C.GREEN + "You have been teleported to " + world.getName() + "."); - } - }.runTask(Iris.instance); - } - - @Decree(description = "Print version information") - public void version() { - sender().sendMessage(C.GREEN + "Iris v" + Iris.instance.getDescription().getVersion() + " by Volmit Software"); - } - - //todo Move to React - @Decree(description = "Benchmark your server", origin = DecreeOrigin.CONSOLE) - public void serverbenchmark() throws InterruptedException { - if(!inProgress) { - IrisBenchmarking.runBenchmark(); - } else { - Iris.info(C.RED + "Benchmark already is in progress."); - } - } - - /* - /todo - @Decree(description = "Benchmark a pack", origin = DecreeOrigin.CONSOLE) - public void packbenchmark( - @Param(description = "Dimension to benchmark") - IrisDimension type - ) throws InterruptedException { - - BenchDimension = type.getLoadKey(); - - IrisPackBenchmarking.runBenchmark(); - } */ - - @Decree(description = "Print world height information", origin = DecreeOrigin.PLAYER) - public void height() { - if (sender().isPlayer()) { - sender().sendMessage(C.GREEN + "" + sender().player().getWorld().getMinHeight() + " to " + sender().player().getWorld().getMaxHeight()); - sender().sendMessage(C.GREEN + "Total Height: " + (sender().player().getWorld().getMaxHeight() - sender().player().getWorld().getMinHeight())); - } else { - World mainWorld = getServer().getWorlds().get(0); - Iris.info(C.GREEN + "" + mainWorld.getMinHeight() + " to " + mainWorld.getMaxHeight()); - Iris.info(C.GREEN + "Total Height: " + (mainWorld.getMaxHeight() - mainWorld.getMinHeight())); - } - } - - @Decree(description = "QOL command to open a overworld studio world.", sync = true) - public void so() { - sender().sendMessage(C.GREEN + "Opening studio for the \"Overworld\" pack (seed: 1337)"); - Iris.service(StudioSVC.class).open(sender(), 1337, "overworld"); - } - - @Decree(description = "Check access of all worlds.", aliases = {"accesslist"}) - public void worlds() { - KList IrisWorlds = new KList<>(); - KList BukkitWorlds = new KList<>(); - - for (World w : Bukkit.getServer().getWorlds()) { - try { - Engine engine = IrisToolbelt.access(w).getEngine(); - if (engine != null) { - IrisWorlds.add(w); - } - } catch (Exception e) { - BukkitWorlds.add(w); - } - } - - if (sender().isPlayer()) { - sender().sendMessage(C.BLUE + "Iris Worlds: "); - for (World IrisWorld : IrisWorlds.copy()) { - sender().sendMessage(C.IRIS + "- " +IrisWorld.getName()); - } - sender().sendMessage(C.GOLD + "Bukkit Worlds: "); - for (World BukkitWorld : BukkitWorlds.copy()) { - sender().sendMessage(C.GRAY + "- " +BukkitWorld.getName()); - } - } else { - Iris.info(C.BLUE + "Iris Worlds: "); - for (World IrisWorld : IrisWorlds.copy()) { - Iris.info(C.IRIS + "- " +IrisWorld.getName()); - } - Iris.info(C.GOLD + "Bukkit Worlds: "); - for (World BukkitWorld : BukkitWorlds.copy()) { - Iris.info(C.GRAY + "- " +BukkitWorld.getName()); - } - - } - } - - @Decree(description = "Remove an Iris world", aliases = {"del", "rm", "delete"}, sync = true) - public void remove( - @Param(description = "The world to remove") - World world, - @Param(description = "Whether to also remove the folder (if set to false, just does not load the world)", defaultValue = "true") - boolean delete - ) { - if (!IrisToolbelt.isIrisWorld(world)) { - sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList())); - return; - } - sender().sendMessage(C.GREEN + "Removing world: " + world.getName()); - - if (!IrisToolbelt.evacuate(world)) { - sender().sendMessage(C.RED + "Failed to evacuate world: " + world.getName()); - return; - } - - if (!Bukkit.unloadWorld(world, false)) { - sender().sendMessage(C.RED + "Failed to unload world: " + world.getName()); - return; - } - - try { - if (IrisToolbelt.removeWorld(world)) { - sender().sendMessage(C.GREEN + "Successfully removed " + world.getName() + " from bukkit.yml"); - } else { - sender().sendMessage(C.YELLOW + "Looks like the world was already removed from bukkit.yml"); - } - } catch (IOException e) { - sender().sendMessage(C.RED + "Failed to save bukkit.yml because of " + e.getMessage()); - e.printStackTrace(); - } - IrisToolbelt.evacuate(world, "Deleting world"); - deletingWorld = true; - if (!delete) { - deletingWorld = false; - return; - } - VolmitSender sender = sender(); - J.a(() -> { - int retries = 12; - - if (deleteDirectory(world.getWorldFolder())) { - sender.sendMessage(C.GREEN + "Successfully removed world folder"); - } else { - while(true){ - if (deleteDirectory(world.getWorldFolder())){ - sender.sendMessage(C.GREEN + "Successfully removed world folder"); - break; - } - retries--; - if (retries == 0){ - sender.sendMessage(C.RED + "Failed to remove world folder"); - break; - } - J.sleep(3000); - } - } - deletingWorld = false; - }); - } - - public static boolean deleteDirectory(File dir) { - if (dir.isDirectory()) { - File[] children = dir.listFiles(); - for (int i = 0; i < children.length; i++) { - boolean success = deleteDirectory(children[i]); - if (!success) { - return false; - } - } - } - return dir.delete(); - } - - @Decree(description = "Updates all chunk in the specified world") - public void updater( - @Param(description = "World to update chunks at") - World world - ) { - if (!IrisToolbelt.isIrisWorld(world)) { - sender().sendMessage(C.GOLD + "This is not an Iris world"); - return; - } - ChunkUpdater updater = new ChunkUpdater(world); - if (sender().isPlayer()) { - sender().sendMessage(C.GREEN + "Updating " + world.getName() + " Total chunks: " + Form.f(updater.getChunks())); - } else { - Iris.info(C.GREEN + "Updating " + world.getName() + " Total chunks: " + Form.f(updater.getChunks())); - } - updater.start(); - } - - @Decree(description = "Set aura spins") - public void aura( - @Param(description = "The h color value", defaultValue = "-20") - int h, - @Param(description = "The s color value", defaultValue = "7") - int s, - @Param(description = "The b color value", defaultValue = "8") - int b - ) { - IrisSettings.get().getGeneral().setSpinh(h); - IrisSettings.get().getGeneral().setSpins(s); - IrisSettings.get().getGeneral().setSpinb(b); - IrisSettings.get().forceSave(); - sender().sendMessage("Aura Spins updated to " + h + " " + s + " " + b); - } - - @Decree(description = "Bitwise calculations") - public void bitwise( - @Param(description = "The first value to run calculations on") - int value1, - @Param(description = "The operator: | & ^ ≺≺ ≻≻ %") - String operator, - @Param(description = "The second value to run calculations on") - int value2 - ) { - Integer v = null; - switch (operator) { - case "|" -> v = value1 | value2; - case "&" -> v = value1 & value2; - case "^" -> v = value1 ^ value2; - case "%" -> v = value1 % value2; - case ">>" -> v = value1 >> value2; - case "<<" -> v = value1 << value2; - } - if (v == null) { - sender().sendMessage(C.RED + "The operator you entered: (" + operator + ") is invalid!"); - return; - } - sender().sendMessage(C.GREEN + "" + value1 + " " + C.GREEN + operator.replaceAll("<", "≺").replaceAll(">", "≻").replaceAll("%", "%") + " " + C.GREEN + value2 + C.GREEN + " returns " + C.GREEN + v); - } - - @Decree(description = "Toggle debug") - public void debug( - @Param(name = "on", description = "Whether or not debug should be on", defaultValue = "other") - Boolean on - ) { - boolean to = on == null ? !IrisSettings.get().getGeneral().isDebug() : on; - IrisSettings.get().getGeneral().setDebug(to); - IrisSettings.get().forceSave(); - sender().sendMessage(C.GREEN + "Set debug to: " + to); - } - - @Decree(description = "Download a project.", aliases = "dl") - public void download( - @Param(name = "pack", description = "The pack to download", defaultValue = "overworld", aliases = "project") - String pack, - @Param(name = "branch", description = "The branch to download from", defaultValue = "main") - String branch, - @Param(name = "trim", description = "Whether or not to download a trimmed version (do not enable when editing)", defaultValue = "false") - boolean trim, - @Param(name = "overwrite", description = "Whether or not to overwrite the pack with the downloaded one", aliases = "force", defaultValue = "false") - boolean overwrite - ) { - sender().sendMessage(C.GREEN + "Downloading pack: " + pack + "/" + branch + (trim ? " trimmed" : "") + (overwrite ? " overwriting" : "")); - if (pack.equals("overworld")) { - String url = "https://github.com/IrisDimensions/overworld/releases/download/" + INMS.OVERWORLD_TAG + "/overworld.zip"; - Iris.service(StudioSVC.class).downloadRelease(sender(), url, trim, overwrite); - } else { - Iris.service(StudioSVC.class).downloadSearch(sender(), "IrisDimensions/" + pack + "/" + branch, trim, overwrite); - } - } - - @Decree(description = "Get metrics for your world", aliases = "measure", origin = DecreeOrigin.PLAYER) - public void metrics() { - if (!IrisToolbelt.isIrisWorld(world())) { - sender().sendMessage(C.RED + "You must be in an Iris world"); - return; - } - sender().sendMessage(C.GREEN + "Sending metrics..."); - engine().printMetrics(sender()); - } - - @Decree(description = "Reload configuration file (this is also done automatically)") - public void reload() { - IrisSettings.invalidate(); - IrisSettings.get(); - sender().sendMessage(C.GREEN + "Hotloaded settings"); - } - - @Decree(description = "Update the pack of a world (UNSAFE!)", name = "^world", aliases = "update-world") - public void updateWorld( - @Param(description = "The world to update", contextual = true) - World world, - @Param(description = "The pack to install into the world", contextual = true, aliases = "dimension") - IrisDimension pack, - @Param(description = "Make sure to make a backup & read the warnings first!", defaultValue = "false", aliases = "c") - boolean confirm, - @Param(description = "Should Iris download the pack again for you", defaultValue = "false", name = "fresh-download", aliases = {"fresh", "new"}) - boolean freshDownload - ) { - if (!confirm) { - sender().sendMessage(new String[]{ - C.RED + "You should always make a backup before using this", - C.YELLOW + "Issues caused by this can be, but are not limited to:", - C.YELLOW + " - Broken chunks (cut-offs) between old and new chunks (before & after the update)", - C.YELLOW + " - Regenerated chunks that do not fit in with the old chunks", - C.YELLOW + " - Structures not spawning again when regenerating", - C.YELLOW + " - Caves not lining up", - C.YELLOW + " - Terrain layers not lining up", - C.RED + "Now that you are aware of the risks, and have made a back-up:", - C.RED + "/iris ^world " + world.getName() + " " + pack.getLoadKey() + " confirm=true" - }); - return; - } - - File folder = world.getWorldFolder(); - folder.mkdirs(); - - if (freshDownload) { - Iris.service(StudioSVC.class).downloadSearch(sender(), pack.getLoadKey(), false, true); - } - - Iris.service(StudioSVC.class).installIntoWorld(sender(), pack.getLoadKey(), folder); - } - - @Decree(description = "Unload an Iris World", origin = DecreeOrigin.PLAYER, sync = true) - public void unloadWorld( - @Param(description = "The world to unload") - World world - ) { - if (!IrisToolbelt.isIrisWorld(world)) { - sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList())); - return; - } - sender().sendMessage(C.GREEN + "Unloading world: " + world.getName()); - try { - IrisToolbelt.evacuate(world); - Bukkit.unloadWorld(world, false); - sender().sendMessage(C.GREEN + "World unloaded successfully."); - } catch (Exception e) { - sender().sendMessage(C.RED + "Failed to unload the world: " + e.getMessage()); - e.printStackTrace(); - } - } - - @Decree(description = "Load an Iris World", origin = DecreeOrigin.PLAYER, sync = true, aliases = {"import"}) - public void loadWorld( - @Param(description = "The name of the world to load") - String world - ) { - World worldloaded = Bukkit.getWorld(world); - worldNameToCheck = world; - boolean worldExists = doesWorldExist(worldNameToCheck); - WorldEngine = world; - - if (!worldExists) { - sender().sendMessage(C.YELLOW + world + " Doesnt exist on the server."); - return; - } - - File BUKKIT_YML = new File("bukkit.yml"); - String pathtodim = world + File.separator +"iris"+File.separator +"pack"+File.separator +"dimensions"+File.separator; - File directory = new File(Bukkit.getWorldContainer(), pathtodim); - - String dimension = null; - if (directory.exists() && directory.isDirectory()) { - File[] files = directory.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isFile()) { - String fileName = file.getName(); - if (fileName.endsWith(".json")) { - dimension = fileName.substring(0, fileName.length() - 5); - sender().sendMessage(C.BLUE + "Generator: " + dimension); - } - } - } - } - } else { - sender().sendMessage(C.GOLD + world + " is not an iris world."); - return; - } - sender().sendMessage(C.GREEN + "Loading world: " + world); - - YamlConfiguration yml = YamlConfiguration.loadConfiguration(BUKKIT_YML); - String gen = "Iris:" + dimension; - ConfigurationSection section = yml.contains("worlds") ? yml.getConfigurationSection("worlds") : yml.createSection("worlds"); - if (!section.contains(world)) { - section.createSection(world).set("generator", gen); - try { - yml.save(BUKKIT_YML); - Iris.info("Registered \"" + world + "\" in bukkit.yml"); - } catch (IOException e) { - Iris.error("Failed to update bukkit.yml!"); - e.printStackTrace(); - return; - } - } - checkForBukkitWorlds(world); - sender().sendMessage(C.GREEN + world + " loaded successfully."); - } - @Decree(description = "Evacuate an iris world", origin = DecreeOrigin.PLAYER, sync = true) - public void evacuate( - @Param(description = "Evacuate the world") - World world - ) { - if (!IrisToolbelt.isIrisWorld(world)) { - sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList())); - return; - } - sender().sendMessage(C.GREEN + "Evacuating world" + world.getName()); - IrisToolbelt.evacuate(world); - } - - boolean doesWorldExist(String worldName) { - File worldContainer = Bukkit.getWorldContainer(); - File worldDirectory = new File(worldContainer, worldName); - return worldDirectory.exists() && worldDirectory.isDirectory(); - } - private void checkForBukkitWorlds(String world) { - FileConfiguration fc = new YamlConfiguration(); - try { - fc.load(new File("bukkit.yml")); - ConfigurationSection section = fc.getConfigurationSection("worlds"); - if (section == null) { - return; - } - - List worldsToLoad = Collections.singletonList(world); - - for (String s : section.getKeys(false)) { - if (!worldsToLoad.contains(s)) { - continue; - } - ConfigurationSection entry = section.getConfigurationSection(s); - if (!entry.contains("generator", true)) { - continue; - } - String generator = entry.getString("generator"); - if (generator.startsWith("Iris:")) { - generator = generator.split("\\Q:\\E")[1]; - } else if (generator.equalsIgnoreCase("Iris")) { - generator = IrisSettings.get().getGenerator().getDefaultWorldType(); - } else { - continue; - } - Iris.info("2 World: %s | Generator: %s", s, generator); - if (Bukkit.getWorlds().stream().anyMatch(w -> w.getName().equals(s))) { - continue; - } - Iris.info(C.LIGHT_PURPLE + "Preparing Spawn for " + s + "' using Iris:" + generator + "..."); - WorldCreator c = new WorldCreator(s) - .generator(getDefaultWorldGenerator(s, generator)) - .environment(IrisData.loadAnyDimension(generator).getEnvironment()); - INMS.get().createWorld(c); - Iris.info(C.LIGHT_PURPLE + "Loaded " + s + "!"); - } - } catch (Throwable e) { - e.printStackTrace(); - } - } - 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); - } - Iris.debug("Generator ID: " + id + " requested by bukkit/plugin"); - - 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."); - } - } - Iris.debug("Assuming IrisDimension: " + dim.getName()); - IrisWorld w = IrisWorld.builder() - .name(worldName) - .seed(1337) - .environment(dim.getEnvironment()) - .worldFolder(new File(Bukkit.getWorldContainer(), worldName)) - .minHeight(dim.getMinHeight()) - .maxHeight(dim.getMaxHeight()) - .build(); - Iris.debug("Generator Config: " + w.toString()); - File ff = new File(w.worldFolder(), "iris/pack"); - if (!ff.exists() || ff.listFiles().length == 0) { - ff.mkdirs(); - service(StudioSVC.class).installIntoWorld(sender, dim.getLoadKey(), ff.getParentFile()); - } - return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey()); - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.core.commands; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.pregenerator.ChunkUpdater; +import com.volmit.iris.core.service.StudioSVC; +import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.engine.framework.Engine; +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.util.collection.KList; +import com.volmit.iris.util.decree.DecreeExecutor; +import com.volmit.iris.util.decree.DecreeOrigin; +import com.volmit.iris.util.decree.annotations.Decree; +import com.volmit.iris.util.decree.annotations.Param; +import com.volmit.iris.util.decree.specialhandlers.NullablePlayerHandler; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.misc.ServerProperties; +import com.volmit.iris.util.plugin.VolmitSender; +import com.volmit.iris.util.scheduling.J; +import lombok.SneakyThrows; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.scheduler.BukkitRunnable; + +import java.io.*; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import static com.volmit.iris.Iris.service; +import static com.volmit.iris.core.service.EditSVC.deletingWorld; +import static org.bukkit.Bukkit.getServer; + +@Decree(name = "iris", aliases = {"ir", "irs"}, description = "Basic Command") +public class CommandIris implements DecreeExecutor { + private CommandStudio studio; + private CommandPregen pregen; + private CommandSettings settings; + private CommandObject object; + private CommandJigsaw jigsaw; + private CommandWhat what; + private CommandEdit edit; + private CommandFind find; + private CommandDeveloper developer; + public static boolean worldCreation = false; + private static final AtomicReference mainWorld = new AtomicReference<>(); + String WorldEngine; + String worldNameToCheck = "YourWorldName"; + VolmitSender sender = Iris.getSender(); + + @Decree(description = "Create a new world", aliases = {"+", "c"}) + public void create( + @Param(aliases = "world-name", description = "The name of the world to create") + String name, + @Param(aliases = "dimension", description = "The dimension type to create the world with", defaultValue = "default") + IrisDimension type, + @Param(description = "The seed to generate the world with", defaultValue = "1337") + long seed, + @Param(aliases = "main-world", description = "Whether or not to automatically use this world as the main world", defaultValue = "false") + boolean main + ) { + if (name.equalsIgnoreCase("iris")) { + sender().sendMessage(C.RED + "You cannot use the world name \"iris\" for creating worlds as Iris uses this directory for studio worlds."); + sender().sendMessage(C.RED + "May we suggest the name \"IrisWorld\" instead?"); + return; + } + + if (name.equalsIgnoreCase("benchmark")) { + sender().sendMessage(C.RED + "You cannot use the world name \"benchmark\" for creating worlds as Iris uses this directory for Benchmarking Packs."); + sender().sendMessage(C.RED + "May we suggest the name \"IrisWorld\" instead?"); + return; + } + + if (new File(Bukkit.getWorldContainer(), name).exists()) { + sender().sendMessage(C.RED + "That folder already exists!"); + return; + } + + try { + worldCreation = true; + IrisToolbelt.createWorld() + .dimension(type.getLoadKey()) + .name(name) + .seed(seed) + .sender(sender()) + .studio(false) + .create(); + if (main) { + Runtime.getRuntime().addShutdownHook(mainWorld.updateAndGet(old -> { + if (old != null) Runtime.getRuntime().removeShutdownHook(old); + return new Thread(() -> updateMainWorld(name)); + })); + } + } catch (Throwable e) { + sender().sendMessage(C.RED + "Exception raised during creation. See the console for more details."); + Iris.error("Exception raised during world creation: " + e.getMessage()); + Iris.reportError(e); + worldCreation = false; + return; + } + worldCreation = false; + sender().sendMessage(C.GREEN + "Successfully created your world!"); + } + + @SneakyThrows + private void updateMainWorld(String newName) { + File worlds = Bukkit.getWorldContainer(); + var data = ServerProperties.DATA; + try (var in = new FileInputStream(ServerProperties.SERVER_PROPERTIES)) { + data.load(in); + } + for (String sub : List.of("datapacks", "playerdata", "advancements", "stats")) { + IO.copyDirectory(new File(worlds, ServerProperties.LEVEL_NAME + "/" + sub).toPath(), new File(worlds, newName + "/" + sub).toPath()); + } + + data.setProperty("level-name", newName); + try (var out = new FileOutputStream(ServerProperties.SERVER_PROPERTIES)) { + data.store(out, null); + } + } + + @Decree(description = "Teleport to another world", aliases = {"tp"}, sync = true) + public void teleport( + @Param(description = "World to teleport to") + World world, + @Param(description = "Player to teleport", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + ) { + if (player == null && sender().isPlayer()) + player = sender().player(); + + final Player target = player; + if (target == null) { + sender().sendMessage(C.RED + "The specified player does not exist."); + return; + } + + new BukkitRunnable() { + @Override + public void run() { + target.teleport(world.getSpawnLocation()); + new VolmitSender(target).sendMessage(C.GREEN + "You have been teleported to " + world.getName() + "."); + } + }.runTask(Iris.instance); + } + + @Decree(description = "Print version information") + public void version() { + sender().sendMessage(C.GREEN + "Iris v" + Iris.instance.getDescription().getVersion() + " by Volmit Software"); + } + + /* + /todo + @Decree(description = "Benchmark a pack", origin = DecreeOrigin.CONSOLE) + public void packbenchmark( + @Param(description = "Dimension to benchmark") + IrisDimension type + ) throws InterruptedException { + + BenchDimension = type.getLoadKey(); + + IrisPackBenchmarking.runBenchmark(); + } */ + + @Decree(description = "Print world height information", origin = DecreeOrigin.PLAYER) + public void height() { + if (sender().isPlayer()) { + sender().sendMessage(C.GREEN + "" + sender().player().getWorld().getMinHeight() + " to " + sender().player().getWorld().getMaxHeight()); + sender().sendMessage(C.GREEN + "Total Height: " + (sender().player().getWorld().getMaxHeight() - sender().player().getWorld().getMinHeight())); + } else { + World mainWorld = getServer().getWorlds().get(0); + Iris.info(C.GREEN + "" + mainWorld.getMinHeight() + " to " + mainWorld.getMaxHeight()); + Iris.info(C.GREEN + "Total Height: " + (mainWorld.getMaxHeight() - mainWorld.getMinHeight())); + } + } + + @Decree(description = "QOL command to open a overworld studio world.", sync = true) + public void so() { + sender().sendMessage(C.GREEN + "Opening studio for the \"Overworld\" pack (seed: 1337)"); + Iris.service(StudioSVC.class).open(sender(), 1337, "overworld"); + } + + @Decree(description = "Check access of all worlds.", aliases = {"accesslist"}) + public void worlds() { + KList IrisWorlds = new KList<>(); + KList BukkitWorlds = new KList<>(); + + for (World w : Bukkit.getServer().getWorlds()) { + try { + Engine engine = IrisToolbelt.access(w).getEngine(); + if (engine != null) { + IrisWorlds.add(w); + } + } catch (Exception e) { + BukkitWorlds.add(w); + } + } + + if (sender().isPlayer()) { + sender().sendMessage(C.BLUE + "Iris Worlds: "); + for (World IrisWorld : IrisWorlds.copy()) { + sender().sendMessage(C.IRIS + "- " +IrisWorld.getName()); + } + sender().sendMessage(C.GOLD + "Bukkit Worlds: "); + for (World BukkitWorld : BukkitWorlds.copy()) { + sender().sendMessage(C.GRAY + "- " +BukkitWorld.getName()); + } + } else { + Iris.info(C.BLUE + "Iris Worlds: "); + for (World IrisWorld : IrisWorlds.copy()) { + Iris.info(C.IRIS + "- " +IrisWorld.getName()); + } + Iris.info(C.GOLD + "Bukkit Worlds: "); + for (World BukkitWorld : BukkitWorlds.copy()) { + Iris.info(C.GRAY + "- " +BukkitWorld.getName()); + } + + } + } + + @Decree(description = "Remove an Iris world", aliases = {"del", "rm", "delete"}, sync = true) + public void remove( + @Param(description = "The world to remove") + World world, + @Param(description = "Whether to also remove the folder (if set to false, just does not load the world)", defaultValue = "true") + boolean delete + ) { + if (!IrisToolbelt.isIrisWorld(world)) { + sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList())); + return; + } + sender().sendMessage(C.GREEN + "Removing world: " + world.getName()); + + if (!IrisToolbelt.evacuate(world)) { + sender().sendMessage(C.RED + "Failed to evacuate world: " + world.getName()); + return; + } + + if (!Bukkit.unloadWorld(world, false)) { + sender().sendMessage(C.RED + "Failed to unload world: " + world.getName()); + return; + } + + try { + if (IrisToolbelt.removeWorld(world)) { + sender().sendMessage(C.GREEN + "Successfully removed " + world.getName() + " from bukkit.yml"); + } else { + sender().sendMessage(C.YELLOW + "Looks like the world was already removed from bukkit.yml"); + } + } catch (IOException e) { + sender().sendMessage(C.RED + "Failed to save bukkit.yml because of " + e.getMessage()); + e.printStackTrace(); + } + IrisToolbelt.evacuate(world, "Deleting world"); + deletingWorld = true; + if (!delete) { + deletingWorld = false; + return; + } + VolmitSender sender = sender(); + J.a(() -> { + int retries = 12; + + if (deleteDirectory(world.getWorldFolder())) { + sender.sendMessage(C.GREEN + "Successfully removed world folder"); + } else { + while(true){ + if (deleteDirectory(world.getWorldFolder())){ + sender.sendMessage(C.GREEN + "Successfully removed world folder"); + break; + } + retries--; + if (retries == 0){ + sender.sendMessage(C.RED + "Failed to remove world folder"); + break; + } + J.sleep(3000); + } + } + deletingWorld = false; + }); + } + + public static boolean deleteDirectory(File dir) { + if (dir.isDirectory()) { + File[] children = dir.listFiles(); + for (int i = 0; i < children.length; i++) { + boolean success = deleteDirectory(children[i]); + if (!success) { + return false; + } + } + } + return dir.delete(); + } + + @Decree(description = "Updates all chunk in the specified world") + public void updater( + @Param(description = "World to update chunks at") + World world + ) { + if (!IrisToolbelt.isIrisWorld(world)) { + sender().sendMessage(C.GOLD + "This is not an Iris world"); + return; + } + ChunkUpdater updater = new ChunkUpdater(world); + if (sender().isPlayer()) { + sender().sendMessage(C.GREEN + "Updating " + world.getName() + " Total chunks: " + Form.f(updater.getChunks())); + } else { + Iris.info(C.GREEN + "Updating " + world.getName() + " Total chunks: " + Form.f(updater.getChunks())); + } + updater.start(); + } + + @Decree(description = "Set aura spins") + public void aura( + @Param(description = "The h color value", defaultValue = "-20") + int h, + @Param(description = "The s color value", defaultValue = "7") + int s, + @Param(description = "The b color value", defaultValue = "8") + int b + ) { + IrisSettings.get().getGeneral().setSpinh(h); + IrisSettings.get().getGeneral().setSpins(s); + IrisSettings.get().getGeneral().setSpinb(b); + IrisSettings.get().forceSave(); + sender().sendMessage("Aura Spins updated to " + h + " " + s + " " + b); + } + + @Decree(description = "Bitwise calculations") + public void bitwise( + @Param(description = "The first value to run calculations on") + int value1, + @Param(description = "The operator: | & ^ ≺≺ ≻≻ %") + String operator, + @Param(description = "The second value to run calculations on") + int value2 + ) { + Integer v = null; + switch (operator) { + case "|" -> v = value1 | value2; + case "&" -> v = value1 & value2; + case "^" -> v = value1 ^ value2; + case "%" -> v = value1 % value2; + case ">>" -> v = value1 >> value2; + case "<<" -> v = value1 << value2; + } + if (v == null) { + sender().sendMessage(C.RED + "The operator you entered: (" + operator + ") is invalid!"); + return; + } + sender().sendMessage(C.GREEN + "" + value1 + " " + C.GREEN + operator.replaceAll("<", "≺").replaceAll(">", "≻").replaceAll("%", "%") + " " + C.GREEN + value2 + C.GREEN + " returns " + C.GREEN + v); + } + + @Decree(description = "Toggle debug") + public void debug( + @Param(name = "on", description = "Whether or not debug should be on", defaultValue = "other") + Boolean on + ) { + boolean to = on == null ? !IrisSettings.get().getGeneral().isDebug() : on; + IrisSettings.get().getGeneral().setDebug(to); + IrisSettings.get().forceSave(); + sender().sendMessage(C.GREEN + "Set debug to: " + to); + } + + @Decree(description = "Download a project.", aliases = "dl") + public void download( + @Param(name = "pack", description = "The pack to download", defaultValue = "overworld", aliases = "project") + String pack, + @Param(name = "branch", description = "The branch to download from", defaultValue = "main") + String branch, + @Param(name = "trim", description = "Whether or not to download a trimmed version (do not enable when editing)", defaultValue = "false") + boolean trim, + @Param(name = "overwrite", description = "Whether or not to overwrite the pack with the downloaded one", aliases = "force", defaultValue = "false") + boolean overwrite + ) { + sender().sendMessage(C.GREEN + "Downloading pack: " + pack + "/" + branch + (trim ? " trimmed" : "") + (overwrite ? " overwriting" : "")); + if (pack.equals("overworld")) { + String url = "https://github.com/IrisDimensions/overworld/releases/download/" + INMS.OVERWORLD_TAG + "/overworld.zip"; + Iris.service(StudioSVC.class).downloadRelease(sender(), url, trim, overwrite); + } else { + Iris.service(StudioSVC.class).downloadSearch(sender(), "IrisDimensions/" + pack + "/" + branch, trim, overwrite); + } + } + + @Decree(description = "Get metrics for your world", aliases = "measure", origin = DecreeOrigin.PLAYER) + public void metrics() { + if (!IrisToolbelt.isIrisWorld(world())) { + sender().sendMessage(C.RED + "You must be in an Iris world"); + return; + } + sender().sendMessage(C.GREEN + "Sending metrics..."); + engine().printMetrics(sender()); + } + + @Decree(description = "Reload configuration file (this is also done automatically)") + public void reload() { + IrisSettings.invalidate(); + IrisSettings.get(); + sender().sendMessage(C.GREEN + "Hotloaded settings"); + } + + @Decree(description = "Update the pack of a world (UNSAFE!)", name = "^world", aliases = "update-world") + public void updateWorld( + @Param(description = "The world to update", contextual = true) + World world, + @Param(description = "The pack to install into the world", contextual = true, aliases = "dimension") + IrisDimension pack, + @Param(description = "Make sure to make a backup & read the warnings first!", defaultValue = "false", aliases = "c") + boolean confirm, + @Param(description = "Should Iris download the pack again for you", defaultValue = "false", name = "fresh-download", aliases = {"fresh", "new"}) + boolean freshDownload + ) { + if (!confirm) { + sender().sendMessage(new String[]{ + C.RED + "You should always make a backup before using this", + C.YELLOW + "Issues caused by this can be, but are not limited to:", + C.YELLOW + " - Broken chunks (cut-offs) between old and new chunks (before & after the update)", + C.YELLOW + " - Regenerated chunks that do not fit in with the old chunks", + C.YELLOW + " - Structures not spawning again when regenerating", + C.YELLOW + " - Caves not lining up", + C.YELLOW + " - Terrain layers not lining up", + C.RED + "Now that you are aware of the risks, and have made a back-up:", + C.RED + "/iris ^world " + world.getName() + " " + pack.getLoadKey() + " confirm=true" + }); + return; + } + + File folder = world.getWorldFolder(); + folder.mkdirs(); + + if (freshDownload) { + Iris.service(StudioSVC.class).downloadSearch(sender(), pack.getLoadKey(), false, true); + } + + Iris.service(StudioSVC.class).installIntoWorld(sender(), pack.getLoadKey(), folder); + } + + @Decree(description = "Unload an Iris World", origin = DecreeOrigin.PLAYER, sync = true) + public void unloadWorld( + @Param(description = "The world to unload") + World world + ) { + if (!IrisToolbelt.isIrisWorld(world)) { + sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList())); + return; + } + sender().sendMessage(C.GREEN + "Unloading world: " + world.getName()); + try { + IrisToolbelt.evacuate(world); + Bukkit.unloadWorld(world, false); + sender().sendMessage(C.GREEN + "World unloaded successfully."); + } catch (Exception e) { + sender().sendMessage(C.RED + "Failed to unload the world: " + e.getMessage()); + e.printStackTrace(); + } + } + + @Decree(description = "Load an Iris World", origin = DecreeOrigin.PLAYER, sync = true, aliases = {"import"}) + public void loadWorld( + @Param(description = "The name of the world to load") + String world + ) { + World worldloaded = Bukkit.getWorld(world); + worldNameToCheck = world; + boolean worldExists = doesWorldExist(worldNameToCheck); + WorldEngine = world; + + if (!worldExists) { + sender().sendMessage(C.YELLOW + world + " Doesnt exist on the server."); + return; + } + + File BUKKIT_YML = new File("bukkit.yml"); + String pathtodim = world + File.separator +"iris"+File.separator +"pack"+File.separator +"dimensions"+File.separator; + File directory = new File(Bukkit.getWorldContainer(), pathtodim); + + String dimension = null; + if (directory.exists() && directory.isDirectory()) { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isFile()) { + String fileName = file.getName(); + if (fileName.endsWith(".json")) { + dimension = fileName.substring(0, fileName.length() - 5); + sender().sendMessage(C.BLUE + "Generator: " + dimension); + } + } + } + } + } else { + sender().sendMessage(C.GOLD + world + " is not an iris world."); + return; + } + sender().sendMessage(C.GREEN + "Loading world: " + world); + + YamlConfiguration yml = YamlConfiguration.loadConfiguration(BUKKIT_YML); + String gen = "Iris:" + dimension; + ConfigurationSection section = yml.contains("worlds") ? yml.getConfigurationSection("worlds") : yml.createSection("worlds"); + if (!section.contains(world)) { + section.createSection(world).set("generator", gen); + try { + yml.save(BUKKIT_YML); + Iris.info("Registered \"" + world + "\" in bukkit.yml"); + } catch (IOException e) { + Iris.error("Failed to update bukkit.yml!"); + e.printStackTrace(); + return; + } + } + checkForBukkitWorlds(world); + sender().sendMessage(C.GREEN + world + " loaded successfully."); + } + @Decree(description = "Evacuate an iris world", origin = DecreeOrigin.PLAYER, sync = true) + public void evacuate( + @Param(description = "Evacuate the world") + World world + ) { + if (!IrisToolbelt.isIrisWorld(world)) { + sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList())); + return; + } + sender().sendMessage(C.GREEN + "Evacuating world" + world.getName()); + IrisToolbelt.evacuate(world); + } + + boolean doesWorldExist(String worldName) { + File worldContainer = Bukkit.getWorldContainer(); + File worldDirectory = new File(worldContainer, worldName); + return worldDirectory.exists() && worldDirectory.isDirectory(); + } + private void checkForBukkitWorlds(String world) { + FileConfiguration fc = new YamlConfiguration(); + try { + fc.load(new File("bukkit.yml")); + ConfigurationSection section = fc.getConfigurationSection("worlds"); + if (section == null) { + return; + } + + List worldsToLoad = Collections.singletonList(world); + + for (String s : section.getKeys(false)) { + if (!worldsToLoad.contains(s)) { + continue; + } + ConfigurationSection entry = section.getConfigurationSection(s); + if (!entry.contains("generator", true)) { + continue; + } + String generator = entry.getString("generator"); + if (generator.startsWith("Iris:")) { + generator = generator.split("\\Q:\\E")[1]; + } else if (generator.equalsIgnoreCase("Iris")) { + generator = IrisSettings.get().getGenerator().getDefaultWorldType(); + } else { + continue; + } + Iris.info("2 World: %s | Generator: %s", s, generator); + if (Bukkit.getWorlds().stream().anyMatch(w -> w.getName().equals(s))) { + continue; + } + Iris.info(C.LIGHT_PURPLE + "Preparing Spawn for " + s + "' using Iris:" + generator + "..."); + WorldCreator c = new WorldCreator(s) + .generator(getDefaultWorldGenerator(s, generator)) + .environment(IrisData.loadAnyDimension(generator).getEnvironment()); + INMS.get().createWorld(c); + Iris.info(C.LIGHT_PURPLE + "Loaded " + s + "!"); + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { + Iris.debug("Default World Generator Called for " + worldName + " using ID: " + id); + IrisDimension dim; + if (id == null || id.isEmpty()) { + dim = IrisData.loadAnyDimension(IrisSettings.get().getGenerator().getDefaultWorldType()); + } else { + dim = IrisData.loadAnyDimension(id); + } + Iris.debug("Generator ID: " + id + " requested by bukkit/plugin"); + + 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."); + } + } + Iris.debug("Assuming IrisDimension: " + dim.getName()); + IrisWorld w = IrisWorld.builder() + .name(worldName) + .seed(1337) + .environment(dim.getEnvironment()) + .worldFolder(new File(Bukkit.getWorldContainer(), worldName)) + .minHeight(dim.getMinHeight()) + .maxHeight(dim.getMaxHeight()) + .build(); + Iris.debug("Generator Config: " + w.toString()); + File ff = new File(w.worldFolder(), "iris/pack"); + if (!ff.exists() || ff.listFiles().length == 0) { + ff.mkdirs(); + service(StudioSVC.class).installIntoWorld(sender, dim.getLoadKey(), ff.getParentFile()); + } + return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey()); + } +} diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java b/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java index 899ff7270..ccbf91c11 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java @@ -241,7 +241,8 @@ public class CommandObject implements DecreeExecutor { Location[] b = WandSVC.getCuboid(player()); - if (b == null) { + if (b == null || b[0] == null || b[1] == null) { + sender().sendMessage("No area selected."); return; } Location a1 = b[0].clone(); @@ -417,6 +418,10 @@ public class CommandObject implements DecreeExecutor { } Location[] b = WandSVC.getCuboid(player()); + if (b == null || b[0] == null || b[1] == null) { + sender().sendMessage("No area selected."); + return; + } Location a1 = b[0].clone(); Location a2 = b[1].clone(); Direction d = Direction.closest(player().getLocation().getDirection()).reverse(); @@ -477,6 +482,10 @@ public class CommandObject implements DecreeExecutor { } Location[] b = WandSVC.getCuboid(player()); + if (b == null || b[0] == null || b[1] == null) { + sender().sendMessage("No area selected."); + return; + } Location a1 = b[0].clone(); Location a2 = b[1].clone(); Location a1x = b[0].clone(); @@ -524,6 +533,10 @@ public class CommandObject implements DecreeExecutor { } Location[] b = WandSVC.getCuboid(player()); + if (b == null || b[0] == null || b[1] == null) { + sender().sendMessage("No area selected."); + return; + } b[0].add(new Vector(0, 1, 0)); b[1].add(new Vector(0, 1, 0)); Location a1 = b[0].clone(); diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandPregen.java b/core/src/main/java/com/volmit/iris/core/commands/CommandPregen.java index 6d7bc42a6..2de5cef25 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandPregen.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandPregen.java @@ -39,7 +39,9 @@ public class CommandPregen implements DecreeExecutor { @Param(description = "The world to pregen", contextual = true) World world, @Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0") - Vector center + Vector center, + @Param(description = "Open the Iris pregen gui", defaultValue = "true") + boolean gui ) { try { if (sender().isPlayer() && access() == null) { @@ -50,7 +52,7 @@ public class CommandPregen implements DecreeExecutor { IrisToolbelt.pregenerate(PregenTask .builder() .center(new Position2(center.getBlockX(), center.getBlockZ())) - .gui(true) + .gui(gui) .radiusX(radius) .radiusZ(radius) .build(), world); diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java index 0abb77a15..1635dd3bb 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java @@ -46,18 +46,20 @@ import com.volmit.iris.util.interpolation.InterpolationMethod; import com.volmit.iris.util.io.IO; import com.volmit.iris.util.json.JSONArray; import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.mantle.MantleChunk; +import com.volmit.iris.util.mantle.MantleFlag; import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.math.Spiraler; import com.volmit.iris.util.noise.CNG; -import com.volmit.iris.util.parallel.BurstExecutor; import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.parallel.SyncExecutor; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.O; import com.volmit.iris.util.scheduling.PrecisionStopwatch; -import com.volmit.iris.util.scheduling.jobs.QueueJob; +import com.volmit.iris.util.scheduling.jobs.ParallelQueueJob; import io.papermc.lib.PaperLib; import org.bukkit.*; import org.bukkit.event.inventory.InventoryType; @@ -76,8 +78,7 @@ import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Date; import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; @@ -161,70 +162,77 @@ public class CommandStudio implements DecreeExecutor { @Param(name = "radius", description = "The radius of nearby cunks", defaultValue = "5") int radius ) { - if (IrisToolbelt.isIrisWorld(player().getWorld())) { - VolmitSender sender = sender(); - J.a(() -> { - DecreeContext.touch(sender); - PlatformChunkGenerator plat = IrisToolbelt.access(player().getWorld()); - Engine engine = plat.getEngine(); - try { - Chunk cx = player().getLocation().getChunk(); - KList js = new KList<>(); - BurstExecutor b = MultiBurst.burst.burst(); - b.setMulticore(false); - int rad = engine.getMantle().getRadius(); - for (int i = -(radius + rad); i <= radius + rad; i++) { - for (int j = -(radius + rad); j <= radius + rad; j++) { - engine.getMantle().getMantle().deleteChunk(i + cx.getX(), j + cx.getZ()); - } - } - - for (int i = -radius; i <= radius; i++) { - for (int j = -radius; j <= radius; j++) { - int finalJ = j; - int finalI = i; - b.queue(() -> plat.injectChunkReplacement(player().getWorld(), finalI + cx.getX(), finalJ + cx.getZ(), (f) -> { - synchronized (js) { - js.add(f); - } - })); - } - } - - b.complete(); - sender().sendMessage(C.GREEN + "Regenerating " + Form.f(js.size()) + " Sections"); - QueueJob r = new QueueJob<>() { - final KList> futures = new KList<>(); - - @Override - public void execute(Runnable runnable) { - futures.add(J.sfut(runnable)); - - if (futures.size() > 64) { - while (futures.isNotEmpty()) { - try { - futures.remove(0).get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - } - } - - @Override - public String getName() { - return "Regenerating"; - } - }; - r.queue(js); - r.execute(sender()); - } catch (Throwable e) { - sender().sendMessage("Unable to parse view-distance"); - } - }); - } else { + World world = player().getWorld(); + if (!IrisToolbelt.isIrisWorld(world)) { sender().sendMessage(C.RED + "You must be in an Iris World to use regen!"); } + + VolmitSender sender = sender(); + var loc = player().getLocation().clone(); + + J.a(() -> { + DecreeContext.touch(sender); + PlatformChunkGenerator plat = IrisToolbelt.access(world); + Engine engine = plat.getEngine(); + try (SyncExecutor executor = new SyncExecutor(20)) { + int x = loc.getBlockX() >> 4; + int z = loc.getBlockZ() >> 4; + + int rad = engine.getMantle().getRadius(); + var mantle = engine.getMantle().getMantle(); + var chunkMap = new KMap(); + for (int i = -(radius + rad); i <= radius + rad; i++) { + for (int j = -(radius + rad); j <= radius + rad; j++) { + int xx = i + x, zz = j + z; + if (Math.abs(i) <= radius && Math.abs(j) <= radius) { + mantle.deleteChunk(xx, zz); + continue; + } + chunkMap.put(new Position2(xx, zz), mantle.getChunk(xx, zz)); + mantle.deleteChunk(xx, zz); + } + } + + ParallelQueueJob job = new ParallelQueueJob<>() { + @Override + public void execute(Position2 p) { + plat.injectChunkReplacement(world, p.getX(), p.getZ(), executor); + } + + @Override + public String getName() { + return "Regenerating"; + } + }; + for (int i = -radius; i <= radius; i++) { + for (int j = -radius; j <= radius; j++) { + job.queue(new Position2(i + x, j + z)); + } + } + + CountDownLatch latch = new CountDownLatch(1); + job.execute(sender(), latch::countDown); + latch.await(); + + int sections = mantle.getWorldHeight() >> 4; + chunkMap.forEach((pos, chunk) -> { + var c = mantle.getChunk(pos.getX(), pos.getZ()); + for (MantleFlag flag : MantleFlag.values()) { + c.flag(flag, chunk.isFlagged(flag)); + } + c.clear(); + for (int y = 0; y < sections; y++) { + var slice = chunk.get(y); + if (slice == null) continue; + var s = c.getOrCreate(y); + slice.getSliceMap().forEach(s::putSlice); + } + }); + } catch (Throwable e) { + sender().sendMessage("Error while regenerating chunks"); + e.printStackTrace(); + } + }); } @Decree(description = "Convert objects in the \"convert\" folder") diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandSupport.java b/core/src/main/java/com/volmit/iris/core/commands/CommandSupport.java deleted file mode 100644 index 0f7baad0d..000000000 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandSupport.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.core.commands; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; -import com.volmit.iris.core.pregenerator.ChunkUpdater; -import com.volmit.iris.core.service.IrisEngineSVC; -import com.volmit.iris.core.tools.IrisPackBenchmarking; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.engine.object.IrisDimension; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.decree.DecreeExecutor; -import com.volmit.iris.util.decree.DecreeOrigin; -import com.volmit.iris.util.decree.annotations.Decree; -import com.volmit.iris.util.decree.annotations.Param; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.io.IO; -import com.volmit.iris.util.mantle.TectonicPlate; -import com.volmit.iris.util.misc.Hastebin; -import com.volmit.iris.util.misc.Platform; -import com.volmit.iris.util.misc.getHardware; -import com.volmit.iris.util.nbt.mca.MCAFile; -import com.volmit.iris.util.nbt.mca.MCAUtil; -import com.volmit.iris.util.plugin.VolmitSender; -import net.jpountz.lz4.LZ4BlockInputStream; -import net.jpountz.lz4.LZ4BlockOutputStream; -import net.jpountz.lz4.LZ4FrameInputStream; -import net.jpountz.lz4.LZ4FrameOutputStream; -import org.apache.commons.lang.RandomStringUtils; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.World; -import oshi.SystemInfo; - -import java.io.*; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.util.*; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -@Decree(name = "Support", origin = DecreeOrigin.BOTH, description = "Iris World Manager", aliases = {"support"}) -public class CommandSupport implements DecreeExecutor { - - @Decree(description = "report") - public void report() { - try { - if (sender().isPlayer()) sender().sendMessage(C.GOLD + "Creating report.."); - if (!sender().isPlayer()) Iris.info(C.GOLD + "Creating report.."); - Hastebin.enviornment(sender()); - - } catch (Exception e) { - Iris.info(C.RED + "Something went wrong: "); - e.printStackTrace(); - } - } - - -} - - diff --git a/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java b/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java index 63b8919db..348b42349 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java +++ b/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java @@ -40,7 +40,10 @@ import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.image.BufferedImage; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; @@ -53,7 +56,7 @@ public class PregeneratorJob implements PregenListener { private static final Color COLOR_NETWORK_GENERATING = parseColor("#836b8c"); private static final Color COLOR_GENERATED = parseColor("#65c295"); private static final Color COLOR_CLEANED = parseColor("#34eb93"); - public static PregeneratorJob instance; + private static final AtomicReference instance = new AtomicReference<>(); private final MemoryMonitor monitor; private final PregenTask task; private final boolean saving; @@ -64,14 +67,21 @@ public class PregeneratorJob implements PregenListener { private final Position2 max; private final ChronoLatch cl = new ChronoLatch(TimeUnit.MINUTES.toMillis(1)); private final Engine engine; + private final ExecutorService service; private JFrame frame; private PregenRenderer renderer; private int rgc = 0; private String[] info; public PregeneratorJob(PregenTask task, PregeneratorMethod method, Engine engine) { + instance.updateAndGet(old -> { + if (old != null) { + old.pregenerator.close(); + old.close(); + } + return this; + }); this.engine = engine; - instance = this; monitor = new MemoryMonitor(50); saving = false; info = new String[]{"Initializing..."}; @@ -96,40 +106,44 @@ public class PregeneratorJob implements PregenListener { }, "Iris Pregenerator"); t.setPriority(Thread.MIN_PRIORITY); t.start(); + service = Executors.newVirtualThreadPerTaskExecutor(); } public static boolean shutdownInstance() { - if (instance == null) { + PregeneratorJob inst = instance.get(); + if (inst == null) { return false; } - J.a(() -> instance.pregenerator.close()); + J.a(inst.pregenerator::close); return true; } public static PregeneratorJob getInstance() { - return instance; + return instance.get(); } public static boolean pauseResume() { - if (instance == null) { + PregeneratorJob inst = instance.get(); + if (inst == null) { return false; } if (isPaused()) { - instance.pregenerator.resume(); + inst.pregenerator.resume(); } else { - instance.pregenerator.pause(); + inst.pregenerator.pause(); } return true; } public static boolean isPaused() { - if (instance == null) { + PregeneratorJob inst = instance.get(); + if (inst == null) { return true; } - return instance.paused(); + return inst.paused(); } private static Color parseColor(String c) { @@ -179,7 +193,7 @@ public class PregeneratorJob implements PregenListener { J.a(() -> { pregenerator.close(); close(); - instance = null; + instance.compareAndSet(this, null); }); } @@ -219,10 +233,10 @@ public class PregeneratorJob implements PregenListener { } @Override - public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method) { + public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached) { info = new String[]{ (paused() ? "PAUSED" : (saving ? "Saving... " : "Generating")) + " " + Form.f(generated) + " of " + Form.f(totalChunks) + " (" + Form.pc(percent, 0) + " Complete)", - "Speed: " + Form.f(chunksPerSecond, 0) + " Chunks/s, " + Form.f(regionsPerMinute, 1) + " Regions/m, " + Form.f(chunksPerMinute, 0) + " Chunks/m", + "Speed: " + (cached ? "Cached " : "") + Form.f(chunksPerSecond, 0) + " Chunks/s, " + Form.f(regionsPerMinute, 1) + " Regions/m, " + Form.f(chunksPerMinute, 0) + " Chunks/m", Form.duration(eta, 2) + " Remaining " + " (" + Form.duration(elapsed, 2) + " Elapsed)", "Generation Method: " + method, "Memory: " + Form.memSize(monitor.getUsedBytes(), 2) + " (" + Form.pc(monitor.getUsagePercent(), 0) + ") Pressure: " + Form.memSize(monitor.getPressure(), 0) + "/s", @@ -240,13 +254,16 @@ public class PregeneratorJob implements PregenListener { } @Override - public void onChunkGenerated(int x, int z) { - if (engine != null) { - draw(x, z, engine.draw((x << 4) + 8, (z << 4) + 8)); - return; - } + public void onChunkGenerated(int x, int z, boolean cached) { + if (renderer == null || frame == null || !frame.isVisible()) return; + service.submit(() -> { + if (engine != null) { + draw(x, z, engine.draw((x << 4) + 8, (z << 4) + 8)); + return; + } - draw(x, z, COLOR_GENERATED); + draw(x, z, COLOR_GENERATED); + }); } @Override @@ -304,8 +321,9 @@ public class PregeneratorJob implements PregenListener { @Override public void onClose() { close(); - instance = null; + instance.compareAndSet(this, null); whenDone.forEach(Runnable::run); + service.shutdownNow(); } @Override diff --git a/core/src/main/java/com/volmit/iris/core/link/ExternalDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/ExternalDataProvider.java index 02bf058a8..bbc7e8088 100644 --- a/core/src/main/java/com/volmit/iris/core/link/ExternalDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/ExternalDataProvider.java @@ -1,24 +1,33 @@ package com.volmit.iris.core.link; +import com.volmit.iris.core.link.data.DataType; +import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.data.IrisCustomData; +import com.volmit.iris.util.math.RNG; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Entity; +import org.bukkit.event.Listener; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.MissingResourceException; @Getter @RequiredArgsConstructor -public abstract class ExternalDataProvider { +public abstract class ExternalDataProvider implements Listener { @NonNull private final String pluginId; @@ -53,7 +62,9 @@ public abstract class ExternalDataProvider { * @throws MissingResourceException when the blockId is invalid */ @NotNull - public abstract BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException; + public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { + throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); + } /** * @see ExternalDataProvider#getItemStack(Identifier) @@ -73,7 +84,9 @@ public abstract class ExternalDataProvider { * @throws MissingResourceException when the itemId is invalid */ @NotNull - public abstract ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException; + public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException { + throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key()); + } /** * This method is used for placing blocks that need to use the plugins api @@ -85,9 +98,43 @@ public abstract class ExternalDataProvider { */ public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) {} - public abstract @NotNull Identifier[] getBlockTypes(); + /** + * Spawns a mob in the specified location using the given engine and entity identifier. + * + * @param location The location in the world where the mob should spawn. Must not be null. + * @param entityId The identifier of the mob entity to spawn. Must not be null. + * @return The spawned {@link Entity} if successful, or null if the mob could not be spawned. + */ + @Nullable + public Entity spawnMob(@NotNull Location location, @NotNull Identifier entityId) throws MissingResourceException { + throw new MissingResourceException("Failed to find Entity!", entityId.namespace(), entityId.key()); + } - public abstract @NotNull Identifier[] getItemTypes(); + public abstract @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType); - public abstract boolean isValidProvider(@NotNull Identifier id, boolean isItem); + public abstract boolean isValidProvider(@NotNull Identifier id, DataType dataType); + + protected static Pair parseYawAndFace(@NotNull Engine engine, @NotNull Block block, @NotNull KMap<@NotNull String, @NotNull String> state) { + float yaw = 0; + BlockFace face = BlockFace.NORTH; + + long seed = engine.getSeedManager().getSeed() + Cache.key(block.getX(), block.getZ()) + block.getY(); + RNG rng = new RNG(seed); + if ("true".equals(state.get("randomYaw"))) { + yaw = rng.f(0, 360); + } else if (state.containsKey("yaw")) { + yaw = Float.parseFloat(state.get("yaw")); + } + if ("true".equals(state.get("randomFace"))) { + BlockFace[] faces = BlockFace.values(); + face = faces[rng.i(0, faces.length - 1)]; + } else if (state.containsKey("face")) { + face = BlockFace.valueOf(state.get("face").toUpperCase()); + } + if (face == BlockFace.SELF) { + face = BlockFace.NORTH; + } + + return new Pair<>(yaw, face); + } } 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/link/MythicMobsLink.java b/core/src/main/java/com/volmit/iris/core/link/MythicMobsLink.java deleted file mode 100644 index 8a7d1d7be..000000000 --- a/core/src/main/java/com/volmit/iris/core/link/MythicMobsLink.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.core.link; - -import io.lumine.mythic.bukkit.MythicBukkit; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.entity.Entity; -import org.bukkit.plugin.Plugin; - -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.List; - -public class MythicMobsLink { - - public MythicMobsLink() { - - } - - public boolean isEnabled() { - return getPlugin() != null; - } - - public Plugin getPlugin() { - return Bukkit.getPluginManager().getPlugin("MythicMobs"); - } - - /** - * Spawn a mythic mob at this location - * - * @param mob The mob - * @param location The location - * @return The mob, or null if it can't be spawned - */ - public @Nullable - Entity spawnMob(String mob, Location location) { - return isEnabled() ? MythicBukkit.inst().getMobManager().spawnMob(mob, location).getEntity().getBukkitEntity() : null; - } - - public Collection getMythicMobTypes() { - return isEnabled() ? MythicBukkit.inst().getMobManager().getMobNames() : List.of(); - } -} diff --git a/core/src/main/java/com/volmit/iris/core/link/WorldEditLink.java b/core/src/main/java/com/volmit/iris/core/link/WorldEditLink.java index 42e335a10..638c66cc2 100644 --- a/core/src/main/java/com/volmit/iris/core/link/WorldEditLink.java +++ b/core/src/main/java/com/volmit/iris/core/link/WorldEditLink.java @@ -47,6 +47,7 @@ public class WorldEditLink { } catch (Throwable e) { Iris.error("Could not get selection"); e.printStackTrace(); + Iris.reportError(e); active.reset(); active.aquire(() -> false); } diff --git a/core/src/main/java/com/volmit/iris/core/link/data/DataType.java b/core/src/main/java/com/volmit/iris/core/link/data/DataType.java new file mode 100644 index 000000000..65edfc2a3 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/link/data/DataType.java @@ -0,0 +1,33 @@ +package com.volmit.iris.core.link.data; + +import com.volmit.iris.core.link.ExternalDataProvider; +import com.volmit.iris.core.link.Identifier; + +import java.util.MissingResourceException; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + +public enum DataType implements BiPredicate { + ITEM, + BLOCK, + ENTITY; + + @Override + public boolean test(ExternalDataProvider dataProvider, Identifier identifier) { + if (!dataProvider.isValidProvider(identifier, this)) return false; + try { + switch (this) { + case ITEM -> dataProvider.getItemStack(identifier); + case BLOCK -> dataProvider.getBlockData(identifier); + case ENTITY -> {} + } + return true; + } catch (MissingResourceException e) { + return false; + } + } + + public Predicate asPredicate(ExternalDataProvider dataProvider) { + return i -> test(dataProvider, i); + } +} diff --git a/core/src/main/java/com/volmit/iris/core/link/EcoItemsDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/EcoItemsDataProvider.java similarity index 57% rename from core/src/main/java/com/volmit/iris/core/link/EcoItemsDataProvider.java rename to core/src/main/java/com/volmit/iris/core/link/data/EcoItemsDataProvider.java index 81eb9669a..05e9cd221 100644 --- a/core/src/main/java/com/volmit/iris/core/link/EcoItemsDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/EcoItemsDataProvider.java @@ -1,16 +1,18 @@ -package com.volmit.iris.core.link; +package com.volmit.iris.core.link.data; import com.volmit.iris.Iris; -import com.volmit.iris.util.collection.KList; +import com.volmit.iris.core.link.ExternalDataProvider; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.reflect.WrappedField; import com.willfp.ecoitems.items.EcoItem; import com.willfp.ecoitems.items.EcoItems; import org.bukkit.NamespacedKey; -import org.bukkit.block.data.BlockData; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import java.util.Collection; +import java.util.List; import java.util.MissingResourceException; public class EcoItemsDataProvider extends ExternalDataProvider { @@ -34,12 +36,6 @@ public class EcoItemsDataProvider extends ExternalDataProvider { } } - @NotNull - @Override - public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { - throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); - } - @NotNull @Override public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException { @@ -48,30 +44,18 @@ public class EcoItemsDataProvider extends ExternalDataProvider { return itemStack.get(item).clone(); } - @NotNull @Override - public Identifier[] getBlockTypes() { - return new Identifier[0]; - } - - @NotNull - @Override - public Identifier[] getItemTypes() { - KList names = new KList<>(); - for (EcoItem item : EcoItems.INSTANCE.values()) { - try { - Identifier key = Identifier.fromNamespacedKey(id.get(item)); - if (getItemStack(key) != null) - names.add(key); - } catch (MissingResourceException ignored) { - } - } - - return names.toArray(new Identifier[0]); + public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) { + if (dataType != DataType.ITEM) return List.of(); + return EcoItems.INSTANCE.values() + .stream() + .map(x -> Identifier.fromNamespacedKey(id.get(x))) + .filter(dataType.asPredicate(this)) + .toList(); } @Override - public boolean isValidProvider(@NotNull Identifier id, boolean isItem) { - return id.namespace().equalsIgnoreCase("ecoitems") && isItem; + public boolean isValidProvider(@NotNull Identifier id, DataType dataType) { + return id.namespace().equalsIgnoreCase("ecoitems") && dataType == DataType.ITEM; } } diff --git a/core/src/main/java/com/volmit/iris/core/link/ExecutableItemsDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/ExecutableItemsDataProvider.java similarity index 50% rename from core/src/main/java/com/volmit/iris/core/link/ExecutableItemsDataProvider.java rename to core/src/main/java/com/volmit/iris/core/link/data/ExecutableItemsDataProvider.java index 80fc25860..88a2dc943 100644 --- a/core/src/main/java/com/volmit/iris/core/link/ExecutableItemsDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/ExecutableItemsDataProvider.java @@ -1,13 +1,15 @@ -package com.volmit.iris.core.link; +package com.volmit.iris.core.link.data; import com.ssomar.score.api.executableitems.ExecutableItemsAPI; import com.volmit.iris.Iris; -import com.volmit.iris.util.collection.KList; +import com.volmit.iris.core.link.ExternalDataProvider; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.util.collection.KMap; -import org.bukkit.block.data.BlockData; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import java.util.Collection; +import java.util.List; import java.util.MissingResourceException; import java.util.Optional; @@ -21,12 +23,6 @@ public class ExecutableItemsDataProvider extends ExternalDataProvider { Iris.info("Setting up ExecutableItems Link..."); } - @NotNull - @Override - public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { - throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); - } - @NotNull @Override public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException { @@ -35,30 +31,19 @@ public class ExecutableItemsDataProvider extends ExternalDataProvider { .orElseThrow(() -> new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key())); } - @NotNull @Override - public Identifier[] getBlockTypes() { - return new Identifier[0]; - } - - @NotNull - @Override - public Identifier[] getItemTypes() { - KList names = new KList<>(); - for (String name : ExecutableItemsAPI.getExecutableItemsManager().getExecutableItemIdsList()) { - try { - Identifier key = new Identifier("executable_items", name); - if (getItemStack(key) != null) - names.add(key); - } catch (MissingResourceException ignored) { - } - } - - return names.toArray(new Identifier[0]); + public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) { + if (dataType != DataType.ITEM) return List.of(); + return ExecutableItemsAPI.getExecutableItemsManager() + .getExecutableItemIdsList() + .stream() + .map(name -> new Identifier("executable_items", name)) + .filter(dataType.asPredicate(this)) + .toList(); } @Override - public boolean isValidProvider(@NotNull Identifier key, boolean isItem) { - return key.namespace().equalsIgnoreCase("executable_items") && isItem; + public boolean isValidProvider(@NotNull Identifier key, DataType dataType) { + return key.namespace().equalsIgnoreCase("executable_items") && dataType == DataType.ITEM; } } diff --git a/core/src/main/java/com/volmit/iris/core/link/HMCLeavesDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/HMCLeavesDataProvider.java similarity index 81% rename from core/src/main/java/com/volmit/iris/core/link/HMCLeavesDataProvider.java rename to core/src/main/java/com/volmit/iris/core/link/data/HMCLeavesDataProvider.java index 5f5a358fd..bbf2cd197 100644 --- a/core/src/main/java/com/volmit/iris/core/link/HMCLeavesDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/HMCLeavesDataProvider.java @@ -1,10 +1,11 @@ -package com.volmit.iris.core.link; +package com.volmit.iris.core.link.data; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.link.ExternalDataProvider; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.service.ExternalDataSVC; 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.data.IrisCustomData; import com.volmit.iris.util.reflect.WrappedField; @@ -18,6 +19,8 @@ import org.bukkit.block.data.type.Leaves; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.MissingResourceException; import java.util.function.Supplier; @@ -89,41 +92,20 @@ public class HMCLeavesDataProvider extends ExternalDataProvider { } } - @NotNull @Override - public Identifier[] getBlockTypes() { - KList names = new KList<>(); - for (String name : blockDataMap.keySet()) { - try { - Identifier key = new Identifier("hmcleaves", name); - if (getBlockData(key) != null) - names.add(key); - } catch (MissingResourceException ignored) { - } - } - - return names.toArray(new Identifier[0]); - } - - @NotNull - @Override - public Identifier[] getItemTypes() { - KList names = new KList<>(); - for (String name : itemDataField.keySet()) { - try { - Identifier key = new Identifier("hmcleaves", name); - if (getItemStack(key) != null) - names.add(key); - } catch (MissingResourceException ignored) { - } - } - - return names.toArray(new Identifier[0]); + public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) { + if (dataType == DataType.ENTITY) return List.of(); + return (dataType == DataType.BLOCK ? blockDataMap.keySet() : itemDataField.keySet()) + .stream() + .map(x -> new Identifier("hmcleaves", x)) + .filter(dataType.asPredicate(this)) + .toList(); } @Override - public boolean isValidProvider(@NotNull Identifier id, boolean isItem) { - return (isItem ? itemDataField.keySet() : blockDataMap.keySet()).contains(id.key()); + public boolean isValidProvider(@NotNull Identifier id, DataType dataType) { + if (dataType == DataType.ENTITY) return false; + return (dataType == DataType.ITEM ? itemDataField.keySet() : blockDataMap.keySet()).contains(id.key()); } private Map getMap(C config, String name) { diff --git a/core/src/main/java/com/volmit/iris/core/link/ItemAdderDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java similarity index 59% rename from core/src/main/java/com/volmit/iris/core/link/ItemAdderDataProvider.java rename to core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java index d7e891da0..c0bf27ef4 100644 --- a/core/src/main/java/com/volmit/iris/core/link/ItemAdderDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java @@ -1,76 +1,76 @@ -package com.volmit.iris.core.link; - -import com.volmit.iris.Iris; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import dev.lone.itemsadder.api.CustomBlock; -import dev.lone.itemsadder.api.CustomStack; -import org.bukkit.block.data.BlockData; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; - -import java.util.MissingResourceException; - -public class ItemAdderDataProvider extends ExternalDataProvider { - - private KList itemNamespaces, blockNamespaces; - - public ItemAdderDataProvider() { - super("ItemsAdder"); - } - - @Override - public void init() { - this.itemNamespaces = new KList<>(); - this.blockNamespaces = new KList<>(); - - for (Identifier i : getItemTypes()) { - itemNamespaces.addIfMissing(i.namespace()); - } - for (Identifier i : getBlockTypes()) { - blockNamespaces.addIfMissing(i.namespace()); - Iris.info("Found ItemAdder Block: " + i); - } - } - - @NotNull - @Override - public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { - return CustomBlock.getBaseBlockData(blockId.toString()); - } - - @NotNull - @Override - public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException { - CustomStack stack = CustomStack.getInstance(itemId.toString()); - if (stack == null) { - throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key()); - } - return stack.getItemStack(); - } - - @NotNull - @Override - public Identifier[] getBlockTypes() { - KList keys = new KList<>(); - for (String s : CustomBlock.getNamespacedIdsInRegistry()) { - keys.add(Identifier.fromString(s)); - } - return keys.toArray(new Identifier[0]); - } - - @NotNull - @Override - public Identifier[] getItemTypes() { - KList keys = new KList<>(); - for (String s : CustomStack.getNamespacedIdsInRegistry()) { - keys.add(Identifier.fromString(s)); - } - return keys.toArray(new Identifier[0]); - } - - @Override - public boolean isValidProvider(@NotNull Identifier id, boolean isItem) { - return isItem ? this.itemNamespaces.contains(id.namespace()) : this.blockNamespaces.contains(id.namespace()); - } -} +package com.volmit.iris.core.link.data; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.link.ExternalDataProvider; +import com.volmit.iris.core.link.Identifier; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import dev.lone.itemsadder.api.CustomBlock; +import dev.lone.itemsadder.api.CustomStack; +import org.bukkit.block.data.BlockData; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.List; +import java.util.MissingResourceException; + +public class ItemAdderDataProvider extends ExternalDataProvider { + + private KList itemNamespaces, blockNamespaces; + + public ItemAdderDataProvider() { + super("ItemsAdder"); + } + + @Override + public void init() { + this.itemNamespaces = new KList<>(); + this.blockNamespaces = new KList<>(); + + for (Identifier i : getTypes(DataType.ITEM)) { + itemNamespaces.addIfMissing(i.namespace()); + } + for (Identifier i : getTypes(DataType.BLOCK)) { + blockNamespaces.addIfMissing(i.namespace()); + Iris.info("Found ItemAdder Block: " + i); + } + } + + @NotNull + @Override + public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { + return CustomBlock.getBaseBlockData(blockId.toString()); + } + + @NotNull + @Override + public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException { + CustomStack stack = CustomStack.getInstance(itemId.toString()); + if (stack == null) { + throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key()); + } + return stack.getItemStack(); + } + + @Override + public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) { + return switch (dataType) { + case ENTITY -> List.of(); + case ITEM -> CustomStack.getNamespacedIdsInRegistry() + .stream() + .map(Identifier::fromString) + .toList(); + case BLOCK -> CustomBlock.getNamespacedIdsInRegistry() + .stream() + .map(Identifier::fromString) + .toList(); + }; + } + + @Override + public boolean isValidProvider(@NotNull Identifier id, DataType dataType) { + if (dataType == DataType.ENTITY) return false; + return dataType == DataType.ITEM ? this.itemNamespaces.contains(id.namespace()) : this.blockNamespaces.contains(id.namespace()); + } +} diff --git a/core/src/main/java/com/volmit/iris/core/link/data/KGeneratorsDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/KGeneratorsDataProvider.java new file mode 100644 index 000000000..ea4f6e4ef --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/link/data/KGeneratorsDataProvider.java @@ -0,0 +1,74 @@ +package com.volmit.iris.core.link.data; + +import com.volmit.iris.core.link.ExternalDataProvider; +import com.volmit.iris.core.link.Identifier; +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.Collection; +import java.util.List; +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 Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) { + if (dataType == DataType.ENTITY) return List.of(); + return Main.getGenerators().getAll().stream() + .map(gen -> new Identifier("kgenerators", gen.getId())) + .filter(dataType.asPredicate(this)) + .toList(); + } + + @Override + public boolean isValidProvider(@NotNull Identifier id, DataType dataType) { + if (dataType == DataType.ENTITY) return false; + return "kgenerators".equalsIgnoreCase(id.namespace()); + } +} diff --git a/core/src/main/java/com/volmit/iris/core/link/MMOItemsDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/MMOItemsDataProvider.java similarity index 64% rename from core/src/main/java/com/volmit/iris/core/link/MMOItemsDataProvider.java rename to core/src/main/java/com/volmit/iris/core/link/data/MMOItemsDataProvider.java index 251665a54..0bf3fc490 100644 --- a/core/src/main/java/com/volmit/iris/core/link/MMOItemsDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/MMOItemsDataProvider.java @@ -1,21 +1,24 @@ -package com.volmit.iris.core.link; +package com.volmit.iris.core.link.data; import com.volmit.iris.Iris; -import com.volmit.iris.util.collection.KList; +import com.volmit.iris.core.link.ExternalDataProvider; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.scheduling.J; import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.api.ItemTier; -import net.Indyuce.mmoitems.api.Type; import net.Indyuce.mmoitems.api.block.CustomBlock; import org.bukkit.Bukkit; import org.bukkit.block.data.BlockData; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import java.util.Collection; +import java.util.List; import java.util.MissingResourceException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; public class MMOItemsDataProvider extends ExternalDataProvider { @@ -85,52 +88,35 @@ public class MMOItemsDataProvider extends ExternalDataProvider { return item; } - @NotNull @Override - public Identifier[] getBlockTypes() { - KList names = new KList<>(); - for (Integer id : api().getCustomBlocks().getBlockIds()) { - try { - Identifier key = new Identifier("mmoitems", String.valueOf(id)); - if (getBlockData(key) != null) - names.add(key); - } catch (MissingResourceException ignored) { - } - } - return names.toArray(new Identifier[0]); - } + public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) { + return switch (dataType) { + case ENTITY -> List.of(); + case BLOCK -> api().getCustomBlocks().getBlockIds().stream().map(id -> new Identifier("mmoitems", String.valueOf(id))) + .filter(dataType.asPredicate(this)) + .toList(); + case ITEM -> { + Supplier> supplier = () -> api().getTypes() + .getAll() + .stream() + .flatMap(type -> api() + .getTemplates() + .getTemplateNames(type) + .stream() + .map(name -> new Identifier("mmoitems_" + type.getId(), name))) + .filter(dataType.asPredicate(this)) + .toList(); - @NotNull - @Override - public Identifier[] getItemTypes() { - KList names = new KList<>(); - Runnable run = () -> { - for (Type type : api().getTypes().getAll()) { - for (String name : api().getTemplates().getTemplateNames(type)) { - try { - Identifier key = new Identifier("mmoitems_" + type.getId(), name); - if (getItemStack(key) != null) - names.add(key); - } catch (MissingResourceException ignored) { - } - } + if (Bukkit.isPrimaryThread()) yield supplier.get(); + else yield J.sfut(supplier).join(); } }; - if (Bukkit.isPrimaryThread()) run.run(); - else { - try { - J.sfut(run).get(); - } catch (InterruptedException | ExecutionException e) { - Iris.error("Failed getting MMOItems item types!"); - Iris.reportError(e); - } - } - return names.toArray(new Identifier[0]); } @Override - public boolean isValidProvider(@NotNull Identifier id, boolean isItem) { - return isItem ? id.namespace().split("_", 2).length == 2 : id.namespace().equals("mmoitems"); + public boolean isValidProvider(@NotNull Identifier id, DataType dataType) { + if (dataType == DataType.ENTITY) return false; + return dataType == DataType.ITEM ? id.namespace().split("_", 2).length == 2 : id.namespace().equals("mmoitems"); } private MMOItems api() { diff --git a/core/src/main/java/com/volmit/iris/core/link/MythicCrucibleDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/MythicCrucibleDataProvider.java similarity index 64% rename from core/src/main/java/com/volmit/iris/core/link/MythicCrucibleDataProvider.java rename to core/src/main/java/com/volmit/iris/core/link/data/MythicCrucibleDataProvider.java index d62462c38..4194f0c16 100644 --- a/core/src/main/java/com/volmit/iris/core/link/MythicCrucibleDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/MythicCrucibleDataProvider.java @@ -16,19 +16,18 @@ * along with this program. If not, see . */ -package com.volmit.iris.core.link; +package com.volmit.iris.core.link.data; import com.volmit.iris.Iris; +import com.volmit.iris.core.link.ExternalDataProvider; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.service.ExternalDataSVC; -import com.volmit.iris.engine.data.cache.Cache; 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.data.B; import com.volmit.iris.util.data.IrisCustomData; -import com.volmit.iris.util.math.RNG; import io.lumine.mythic.bukkit.BukkitAdapter; import io.lumine.mythic.bukkit.utils.serialize.Chroma; import io.lumine.mythiccrucible.MythicCrucible; @@ -37,11 +36,11 @@ import io.lumine.mythiccrucible.items.ItemManager; import io.lumine.mythiccrucible.items.blocks.CustomBlockItemContext; import io.lumine.mythiccrucible.items.furniture.FurnitureItemContext; import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; import org.bukkit.block.data.BlockData; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.MissingResourceException; import java.util.Optional; @@ -88,69 +87,27 @@ public class MythicCrucibleDataProvider extends ExternalDataProvider { .generateItemStack(1)); } - @NotNull @Override - public Identifier[] getBlockTypes() { - KList names = new KList<>(); - for (CrucibleItem item : this.itemManager.getItems()) { - if (item.getBlockData() == null) continue; - try { - Identifier key = new Identifier("crucible", item.getInternalName()); - if (getBlockData(key) != null) { - Iris.info("getBlockTypes: Block loaded '" + item.getInternalName() + "'"); - names.add(key); - } - } catch (MissingResourceException ignored) {} - } - return names.toArray(new Identifier[0]); - } - - @NotNull - @Override - public Identifier[] getItemTypes() { - KList names = new KList<>(); - for (CrucibleItem item : this.itemManager.getItems()) { - try { - Identifier key = new Identifier("crucible", item.getInternalName()); - if (getItemStack(key) != null) { - Iris.info("getItemTypes: Item loaded '" + item.getInternalName() + "'"); - names.add(key); - } - } catch (MissingResourceException ignored) {} - } - return names.toArray(new Identifier[0]); + public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) { + return itemManager.getItems() + .stream() + .map(i -> new Identifier("crucible", i.getInternalName())) + .filter(dataType.asPredicate(this)) + .toList(); } @Override public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) { - var pair = ExternalDataSVC.parseState(blockId); - var state = pair.getB(); - blockId = pair.getA(); + var parsedState = ExternalDataSVC.parseState(blockId); + var state = parsedState.getB(); + blockId = parsedState.getA(); Optional item = itemManager.getItem(blockId.key()); if (item.isEmpty()) return; FurnitureItemContext furniture = item.get().getFurnitureData(); if (furniture == null) return; - float yaw = 0; - BlockFace face = BlockFace.NORTH; - long seed = engine.getSeedManager().getSeed() + Cache.key(block.getX(), block.getZ()) + block.getY(); - RNG rng = new RNG(seed); - if ("true".equals(state.get("randomYaw"))) { - yaw = rng.f(0, 360); - } else if (state.containsKey("yaw")) { - yaw = Float.parseFloat(state.get("yaw")); - } - if ("true".equals(state.get("randomFace"))) { - BlockFace[] faces = BlockFace.values(); - face = faces[rng.i(0, faces.length - 1)]; - } else if (state.containsKey("face")) { - face = BlockFace.valueOf(state.get("face").toUpperCase()); - } - if (face == BlockFace.SELF) { - face = BlockFace.NORTH; - } - + var pair = parseYawAndFace(engine, block, state); BiomeColor type = null; Chroma color = null; try { @@ -161,11 +118,12 @@ public class MythicCrucibleDataProvider extends ExternalDataProvider { if (biomeColor == null) return; color = Chroma.of(biomeColor.getRGB()); } - furniture.place(block, face, yaw, color); + furniture.place(block, pair.getB(), pair.getA(), color); } @Override - public boolean isValidProvider(@NotNull Identifier key, boolean isItem) { + public boolean isValidProvider(@NotNull Identifier key, DataType dataType) { + if (dataType == DataType.ENTITY) return false; return key.namespace().equalsIgnoreCase("crucible"); } } diff --git a/core/src/main/java/com/volmit/iris/core/link/data/MythicMobsDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/MythicMobsDataProvider.java new file mode 100644 index 000000000..7c08b8e6a --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/link/data/MythicMobsDataProvider.java @@ -0,0 +1,112 @@ +package com.volmit.iris.core.link.data; + +import com.volmit.iris.core.link.ExternalDataProvider; +import com.volmit.iris.core.link.Identifier; +import com.volmit.iris.core.tools.IrisToolbelt; +import io.lumine.mythic.api.adapters.AbstractLocation; +import io.lumine.mythic.api.config.MythicLineConfig; +import io.lumine.mythic.api.skills.conditions.ILocationCondition; +import io.lumine.mythic.bukkit.MythicBukkit; +import io.lumine.mythic.bukkit.adapters.BukkitWorld; +import io.lumine.mythic.bukkit.events.MythicConditionLoadEvent; +import io.lumine.mythic.core.skills.SkillCondition; +import io.lumine.mythic.core.utils.annotations.MythicCondition; +import io.lumine.mythic.core.utils.annotations.MythicField; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.EventHandler; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class MythicMobsDataProvider extends ExternalDataProvider { + public MythicMobsDataProvider() { + super("MythicMobs"); + } + + @Override + public void init() { + } + + @Override + public @Nullable Entity spawnMob(@NotNull Location location, @NotNull Identifier entityId) throws MissingResourceException { + var mm = MythicBukkit.inst().getMobManager().spawnMob(entityId.key(), location); + if (mm == null) throw new MissingResourceException("Failed to find mob!", entityId.namespace(), entityId.key()); + return mm.getEntity().getBukkitEntity(); + } + + @Override + public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) { + if (dataType != DataType.ENTITY) return List.of(); + return MythicBukkit.inst() + .getMobManager() + .getMobNames() + .stream() + .map(name -> new Identifier("mythicmobs", name)) + .toList(); + } + + @Override + public boolean isValidProvider(@NotNull Identifier id, DataType dataType) { + return id.namespace().equalsIgnoreCase("mythicmobs") && dataType == DataType.ENTITY; + } + + @EventHandler + public void on(MythicConditionLoadEvent event) { + switch (event.getConditionName()) { + case "irisbiome" -> event.register(new IrisBiomeCondition(event.getConditionName(), event.getConfig())); + case "irisregion" -> event.register(new IrisRegionCondition(event.getConditionName(), event.getConfig())); + } + } + + @MythicCondition(author = "CrazyDev22", name = "irisbiome", description = "Tests if the target is within the given list of biomes") + public static class IrisBiomeCondition extends SkillCondition implements ILocationCondition { + @MythicField(name = "biome", aliases = {"b"}, description = "A list of biomes to check") + private Set biomes = ConcurrentHashMap.newKeySet(); + @MythicField(name = "surface", aliases = {"s"}, description = "If the biome check should only be performed on the surface") + private boolean surface; + + public IrisBiomeCondition(String line, MythicLineConfig mlc) { + super(line); + String b = mlc.getString(new String[]{"biome", "b"}, ""); + biomes.addAll(Arrays.asList(b.split(","))); + surface = mlc.getBoolean(new String[]{"surface", "s"}, false); + } + + @Override + public boolean check(AbstractLocation target) { + var access = IrisToolbelt.access(((BukkitWorld) target.getWorld()).getBukkitWorld()); + if (access == null) return false; + var engine = access.getEngine(); + if (engine == null) return false; + var biome = surface ? + engine.getSurfaceBiome(target.getBlockX(), target.getBlockZ()) : + engine.getBiomeOrMantle(target.getBlockX(), target.getBlockY() - engine.getMinHeight(), target.getBlockZ()); + return biomes.contains(biome.getLoadKey()); + } + } + + @MythicCondition(author = "CrazyDev22", name = "irisregion", description = "Tests if the target is within the given list of biomes") + public static class IrisRegionCondition extends SkillCondition implements ILocationCondition { + @MythicField(name = "region", aliases = {"r"}, description = "A list of regions to check") + private Set regions = ConcurrentHashMap.newKeySet(); + + public IrisRegionCondition(String line, MythicLineConfig mlc) { + super(line); + String b = mlc.getString(new String[]{"region", "r"}, ""); + regions.addAll(Arrays.asList(b.split(","))); + } + + @Override + public boolean check(AbstractLocation target) { + var access = IrisToolbelt.access(((BukkitWorld) target.getWorld()).getBukkitWorld()); + if (access == null) return false; + var engine = access.getEngine(); + if (engine == null) return false; + var region = engine.getRegion(target.getBlockX(), target.getBlockZ()); + return regions.contains(region.getLoadKey()); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/core/link/NexoDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/NexoDataProvider.java similarity index 63% rename from core/src/main/java/com/volmit/iris/core/link/NexoDataProvider.java rename to core/src/main/java/com/volmit/iris/core/link/data/NexoDataProvider.java index 066fb448b..998fbf06f 100644 --- a/core/src/main/java/com/volmit/iris/core/link/NexoDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/NexoDataProvider.java @@ -1,28 +1,30 @@ -package com.volmit.iris.core.link; +package com.volmit.iris.core.link.data; import com.nexomc.nexo.api.NexoBlocks; import com.nexomc.nexo.api.NexoFurniture; import com.nexomc.nexo.api.NexoItems; import com.nexomc.nexo.items.ItemBuilder; +import com.volmit.iris.core.link.ExternalDataProvider; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.service.ExternalDataSVC; -import com.volmit.iris.engine.data.cache.Cache; 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 com.volmit.iris.util.math.RNG; import org.bukkit.Color; import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; import org.bukkit.block.data.BlockData; import org.bukkit.entity.ItemDisplay; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.LeatherArmorMeta; +import org.bukkit.inventory.meta.MapMeta; import org.bukkit.inventory.meta.PotionMeta; import org.jetbrains.annotations.NotNull; -import java.util.Arrays; +import java.util.Collection; +import java.util.List; import java.util.MissingResourceException; import java.util.concurrent.atomic.AtomicBoolean; @@ -69,9 +71,9 @@ public class NexoDataProvider extends ExternalDataProvider { @Override public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) { - var pair = ExternalDataSVC.parseState(blockId); - var state = pair.getB(); - blockId = pair.getA(); + var statePair = ExternalDataSVC.parseState(blockId); + var state = statePair.getB(); + blockId = statePair.getA(); if (NexoBlocks.isCustomBlock(blockId.key())) { NexoBlocks.place(blockId.key(), block.getLocation()); @@ -81,26 +83,8 @@ public class NexoDataProvider extends ExternalDataProvider { if (!NexoFurniture.isFurniture(blockId.key())) return; - float yaw = 0; - BlockFace face = BlockFace.NORTH; - - long seed = engine.getSeedManager().getSeed() + Cache.key(block.getX(), block.getZ()) + block.getY(); - RNG rng = new RNG(seed); - if ("true".equals(state.get("randomYaw"))) { - yaw = rng.f(0, 360); - } else if (state.containsKey("yaw")) { - yaw = Float.parseFloat(state.get("yaw")); - } - if ("true".equals(state.get("randomFace"))) { - BlockFace[] faces = BlockFace.values(); - face = faces[rng.i(0, faces.length - 1)]; - } else if (state.containsKey("face")) { - face = BlockFace.valueOf(state.get("face").toUpperCase()); - } - if (face == BlockFace.SELF) { - face = BlockFace.NORTH; - } - ItemDisplay display = NexoFurniture.place(blockId.key(), block.getLocation(), yaw, face); + var pair = parseYawAndFace(engine, block, state); + ItemDisplay display = NexoFurniture.place(blockId.key(), block.getLocation(), pair.getA(), pair.getB()); if (display == null) return; ItemStack itemStack = display.getItemStack(); if (itemStack == null) return; @@ -114,46 +98,31 @@ public class NexoDataProvider extends ExternalDataProvider { var biomeColor = INMS.get().getBiomeColor(block.getLocation(), type); if (biomeColor == null) return; var potionColor = Color.fromARGB(biomeColor.getAlpha(), biomeColor.getRed(), biomeColor.getGreen(), biomeColor.getBlue()); - if (itemStack.getItemMeta() instanceof PotionMeta meta) { - meta.setColor(potionColor); - itemStack.setItemMeta(meta); + var meta = itemStack.getItemMeta(); + switch (meta) { + case LeatherArmorMeta armor -> armor.setColor(potionColor); + case PotionMeta potion -> potion.setColor(potionColor); + case MapMeta map -> map.setColor(potionColor); + case null, default -> {} } + itemStack.setItemMeta(meta); } display.setItemStack(itemStack); } - @NotNull @Override - public Identifier[] getBlockTypes() { - return NexoItems.itemNames().stream() + public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) { + if (dataType == DataType.ENTITY) return List.of(); + return NexoItems.itemNames() + .stream() .map(i -> new Identifier("nexo", i)) - .filter(i -> { - try { - return getBlockData(i) != null; - } catch (MissingResourceException e) { - return false; - } - }) - .toArray(Identifier[]::new); - } - - @NotNull - @Override - public Identifier[] getItemTypes() { - return NexoItems.itemNames().stream() - .map(i -> new Identifier("nexo", i)) - .filter(i -> { - try { - return getItemStack(i) != null; - } catch (MissingResourceException e) { - return false; - } - }) - .toArray(Identifier[]::new); + .filter(dataType.asPredicate(this)) + .toList(); } @Override - public boolean isValidProvider(@NotNull Identifier id, boolean isItem) { + public boolean isValidProvider(@NotNull Identifier id, DataType dataType) { + if (dataType == DataType.ENTITY) return false; return "nexo".equalsIgnoreCase(id.namespace()); } 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 e51c93176..e6d410a5b 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; @@ -300,6 +302,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { return r; } catch (Throwable e) { + Iris.reportError(e); e.printStackTrace(); Iris.error("Failed to create loader! " + registrant.getCanonicalName()); } @@ -360,6 +363,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { for (ResourceLoader i : loaders.values()) { i.clearList(); } + possibleSnippets.clear(); } public String toLoadKey(File f) { @@ -426,8 +430,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() + ")"); @@ -461,11 +464,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/loader/ResourceLoader.java b/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java index 6575abd37..a9b468e57 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java +++ b/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java @@ -45,6 +45,7 @@ import lombok.ToString; import java.io.*; import java.util.Locale; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -240,8 +241,10 @@ public class ResourceLoader implements MeteredCache { for (String i : s) { burst.queue(() -> { T t = load(i); + if (t == null) + return; - if (t != null) { + synchronized (m) { m.add(t); } }); 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 3daefa900..e4d6bc682 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 @@ -24,19 +24,23 @@ import com.volmit.iris.core.nms.v1X.NMSBinding1X; import org.bukkit.Bukkit; import java.util.List; -import java.util.Map; public class INMS { - private static final Map REVISION = Map.of( - "1.20.5", "v1_20_R4", - "1.20.6", "v1_20_R4", - "1.21", "v1_21_R1", - "1.21.1", "v1_21_R1", - "1.21.2", "v1_21_R2", - "1.21.3", "v1_21_R2", - "1.21.4", "v1_21_R3" + private static final Version CURRENT = Boolean.getBoolean("iris.no-version-limit") ? + new Version(Integer.MAX_VALUE, Integer.MAX_VALUE, null) : + new Version(21, 8, null); + + private static final List REVISION = List.of( + new Version(21, 6, "v1_21_R5"), + new Version(21, 5, "v1_21_R4"), + new Version(21, 4, "v1_21_R3"), + new Version(21, 2, "v1_21_R2"), + new Version(21, 0, "v1_21_R1"), + new Version(20, 5, "v1_20_R4") ); + private static final List PACKS = List.of( + new Version(21, 5, "31100"), new Version(21, 4, "31020"), new Version(21, 2, "31000"), new Version(20, 1, "3910") @@ -44,7 +48,7 @@ public class INMS { //@done private static final INMSBinding binding = bind(); - public static final String OVERWORLD_TAG = getOverworldTag(); + public static final String OVERWORLD_TAG = getTag(PACKS, "3910"); public static INMSBinding get() { return binding; @@ -58,7 +62,7 @@ public class INMS { try { String name = Bukkit.getServer().getClass().getCanonicalName(); if (name.equals("org.bukkit.craftbukkit.CraftServer")) { - return REVISION.getOrDefault(Bukkit.getServer().getBukkitVersion().split("-")[0], "BUKKIT"); + return getTag(REVISION, "BUKKIT"); } else { return name.split("\\Q.\\E")[3]; } @@ -96,7 +100,7 @@ public class INMS { return new NMSBinding1X(); } - private static String getOverworldTag() { + private static String getTag(List versions, String def) { var version = Bukkit.getServer().getBukkitVersion().split("-")[0].split("\\.", 3); int major = 0; int minor = 0; @@ -107,13 +111,16 @@ public class INMS { } else if (version.length == 2) { major = Integer.parseInt(version[1]); } + if (CURRENT.major < major || CURRENT.minor < minor) { + return versions.getFirst().tag; + } - for (var p : PACKS) { + for (var p : versions) { if (p.major > major || p.minor > minor) continue; return p.tag; } - return "3910"; + return def; } private record Version(int major, int minor, String tag) {} 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 8b75a6b58..0f491d9ca 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 @@ -25,6 +25,7 @@ import com.volmit.iris.core.nms.container.Pair; import com.volmit.iris.core.nms.container.StructurePlacement; 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; @@ -39,6 +40,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; @@ -92,12 +94,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()) { - 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(); @@ -132,11 +132,11 @@ public interface INMSBinding { KList getStructureKeys(); - AutoClosing injectLevelStems(); + boolean missingDimensionTypes(String... keys); - Pair injectUncached(boolean overworld, boolean nether, boolean end); - - boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end); + default boolean injectBukkit() { + return true; + } void placeStructures(Chunk chunk); diff --git a/core/src/main/java/com/volmit/iris/core/nms/container/AutoClosing.java b/core/src/main/java/com/volmit/iris/core/nms/container/AutoClosing.java index afa2ba9dc..a4771ca84 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/container/AutoClosing.java +++ b/core/src/main/java/com/volmit/iris/core/nms/container/AutoClosing.java @@ -1,5 +1,6 @@ package com.volmit.iris.core.nms.container; +import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.function.NastyRunnable; import lombok.AllArgsConstructor; @@ -7,6 +8,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @AllArgsConstructor public class AutoClosing implements AutoCloseable { + private static final KMap CONTEXTS = new KMap<>(); private final AtomicBoolean closed = new AtomicBoolean(); private final NastyRunnable action; @@ -14,9 +16,24 @@ public class AutoClosing implements AutoCloseable { public void close() { if (closed.getAndSet(true)) return; try { + removeContext(); action.run(); } catch (Throwable e) { throw new RuntimeException(e); } } + + public void storeContext() { + CONTEXTS.put(Thread.currentThread(), this); + } + + public void removeContext() { + CONTEXTS.values().removeIf(c -> c == this); + } + + public static void closeContext() { + AutoClosing closing = CONTEXTS.remove(Thread.currentThread()); + if (closing == null) return; + closing.close(); + } } 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 a66ab2c2a..773c35ea4 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 @@ -21,8 +21,8 @@ package com.volmit.iris.core.nms.v1X; import com.volmit.iris.Iris; import com.volmit.iris.core.link.Identifier; 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.core.nms.container.Pair; import com.volmit.iris.core.nms.container.StructurePlacement; import com.volmit.iris.engine.framework.Engine; @@ -123,17 +123,7 @@ public class NMSBinding1X implements INMSBinding { } @Override - public AutoClosing injectLevelStems() { - return new AutoClosing(() -> {}); - } - - @Override - public Pair injectUncached(boolean overworld, boolean nether, boolean end) { - return new Pair<>(0, new AutoClosing(() -> {})); - } - - @Override - public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { + public boolean missingDimensionTypes(String... keys) { return false; } @@ -231,6 +221,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/pregenerator/ChunkUpdater.java b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java index cb8407272..572ce98d4 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java @@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; public class ChunkUpdater { + private static final String REGION_PATH = "region" + File.separator + "r."; private final AtomicBoolean paused = new AtomicBoolean(); private final AtomicBoolean cancelled = new AtomicBoolean(); private final KMap> lastUse = new KMap<>(); @@ -108,6 +109,7 @@ public class ChunkUpdater { } } } catch (Exception e) { + Iris.reportError(e); e.printStackTrace(); } }, 0, 3, TimeUnit.SECONDS); @@ -162,12 +164,12 @@ public class ChunkUpdater { J.sleep(50); } - if (rX < dimensions.min.getX() || rX > dimensions.max.getX() || rZ < dimensions.min.getZ() || rZ > dimensions.max.getZ()) { - return; - } - if (!new File(world.getWorldFolder(), "region" + File.separator + rX + "." + rZ + ".mca").exists()) { - return; - } + if (rX < dimensions.min.getX() || + rX > dimensions.max.getX() || + rZ < dimensions.min.getZ() || + rZ > dimensions.max.getZ() || + !new File(world.getWorldFolder(), REGION_PATH + rX + "." + rZ + ".mca").exists() + ) return; task.iterateChunks(rX, rZ, (x, z) -> { while (paused.get() && !cancelled.get()) { @@ -313,6 +315,7 @@ public class ChunkUpdater { world.save(); }).get(); } catch (Throwable e) { + Iris.reportError(e); e.printStackTrace(); } } diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java b/core/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java index 57aa8b0eb..e90307dc9 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java @@ -31,28 +31,33 @@ import com.volmit.iris.util.math.RollingSequence; import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.Looper; +import com.volmit.iris.util.scheduling.PrecisionStopwatch; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; public class IrisPregenerator { + private static final double INVALID = 9223372036854775807d; private final PregenTask task; private final PregeneratorMethod generator; private final PregenListener listener; private final Looper ticker; private final AtomicBoolean paused; private final AtomicBoolean shutdown; + private final RollingSequence cachedPerSecond; private final RollingSequence chunksPerSecond; private final RollingSequence chunksPerMinute; private final RollingSequence regionsPerMinute; private final KList chunksPerSecondHistory; - private static AtomicInteger generated; - private final AtomicInteger generatedLast; - private final AtomicInteger generatedLastMinute; - private static AtomicInteger totalChunks; + private final AtomicLong generated; + private final AtomicLong generatedLast; + private final AtomicLong generatedLastMinute; + private final AtomicLong cached; + private final AtomicLong cachedLast; + private final AtomicLong cachedLastMinute; + private final AtomicLong totalChunks; private final AtomicLong startTime; private final ChronoLatch minuteLatch; private final AtomicReference currentGeneratorMethod; @@ -61,8 +66,10 @@ public class IrisPregenerator { private final KSet net; private final ChronoLatch cl; private final ChronoLatch saveLatch = new ChronoLatch(30000); + private final IrisPackBenchmarking benchmarking; public IrisPregenerator(PregenTask task, PregeneratorMethod generator, PregenListener listener) { + benchmarking = IrisPackBenchmarking.getInstance(); this.listener = listenify(listener); cl = new ChronoLatch(5000); generatedRegions = new KSet<>(); @@ -74,46 +81,71 @@ public class IrisPregenerator { net = new KSet<>(); currentGeneratorMethod = new AtomicReference<>("Void"); minuteLatch = new ChronoLatch(60000, false); + cachedPerSecond = new RollingSequence(5); chunksPerSecond = new RollingSequence(10); chunksPerMinute = new RollingSequence(10); regionsPerMinute = new RollingSequence(10); chunksPerSecondHistory = new KList<>(); - generated = new AtomicInteger(0); - generatedLast = new AtomicInteger(0); - generatedLastMinute = new AtomicInteger(0); - totalChunks = new AtomicInteger(0); + generated = new AtomicLong(0); + generatedLast = new AtomicLong(0); + generatedLastMinute = new AtomicLong(0); + cached = new AtomicLong(); + cachedLast = new AtomicLong(0); + cachedLastMinute = new AtomicLong(0); + totalChunks = new AtomicLong(0); task.iterateAllChunks((_a, _b) -> totalChunks.incrementAndGet()); startTime = new AtomicLong(M.ms()); ticker = new Looper() { @Override protected long loop() { long eta = computeETA(); - int secondGenerated = generated.get() - generatedLast.get(); - generatedLast.set(generated.get()); - chunksPerSecond.put(secondGenerated); - chunksPerSecondHistory.add(secondGenerated); - if (minuteLatch.flip()) { - int minuteGenerated = generated.get() - generatedLastMinute.get(); - generatedLastMinute.set(generated.get()); - chunksPerMinute.put(minuteGenerated); - regionsPerMinute.put((double) minuteGenerated / 1024D); + long secondCached = cached.get() - cachedLast.get(); + cachedLast.set(cached.get()); + cachedPerSecond.put(secondCached); + + long secondGenerated = generated.get() - generatedLast.get() - secondCached; + generatedLast.set(generated.get()); + if (secondCached == 0 || secondGenerated != 0) { + chunksPerSecond.put(secondGenerated); + chunksPerSecondHistory.add((int) secondGenerated); } - listener.onTick(chunksPerSecond.getAverage(), chunksPerMinute.getAverage(), + if (minuteLatch.flip()) { + long minuteCached = cached.get() - cachedLastMinute.get(); + cachedLastMinute.set(cached.get()); + + long minuteGenerated = generated.get() - generatedLastMinute.get() - minuteCached; + generatedLastMinute.set(generated.get()); + if (minuteCached == 0 || minuteGenerated != 0) { + chunksPerMinute.put(minuteGenerated); + regionsPerMinute.put((double) minuteGenerated / 1024D); + } + } + boolean cached = cachedPerSecond.getAverage() != 0; + + listener.onTick( + cached ? cachedPerSecond.getAverage() : chunksPerSecond.getAverage(), + chunksPerMinute.getAverage(), regionsPerMinute.getAverage(), - (double) generated.get() / (double) totalChunks.get(), - generated.get(), totalChunks.get(), - totalChunks.get() - generated.get(), - eta, M.ms() - startTime.get(), currentGeneratorMethod.get()); + (double) generated.get() / (double) totalChunks.get(), generated.get(), + totalChunks.get(), + totalChunks.get() - generated.get(), eta, M.ms() - startTime.get(), currentGeneratorMethod.get(), + cached); if (cl.flip()) { double percentage = ((double) generated.get() / (double) totalChunks.get()) * 100; - if (!IrisPackBenchmarking.benchmarkInProgress) { - Iris.info("Pregen: " + Form.f(generated.get()) + " of " + Form.f(totalChunks.get()) + " (%.0f%%) " + Form.f((int) chunksPerSecond.getAverage()) + "/s ETA: " + Form.duration(eta, 2), percentage); - } else { - Iris.info("Benchmarking: " + Form.f(generated.get()) + " of " + Form.f(totalChunks.get()) + " (%.0f%%) " + Form.f((int) chunksPerSecond.getAverage()) + "/s ETA: " + Form.duration(eta, 2), percentage); - } + + Iris.info("%s: %s of %s (%.0f%%), %s/s ETA: %s", + benchmarking != null ? "Benchmarking" : "Pregen", + Form.f(generated.get()), + Form.f(totalChunks.get()), + percentage, + cached ? + "Cached " + Form.f((int) cachedPerSecond.getAverage()) : + Form.f((int) chunksPerSecond.getAverage()), + Form.duration(eta, 2) + ); } return 1000; } @@ -121,12 +153,12 @@ public class IrisPregenerator { } private long computeETA() { - return (long) (totalChunks.get() > 1024 ? // Generated chunks exceed 1/8th of total? + double d = (long) (generated.get() > 1024 ? // Generated chunks exceed 1/8th of total? // If yes, use smooth function (which gets more accurate over time since its less sensitive to outliers) ((totalChunks.get() - generated.get()) * ((double) (M.ms() - startTime.get()) / (double) generated.get())) : // If no, use quick function (which is less accurate over time but responds better to the initial delay) - ((totalChunks.get() - generated.get()) / chunksPerSecond.getAverage()) * 1000 - ); + ((totalChunks.get() - generated.get()) / chunksPerSecond.getAverage()) * 1000); + return Double.isFinite(d) && d != INVALID ? (long) d : 0; } @@ -138,13 +170,15 @@ public class IrisPregenerator { init(); ticker.start(); checkRegions(); + var p = PrecisionStopwatch.start(); task.iterateRegions((x, z) -> visitRegion(x, z, true)); task.iterateRegions((x, z) -> visitRegion(x, z, false)); + Iris.info("Pregen took " + Form.duration((long) p.getMilliseconds())); shutdown(); - if (!IrisPackBenchmarking.benchmarkInProgress) { + if (benchmarking == null) { Iris.info(C.IRIS + "Pregen stopped."); } else { - IrisPackBenchmarking.instance.finishedBenchmark(chunksPerSecondHistory); + benchmarking.finishedBenchmark(chunksPerSecondHistory); } } @@ -234,8 +268,8 @@ public class IrisPregenerator { private PregenListener listenify(PregenListener listener) { return new PregenListener() { @Override - public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method) { - listener.onTick(chunksPerSecond, chunksPerMinute, regionsPerMinute, percent, generated, totalChunks, chunksRemaining, eta, elapsed, method); + public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached) { + listener.onTick(chunksPerSecond, chunksPerMinute, regionsPerMinute, percent, generated, totalChunks, chunksRemaining, eta, elapsed, method, cached); } @Override @@ -244,9 +278,10 @@ public class IrisPregenerator { } @Override - public void onChunkGenerated(int x, int z) { - listener.onChunkGenerated(x, z); + public void onChunkGenerated(int x, int z, boolean c) { + listener.onChunkGenerated(x, z, c); generated.addAndGet(1); + if (c) cached.addAndGet(1); } @Override diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/PregenListener.java b/core/src/main/java/com/volmit/iris/core/pregenerator/PregenListener.java index fb3ab3952..6f5d83194 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/PregenListener.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/PregenListener.java @@ -19,11 +19,15 @@ package com.volmit.iris.core.pregenerator; public interface PregenListener { - void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method); + void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached); void onChunkGenerating(int x, int z); - void onChunkGenerated(int x, int z); + default void onChunkGenerated(int x, int z) { + onChunkGenerated(x, z, false); + } + + void onChunkGenerated(int x, int z, boolean cached); void onRegionGenerated(int x, int z); diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCache.java b/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCache.java new file mode 100644 index 000000000..dff3840cd --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCache.java @@ -0,0 +1,70 @@ +package com.volmit.iris.core.pregenerator.cache; + +import com.volmit.iris.util.documentation.ChunkCoordinates; +import com.volmit.iris.util.documentation.RegionCoordinates; + +import java.io.File; + +public interface PregenCache { + default boolean isThreadSafe() { + return false; + } + + @ChunkCoordinates + boolean isChunkCached(int x, int z); + + @RegionCoordinates + boolean isRegionCached(int x, int z); + + @ChunkCoordinates + void cacheChunk(int x, int z); + + @RegionCoordinates + void cacheRegion(int x, int z); + + void write(); + + static PregenCache create(File directory) { + if (directory == null) return EMPTY; + return new PregenCacheImpl(directory); + } + + default PregenCache sync() { + if (isThreadSafe()) return this; + return new SynchronizedCache(this); + } + + PregenCache EMPTY = new PregenCache() { + @Override + public boolean isThreadSafe() { + return true; + } + + @Override + public boolean isChunkCached(int x, int z) { + return false; + } + + @Override + public boolean isRegionCached(int x, int z) { + return false; + } + + @Override + public void cacheChunk(int x, int z) { + + } + + @Override + public void cacheRegion(int x, int z) { + + } + + @Override + public void write() { + + } + }; + + +} diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.java b/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.java new file mode 100644 index 000000000..b9a3de943 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.java @@ -0,0 +1,218 @@ +package com.volmit.iris.core.pregenerator.cache; + +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.github.benmanes.caffeine.cache.RemovalCause; +import com.volmit.iris.Iris; +import com.volmit.iris.util.data.Varint; +import com.volmit.iris.util.documentation.ChunkCoordinates; +import com.volmit.iris.util.documentation.RegionCoordinates; +import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.parallel.HyperLock; +import lombok.RequiredArgsConstructor; +import net.jpountz.lz4.LZ4BlockInputStream; +import net.jpountz.lz4.LZ4BlockOutputStream; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.concurrent.NotThreadSafe; +import java.io.*; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +@NotThreadSafe +@RequiredArgsConstructor +class PregenCacheImpl implements PregenCache { + private static final int SIZE = 32; + private final File directory; + private final HyperLock hyperLock = new HyperLock(SIZE * 2, true); + private final LoadingCache cache = Caffeine.newBuilder() + .expireAfterAccess(10, TimeUnit.SECONDS) + .maximumSize(SIZE) + .removalListener(this::onRemoval) + .evictionListener(this::onRemoval) + .build(this::load); + + @ChunkCoordinates + public boolean isChunkCached(int x, int z) { + var plate = cache.get(new Pos(x >> 10, z >> 10)); + if (plate == null) return false; + return plate.isCached((x >> 5) & 31, (z >> 5) & 31, r -> r.isCached(x & 31, z & 31)); + } + + @RegionCoordinates + public boolean isRegionCached(int x, int z) { + var plate = cache.get(new Pos(x >> 5, z >> 5)); + if (plate == null) return false; + return plate.isCached(x & 31, z & 31, Region::isCached); + } + + @ChunkCoordinates + public void cacheChunk(int x, int z) { + var plate = cache.get(new Pos(x >> 10, z >> 10)); + plate.cache((x >> 5) & 31, (z >> 5) & 31, r -> r.cache(x & 31, z & 31)); + } + + @RegionCoordinates + public void cacheRegion(int x, int z) { + var plate = cache.get(new Pos(x >> 5, z >> 5)); + plate.cache(x & 31, z & 31, Region::cache); + } + + public void write() { + cache.asMap().values().forEach(this::write); + } + + private Plate load(Pos key) { + hyperLock.lock(key.x, key.z); + try { + File file = fileForPlate(key); + if (!file.exists()) return new Plate(key); + try (var in = new DataInputStream(new LZ4BlockInputStream(new FileInputStream(file)))) { + return new Plate(key, in); + } catch (IOException e){ + Iris.error("Failed to read pregen cache " + file); + Iris.reportError(e); + e.printStackTrace(); + return new Plate(key); + } + } finally { + hyperLock.unlock(key.x, key.z); + } + } + + private void write(Plate plate) { + hyperLock.lock(plate.pos.x, plate.pos.z); + try { + File file = fileForPlate(plate.pos); + try { + IO.write(file, out -> new DataOutputStream(new LZ4BlockOutputStream(out)), plate::write); + } catch (IOException e) { + Iris.error("Failed to write pregen cache " + file); + Iris.reportError(e); + e.printStackTrace(); + } + } finally { + hyperLock.unlock(plate.pos.x, plate.pos.z); + } + } + + private void onRemoval(@Nullable Pos key, @Nullable Plate plate, RemovalCause cause) { + if (plate == null) return; + write(plate); + } + + private File fileForPlate(Pos pos) { + if (!directory.exists() && !directory.mkdirs()) + throw new IllegalStateException("Cannot create directory: " + directory.getAbsolutePath()); + return new File(directory, "c." + pos.x + "." + pos.z + ".lz4b"); + } + + private static class Plate { + private final Pos pos; + private short count; + private Region[] regions; + + public Plate(Pos pos) { + this.pos = pos; + count = 0; + regions = new Region[1024]; + } + + public Plate(Pos pos, DataInput in) throws IOException { + this.pos = pos; + count = (short) Varint.readSignedVarInt(in); + if (count == 1024) return; + regions = new Region[1024]; + for (int i = 0; i < 1024; i++) { + if (in.readBoolean()) continue; + regions[i] = new Region(in); + } + } + + public boolean isCached(int x, int z, Predicate predicate) { + if (count == 1024) return true; + Region region = regions[x * 32 + z]; + if (region == null) return false; + return predicate.test(region); + } + + public void cache(int x, int z, Predicate predicate) { + if (count == 1024) return; + Region region = regions[x * 32 + z]; + if (region == null) regions[x * 32 + z] = region = new Region(); + if (predicate.test(region)) count++; + } + + public void write(DataOutput out) throws IOException { + Varint.writeSignedVarInt(count, out); + if (count == 1024) return; + for (Region region : regions) { + out.writeBoolean(region == null); + if (region == null) continue; + region.write(out); + } + } + } + + private static class Region { + private short count; + private long[] words; + + public Region() { + count = 0; + words = new long[64]; + } + + public Region(DataInput in) throws IOException { + count = (short) Varint.readSignedVarInt(in); + if (count == 1024) return; + words = new long[64]; + for (int i = 0; i < 64; i++) { + words[i] = Varint.readUnsignedVarLong(in); + } + } + + public boolean cache() { + if (count == 1024) return false; + count = 1024; + words = null; + return true; + } + + public boolean cache(int x, int z) { + if (count == 1024) return false; + + int i = x * 32 + z; + int w = i >> 6; + long b = 1L << (i & 63); + + var cur = (words[w] & b) != 0; + if (cur) return false; + + if (++count == 1024) { + words = null; + return true; + } else words[w] |= b; + return false; + } + + public boolean isCached() { + return count == 1024; + } + + public boolean isCached(int x, int z) { + int i = x * 32 + z; + return count == 1024 || (words[i >> 6] & 1L << (i & 63)) != 0; + } + + public void write(DataOutput out) throws IOException { + Varint.writeSignedVarInt(count, out); + if (isCached()) return; + for (long word : words) { + Varint.writeUnsignedVarLong(word, out); + } + } + } + + private record Pos(int x, int z) {} +} diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/cache/SynchronizedCache.java b/core/src/main/java/com/volmit/iris/core/pregenerator/cache/SynchronizedCache.java new file mode 100644 index 000000000..52f3b7774 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/cache/SynchronizedCache.java @@ -0,0 +1,48 @@ +package com.volmit.iris.core.pregenerator.cache; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +class SynchronizedCache implements PregenCache { + private final PregenCache cache; + + @Override + public boolean isThreadSafe() { + return true; + } + + @Override + public boolean isChunkCached(int x, int z) { + synchronized (cache) { + return cache.isChunkCached(x, z); + } + } + + @Override + public boolean isRegionCached(int x, int z) { + synchronized (cache) { + return cache.isRegionCached(x, z); + } + } + + @Override + public void cacheChunk(int x, int z) { + synchronized (cache) { + cache.cacheChunk(x, z); + } + } + + @Override + public void cacheRegion(int x, int z) { + synchronized (cache) { + cache.cacheRegion(x, z); + } + } + + @Override + public void write() { + synchronized (cache) { + cache.write(); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java index 8496f924c..0dcfffcfc 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java @@ -19,6 +19,7 @@ package com.volmit.iris.core.pregenerator.methods; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.pregenerator.PregenListener; import com.volmit.iris.core.pregenerator.PregeneratorMethod; import com.volmit.iris.core.tools.IrisToolbelt; @@ -31,24 +32,32 @@ import io.papermc.lib.PaperLib; import org.bukkit.Chunk; import org.bukkit.World; -import java.util.ArrayList; +import java.lang.reflect.InvocationTargetException; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicInteger; public class AsyncPregenMethod implements PregeneratorMethod { + private static final AtomicInteger THREAD_COUNT = new AtomicInteger(); private final World world; - private final MultiBurst burst; + private final Executor executor; private final Semaphore semaphore; + private final int threads; + private final boolean urgent; private final Map lastUse; - public AsyncPregenMethod(World world, int threads) { + public AsyncPregenMethod(World world, int unusedThreads) { if (!PaperLib.isPaper()) { throw new UnsupportedOperationException("Cannot use PaperAsync on non paper!"); } this.world = world; - burst = new MultiBurst("Iris Async Pregen", Thread.MIN_PRIORITY); - semaphore = new Semaphore(256); + this.executor = IrisSettings.get().getPregen().isUseTicketQueue() ? new TicketExecutor() : new ServiceExecutor(); + this.threads = IrisSettings.get().getPregen().getMaxConcurrency(); + this.semaphore = new Semaphore(this.threads, true); + this.urgent = IrisSettings.get().getPregen().useHighPriority; this.lastUse = new KMap<>(); } @@ -60,13 +69,18 @@ public class AsyncPregenMethod implements PregeneratorMethod { return; } - for (Chunk i : new ArrayList<>(lastUse.keySet())) { - Long lastUseTime = lastUse.get(i); - if (!i.isLoaded() || (lastUseTime != null && M.ms() - lastUseTime >= 10000)) { - i.unload(); - lastUse.remove(i); + long minTime = M.ms() - 10_000; + lastUse.entrySet().removeIf(i -> { + final Chunk chunk = i.getKey(); + final Long lastUseTime = i.getValue(); + if (!chunk.isLoaded() || lastUseTime == null) + return true; + if (lastUseTime < minTime) { + chunk.unload(); + return true; } - } + return false; + }); world.save(); }).get(); } catch (Throwable e) { @@ -74,24 +88,10 @@ public class AsyncPregenMethod implements PregeneratorMethod { } } - private void completeChunk(int x, int z, PregenListener listener) { - try { - PaperLib.getChunkAtAsync(world, x, z, true).thenAccept((i) -> { - lastUse.put(i, M.ms()); - listener.onChunkGenerated(x, z); - listener.onChunkCleaned(x, z); - }).get(); - } catch (InterruptedException ignored) { - } catch (Throwable e) { - e.printStackTrace(); - } finally { - semaphore.release(); - } - } - @Override public void init() { unloadAndSaveAllChunks(); + increaseWorkerThreads(); } @Override @@ -101,9 +101,10 @@ public class AsyncPregenMethod implements PregeneratorMethod { @Override public void close() { - semaphore.acquireUninterruptibly(256); + semaphore.acquireUninterruptibly(threads); unloadAndSaveAllChunks(); - burst.close(); + executor.shutdown(); + resetWorkerThreads(); } @Override @@ -129,7 +130,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { } catch (InterruptedException e) { return; } - burst.complete(() -> completeChunk(x, z, listener)); + executor.generate(x, z, listener); } @Override @@ -140,4 +141,100 @@ public class AsyncPregenMethod implements PregeneratorMethod { return null; } + + public static void increaseWorkerThreads() { + THREAD_COUNT.updateAndGet(i -> { + if (i > 0) return 1; + var adjusted = IrisSettings.get().getConcurrency().getWorldGenThreads(); + try { + var field = Class.forName("ca.spottedleaf.moonrise.common.util.MoonriseCommon").getDeclaredField("WORKER_POOL"); + var pool = field.get(null); + var threads = ((Thread[]) pool.getClass().getDeclaredMethod("getCoreThreads").invoke(pool)).length; + if (threads >= adjusted) return 0; + + pool.getClass().getDeclaredMethod("adjustThreadCount", int.class).invoke(pool, adjusted); + return threads; + } catch (Throwable e) { + Iris.warn("Failed to increase worker threads, if you are on paper or a fork of it please increase it manually to " + adjusted); + Iris.warn("For more information see https://docs.papermc.io/paper/reference/global-configuration#chunk_system_worker_threads"); + if (e instanceof InvocationTargetException) { + Iris.reportError(e); + e.printStackTrace(); + } + } + return 0; + }); + } + + public static void resetWorkerThreads() { + THREAD_COUNT.updateAndGet(i -> { + if (i == 0) return 0; + try { + var field = Class.forName("ca.spottedleaf.moonrise.common.util.MoonriseCommon").getDeclaredField("WORKER_POOL"); + var pool = field.get(null); + var method = pool.getClass().getDeclaredMethod("adjustThreadCount", int.class); + method.invoke(pool, i); + return 0; + } catch (Throwable e) { + Iris.reportError(e); + Iris.error("Failed to reset worker threads"); + e.printStackTrace(); + } + return i; + }); + } + + private interface Executor { + void generate(int x, int z, PregenListener listener); + default void shutdown() {} + } + + private class ServiceExecutor implements Executor { + private final ExecutorService service = IrisSettings.get().getPregen().isUseVirtualThreads() ? + Executors.newVirtualThreadPerTaskExecutor() : + new MultiBurst("Iris Async Pregen", Thread.MIN_PRIORITY); + + public void generate(int x, int z, PregenListener listener) { + service.submit(() -> { + try { + PaperLib.getChunkAtAsync(world, x, z, true, urgent).thenAccept((i) -> { + listener.onChunkGenerated(x, z); + listener.onChunkCleaned(x, z); + if (i == null) return; + lastUse.put(i, M.ms()); + }).get(); + } catch (InterruptedException ignored) { + } catch (Throwable e) { + Iris.reportError(e); + e.printStackTrace(); + } finally { + semaphore.release(); + } + }); + } + + @Override + public void shutdown() { + service.shutdown(); + } + } + + private class TicketExecutor implements Executor { + @Override + public void generate(int x, int z, PregenListener listener) { + PaperLib.getChunkAtAsync(world, x, z, true, urgent) + .exceptionally(e -> { + Iris.reportError(e); + e.printStackTrace(); + return null; + }) + .thenAccept(i -> { + semaphore.release(); + listener.onChunkGenerated(x, z); + listener.onChunkCleaned(x, z); + if (i == null) return; + lastUse.put(i, M.ms()); + }); + } + } } diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/CachedPregenMethod.java b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/CachedPregenMethod.java new file mode 100644 index 000000000..91c4ddb87 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/CachedPregenMethod.java @@ -0,0 +1,86 @@ +package com.volmit.iris.core.pregenerator.methods; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.core.pregenerator.PregeneratorMethod; +import com.volmit.iris.core.pregenerator.cache.PregenCache; +import com.volmit.iris.core.service.GlobalCacheSVC; +import com.volmit.iris.util.mantle.Mantle; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class CachedPregenMethod implements PregeneratorMethod { + private final PregeneratorMethod method; + private final PregenCache cache; + + public CachedPregenMethod(PregeneratorMethod method, String worldName) { + this.method = method; + var cache = Iris.service(GlobalCacheSVC.class).get(worldName); + if (cache == null) { + Iris.debug("Could not find existing cache for " + worldName + " creating fallback"); + cache = GlobalCacheSVC.createDefault(worldName); + } + this.cache = cache; + } + + @Override + public void init() { + method.init(); + } + + @Override + public void close() { + method.close(); + cache.write(); + } + + @Override + public void save() { + method.save(); + cache.write(); + } + + @Override + public boolean supportsRegions(int x, int z, PregenListener listener) { + return cache.isRegionCached(x, z) || method.supportsRegions(x, z, listener); + } + + @Override + public String getMethod(int x, int z) { + return method.getMethod(x, z); + } + + @Override + public void generateRegion(int x, int z, PregenListener listener) { + if (cache.isRegionCached(x, z)) { + listener.onRegionGenerated(x, z); + + int rX = x << 5, rZ = z << 5; + for (int cX = 0; cX < 32; cX++) { + for (int cZ = 0; cZ < 32; cZ++) { + listener.onChunkGenerated(rX + cX, rZ + cZ, true); + listener.onChunkCleaned(rX + cX, rZ + cZ); + } + } + return; + } + method.generateRegion(x, z, listener); + cache.cacheRegion(x, z); + } + + @Override + public void generateChunk(int x, int z, PregenListener listener) { + if (cache.isChunkCached(x, z)) { + listener.onChunkGenerated(x, z, true); + listener.onChunkCleaned(x, z); + return; + } + method.generateChunk(x, z, listener); + cache.cacheChunk(x, z); + } + + @Override + public Mantle getMantle() { + return method.getMantle(); + } +} 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 d094c82ff..c0f327b6f 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 @@ -377,17 +377,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 398d1630b..50ff64886 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 @@ -19,9 +19,12 @@ package com.volmit.iris.core.project; import com.volmit.iris.Iris; +import com.volmit.iris.core.link.Identifier; +import com.volmit.iris.core.link.data.DataType; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.loader.IrisRegistrant; import com.volmit.iris.core.loader.ResourceLoader; +import com.volmit.iris.core.service.ExternalDataSVC; import com.volmit.iris.engine.object.annotations.*; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; @@ -39,7 +42,6 @@ import java.lang.reflect.Modifier; import java.util.List; import java.util.Map; import java.util.function.Function; -import java.util.stream.Collectors; public class SchemaBuilder { private static final String SYMBOL_LIMIT__N = "*"; @@ -140,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); } @@ -151,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) { @@ -276,16 +268,18 @@ public class SchemaBuilder { if (!definitions.containsKey(key)) { JSONObject j = new JSONObject(); - KList list = new KList<>(); - list.addAll(Iris.linkMythicMobs.getMythicMobTypes().stream().map(s -> "MythicMobs:" + s).collect(Collectors.toList())); - //TODO add Citizens stuff here too + KList list = Iris.service(ExternalDataSVC.class) + .getAllIdentifiers(DataType.ENTITY) + .stream() + .map(Identifier::toString) + .collect(KList.collector()); j.put("enum", list.toJSONStringArray()); definitions.put(key, j); } - fancyType = "Mythic Mob Type"; + fancyType = "Custom Mob Type"; prop.put("$ref", "#/definitions/" + key); - description.add(SYMBOL_TYPE__N + " Must be a valid Mythic Mob Type (use ctrl+space for auto complete!) Define mythic mobs with the mythic mobs plugin configuration files."); + description.add(SYMBOL_TYPE__N + " Must be a valid Custom Mob Type (use ctrl+space for auto complete!)"); } else if (k.isAnnotationPresent(RegistryListFont.class)) { String key = "enum-font"; @@ -476,6 +470,26 @@ public class SchemaBuilder { items.put("$ref", "#/definitions/" + key); prop.put("items", items); description.add(SYMBOL_TYPE__N + " Must be a valid Enchantment Type (use ctrl+space for auto complete!)"); + } else if (k.isAnnotationPresent(RegistryListFunction.class)) { + var functionClass = k.getDeclaredAnnotation(RegistryListFunction.class).value(); + try { + var instance = functionClass.getDeclaredConstructor().newInstance(); + String key = instance.key(); + fancyType = instance.fancyName(); + + if (!definitions.containsKey(key)) { + JSONObject j = new JSONObject(); + j.put("enum", instance.apply(data)); + definitions.put(key, j); + } + + JSONObject items = new JSONObject(); + items.put("$ref", "#/definitions/" + key); + prop.put("items", items); + description.add(SYMBOL_TYPE__N + " Must be a valid " + fancyType + " (use ctrl+space for auto complete!)"); + } catch (Throwable e) { + Iris.error("Could not execute apply method in " + functionClass.getName()); + } } else if (t.type().equals(PotionEffectType.class)) { fancyType = "List of Potion Effect Types"; String key = "enum-potion-effect-type"; @@ -512,8 +526,16 @@ public class SchemaBuilder { d.add(fancyType); d.add(getDescription(k.getType())); - 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(); d.add(" "); d.add("You can instead specify \"snippet/" + sm + "/some-name.json\" to use a snippet file instead of specifying it here."); } @@ -541,35 +563,36 @@ public class SchemaBuilder { description.forEach((g) -> d.add(g.trim())); prop.put("type", type); prop.put("description", d.toString("\n")); + 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", d.toString("\n")); - str.put("description", d.toString("\n")); - anyOf.put("anyOf", arr); - anyOf.put("description", d.toString("\n")); - 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")); + anyOf.put("anyOf", arr); + anyOf.put("description", prop.getString("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 5217be9f2..bef5f165a 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,14 @@ 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 io.papermc.lib.PaperLib; + +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 +19,46 @@ 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() { + if (unstablemode) { + return "unstable"; + } else if (warningmode) { + return "warning"; + } else { + return "stable"; + } + } + + public static void suggestPaper() { + PaperLib.suggestPaper(Iris.instance); + } + + 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 2c59c2ae9..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(); @@ -160,17 +173,41 @@ public class ServerBootSFG { } public static boolean enoughDiskSpace() { - File freeSpace = new File(Bukkit.getWorldContainer() + "."); + File freeSpace = Bukkit.getWorldContainer(); double gigabytes = freeSpace.getFreeSpace() / (1024.0 * 1024.0 * 1024.0); - if (gigabytes > 3){ - return true; - } else { - return false; - } + return gigabytes > 3; } private static boolean checkJavac(String path) { 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..49303b60c 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 @@ -20,16 +20,21 @@ package com.volmit.iris.core.service; import com.volmit.iris.Iris; import com.volmit.iris.core.link.*; +import com.volmit.iris.core.link.data.DataType; import com.volmit.iris.core.nms.container.Pair; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.io.JarScanner; import com.volmit.iris.util.plugin.IrisService; +import com.volmit.iris.util.scheduling.J; import lombok.Data; import lombok.NonNull; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Entity; import org.bukkit.event.EventHandler; import org.bukkit.event.server.PluginEnableEvent; import org.bukkit.inventory.ItemStack; @@ -47,39 +52,12 @@ public class ExternalDataSVC implements IrisService { Iris.info("Loading ExternalDataProvider..."); Bukkit.getPluginManager().registerEvents(this, Iris.instance); - providers.add(new NexoDataProvider()); - if (Bukkit.getPluginManager().getPlugin("Nexo") != null) { - Iris.info("Nexo found, loading NexoDataProvider..."); - } - providers.add(new MythicCrucibleDataProvider()); - if (Bukkit.getPluginManager().getPlugin("MythicCrucible") != null) { - Iris.info("MythicCrucible found, loading MythicCrucibleDataProvider..."); - } - providers.add(new ItemAdderDataProvider()); - if (Bukkit.getPluginManager().getPlugin("ItemAdder") != null) { - Iris.info("ItemAdder found, loading ItemAdderDataProvider..."); - } - providers.add(new ExecutableItemsDataProvider()); - if (Bukkit.getPluginManager().getPlugin("ExecutableItems") != null) { - Iris.info("ExecutableItems found, loading ExecutableItemsDataProvider..."); - } - providers.add(new HMCLeavesDataProvider()); - if (Bukkit.getPluginManager().getPlugin("HMCLeaves") != null) { - Iris.info("BlockAdder found, loading HMCLeavesDataProvider..."); - } - providers.add(new MMOItemsDataProvider()); - if (Bukkit.getPluginManager().getPlugin("MMOItems") != null) { - Iris.info("MMOItems found, loading MMOItemsDataProvider..."); - } - providers.add(new EcoItemsDataProvider()); - if (Bukkit.getPluginManager().getPlugin("EcoItems") != null) { - Iris.info("EcoItems found, loading EcoItemsDataProvider..."); - } - + providers.addAll(createProviders()); for (ExternalDataProvider p : providers) { if (p.isReady()) { activeProviders.add(p); p.init(); + Iris.instance.registerListener(p); Iris.info("Enabled ExternalDataProvider for %s.", p.getPluginId()); } } @@ -95,6 +73,7 @@ public class ExternalDataSVC implements IrisService { providers.stream().filter(p -> p.isReady() && p.getPlugin().equals(e.getPlugin())).findFirst().ifPresent(edp -> { activeProviders.add(edp); edp.init(); + Iris.instance.registerListener(edp); Iris.info("Enabled ExternalDataProvider for %s.", edp.getPluginId()); }); } @@ -109,6 +88,7 @@ public class ExternalDataSVC implements IrisService { if (provider.isReady()) { activeProviders.add(provider); provider.init(); + Iris.instance.registerListener(provider); } } @@ -116,7 +96,7 @@ public class ExternalDataSVC implements IrisService { var pair = parseState(key); Identifier mod = pair.getA(); - Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(mod, false)).findFirst(); + Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(mod, DataType.BLOCK)).findFirst(); if (provider.isEmpty()) return Optional.empty(); try { @@ -128,7 +108,7 @@ public class ExternalDataSVC implements IrisService { } public Optional getItemStack(Identifier key, KMap customNbt) { - Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(key, true)).findFirst(); + Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(key, DataType.ITEM)).findFirst(); if (provider.isEmpty()) { Iris.warn("No matching Provider found for modded material \"%s\"!", key); return Optional.empty(); @@ -142,7 +122,7 @@ public class ExternalDataSVC implements IrisService { } public void processUpdate(Engine engine, Block block, Identifier blockId) { - Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(blockId, false)).findFirst(); + Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(blockId, DataType.BLOCK)).findFirst(); if (provider.isEmpty()) { Iris.warn("No matching Provider found for modded material \"%s\"!", blockId); return; @@ -150,16 +130,24 @@ public class ExternalDataSVC implements IrisService { provider.get().processUpdate(engine, block, blockId); } - public Identifier[] getAllBlockIdentifiers() { - KList names = new KList<>(); - activeProviders.forEach(p -> names.add(p.getBlockTypes())); - return names.toArray(new Identifier[0]); + public Entity spawnMob(Location location, Identifier mobId) { + Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(mobId, DataType.ENTITY)).findFirst(); + if (provider.isEmpty()) { + Iris.warn("No matching Provider found for modded mob \"%s\"!", mobId); + return null; + } + try { + return provider.get().spawnMob(location, mobId); + } catch (MissingResourceException e) { + Iris.error(e.getMessage() + " - [" + e.getClassName() + ":" + e.getKey() + "]"); + return null; + } } - public Identifier[] getAllItemIdentifiers() { - KList names = new KList<>(); - activeProviders.forEach(p -> names.add(p.getItemTypes())); - return names.toArray(new Identifier[0]); + public Collection getAllIdentifiers(DataType dataType) { + return activeProviders.stream() + .flatMap(p -> p.getTypes(dataType).stream()) + .toList(); } public static Pair> parseState(Identifier key) { @@ -184,4 +172,21 @@ public class ExternalDataSVC implements IrisService { .collect(Collectors.joining(",", key.key() + "[", "]")); return new Identifier(key.namespace(), path); } + + private static KList createProviders() { + JarScanner jar = new JarScanner(Iris.instance.getJarFile(), "com.volmit.iris.core.link.data", false); + J.attempt(jar::scan); + KList providers = new KList<>(); + + for (Class c : jar.getClasses()) { + if (ExternalDataProvider.class.isAssignableFrom(c)) { + try { + ExternalDataProvider p = (ExternalDataProvider) c.getDeclaredConstructor().newInstance(); + if (p.getPlugin() != null) Iris.info(p.getPluginId() + " found, loading " + c.getSimpleName() + "..."); + providers.add(p); + } catch (Throwable ignored) {} + } + } + return providers; + } } diff --git a/core/src/main/java/com/volmit/iris/core/service/GlobalCacheSVC.java b/core/src/main/java/com/volmit/iris/core/service/GlobalCacheSVC.java new file mode 100644 index 000000000..e8c194974 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/service/GlobalCacheSVC.java @@ -0,0 +1,108 @@ +package com.volmit.iris.core.service; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.pregenerator.cache.PregenCache; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.plugin.IrisService; +import lombok.NonNull; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.event.world.WorldInitEvent; +import org.bukkit.event.world.WorldUnloadEvent; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.function.Function; + +public class GlobalCacheSVC implements IrisService { + private static final Cache REFERENCE_CACHE = Caffeine.newBuilder().weakValues().build(); + private final KMap globalCache = new KMap<>(); + private transient boolean lastState; + private static boolean disabled = true; + + @Override + public void onEnable() { + disabled = false; + lastState = !IrisSettings.get().getWorld().isGlobalPregenCache(); + if (lastState) return; + Bukkit.getWorlds().forEach(this::createCache); + } + + @Override + public void onDisable() { + disabled = true; + globalCache.qclear((world, cache) -> cache.write()); + } + + @Nullable + public PregenCache get(@NonNull World world) { + return globalCache.get(world.getName()); + } + + @Nullable + public PregenCache get(@NonNull String world) { + return globalCache.get(world); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(WorldInitEvent event) { + if (isDisabled()) return; + createCache(event.getWorld()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(WorldUnloadEvent event) { + var cache = globalCache.remove(event.getWorld().getName()); + if (cache == null) return; + cache.write(); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(ChunkLoadEvent event) { + var cache = get(event.getWorld()); + if (cache == null) return; + cache.cacheChunk(event.getChunk().getX(), event.getChunk().getZ()); + } + + private void createCache(World world) { + globalCache.computeIfAbsent(world.getName(), GlobalCacheSVC::createDefault); + } + + private boolean isDisabled() { + boolean conf = IrisSettings.get().getWorld().isGlobalPregenCache(); + if (lastState != conf) + return lastState; + + if (conf) { + Bukkit.getWorlds().forEach(this::createCache); + } else { + globalCache.values().removeIf(cache -> { + cache.write(); + return true; + }); + } + + return lastState = !conf; + } + + + @NonNull + public static PregenCache createCache(@NonNull String worldName, @NonNull Function provider) { + return REFERENCE_CACHE.get(worldName, provider); + } + + @NonNull + public static PregenCache createDefault(@NonNull String worldName) { + return createCache(worldName, GlobalCacheSVC::createDefault0); + } + + private static PregenCache createDefault0(String worldName) { + if (disabled) return PregenCache.EMPTY; + return PregenCache.create(new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pregen"))).sync(); + } +} diff --git a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java b/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java index 2220ccc26..df3951ea1 100644 --- a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java @@ -1,317 +1,246 @@ package com.volmit.iris.core.service; +import com.google.common.util.concurrent.AtomicDouble; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.tools.IrisToolbelt; 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.format.C; import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.mantle.TectonicPlate; -import com.volmit.iris.util.misc.getHardware; +import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.plugin.IrisService; -import com.volmit.iris.util.scheduling.ChronoLatch; +import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.Looper; -import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import lombok.Synchronized; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.event.EventHandler; -import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.server.ServerLoadEvent; import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldUnloadEvent; -import org.checkerframework.checker.units.qual.A; +import org.jetbrains.annotations.Nullable; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Supplier; public class IrisEngineSVC implements IrisService { - public static IrisEngineSVC instance; - public boolean isServerShuttingDown = false; - public boolean isServerLoaded = false; - private static final AtomicInteger tectonicLimit = new AtomicInteger(30); - private ReentrantLock lastUseLock; - private KMap lastUse; - private List IrisWorlds; - private Looper cacheTicker; - private Looper trimTicker; - private Looper unloadTicker; + private final AtomicInteger tectonicLimit = new AtomicInteger(30); + private final AtomicInteger tectonicPlates = new AtomicInteger(); + private final AtomicInteger queuedTectonicPlates = new AtomicInteger(); + private final AtomicInteger trimmerAlive = new AtomicInteger(); + private final AtomicInteger unloaderAlive = new AtomicInteger(); + private final AtomicInteger totalWorlds = new AtomicInteger(); + private final AtomicDouble maxIdleDuration = new AtomicDouble(); + private final AtomicDouble minIdleDuration = new AtomicDouble(); + private final AtomicLong loadedChunks = new AtomicLong(); + private final KMap worlds = new KMap<>(); + private ScheduledExecutorService service; private Looper updateTicker; - private PrecisionStopwatch trimAlive; - private PrecisionStopwatch unloadAlive; - public PrecisionStopwatch trimActiveAlive; - public PrecisionStopwatch unloadActiveAlive; - private AtomicInteger TotalTectonicPlates; - private AtomicInteger TotalQueuedTectonicPlates; - private AtomicInteger TotalNotQueuedTectonicPlates; - private AtomicBoolean IsUnloadAlive; - private AtomicBoolean IsTrimAlive; - ChronoLatch cl; - - public List corruptedIrisWorlds = new ArrayList<>(); @Override public void onEnable() { - this.cl = new ChronoLatch(5000); - lastUse = new KMap<>(); - lastUseLock = new ReentrantLock(); - IrisWorlds = new ArrayList<>(); - IsUnloadAlive = new AtomicBoolean(true); - IsTrimAlive = new AtomicBoolean(true); - trimActiveAlive = new PrecisionStopwatch(); - unloadActiveAlive = new PrecisionStopwatch(); - trimAlive = new PrecisionStopwatch(); - unloadAlive = new PrecisionStopwatch(); - TotalTectonicPlates = new AtomicInteger(); - TotalQueuedTectonicPlates = new AtomicInteger(); - TotalNotQueuedTectonicPlates = new AtomicInteger(); - tectonicLimit.set(2); - long t = getHardware.getProcessMemory(); - while (t > 200) { - tectonicLimit.getAndAdd(1); - t = t - 200; - } - this.setup(); - this.TrimLogic(); - this.UnloadLogic(); - - trimAlive.begin(); - unloadAlive.begin(); - trimActiveAlive.begin(); - unloadActiveAlive.begin(); - - updateTicker.start(); - cacheTicker.start(); - //trimTicker.start(); - //unloadTicker.start(); - instance = this; - + var settings = IrisSettings.get().getPerformance(); + var engine = settings.getEngineSVC(); + service = Executors.newScheduledThreadPool(0, + (engine.isUseVirtualThreads() + ? Thread.ofVirtual() + : Thread.ofPlatform().priority(engine.getPriority())) + .name("Iris EngineSVC-", 0) + .factory()); + tectonicLimit.set(settings.getTectonicPlateSize()); + Bukkit.getWorlds().forEach(this::add); + setup(); } - public void engineStatus() { - boolean trimAlive = trimTicker.isAlive(); - boolean unloadAlive = unloadTicker.isAlive(); - Iris.info("Status:"); - Iris.info("- Trim: " + trimAlive); - Iris.info("- Unload: " + unloadAlive); - + @Override + public void onDisable() { + service.shutdown(); + updateTicker.interrupt(); + worlds.keySet().forEach(this::remove); + worlds.clear(); } - public static int getTectonicLimit() { - return tectonicLimit.get(); + public void engineStatus(VolmitSender sender) { + sender.sendMessage(C.DARK_PURPLE + "-------------------------"); + sender.sendMessage(C.DARK_PURPLE + "Status:"); + sender.sendMessage(C.DARK_PURPLE + "- Service: " + C.LIGHT_PURPLE + (service.isShutdown() ? "Shutdown" : "Running")); + sender.sendMessage(C.DARK_PURPLE + "- Updater: " + C.LIGHT_PURPLE + (updateTicker.isAlive() ? "Running" : "Stopped")); + sender.sendMessage(C.DARK_PURPLE + "- Trimmers: " + C.LIGHT_PURPLE + trimmerAlive.get()); + sender.sendMessage(C.DARK_PURPLE + "- Unloaders: " + C.LIGHT_PURPLE + unloaderAlive.get()); + sender.sendMessage(C.DARK_PURPLE + "Tectonic Plates:"); + sender.sendMessage(C.DARK_PURPLE + "- Limit: " + C.LIGHT_PURPLE + tectonicLimit.get()); + sender.sendMessage(C.DARK_PURPLE + "- Total: " + C.LIGHT_PURPLE + tectonicPlates.get()); + sender.sendMessage(C.DARK_PURPLE + "- Queued: " + C.LIGHT_PURPLE + queuedTectonicPlates.get()); + sender.sendMessage(C.DARK_PURPLE + "- Max Idle Duration: " + C.LIGHT_PURPLE + Form.duration(maxIdleDuration.get(), 2)); + sender.sendMessage(C.DARK_PURPLE + "- Min Idle Duration: " + C.LIGHT_PURPLE + Form.duration(minIdleDuration.get(), 2)); + sender.sendMessage(C.DARK_PURPLE + "Other:"); + sender.sendMessage(C.DARK_PURPLE + "- Iris Worlds: " + C.LIGHT_PURPLE + totalWorlds.get()); + sender.sendMessage(C.DARK_PURPLE + "- Loaded Chunks: " + C.LIGHT_PURPLE + loadedChunks.get()); + sender.sendMessage(C.DARK_PURPLE + "- Cache Size: " + C.LIGHT_PURPLE + Form.f(IrisData.cacheSize())); + sender.sendMessage(C.DARK_PURPLE + "-------------------------"); } @EventHandler public void onWorldUnload(WorldUnloadEvent event) { - updateWorlds(); + remove(event.getWorld()); } @EventHandler public void onWorldLoad(WorldLoadEvent event) { - updateWorlds(); + add(event.getWorld()); } - @EventHandler - public void onServerBoot(ServerLoadEvent event) { - isServerLoaded = true; + private void remove(World world) { + var entry = worlds.remove(world); + if (entry == null) return; + entry.close(); } - @EventHandler - public void onPluginDisable(PluginDisableEvent event) { - if (event.getPlugin().equals(Iris.instance)) { - isServerShuttingDown = true; - } + private void add(World world) { + var access = IrisToolbelt.access(world); + if (access == null) return; + worlds.put(world, new Registered(world.getName(), access)); } - public void updateWorlds() { - for (World world : Bukkit.getWorlds()) { - try { - if (IrisToolbelt.access(world).getEngine() != null) { - IrisWorlds.add(world); - } - } catch (Exception e) { - // no - } - } - } - - private void setup() { - cacheTicker = new Looper() { - @Override - protected long loop() { - long now = System.currentTimeMillis(); - lastUseLock.lock(); - try { - for (World key : new ArrayList<>(lastUse.keySet())) { - Long last = lastUse.get(key); - if (last == null) - continue; - if (now - last > 60000) { - lastUse.remove(key); - } - } - } finally { - lastUseLock.unlock(); - } - return 1000; - } - }; + private synchronized void setup() { + if (updateTicker != null && updateTicker.isAlive()) + return; updateTicker = new Looper() { @Override protected long loop() { try { - TotalQueuedTectonicPlates.set(0); - TotalNotQueuedTectonicPlates.set(0); - TotalTectonicPlates.set(0); - for (World world : IrisWorlds) { - Engine engine = Objects.requireNonNull(IrisToolbelt.access(world)).getEngine(); - TotalQueuedTectonicPlates.addAndGet((int) engine.getMantle().getToUnload()); - TotalNotQueuedTectonicPlates.addAndGet((int) engine.getMantle().getNotQueuedLoadedRegions()); - TotalTectonicPlates.addAndGet(engine.getMantle().getLoadedRegionCount()); - } - if (!isServerShuttingDown && isServerLoaded) { - if (!trimTicker.isAlive()) { - Iris.info(C.RED + "TrimTicker found dead! Booting it up!"); - try { - TrimLogic(); - } catch (Exception e) { - Iris.error("What happened?"); - e.printStackTrace(); - } - } + queuedTectonicPlates.set(0); + tectonicPlates.set(0); + loadedChunks.set(0); + unloaderAlive.set(0); + trimmerAlive.set(0); + totalWorlds.set(0); - if (!unloadTicker.isAlive()) { - Iris.info(C.RED + "UnloadTicker found dead! Booting it up!"); - try { - UnloadLogic(); - } catch (Exception e) { - Iris.error("What happened?"); - e.printStackTrace(); - } - } - } + double maxDuration = Long.MIN_VALUE; + double minDuration = Long.MAX_VALUE; + for (var entry : worlds.entrySet()) { + var registered = entry.getValue(); + if (registered.closed) continue; - } catch (Exception e) { - return -1; + totalWorlds.incrementAndGet(); + unloaderAlive.addAndGet(registered.unloaderAlive() ? 1 : 0); + trimmerAlive.addAndGet(registered.trimmerAlive() ? 1 : 0); + + var engine = registered.getEngine(); + if (engine == null) continue; + + queuedTectonicPlates.addAndGet((int) engine.getMantle().getUnloadRegionCount()); + tectonicPlates.addAndGet(engine.getMantle().getLoadedRegionCount()); + loadedChunks.addAndGet(entry.getKey().getLoadedChunks().length); + + double duration = engine.getMantle().getAdjustedIdleDuration(); + if (duration > maxDuration) maxDuration = duration; + if (duration < minDuration) minDuration = duration; + } + maxIdleDuration.set(maxDuration); + minIdleDuration.set(minDuration); + + worlds.values().forEach(Registered::update); + } catch (Throwable e) { + e.printStackTrace(); } return 1000; } }; + updateTicker.start(); } - public void TrimLogic() { - if (trimTicker == null || !trimTicker.isAlive()) { - trimTicker = new Looper() { - private final Supplier supplier = createSupplier(); - @Override - protected long loop() { - long start = System.currentTimeMillis(); - trimAlive.reset(); + private final class Registered { + private final String name; + private final PlatformChunkGenerator access; + private transient ScheduledFuture trimmer; + private transient ScheduledFuture unloader; + private transient boolean closed; + + private Registered(String name, PlatformChunkGenerator access) { + this.name = name; + this.access = access; + update(); + } + + private boolean unloaderAlive() { + return unloader != null && !unloader.isDone() && !unloader.isCancelled(); + } + + private boolean trimmerAlive() { + return trimmer != null && !trimmer.isDone() && !trimmer.isCancelled(); + } + + @Synchronized + private void update() { + if (closed || service == null || service.isShutdown()) + return; + + if (trimmer == null || trimmer.isDone() || trimmer.isCancelled()) { + trimmer = service.scheduleAtFixedRate(() -> { + Engine engine = getEngine(); + if (engine == null || !engine.getMantle().getMantle().shouldReduce(engine)) + return; + try { - Engine engine = supplier.get(); - if (engine != null) { - engine.getMantle().trim(tectonicLimit.get() / lastUse.size()); + engine.getMantle().trim(tectonicLimit()); + } catch (Throwable e) { + Iris.reportError(e); + Iris.error("EngineSVC: Failed to trim for " + name); + e.printStackTrace(); + } + }, RNG.r.nextInt(1000), 1000, TimeUnit.MILLISECONDS); + } + + if (unloader == null || unloader.isDone() || unloader.isCancelled()) { + unloader = service.scheduleAtFixedRate(() -> { + Engine engine = getEngine(); + if (engine == null || !engine.getMantle().getMantle().shouldReduce(engine)) + return; + + try { + long unloadStart = System.currentTimeMillis(); + int count = engine.getMantle().unloadTectonicPlate(tectonicLimit()); + if (count > 0) { + Iris.debug(C.GOLD + "Unloaded " + C.YELLOW + count + " TectonicPlates in " + C.RED + Form.duration(System.currentTimeMillis() - unloadStart, 2)); } } catch (Throwable e) { Iris.reportError(e); - Iris.info(C.RED + "EngineSVC: Failed to trim."); + Iris.error("EngineSVC: Failed to unload for " + name); e.printStackTrace(); - return -1; } - - int size = lastUse.size(); - long time = (size > 0 ? 1000 / size : 1000) - (System.currentTimeMillis() - start); - if (time <= 0) - return 0; - return time; - } - }; - trimTicker.start(); - } - } - public void UnloadLogic() { - if (unloadTicker == null || !unloadTicker.isAlive()) { - unloadTicker = new Looper() { - private final Supplier supplier = createSupplier(); - - @Override - protected long loop() { - long start = System.currentTimeMillis(); - unloadAlive.reset(); - try { - Engine engine = supplier.get(); - if (engine != null) { - long unloadStart = System.currentTimeMillis(); - int count = engine.getMantle().unloadTectonicPlate(tectonicLimit.get() / lastUse.size()); - if (count > 0) { - Iris.debug(C.GOLD + "Unloaded " + C.YELLOW + count + " TectonicPlates in " + C.RED + Form.duration(System.currentTimeMillis() - unloadStart, 2)); - } - } - } catch (Throwable e) { - Iris.reportError(e); - Iris.info(C.RED + "EngineSVC: Failed to unload."); - e.printStackTrace(); - return -1; - } - - int size = lastUse.size(); - long time = (size > 0 ? 1000 / size : 1000) - (System.currentTimeMillis() - start); - if (time <= 0) - return 0; - return time; - } - }; - unloadTicker.start(); - } - } - - private Supplier createSupplier() { - AtomicInteger i = new AtomicInteger(); - return () -> { - List worlds = Bukkit.getWorlds(); - if (i.get() >= worlds.size()) { - i.set(0); + }, RNG.r.nextInt(1000), 1000, TimeUnit.MILLISECONDS); } - try { - for (int j = 0; j < worlds.size(); j++) { - World world = worlds.get(i.getAndIncrement()); - PlatformChunkGenerator generator = IrisToolbelt.access(world); - if (i.get() >= worlds.size()) { - i.set(0); - } + } - if (generator != null) { - Engine engine = generator.getEngine(); - boolean closed = engine.getMantle().getData().isClosed(); - if (engine != null && !engine.isStudio() && !closed) { - lastUseLock.lock(); - lastUse.put(world, System.currentTimeMillis()); - lastUseLock.unlock(); - return engine; - } - } - } - } catch (Throwable e) { - Iris.info(C.RED + "EngineSVC: Failed to create supplier."); - e.printStackTrace(); - Iris.reportError(e); + private int tectonicLimit() { + return tectonicLimit.get() / Math.max(worlds.size(), 1); + } + + @Synchronized + private void close() { + if (closed) return; + closed = true; + + if (trimmer != null) { + trimmer.cancel(false); + trimmer = null; } - return null; - }; - } - @Override - public void onDisable() { - cacheTicker.interrupt(); - trimTicker.interrupt(); - unloadTicker.interrupt(); - lastUse.clear(); + if (unloader != null) { + unloader.cancel(false); + unloader = null; + } + } + + @Nullable + private Engine getEngine() { + if (closed) return null; + return access.getEngine(); + } } } diff --git a/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java b/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java index 118a11e13..bf070d063 100644 --- a/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java @@ -18,7 +18,6 @@ package com.volmit.iris.core.service; -import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; @@ -250,30 +249,25 @@ public class StudioSVC implements IrisService { return; } - File dimensions = new File(dir, "dimensions"); + IrisData data = IrisData.get(dir); + String[] dimensions = data.getDimensionLoader().getPossibleKeys(); - if (!(dimensions.exists() && dimensions.isDirectory())) { - sender.sendMessage("Invalid Format. Missing dimensions folder"); - return; - } - - if (dimensions.listFiles() == null) { + if (dimensions == null || dimensions.length == 0) { sender.sendMessage("No dimension file found in the extracted zip file."); sender.sendMessage("Check it is there on GitHub and report this to staff!"); - } else if (dimensions.listFiles().length != 1) { + } else if (dimensions.length != 1) { sender.sendMessage("Dimensions folder must have 1 file in it"); return; } - File dim = dimensions.listFiles()[0]; + IrisDimension d = data.getDimensionLoader().load(dimensions[0]); - if (!dim.isFile()) { + if (d == null) { sender.sendMessage("Invalid dimension (folder) in dimensions folder"); return; } - String key = dim.getName().split("\\Q.\\E")[0]; - IrisDimension d = new Gson().fromJson(IO.readAll(dim), IrisDimension.class); + String key = d.getLoadKey(); sender.sendMessage("Importing " + d.getName() + " (" + key + ")"); File packEntry = new File(packs, key); diff --git a/core/src/main/java/com/volmit/iris/core/service/WandSVC.java b/core/src/main/java/com/volmit/iris/core/service/WandSVC.java index 53d3800ac..8d984ba91 100644 --- a/core/src/main/java/com/volmit/iris/core/service/WandSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/WandSVC.java @@ -51,6 +51,7 @@ import org.bukkit.util.Vector; import java.awt.Color; import java.util.ArrayList; +import java.util.Arrays; import java.util.Objects; import java.util.concurrent.CountDownLatch; @@ -80,6 +81,8 @@ public class WandSVC implements IrisService { try { Location[] f = getCuboid(p); + if (f == null || f[0] == null || f[1] == null) + return null; Cuboid c = new Cuboid(f[0], f[1]); IrisObject s = new IrisObject(c.getSizeX(), c.getSizeY(), c.getSizeZ()); @@ -198,7 +201,9 @@ public class WandSVC implements IrisService { public static Location stringToLocation(String s) { try { String[] f = s.split("\\Q in \\E"); + if (f.length != 2) return null; String[] g = f[0].split("\\Q,\\E"); + if (g.length != 3) return null; return new Location(Bukkit.getWorld(f[1]), Integer.parseInt(g[0]), Integer.parseInt(g[1]), Integer.parseInt(g[2])); } catch (Throwable e) { Iris.reportError(e); @@ -357,6 +362,7 @@ public class WandSVC implements IrisService { try { if ((IrisSettings.get().getWorld().worldEditWandCUI && isHoldingWand(p)) || isWand(p.getInventory().getItemInMainHand())) { Location[] d = getCuboid(p); + if (d == null || d[0] == null || d[1] == null) return; new WandSelection(new Cuboid(d[0], d[1]), p).draw(); } } catch (Throwable e) { diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisBenchmarking.java b/core/src/main/java/com/volmit/iris/core/tools/IrisBenchmarking.java deleted file mode 100644 index 79bf1b643..000000000 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisBenchmarking.java +++ /dev/null @@ -1,625 +0,0 @@ -package com.volmit.iris.core.tools; - -import com.volmit.iris.Iris; -import com.volmit.iris.util.format.C; -import oshi.SystemInfo; -import oshi.hardware.CentralProcessor; -import oshi.hardware.GlobalMemory; -import oshi.hardware.HWDiskStore; -import oshi.software.os.OperatingSystem; - -import java.io.*; -import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; -import java.lang.management.MemoryUsage; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.IntStream; -import java.util.zip.Deflater; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; - -import static com.google.common.math.LongMath.isPrime; -import static com.volmit.iris.util.misc.getHardware.getCPUModel; -public class IrisBenchmarking { - static String ServerOS; - static String filePath = "benchmark.dat"; - static double avgWriteSpeedMBps; - static double avgReadSpeedMBps; - static double highestWriteSpeedMBps; - static double highestReadSpeedMBps; - static double lowestWriteSpeedMBps; - static double lowestReadSpeedMBps; - static double calculateIntegerMath; - static double calculateFloatingPoint; - static double calculatePrimeNumbers; - static double calculateStringSorting; - static double calculateDataEncryption; - static double calculateDataCompression; - static String currentRunning = "None"; - static int BenchmarksCompleted = 0; - static int BenchmarksTotal = 7; - static int totalTasks = 10; - static int currentTasks = 0; - static double WindowsCPUCompression; - static double WindowsCPUEncryption; - static double WindowsCPUCSHA1; - static double elapsedTimeNs; - static boolean Winsat = false; - static boolean WindowsDiskSpeed = false; - public static boolean inProgress = false; - static double startTime; - // Good enough for now. . . - - public static void runBenchmark() throws InterruptedException { - inProgress = true; - getServerOS(); - deleteTestFile(filePath); - AtomicReference doneCalculateDiskSpeed = new AtomicReference<>((double) 0); - startBenchmarkTimer(); - Iris.info("Benchmark Started!"); - Iris.warn("Although it may seem momentarily paused, it's actively processing."); - BenchmarksCompleted = 0; - - CompletableFuture future = CompletableFuture.runAsync(() -> { - currentRunning = "calculateDiskSpeed"; - progressBar(); - if (ServerOS.contains("Windows") && isRunningAsAdmin()) { - WindowsDiskSpeed = true; - WindowsDiskSpeedTest(); - } else { - warningFallback(); - try { - Thread.sleep(10); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - doneCalculateDiskSpeed.set(roundToTwoDecimalPlaces(calculateDiskSpeed())); - BenchmarksCompleted++; - } - - - }).thenRun(() -> { - currentRunning = "WindowsCpuSpeedTest"; - progressBar(); - if (ServerOS.contains("Windows") && isRunningAsAdmin()) { - Winsat = true; - WindowsCpuSpeedTest(); - } else { - Iris.info("Skipping:" + C.BLUE + " Windows System Assessment Tool Benchmarks"); - if (!ServerOS.contains("Windows")) { - Iris.info("Required Software:" + C.BLUE + " Windows"); - BenchmarksTotal = 6; - } - if (!isRunningAsAdmin()) { - Iris.info(C.RED + "ERROR: " + C.DARK_RED + "Elevated privileges missing"); - BenchmarksTotal = 6; - } - } - - }).thenRun(() -> { - currentRunning = "calculateIntegerMath"; - progressBar(); - calculateIntegerMath = roundToTwoDecimalPlaces(calculateIntegerMath()); - BenchmarksCompleted++; - }).thenRun(() -> { - currentRunning = "calculateFloatingPoint"; - progressBar(); - calculateFloatingPoint = roundToTwoDecimalPlaces(calculateFloatingPoint()); - BenchmarksCompleted++; - }).thenRun(() -> { - currentRunning = "calculateStringSorting"; - progressBar(); - calculateStringSorting = roundToTwoDecimalPlaces(calculateStringSorting()); - BenchmarksCompleted++; - }).thenRun(() -> { - currentRunning = "calculatePrimeNumbers"; - progressBar(); - calculatePrimeNumbers = roundToTwoDecimalPlaces(calculatePrimeNumbers()); - BenchmarksCompleted++; - }).thenRun(() -> { - currentRunning = "calculateDataEncryption"; - progressBar(); - calculateDataEncryption = roundToTwoDecimalPlaces(calculateDataEncryption()); - BenchmarksCompleted++; - }).thenRun(() -> { - currentRunning = "calculateDataCompression"; - progressBar(); - calculateDataCompression = roundToTwoDecimalPlaces(calculateDataCompression()); - BenchmarksCompleted++; - }).thenRun(() -> { - elapsedTimeNs = stopBenchmarkTimer(); - results(); - inProgress = false; - }); - - try { - future.get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - - public static void progressBar() { - Iris.info("-----------------------------------------------------"); - Iris.info("Currently Running: " + C.BLUE + currentRunning); - // Iris.info("Tasks: " + "Current Tasks: " + C.BLUE + currentTasks + C.WHITE + " / " + "Total Tasks: " + C.BLUE + totalTasks); - Iris.info("Benchmarks Completed: " + C.BLUE + BenchmarksCompleted + C.WHITE + " / " + "Total: " + C.BLUE + BenchmarksTotal); - Iris.info("-----------------------------------------------------"); - } - - public static void results() { - - SystemInfo systemInfo = new SystemInfo(); - GlobalMemory globalMemory = systemInfo.getHardware().getMemory(); - long totalMemoryMB = globalMemory.getTotal() / (1024 * 1024); - long availableMemoryMB = globalMemory.getAvailable() / (1024 * 1024); - long totalPageSize = globalMemory.getPageSize() / (1024 * 1024); - long usedMemoryMB = totalMemoryMB - availableMemoryMB; - MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); - - Iris.info("OS: " + ServerOS); - if (!isRunningAsAdmin() || !ServerOS.contains("Windows")) { - Iris.info(C.GOLD + "For the full results use Windows + Admin Rights.."); - } - Iris.info("CPU Model: " + getCPUModel()); - Iris.info("CPU Score: " + "WIP"); - Iris.info("- Integer Math: " + calculateIntegerMath + " MOps/Sec"); - Iris.info("- Floating Point Math: " + calculateFloatingPoint + " MOps/Sec"); - Iris.info("- Find Prime Numbers: " + calculatePrimeNumbers + " Primes/Sec"); - Iris.info("- Random String Sorting: " + calculateStringSorting + " Thousand Strings/Sec"); - Iris.info("- Data Encryption: " + formatDouble(calculateDataEncryption) + " MBytes/Sec"); - Iris.info("- Data Compression: " + formatDouble(calculateDataCompression) + " MBytes/Sec"); - - if (WindowsDiskSpeed) { - //Iris.info("Disk Model: " + getDiskModel()); - Iris.info(C.BLUE + "- Running with Windows System Assessment Tool"); - Iris.info("- Sequential 64.0 Write: " + C.BLUE + formatDouble(avgWriteSpeedMBps) + " Mbps"); - Iris.info("- Sequential 64.0 Read: " + C.BLUE + formatDouble(avgReadSpeedMBps) + " Mbps"); - } else { - // Iris.info("Disk Model: " + getDiskModel()); - Iris.info(C.GREEN + "- Running in Native Mode"); - Iris.info("- Average Write Speed: " + C.GREEN + formatDouble(avgWriteSpeedMBps) + " Mbps"); - Iris.info("- Average Read Speed: " + C.GREEN + formatDouble(avgReadSpeedMBps) + " Mbps"); - Iris.info("- Highest Write Speed: " + formatDouble(highestWriteSpeedMBps) + " Mbps"); - Iris.info("- Highest Read Speed: " + formatDouble(highestReadSpeedMBps) + " Mbps"); - Iris.info("- Lowest Write Speed: " + formatDouble(lowestWriteSpeedMBps) + " Mbps"); - Iris.info("- Lowest Read Speed: " + formatDouble(lowestReadSpeedMBps) + " Mbps"); - } - Iris.info("Ram Usage: "); - Iris.info("- Total Ram: " + totalMemoryMB + " MB"); - Iris.info("- Used Ram: " + usedMemoryMB + " MB"); - Iris.info("- Total Process Ram: " + C.BLUE + getMaxMemoryUsage() + " MB"); - Iris.info("- Total Paging Size: " + totalPageSize + " MB"); - if (Winsat) { - Iris.info(C.BLUE + "Windows System Assessment Tool: "); - Iris.info("- CPU LZW Compression:" + C.BLUE + formatDouble(WindowsCPUCompression) + " MB/s"); - Iris.info("- CPU AES256 Encryption: " + C.BLUE + formatDouble(WindowsCPUEncryption) + " MB/s"); - Iris.info("- CPU SHA1 Hash: " + C.BLUE + formatDouble(WindowsCPUCSHA1) + " MB/s"); - Iris.info("Duration: " + roundToTwoDecimalPlaces(elapsedTimeNs) + " Seconds"); - } - - } - - public static long getMaxMemoryUsage() { - MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); - MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage(); - MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage(); - long maxHeapMemory = heapMemoryUsage.getMax(); - long maxNonHeapMemory = nonHeapMemoryUsage.getMax(); - long maxMemoryUsageMB = (maxHeapMemory + maxNonHeapMemory) / (1024 * 1024); - return maxMemoryUsageMB; - } - - public static void getServerOS() { - SystemInfo systemInfo = new SystemInfo(); - OperatingSystem os = systemInfo.getOperatingSystem(); - ServerOS = os.toString(); - } - - public static boolean isRunningAsAdmin() { - if (ServerOS.contains("Windows")) { - try { - Process process = Runtime.getRuntime().exec("winsat disk"); - process.waitFor(); - return process.exitValue() == 0; - } catch (IOException | InterruptedException e) { - // Hmm - } - } - return false; - } - - public static void warningFallback() { - Iris.info(C.RED + "Using the " + C.DARK_RED + "FALLBACK" + C.RED + " method due to compatibility issues. "); - Iris.info(C.RED + "Please note that this may result in less accurate results."); - } - - private static String formatDouble(double value) { - return String.format("%.2f", value); - } - - private static void startBenchmarkTimer() { - startTime = System.nanoTime(); - } - - private static double stopBenchmarkTimer() { - long endTime = System.nanoTime(); - return (endTime - startTime) / 1_000_000_000.0; - } - - private static double calculateIntegerMath() { - final int numIterations = 1_000_000_000; - final int numRuns = 30; - double totalMopsPerSec = 0; - - for (int run = 0; run < numRuns; run++) { - long startTime = System.nanoTime(); - int result = 0; - - for (int i = 0; i < numIterations; i++) { - result += i * 2; - result -= i / 2; - result ^= i; - result <<= 1; - result >>= 1; - } - - long endTime = System.nanoTime(); - double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0; - double mopsPerSec = (numIterations / elapsedSeconds) / 1_000_000.0; - - totalMopsPerSec += mopsPerSec; - } - - double averageMopsPerSec = totalMopsPerSec / numRuns; - return averageMopsPerSec; - } - - private static double calculateFloatingPoint() { - long numIterations = 85_000_000; - int numRuns = 30; - double totalMopsPerSec = 0; - for (int run = 0; run < numRuns; run++) { - double result = 0; - long startTime = System.nanoTime(); - - for (int i = 0; i < numIterations; i++) { - result += Math.sqrt(i) * Math.sin(i) / (i + 1); - } - - long endTime = System.nanoTime(); - double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0; - double mopsPerSec = (numIterations / elapsedSeconds) / 1_000_000.0; - - totalMopsPerSec += mopsPerSec; - } - - double averageMopsPerSec = totalMopsPerSec / numRuns; - return averageMopsPerSec; - } - - private static double calculatePrimeNumbers() { - int primeCount; - long numIterations = 1_000_000; - int numRuns = 30; - double totalMopsPerSec = 0; - - for (int run = 0; run < numRuns; run++) { - primeCount = 0; - long startTime = System.nanoTime(); - - for (int num = 2; primeCount < numIterations; num++) { - if (isPrime(num)) { - primeCount++; - } - } - - long endTime = System.nanoTime(); - double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0; - double mopsPerSec = (primeCount / elapsedSeconds) / 1_000_000.0; - - totalMopsPerSec += mopsPerSec; - } - - double averageMopsPerSec = totalMopsPerSec / numRuns; - return averageMopsPerSec; - } - - private static double calculateStringSorting() { - int stringCount = 1_000_000; - int stringLength = 100; - int numRuns = 30; - double totalMopsPerSec = 0; - - for (int run = 0; run < numRuns; run++) { - List randomStrings = generateRandomStrings(stringCount, stringLength); - long startTime = System.nanoTime(); - randomStrings.sort(String::compareTo); - long endTime = System.nanoTime(); - - double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0; - double mopsPerSec = (stringCount / elapsedSeconds) / 1_000.0; - - totalMopsPerSec += mopsPerSec; - } - - double averageMopsPerSec = totalMopsPerSec / numRuns; - return averageMopsPerSec; - } - - public static double calculateDataEncryption() { - int dataSizeMB = 100; - byte[] dataToEncrypt = generateRandomData(dataSizeMB * 1024 * 1024); - int numRuns = 20; - double totalMBytesPerSec = 0; - - for (int run = 0; run < numRuns; run++) { - long startTime = System.nanoTime(); - byte[] encryptedData = performEncryption(dataToEncrypt, 1); - - long endTime = System.nanoTime(); - double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0; - double mbytesPerSec = (dataToEncrypt.length / (1024 * 1024.0)) / elapsedSeconds; - - totalMBytesPerSec += mbytesPerSec; - } - - double averageMBytesPerSec = totalMBytesPerSec / numRuns; - return averageMBytesPerSec; - } - - private static byte[] performEncryption(byte[] data, int numRuns) { - byte[] key = "MyEncryptionKey".getBytes(); - byte[] result = Arrays.copyOf(data, data.length); - for (int run = 0; run < numRuns; run++) { - for (int i = 0; i < result.length; i++) { - result[i] ^= key[i % key.length]; - } - } - return result; - } - - public static double calculateDataCompression() { - int dataSizeMB = 500; - byte[] dataToCompress = generateRandomData(dataSizeMB * 1024 * 1024); - long startTime = System.nanoTime(); - byte[] compressedData = performCompression(dataToCompress); - long endTime = System.nanoTime(); - - double elapsedSeconds = (endTime - startTime) / 1e9; - double mbytesPerSec = (compressedData.length / (1024.0 * 1024.0)) / elapsedSeconds; - - return mbytesPerSec; - } - - private static byte[] performCompression(byte[] data) { - Deflater deflater = new Deflater(); - deflater.setInput(data); - deflater.finish(); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length); - - byte[] buffer = new byte[1024]; - while (!deflater.finished()) { - int count = deflater.deflate(buffer); - outputStream.write(buffer, 0, count); - } - - deflater.end(); - return outputStream.toByteArray(); - } - - private static List generateRandomStrings(int count, int length) { - SecureRandom random = new SecureRandom(); - List randomStrings = new ArrayList<>(); - - IntStream.range(0, count).forEach(i -> { - byte[] bytes = new byte[length]; - random.nextBytes(bytes); - randomStrings.add(Base64.getEncoder().encodeToString(bytes)); - }); - return randomStrings; - } - - private static byte[] generateRandomData(int size) { - SecureRandom random = new SecureRandom(); - byte[] data = new byte[size]; - random.nextBytes(data); - return data; - } - - private static double roundToTwoDecimalPlaces(double value) { - return Double.parseDouble(String.format("%.2f", value)); - } - - private static double calculateCPUScore(long elapsedTimeNs) { - return 1.0 / (elapsedTimeNs / 1_000_000.0); - } - - public static double calculateDiskSpeed() { - int numRuns = 10; - int fileSizeMB = 1000; - - double[] writeSpeeds = new double[numRuns]; - double[] readSpeeds = new double[numRuns]; - - for (int run = 0; run < numRuns; run++) { - long writeStartTime = System.nanoTime(); - deleteTestFile(filePath); - createTestFile(filePath, fileSizeMB); - long writeEndTime = System.nanoTime(); - - long readStartTime = System.nanoTime(); - readTestFile(filePath); - long readEndTime = System.nanoTime(); - - double writeSpeed = calculateDiskSpeedMBps(fileSizeMB, writeStartTime, writeEndTime); - double readSpeed = calculateDiskSpeedMBps(fileSizeMB, readStartTime, readEndTime); - - writeSpeeds[run] = writeSpeed; - readSpeeds[run] = readSpeed; - - if (run == 0) { - lowestWriteSpeedMBps = writeSpeed; - highestWriteSpeedMBps = writeSpeed; - lowestReadSpeedMBps = readSpeed; - highestReadSpeedMBps = readSpeed; - } else { - if (writeSpeed < lowestWriteSpeedMBps) { - lowestWriteSpeedMBps = writeSpeed; - } - if (writeSpeed > highestWriteSpeedMBps) { - highestWriteSpeedMBps = writeSpeed; - } - if (readSpeed < lowestReadSpeedMBps) { - lowestReadSpeedMBps = readSpeed; - } - if (readSpeed > highestReadSpeedMBps) { - highestReadSpeedMBps = readSpeed; - } - } - } - avgWriteSpeedMBps = calculateAverage(writeSpeeds); - avgReadSpeedMBps = calculateAverage(readSpeeds); - return 2; - } - - public static void createTestFile(String filePath, int fileSizeMB) { - try { - File file = new File(filePath); - byte[] data = new byte[1024 * 1024]; - Arrays.fill(data, (byte) 0); - FileOutputStream fos = new FileOutputStream(file); - for (int i = 0; i < fileSizeMB; i++) { - fos.write(data); - } - fos.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public static void readTestFile(String filePath) { - try { - File file = new File(filePath); - FileInputStream fis = new FileInputStream(file); - byte[] buffer = new byte[1024]; - while (fis.read(buffer) != -1) { - } - fis.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public static void deleteTestFile(String filePath) { - File file = new File(filePath); - file.delete(); - } - - public static double calculateDiskSpeedMBps(int fileSizeMB, long startTime, long endTime) { - double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0; - double writeSpeed = (fileSizeMB / elapsedSeconds); - return writeSpeed; - } - - public static double calculateAverage(double[] values) { - double sum = 0; - for (double value : values) { - sum += value; - } - return sum / values.length; - } - - public static void WindowsDiskSpeedTest() { - try { - String command = "winsat disk"; - Process process = Runtime.getRuntime().exec(command); - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - String line; - - while ((line = reader.readLine()) != null) { - Iris.debug(line); - - if (line.contains("Disk Sequential 64.0 Read")) { - avgReadSpeedMBps = extractSpeed(line); - } else if (line.contains("Disk Sequential 64.0 Write")) { - avgWriteSpeedMBps = extractSpeed(line); - } - } - - process.waitFor(); - process.destroy(); - - Iris.debug("Sequential Read Speed: " + avgReadSpeedMBps + " MB/s"); - Iris.debug("Sequential Write Speed: " + avgWriteSpeedMBps + " MB/s"); - } catch (IOException | InterruptedException e) { - e.printStackTrace(); - } - } - - private static double extractSpeed(String line) { - String[] tokens = line.split("\\s+"); - for (int i = 0; i < tokens.length; i++) { - if (tokens[i].endsWith("MB/s") && i > 0) { - try { - return Double.parseDouble(tokens[i - 1]); - } catch (NumberFormatException e) { - e.printStackTrace(); - } - } - } - return 0.0; - } - - public static void WindowsCpuSpeedTest() { - try { - String command = "winsat cpuformal"; - Process process = Runtime.getRuntime().exec(command); - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - String line; - - while ((line = reader.readLine()) != null) { - Iris.debug(line); - - if (line.contains("CPU AES256 Encryption")) { - WindowsCPUEncryption = extractCpuInfo(line); - } - if (line.contains("CPU LZW Compression")) { - WindowsCPUCompression = extractCpuInfo(line); - } - if (line.contains("CPU SHA1 Hash")) { - WindowsCPUCSHA1 = extractCpuInfo(line); - } - } - process.waitFor(); - process.destroy(); - - Iris.debug("Winsat Encryption: " + WindowsCPUEncryption + " MB/s"); - } catch (IOException | InterruptedException e) { - e.printStackTrace(); - } - } - - private static double extractCpuInfo(String line) { - String[] tokens = line.split("\\s+"); - for (int i = 0; i < tokens.length; i++) { - if (tokens[i].endsWith("MB/s") && i > 0) { - try { - return Double.parseDouble(tokens[i - 1]); - } catch (NumberFormatException e) { - e.printStackTrace(); - } - } - } - return 0.0; - } - -} \ No newline at end of file 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/core/tools/IrisPackBenchmarking.java b/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java index 8667542a9..4f39a7527 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java @@ -9,51 +9,43 @@ import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.exceptions.IrisException; import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.io.IO; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.PrecisionStopwatch; -import lombok.Getter; import org.bukkit.Bukkit; import java.io.File; import java.io.FileWriter; import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; import java.time.Clock; import java.time.LocalDateTime; import java.util.Collections; public class IrisPackBenchmarking { - @Getter - public static IrisPackBenchmarking instance; - public static boolean benchmarkInProgress = false; + private static final ThreadLocal instance = new ThreadLocal<>(); private final PrecisionStopwatch stopwatch = new PrecisionStopwatch(); private final IrisDimension dimension; private final int radius; private final boolean gui; public IrisPackBenchmarking(IrisDimension dimension, int radius, boolean gui) { - instance = this; this.dimension = dimension; this.radius = radius; this.gui = gui; runBenchmark(); } + public static IrisPackBenchmarking getInstance() { + return instance.get(); + } + private void runBenchmark() { Thread.ofVirtual() .name("PackBenchmarking") .start(() -> { Iris.info("Setting up benchmark environment "); - benchmarkInProgress = true; - File file = new File("benchmark"); - if (file.exists()) { - deleteDirectory(file.toPath()); - } + IO.delete(new File(Bukkit.getWorldContainer(), "benchmark")); createBenchmark(); while (!IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) { J.sleep(1000); @@ -66,13 +58,9 @@ public class IrisPackBenchmarking { } - public boolean getBenchmarkInProgress() { - return benchmarkInProgress; - } - public void finishedBenchmark(KList cps) { try { - String time = Form.duration(stopwatch.getMillis()); + String time = Form.duration((long) stopwatch.getMilliseconds()); Engine engine = IrisToolbelt.access(Bukkit.getWorld("benchmark")).getEngine(); Iris.info("-----------------"); Iris.info("Results:"); @@ -83,11 +71,7 @@ public class IrisPackBenchmarking { Iris.info(" - Lowest CPS: " + findLowest(cps)); Iris.info("-----------------"); Iris.info("Creating a report.."); - File profilers = new File("plugins" + File.separator + "Iris" + File.separator + "packbenchmarks"); - profilers.mkdir(); - - File results = new File(profilers, dimension.getName() + " " + LocalDateTime.now(Clock.systemDefaultZone()).toString().replace(':', '-') + ".txt"); - results.getParentFile().mkdirs(); + File results = Iris.instance.getDataFile("packbenchmarks", dimension.getName() + " " + LocalDateTime.now(Clock.systemDefaultZone()).toString().replace(':', '-') + ".txt"); KMap metrics = engine.getMetrics().pull(); try (FileWriter writer = new FileWriter(results)) { writer.write("-----------------\n"); @@ -143,13 +127,18 @@ public class IrisPackBenchmarking { } private void startBenchmark() { - IrisToolbelt.pregenerate(PregenTask - .builder() - .gui(gui) - .radiusX(radius) - .radiusZ(radius) - .build(), Bukkit.getWorld("benchmark") - ); + try { + instance.set(this); + IrisToolbelt.pregenerate(PregenTask + .builder() + .gui(gui) + .radiusX(radius) + .radiusZ(radius) + .build(), Bukkit.getWorld("benchmark") + ); + } finally { + instance.remove(); + } } private double calculateAverage(KList list) { @@ -178,26 +167,4 @@ public class IrisPackBenchmarking { private int findHighest(KList list) { return Collections.max(list); } - - private boolean deleteDirectory(Path dir) { - try { - Files.walkFileTree(dir, new SimpleFileVisitor<>() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Files.delete(file); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - Files.delete(dir); - return FileVisitResult.CONTINUE; - } - }); - return true; - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } } \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisToolbelt.java b/core/src/main/java/com/volmit/iris/core/tools/IrisToolbelt.java index ad552ee81..06651213c 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisToolbelt.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisToolbelt.java @@ -24,6 +24,7 @@ import com.volmit.iris.core.gui.PregeneratorJob; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.pregenerator.PregenTask; import com.volmit.iris.core.pregenerator.PregeneratorMethod; +import com.volmit.iris.core.pregenerator.methods.CachedPregenMethod; import com.volmit.iris.core.pregenerator.methods.HybridPregenMethod; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.engine.framework.Engine; @@ -141,7 +142,18 @@ public class IrisToolbelt { * @return the pregenerator job (already started) */ public static PregeneratorJob pregenerate(PregenTask task, PregeneratorMethod method, Engine engine) { - return new PregeneratorJob(task, method, engine); + return pregenerate(task, method, engine, IrisSettings.get().getPregen().useCacheByDefault); + } + + /** + * Start a pregenerator task + * + * @param task the scheduled task + * @param method the method to execute the task + * @return the pregenerator job (already started) + */ + public static PregeneratorJob pregenerate(PregenTask task, PregeneratorMethod method, Engine engine, boolean cached) { + return new PregeneratorJob(task, cached && engine != null ? new CachedPregenMethod(method, engine.getWorld().name()) : method, engine); } /** 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..70422c6a1 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisComplex.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisComplex.java @@ -42,6 +42,7 @@ import org.bukkit.Material; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; +import java.io.File; import java.util.UUID; @Data @@ -108,10 +109,15 @@ 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 { + 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() @@ -245,7 +251,15 @@ public class IrisComplex implements DataProvider { } } - return null; + String key = UUID.randomUUID().toString(); + IrisRegion region = new IrisRegion(); + region.getLandBiomes().add(focus.getLoadKey()); + region.getSeaBiomes().add(focus.getLoadKey()); + region.getShoreBiomes().add(focus.getLoadKey()); + region.setLoadKey(key); + region.setLoader(data); + region.setLoadFile(new File(data.getDataFolder(), data.getRegionLoader().getFolderName() + "/" + key + ".json")); + return region; } private IrisDecorator decorateFor(IrisBiome b, double x, double z, IrisDecorationPart part) { @@ -360,6 +374,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/IrisEngine.java b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java index 88c5fdc14..2f9bd5a88 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisEngine.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java @@ -96,7 +96,6 @@ public class IrisEngine implements Engine { private EngineExecutionEnvironment execution; private EngineWorldManager worldManager; private volatile int parallelism; - private volatile int minHeight; private boolean failing; private boolean closed; private int cacheId; @@ -129,7 +128,6 @@ public class IrisEngine implements Engine { getData().setEngine(this); getData().loadPrefetch(this); Iris.info("Initializing Engine: " + target.getWorld().name() + "/" + target.getDimension().getLoadKey() + " (" + target.getDimension().getDimensionHeight() + " height) Seed: " + getSeedManager().getSeed()); - minHeight = 0; failing = false; closed = false; art = J.ar(this::tickRandomPlayer, 0); @@ -180,7 +178,10 @@ public class IrisEngine implements Engine { File[] roots = getData().getLoaders() .values() .stream() - .map(ResourceLoader::getRoot) + .map(ResourceLoader::getFolderName) + .map(n -> new File(getData().getDataFolder(), n)) + .filter(File::exists) + .filter(File::isDirectory) .toArray(File[]::new); hash32.complete(IO.hashRecursive(roots)); }); @@ -472,7 +473,7 @@ public class IrisEngine implements Engine { getEngineData().getStatistics().generatedChunk(); try { PrecisionStopwatch p = PrecisionStopwatch.start(); - Hunk blocks = vblocks.listen((xx, y, zz, t) -> catchBlockUpdates(x + xx, y + getMinHeight(), z + zz, t)); + Hunk blocks = vblocks.listen((xx, y, zz, t) -> catchBlockUpdates(x + xx, y, z + zz, t)); if (getDimension().isDebugChunkCrossSections() && ((x >> 4) % getDimension().getDebugCrossSectionsMod() == 0 || (z >> 4) % getDimension().getDebugCrossSectionsMod() == 0)) { for (int i = 0; i < 16; i++) { diff --git a/core/src/main/java/com/volmit/iris/engine/IrisEngineMantle.java b/core/src/main/java/com/volmit/iris/engine/IrisEngineMantle.java index c3b385361..e58324ad7 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisEngineMantle.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisEngineMantle.java @@ -29,7 +29,9 @@ import com.volmit.iris.engine.mantle.components.MantleJigsawComponent; import com.volmit.iris.engine.mantle.components.MantleObjectComponent; 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.Mantle; +import com.volmit.iris.util.mantle.MantleFlag; import lombok.*; import java.io.File; @@ -44,7 +46,7 @@ public class IrisEngineMantle implements EngineMantle { @Getter(AccessLevel.NONE) private final KMap> components; private final AtomicCache, Integer>>> componentsCache = new AtomicCache<>(); - private final AtomicCache radCache = new AtomicCache<>(); + private final AtomicCache> disabledFlags = new AtomicCache<>(); private final MantleObjectComponent object; private final MantleJigsawComponent jigsaw; @@ -101,10 +103,20 @@ public class IrisEngineMantle implements EngineMantle { @Override public void registerComponent(MantleComponent c) { + c.setEnabled(!getDimension().getDisabledComponents().contains(c.getFlag())); components.computeIfAbsent(c.getPriority(), k -> new KList<>()).add(c); componentsCache.reset(); } + @Override + public KList getComponentFlags() { + return components.values() + .stream() + .flatMap(KList::stream) + .map(MantleComponent::getFlag) + .collect(KList.collector()); + } + @Override public MantleJigsawComponent getJigsawComponent() { return jigsaw; diff --git a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java index 08962e4af..95ba698c9 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java @@ -55,6 +55,7 @@ import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.inventory.ItemStack; +import java.lang.ref.WeakReference; import java.util.List; import java.util.Map; import java.util.Set; @@ -367,7 +368,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { private void spawn(IrisPosition pos, IrisEntitySpawn i) { IrisSpawner ref = i.getReferenceSpawner(); - if (!ref.canSpawn(getEngine(), pos.getX() >> 4, pos.getZ())) + if (!ref.canSpawn(getEngine(), pos.getX() >> 4, pos.getZ() >> 4)) return; int s = i.spawn(getEngine(), pos, RNG.r); @@ -422,9 +423,16 @@ public class IrisWorldManager extends EngineAssignedWorldManager { return; } - energy += 0.3; - fixEnergy(); - getEngine().cleanupMantleChunk(e.getX(), e.getZ()); + var ref = new WeakReference<>(e.getWorld()); + int x = e.getX(), z = e.getZ(); + J.s(() -> { + World world = ref.get(); + if (world == null || !world.isChunkLoaded(x, z)) + return; + energy += 0.3; + fixEnergy(); + getEngine().cleanupMantleChunk(x, z); + }, IrisSettings.get().getPerformance().mantleCleanupDelay); if (generated) { //INMS.get().injectBiomesFromMantle(e, getMantle()); 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/framework/Engine.java b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java index 8d08771fd..010628b7d 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java @@ -75,9 +75,9 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; import java.awt.Color; -import java.util.Arrays; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -140,7 +140,9 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat return getTarget().getWorld().minHeight(); } - void setMinHeight(int min); + default void setMinHeight(int min) { + getTarget().getWorld().minHeight(min); + } @BlockCoordinates default void generate(int x, int z, TerrainChunk tc, boolean multicore) throws WrongEngineBroException { @@ -287,76 +289,79 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat return; } - var chunk = mantle.getChunk(c); - if (chunk.isFlagged(MantleFlag.ETCHED)) return; - chunk.flag(MantleFlag.ETCHED, true); + var chunk = mantle.getChunk(c).use(); + try { + Semaphore semaphore = new Semaphore(3); + chunk.raiseFlag(MantleFlag.ETCHED, () -> { + chunk.raiseFlag(MantleFlag.TILE, run(semaphore, () -> J.s(() -> { + mantle.iterateChunk(c.getX(), c.getZ(), TileWrapper.class, (x, y, z, v) -> { + int betterY = y + getWorld().minHeight(); + if (!TileData.setTileState(c.getBlock(x, betterY, z), v.getData())) + Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", x, betterY, z, c.getBlock(x, betterY, z).getBlockData().getMaterial().getKey(), v.getData().getMaterial().name()); + }); + }))); + chunk.raiseFlag(MantleFlag.CUSTOM, run(semaphore, () -> J.s(() -> { + mantle.iterateChunk(c.getX(), c.getZ(), Identifier.class, (x, y, z, v) -> { + Iris.service(ExternalDataSVC.class).processUpdate(this, c.getBlock(x & 15, y + getWorld().minHeight(), z & 15), v); + }); + }))); - Semaphore semaphore = new Semaphore(3); - chunk.raiseFlag(MantleFlag.TILE, run(semaphore, () -> J.s(() -> { - mantle.iterateChunk(c.getX(), c.getZ(), TileWrapper.class, (x, y, z, v) -> { - int betterY = y + getWorld().minHeight(); - if (!TileData.setTileState(c.getBlock(x, betterY, z), v.getData())) - Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", x, betterY, z, c.getBlock(x, betterY, z).getBlockData().getMaterial().getKey(), v.getData().getMaterial().name()); - }); - }))); - chunk.raiseFlag(MantleFlag.CUSTOM, run(semaphore, () -> J.s(() -> { - mantle.iterateChunk(c.getX(), c.getZ(), Identifier.class, (x, y, z, v) -> { - Iris.service(ExternalDataSVC.class).processUpdate(this, c.getBlock(x & 15, y + getWorld().minHeight(), z & 15), v); - }); - }))); - - chunk.raiseFlag(MantleFlag.UPDATE, run(semaphore, () -> J.s(() -> { - PrecisionStopwatch p = PrecisionStopwatch.start(); - KMap updates = new KMap<>(); - RNG r = new RNG(Cache.key(c.getX(), c.getZ())); - mantle.iterateChunk(c.getX(), c.getZ(), MatterCavern.class, (x, yf, z, v) -> { - int y = yf + getWorld().minHeight(); - if (!B.isFluid(c.getBlock(x & 15, y, z & 15).getBlockData())) { - return; - } - boolean u = false; - if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.DOWN).getBlockData())) { - u = true; - } else if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.WEST).getBlockData())) { - u = true; - } else if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.EAST).getBlockData())) { - u = true; - } else if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.SOUTH).getBlockData())) { - u = true; - } else if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.NORTH).getBlockData())) { - u = true; - } - - if (u) { - updates.compute(Cache.key(x & 15, z & 15), (k, vv) -> { - if (vv != null) { - return Math.max(vv, y); + chunk.raiseFlag(MantleFlag.UPDATE, run(semaphore, () -> J.s(() -> { + PrecisionStopwatch p = PrecisionStopwatch.start(); + KMap updates = new KMap<>(); + RNG r = new RNG(Cache.key(c.getX(), c.getZ())); + mantle.iterateChunk(c.getX(), c.getZ(), MatterCavern.class, (x, yf, z, v) -> { + int y = yf + getWorld().minHeight(); + if (!B.isFluid(c.getBlock(x & 15, y, z & 15).getBlockData())) { + return; + } + boolean u = false; + if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.DOWN).getBlockData())) { + u = true; + } else if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.WEST).getBlockData())) { + u = true; + } else if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.EAST).getBlockData())) { + u = true; + } else if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.SOUTH).getBlockData())) { + u = true; + } else if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.NORTH).getBlockData())) { + u = true; } - return y; + if (u) { + updates.compute(Cache.key(x & 15, z & 15), (k, vv) -> { + if (vv != null) { + return Math.max(vv, y); + } + + return y; + }); + } }); - } + + updates.forEach((k, v) -> update(Cache.keyX(k), v, Cache.keyZ(k), c, r)); + mantle.iterateChunk(c.getX(), c.getZ(), MatterUpdate.class, (x, yf, z, v) -> { + int y = yf + getWorld().minHeight(); + if (v != null && v.isUpdate()) { + int vx = x & 15; + int vz = z & 15; + update(x, y, z, c, new RNG(Cache.key(c.getX(), c.getZ()))); + if (vx > 0 && vx < 15 && vz > 0 && vz < 15) { + updateLighting(x, y, z, c); + } + } + }); + mantle.deleteChunkSlice(c.getX(), c.getZ(), MatterUpdate.class); + getMetrics().getUpdates().put(p.getMilliseconds()); + }, RNG.r.i(0, 20)))); }); - updates.forEach((k, v) -> update(Cache.keyX(k), v, Cache.keyZ(k), c, r)); - mantle.iterateChunk(c.getX(), c.getZ(), MatterUpdate.class, (x, yf, z, v) -> { - int y = yf + getWorld().minHeight(); - if (v != null && v.isUpdate()) { - int vx = x & 15; - int vz = z & 15; - update(x, y, z, c, new RNG(Cache.key(c.getX(), c.getZ()))); - if (vx > 0 && vx < 15 && vz > 0 && vz < 15) { - updateLighting(x, y, z, c); - } - } - }); - mantle.deleteChunkSlice(c.getX(), c.getZ(), MatterUpdate.class); - getMetrics().getUpdates().put(p.getMilliseconds()); - }, RNG.r.i(0, 20)))); - - try { - semaphore.acquire(3); - } catch (InterruptedException ignored) {} + try { + semaphore.acquire(3); + } catch (InterruptedException ignored) {} + } finally { + chunk.release(); + } } private static Runnable run(Semaphore semaphore, Runnable runnable) { @@ -453,14 +458,11 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat } } - for (int i = 0; i < 4; i++) { - try { - Arrays.parallelSort(nitems, (a, b) -> rng.nextInt()); - break; - } catch (Throwable e) { - Iris.reportError(e); - - } + for (int i = nitems.length; i > 1; i--) { + int j = rng.nextInt(i); + ItemStack tmp = nitems[i - 1]; + nitems[i - 1] = nitems[j]; + nitems[j] = tmp; } inventory.setContents(nitems); @@ -852,6 +854,25 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat return getBiomeOrMantle(l.getBlockX(), l.getBlockY(), l.getBlockZ()); } + @Nullable + @BlockCoordinates + default Position2 getNearestStronghold(Position2 pos) { + KList p = getDimension().getStrongholds(getSeedManager().getMantle()); + if (p.isEmpty()) return null; + + Position2 pr = null; + double d = Double.MAX_VALUE; + + for (Position2 i : p) { + double dx = i.distance(pos); + if (dx < d) { + d = dx; + pr = i; + } + } + return pr; + } + default void gotoBiome(IrisBiome biome, Player player, boolean teleport) { Set regionKeys = getDimension() .getAllRegions(this).stream() @@ -872,31 +893,10 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat default void gotoJigsaw(IrisJigsawStructure s, Player player, boolean teleport) { if (s.getLoadKey().equals(getDimension().getStronghold())) { - KList p = getDimension().getStrongholds(getSeedManager().getMantle()); - - if (p.isEmpty()) { + Position2 pr = getNearestStronghold(new Position2(player.getLocation().getBlockX(), player.getLocation().getBlockZ())); + if (pr == null) { player.sendMessage(C.GOLD + "No strongholds in world."); - } - - Position2 px = new Position2(player.getLocation().getBlockX(), player.getLocation().getBlockZ()); - Position2 pr = null; - double d = Double.MAX_VALUE; - - Iris.debug("Ps: " + p.size()); - - for (Position2 i : p) { - Iris.debug("- " + i.getX() + " " + i.getZ()); - } - - for (Position2 i : p) { - double dx = i.distance(px); - if (dx < d) { - d = dx; - pr = i; - } - } - - if (pr != null) { + } else { Location ll = new Location(player.getWorld(), pr.getX(), 40, pr.getZ()); J.s(() -> player.teleport(ll)); } diff --git a/core/src/main/java/com/volmit/iris/engine/framework/EngineAssignedWorldManager.java b/core/src/main/java/com/volmit/iris/engine/framework/EngineAssignedWorldManager.java index 5f801033f..e53b35e5c 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/EngineAssignedWorldManager.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/EngineAssignedWorldManager.java @@ -97,51 +97,6 @@ public abstract class EngineAssignedWorldManager extends EngineAssignedComponent } } - @EventHandler - public void onItemUse(PlayerInteractEvent e) { - if (e.getItem() == null || e.getHand() != EquipmentSlot.HAND) { - return; - } - if (e.getAction() == Action.LEFT_CLICK_BLOCK || e.getAction() == Action.LEFT_CLICK_AIR) { - return; - } - if (e.getPlayer().getWorld().equals(getTarget().getWorld().realWorld()) && e.getItem().getType() == Material.ENDER_EYE) { - if (e.getClickedBlock() != null && e.getClickedBlock().getType() == Material.END_PORTAL_FRAME) { - return; - } - - KList positions = getEngine().getDimension().getStrongholds(getEngine().getSeedManager().getMantle()); - if (positions.isEmpty()) { - return; - } - - Position2 playerPos = new Position2(e.getPlayer().getLocation().getBlockX(), e.getPlayer().getLocation().getBlockZ()); - Position2 pr = positions.get(0); - double d = pr.distance(playerPos); - - for (Position2 pos : positions) { - double distance = pos.distance(playerPos); - if (distance < d) { - d = distance; - pr = pos; - } - } - - if (e.getPlayer().getGameMode() != GameMode.CREATIVE) { - if (e.getItem().getAmount() > 1) { - e.getPlayer().getInventory().getItemInMainHand().setAmount(e.getItem().getAmount() - 1); - } else { - e.getPlayer().getInventory().setItemInMainHand(null); - } - } - - EnderSignal eye = e.getPlayer().getWorld().spawn(e.getPlayer().getLocation().clone().add(0, 0.5F, 0), EnderSignal.class); - eye.setTargetLocation(new Location(e.getPlayer().getWorld(), pr.getX(), 40, pr.getZ())); - eye.getWorld().playSound(eye, Sound.ENTITY_ENDER_EYE_LAUNCH, 1, 1); - Iris.debug("ESignal: " + eye.getTargetLocation().getBlockX() + " " + eye.getTargetLocation().getBlockX()); - } - } - @EventHandler public void on(WorldUnloadEvent e) { if (e.getWorld().equals(getTarget().getWorld().realWorld())) { diff --git a/core/src/main/java/com/volmit/iris/engine/framework/EnginePlayer.java b/core/src/main/java/com/volmit/iris/engine/framework/EnginePlayer.java index 803819831..2350b3140 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/EnginePlayer.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/EnginePlayer.java @@ -47,9 +47,7 @@ public class EnginePlayer { } public void tick() { - sample(); - - if (!IrisSettings.get().getWorld().isEffectSystem()) + if (sample() || !IrisSettings.get().getWorld().isEffectSystem()) return; J.a(() -> { @@ -81,22 +79,22 @@ public class EnginePlayer { return M.ms() - lastSample; } - public void sample() { + public boolean sample() { + Location current = player.getLocation().clone(); + if (current.getWorld() != engine.getWorld().realWorld()) + return true; try { - if (ticksSinceLastSample() > 55 && player.getLocation().distanceSquared(lastLocation) > 9 * 9) { - lastLocation = player.getLocation().clone(); + if (ticksSinceLastSample() > 55 && current.distanceSquared(lastLocation) > 9 * 9) { + lastLocation = current; lastSample = M.ms(); - sampleBiomeRegion(); + biome = engine.getBiome(current); + region = engine.getRegion(current); } + return false; } catch (Throwable e) { Iris.reportError(e); } - } - - private void sampleBiomeRegion() { - Location l = player.getLocation(); - biome = engine.getBiome(l); - region = engine.getRegion(l); + return true; } } diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/ComponentFlag.java b/core/src/main/java/com/volmit/iris/engine/mantle/ComponentFlag.java new file mode 100644 index 000000000..1c7fe20b2 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/mantle/ComponentFlag.java @@ -0,0 +1,14 @@ +package com.volmit.iris.engine.mantle; + +import com.volmit.iris.util.mantle.MantleFlag; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ComponentFlag { + MantleFlag value(); +} diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java index b55ce9e7f..d37814e0c 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java @@ -65,6 +65,8 @@ public interface EngineMantle extends IObjectPlacer { void registerComponent(MantleComponent c); + KList getComponentFlags(); + default int getHighest(int x, int z) { return getHighest(x, z, getData()); } @@ -227,7 +229,9 @@ public interface EngineMantle extends IObjectPlacer { } default void generateMantleComponent(MantleWriter writer, int x, int z, MantleComponent c, MantleChunk mc, ChunkContext context) { - mc.raiseFlag(c.getFlag(), () -> c.generateLayer(writer, x, z, context)); + mc.raiseFlag(c.getFlag(), () -> { + if (c.isEnabled()) c.generateLayer(writer, x, z, context); + }); } @ChunkCoordinates @@ -289,23 +293,25 @@ public interface EngineMantle extends IObjectPlacer { } default void cleanupChunk(int x, int z) { - if (!getMantle().hasFlag(x, z, MantleFlag.CLEANED) && isCovered(x, z)) { - getMantle().raiseFlag(x, z, MantleFlag.CLEANED, () -> { - getMantle().deleteChunkSlice(x, z, BlockData.class); - getMantle().deleteChunkSlice(x, z, String.class); - getMantle().deleteChunkSlice(x, z, MatterCavern.class); - getMantle().deleteChunkSlice(x, z, MatterFluidBody.class); + if (!isCovered(x, z)) return; + MantleChunk chunk = getMantle().getChunk(x, z).use(); + try { + chunk.raiseFlag(MantleFlag.CLEANED, () -> { + chunk.deleteSlices(BlockData.class); + chunk.deleteSlices(String.class); + chunk.deleteSlices(MatterCavern.class); + chunk.deleteSlices(MatterFluidBody.class); }); + } finally { + chunk.release(); } } - default long getToUnload(){ - return getMantle().getToUnload().size(); + default long getUnloadRegionCount() { + return getMantle().getUnloadRegionCount(); } - default long getNotQueuedLoadedRegions(){ - return getMantle().getLoadedRegions().size() - getMantle().getToUnload().size(); - } - default double getTectonicDuration(){ - return getMantle().getAdjustedIdleDuration().get(); + + default double getAdjustedIdleDuration() { + return getMantle().getAdjustedIdleDuration(); } } \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/IrisMantleComponent.java b/core/src/main/java/com/volmit/iris/engine/mantle/IrisMantleComponent.java index 388518c0d..cfe72ae39 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/IrisMantleComponent.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/IrisMantleComponent.java @@ -30,4 +30,5 @@ public abstract class IrisMantleComponent implements MantleComponent { private final EngineMantle engineMantle; private final MantleFlag flag; private final int priority; + private boolean enabled = true; } diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/MantleComponent.java b/core/src/main/java/com/volmit/iris/engine/mantle/MantleComponent.java index 3c9c78dc5..64c69d5c6 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/MantleComponent.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/MantleComponent.java @@ -61,6 +61,10 @@ public interface MantleComponent extends Comparable { MantleFlag getFlag(); + boolean isEnabled(); + + void setEnabled(boolean b); + @ChunkCoordinates void generateLayer(MantleWriter writer, int x, int z, ChunkContext context); diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java index 4c32af6d8..ee2c9bf2d 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java @@ -35,6 +35,7 @@ import com.volmit.iris.util.mantle.Mantle; import com.volmit.iris.util.mantle.MantleChunk; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.matter.Matter; +import com.volmit.iris.util.noise.CNG; import lombok.Data; import org.bukkit.block.data.BlockData; import org.bukkit.util.Vector; @@ -60,8 +61,9 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { this.x = x; this.z = z; - for (int i = -radius; i <= radius; i++) { - for (int j = -radius; j <= radius; j++) { + int r = radius / 4; + for (int i = -r; i <= r; i++) { + for (int j = -r; j <= r; j++) { cachedChunks.put(Cache.key(i + x, j + z), mantle.getChunk(i + x, j + z).use()); } } @@ -70,6 +72,7 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { private static Set getBallooned(Set vset, double radius) { Set returnset = new HashSet<>(); int ceilrad = (int) Math.ceil(radius); + double r2 = Math.pow(radius, 2); for (IrisPosition v : vset) { int tipx = v.getX(); @@ -79,7 +82,7 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { for (int loopx = tipx - ceilrad; loopx <= tipx + ceilrad; loopx++) { for (int loopy = tipy - ceilrad; loopy <= tipy + ceilrad; loopy++) { for (int loopz = tipz - ceilrad; loopz <= tipz + ceilrad; loopz++) { - if (hypot(loopx - tipx, loopy - tipy, loopz - tipz) <= radius) { + if (hypot(loopx - tipx, loopy - tipy, loopz - tipz) <= r2) { returnset.add(new IrisPosition(loopx, loopy, loopz)); } } @@ -112,7 +115,7 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { for (double d : pars) { sum += Math.pow(d, 2); } - return Math.sqrt(sum); + return sum; } private static double lengthSq(double x, double y, double z) { @@ -143,7 +146,7 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { if (cx >= this.x - radius && cx <= this.x + radius && cz >= this.z - radius && cz <= this.z + radius) { - MantleChunk chunk = cachedChunks.get(Cache.key(cx, cz)); + MantleChunk chunk = cachedChunks.computeIfAbsent(Cache.key(cx, cz), k -> mantle.getChunk(cx, cz).use()); if (chunk == null) { Iris.error("Mantle Writer Accessed " + cx + "," + cz + " and came up null (and yet within bounds!)"); @@ -152,6 +155,8 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { Matter matter = chunk.getOrCreate(y >> 4); matter.slice(matter.getClass(t)).set(x & 15, y & 15, z & 15, t); + } else { + Iris.error("Mantle Writer Accessed chunk out of bounds" + cx + "," + cz); } } @@ -450,6 +455,62 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { * @param the type of data to apply to the mantle */ public void setLineConsumer(List vectors, double radius, boolean filled, Function3 data) { + Set vset = cleanup(vectors); + vset = getBallooned(vset, radius); + + if (!filled) { + vset = getHollowed(vset); + } + + setConsumer(vset, data); + } + + /** + * Set lines for points + * + * @param vectors the points + * @param radius the radius + * @param filled hollow or filled? + * @param data the data to set + * @param the type of data to apply to the mantle + */ + public void setNoiseMasked(List vectors, double radius, double threshold, CNG shape, Set masks, boolean filled, Function3 data) { + Set vset = cleanup(vectors); + vset = masks == null ? getBallooned(vset, radius) : getMasked(vset, masks, radius); + vset.removeIf(p -> shape.noise(p.getX(), p.getY(), p.getZ()) < threshold); + + if (!filled) { + vset = getHollowed(vset); + } + + setConsumer(vset, data); + } + + private static Set getMasked(Set vectors, Set masks, double radius) { + Set vset = new KSet<>(); + int ceil = (int) Math.ceil(radius); + double r2 = Math.pow(radius, 2); + + for (IrisPosition v : vectors) { + int tipX = v.getX(); + int tipY = v.getY(); + int tipZ = v.getZ(); + + for (int x = -ceil; x <= ceil; x++) { + for (int y = -ceil; y <= ceil; y++) { + for (int z = -ceil; z <= ceil; z++) { + if (hypot(x, y, z) > r2 || !masks.contains(new IrisPosition(x, y, z))) + continue; + vset.add(new IrisPosition(tipX + x, tipY + y, tipZ + z)); + } + } + } + } + + return vset; + } + + private static Set cleanup(List vectors) { Set vset = new KSet<>(); for (int i = 0; vectors.size() != 0 && i < vectors.size() - 1; i++) { @@ -501,13 +562,7 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { } } - vset = getBallooned(vset, radius); - - if (!filled) { - vset = getHollowed(vset); - } - - setConsumer(vset, data); + return vset; } /** @@ -639,9 +694,10 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { @Override public void close() { - cachedChunks.values().removeIf(c -> { - c.release(); - return true; - }); + var iterator = cachedChunks.values().iterator(); + while (iterator.hasNext()) { + iterator.next().release(); + iterator.remove(); + } } } diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleCarvingComponent.java b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleCarvingComponent.java index 31762626f..eca862c79 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleCarvingComponent.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleCarvingComponent.java @@ -19,6 +19,7 @@ package com.volmit.iris.engine.mantle.components; import com.volmit.iris.engine.data.cache.Cache; +import com.volmit.iris.engine.mantle.ComponentFlag; import com.volmit.iris.engine.mantle.EngineMantle; import com.volmit.iris.engine.mantle.IrisMantleComponent; import com.volmit.iris.engine.mantle.MantleWriter; @@ -32,6 +33,7 @@ import com.volmit.iris.util.math.RNG; import lombok.Getter; @Getter +@ComponentFlag(MantleFlag.CARVED) public class MantleCarvingComponent extends IrisMantleComponent { private final int radius = computeRadius(); @@ -58,21 +60,21 @@ public class MantleCarvingComponent extends IrisMantleComponent { @ChunkCoordinates private void carve(IrisCarving carving, MantleWriter writer, RNG rng, int cx, int cz) { - carving.doCarving(writer, rng, getEngineMantle().getEngine(), cx << 4, -1, cz << 4); + carving.doCarving(writer, rng, getEngineMantle().getEngine(), cx << 4, -1, cz << 4, 0); } private int computeRadius() { var dimension = getDimension(); int max = 0; - max = Math.max(max, dimension.getCarving().getMaxRange(getData())); + max = Math.max(max, dimension.getCarving().getMaxRange(getData(), 0)); for (var i : dimension.getAllRegions(this::getData)) { - max = Math.max(max, i.getCarving().getMaxRange(getData())); + max = Math.max(max, i.getCarving().getMaxRange(getData(), 0)); } for (var i : dimension.getAllBiomes(this::getData)) { - max = Math.max(max, i.getCarving().getMaxRange(getData())); + max = Math.max(max, i.getCarving().getMaxRange(getData(), 0)); } return max; diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleFluidBodyComponent.java b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleFluidBodyComponent.java index bd45353c2..15389715e 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleFluidBodyComponent.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleFluidBodyComponent.java @@ -19,6 +19,7 @@ package com.volmit.iris.engine.mantle.components; import com.volmit.iris.engine.data.cache.Cache; +import com.volmit.iris.engine.mantle.ComponentFlag; import com.volmit.iris.engine.mantle.EngineMantle; import com.volmit.iris.engine.mantle.IrisMantleComponent; import com.volmit.iris.engine.mantle.MantleWriter; @@ -32,6 +33,7 @@ import com.volmit.iris.util.math.RNG; import lombok.Getter; @Getter +@ComponentFlag(MantleFlag.FLUID_BODIES) public class MantleFluidBodyComponent extends IrisMantleComponent { private final int radius = computeRadius(); diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java index fa7ce9ad8..e951b288e 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java @@ -19,6 +19,7 @@ package com.volmit.iris.engine.mantle.components; import com.volmit.iris.engine.jigsaw.PlannedStructure; +import com.volmit.iris.engine.mantle.ComponentFlag; import com.volmit.iris.engine.mantle.EngineMantle; import com.volmit.iris.engine.mantle.IrisMantleComponent; import com.volmit.iris.engine.mantle.MantleWriter; @@ -39,6 +40,7 @@ import org.jetbrains.annotations.Nullable; import java.util.List; +@ComponentFlag(MantleFlag.JIGSAW) public class MantleJigsawComponent extends IrisMantleComponent { @Getter private final int radius = computeRadius(); diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleObjectComponent.java b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleObjectComponent.java index b8d4f9fd8..a4ca6dd7a 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleObjectComponent.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleObjectComponent.java @@ -20,6 +20,7 @@ package com.volmit.iris.engine.mantle.components; import com.volmit.iris.Iris; import com.volmit.iris.engine.data.cache.Cache; +import com.volmit.iris.engine.mantle.ComponentFlag; import com.volmit.iris.engine.mantle.EngineMantle; import com.volmit.iris.engine.mantle.IrisMantleComponent; import com.volmit.iris.engine.mantle.MantleWriter; @@ -47,6 +48,7 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @Getter +@ComponentFlag(MantleFlag.OBJECT) public class MantleObjectComponent extends IrisMantleComponent { private final int radius = computeRadius(); diff --git a/core/src/main/java/com/volmit/iris/engine/modifier/IrisCarveModifier.java b/core/src/main/java/com/volmit/iris/engine/modifier/IrisCarveModifier.java index 4f15b12ca..a7bb391be 100644 --- a/core/src/main/java/com/volmit/iris/engine/modifier/IrisCarveModifier.java +++ b/core/src/main/java/com/volmit/iris/engine/modifier/IrisCarveModifier.java @@ -43,7 +43,6 @@ import org.bukkit.block.data.BlockData; public class IrisCarveModifier extends EngineAssignedModifier { private final RNG rng; private final BlockData AIR = Material.CAVE_AIR.createBlockData(); - private final BlockData WATER = Material.WATER.createBlockData(); private final BlockData LAVA = Material.LAVA.createBlockData(); private final IrisDecorantActuator decorant; @@ -103,7 +102,7 @@ public class IrisCarveModifier extends EngineAssignedModifier { } if (c.isWater()) { - output.set(rx, yy, rz, WATER); + output.set(rx, yy, rz, context.getFluid().get(rx, rz)); } else if (c.isLava()) { output.set(rx, yy, rz, LAVA); } else { 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/IrisBlockData.java b/core/src/main/java/com/volmit/iris/engine/object/IrisBlockData.java index 65a9a59a8..f3f162a7d 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisBlockData.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisBlockData.java @@ -19,6 +19,7 @@ package com.volmit.iris.engine.object; import com.volmit.iris.Iris; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.loader.IrisRegistrant; import com.volmit.iris.core.nms.INMS; @@ -34,8 +35,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; +import org.bukkit.Material; import org.bukkit.block.data.BlockData; -import org.bukkit.entity.EntityType; import java.util.Map; @@ -202,6 +203,14 @@ public class IrisBlockData extends IrisRegistrant { public TileData tryGetTile(IrisData data) { //TODO Do like a registry thing with the tile data registry. Also update the parsing of data to include **block** entities. var type = getBlockData(data).getMaterial(); + if (type == Material.SPAWNER && this.data.containsKey("entitySpawn")) { + String id = (String) this.data.get("entitySpawn"); + if (tileData == null) tileData = new KMap<>(); + KMap spawnData = (KMap) tileData.computeIfAbsent("SpawnData", k -> new KMap<>()); + KMap entity = (KMap) spawnData.computeIfAbsent("entity", k -> new KMap<>()); + entity.putIfAbsent("id", Identifier.fromString(id).toString()); + } + if (!INMS.get().hasTile(type) || tileData == null || tileData.isEmpty()) return null; return new TileData(type, this.tileData); diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisCarving.java b/core/src/main/java/com/volmit/iris/engine/object/IrisCarving.java index bf7efa6af..8287db8c4 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisCarving.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisCarving.java @@ -61,28 +61,32 @@ public class IrisCarving { @BlockCoordinates - public void doCarving(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z) { - doCarving(writer, rng, engine, x, y, z, -1); + public void doCarving(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z, int depth) { + doCarving(writer, rng, new RNG(engine.getSeedManager().getCarve()), engine, x, y, z, depth, -1); } @BlockCoordinates - public void doCarving(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z, int waterHint) { + public void doCarving(MantleWriter writer, RNG rng, RNG base, Engine engine, int x, int y, int z, int recursion, int waterHint) { + int nextRecursion = recursion + 1; + if (caves.isNotEmpty()) { for (IrisCavePlacer i : caves) { - i.generateCave(writer, rng, engine, x, y, z, waterHint); + if (recursion > i.getMaxRecursion()) continue; + i.generateCave(writer, rng, base, engine, x, y, z, nextRecursion, waterHint); } } if (ravines.isNotEmpty()) { for (IrisRavinePlacer i : ravines) { - i.generateRavine(writer, rng, engine, x, y, z, waterHint); + if (recursion > i.getMaxRecursion()) continue; + i.generateRavine(writer, rng, base, engine, x, y, z, nextRecursion, waterHint); } } if (spheres.isNotEmpty()) { for (IrisSphere i : spheres) { if (rng.nextInt(i.getRarity()) == 0) { - i.generate(rng, engine, writer, x, y, z); + i.generate(base, engine, writer, x, y, z); } } } @@ -90,7 +94,7 @@ public class IrisCarving { if (elipsoids.isNotEmpty()) { for (IrisElipsoid i : elipsoids) { if (rng.nextInt(i.getRarity()) == 0) { - i.generate(rng, engine, writer, x, y, z); + i.generate(base, engine, writer, x, y, z); } } } @@ -98,21 +102,24 @@ public class IrisCarving { if (pyramids.isNotEmpty()) { for (IrisPyramid i : pyramids) { if (rng.nextInt(i.getRarity()) == 0) { - i.generate(rng, engine, writer, x, y, z); + i.generate(base, engine, writer, x, y, z); } } } } - public int getMaxRange(IrisData data) { + public int getMaxRange(IrisData data, int recursion) { int max = 0; + int nextRecursion = recursion + 1; for (IrisCavePlacer i : caves) { - max = Math.max(max, i.getSize(data)); + if (recursion > i.getMaxRecursion()) continue; + max = Math.max(max, i.getSize(data, nextRecursion)); } for (IrisRavinePlacer i : ravines) { - max = Math.max(max, i.getSize(data)); + if (recursion > i.getMaxRecursion()) continue; + max = Math.max(max, i.getSize(data, nextRecursion)); } if (elipsoids.isNotEmpty()) { 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 32ab22439..b780d257d 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 @@ -25,9 +25,11 @@ import com.volmit.iris.engine.mantle.MantleWriter; import com.volmit.iris.engine.object.annotations.Desc; import com.volmit.iris.engine.object.annotations.RegistryListResource; import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.matter.MatterCavern; +import com.volmit.iris.util.noise.CNG; import com.volmit.iris.util.plugin.VolmitSender; import lombok.AllArgsConstructor; import lombok.Data; @@ -55,6 +57,9 @@ public class IrisCave extends IrisRegistrant { @Desc("Limit the worm from ever getting higher or lower than this range") private IrisRange verticalRange = new IrisRange(3, 255); + @Desc("Shape of the caves") + private IrisCaveShape shape = new IrisCaveShape(); + @Override public String getFolderName() { return "caves"; @@ -66,14 +71,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, -1); + generate(writer, rng, new RNG(engine.getSeedManager().getCarve()), engine, x, y, z, 0, -1, true); } - public void generate(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z, int waterHint) { - - double girth = getWorm().getGirth().get(rng, x, z, engine.getData()); - KList points = getWorm().generate(rng, engine.getData(), writer, verticalRange, x, y, z, (at) -> { - }); + public void generate(MantleWriter writer, RNG rng, RNG base, Engine engine, int x, int y, int z, int recursion, int waterHint, boolean breakSurface) { + double girth = getWorm().getGirth().get(base.nextParallelRNG(465156), x, z, engine.getData()); + KList points = getWorm().generate(base.nextParallelRNG(784684), engine.getData(), writer, verticalRange, x, y, z, breakSurface, girth + 9); int highestWater = Math.max(waterHint, -1); if (highestWater == -1) { @@ -89,17 +92,19 @@ public class IrisCave extends IrisRegistrant { } - int h = Math.min(Math.max(highestWater, waterHint), engine.getDimension().getFluidHeight()); + int h = Math.min(highestWater, engine.getDimension().getFluidHeight()); for (IrisPosition i : points) { - fork.doCarving(writer, rng, engine, i.getX(), i.getY(), i.getZ(), h); + fork.doCarving(writer, rng, base, engine, i.getX(), i.getY(), i.getZ(), recursion, h); } MatterCavern c = new MatterCavern(true, customBiome, (byte) 0); MatterCavern w = new MatterCavern(true, customBiome, (byte) 1); - writer.setLineConsumer(points, - girth, true, + CNG cng = shape.getNoise(base.nextParallelRNG(8131545), engine); + KSet mask = shape.getMasked(rng, engine); + writer.setNoiseMasked(points, + girth, cng.noise(x, y, z), cng, mask, true, (xf, yf, zf) -> yf <= h ? w : c); } @@ -108,7 +113,7 @@ public class IrisCave extends IrisRegistrant { } - public int getMaxSize(IrisData data) { - return getWorm().getMaxDistance() + fork.getMaxRange(data); + public int getMaxSize(IrisData data, int depth) { + return (int) (Math.ceil(getWorm().getGirth().getMax() * 2) + getWorm().getMaxDistance() + fork.getMaxRange(data, depth)); } } 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 acaa55904..ccdcaafb9 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 @@ -50,6 +50,10 @@ public class IrisCavePlacer implements IRare { @Desc("The cave to place") @RegistryListResource(IrisCave.class) private String cave; + @MinNumber(1) + @MaxNumber(256) + @Desc("The maximum recursion depth") + private int maxRecursion = 16; @Desc("If set to true, this cave is allowed to break the surface") private boolean breakSurface = true; @Desc("The height range this cave can spawn at. If breakSurface is false, the output of this range will be clamped by the current world height to prevent surface breaking.") @@ -60,10 +64,10 @@ public class IrisCavePlacer implements IRare { } public void generateCave(MantleWriter mantle, RNG rng, Engine engine, int x, int y, int z) { - generateCave(mantle, rng, engine, x, y, z, -1); + generateCave(mantle, rng, new RNG(engine.getSeedManager().getCarve()), engine, x, y, z, 0, -1); } - public void generateCave(MantleWriter mantle, RNG rng, Engine engine, int x, int y, int z, int waterHint) { + public void generateCave(MantleWriter mantle, RNG rng, RNG base, Engine engine, int x, int y, int z, int recursion, int waterHint) { if (fail.get()) { return; } @@ -82,28 +86,24 @@ 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(base, 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), waterHint); + cave.generate(mantle, rng, base, engine, x + rng.nextInt(15), y, z + rng.nextInt(15), recursion, waterHint, breakSurface); } catch (Throwable e) { e.printStackTrace(); fail.set(true); } } - public int getSize(IrisData data) { + public int getSize(IrisData data, int depth) { IrisCave cave = getRealCave(data); if (cave != null) { - return cave.getMaxSize(data); + return cave.getMaxSize(data, depth); } return 32; diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisCaveShape.java b/core/src/main/java/com/volmit/iris/engine/object/IrisCaveShape.java new file mode 100644 index 000000000..4972ce516 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisCaveShape.java @@ -0,0 +1,76 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.annotations.Desc; +import com.volmit.iris.engine.object.annotations.RegistryListResource; +import com.volmit.iris.engine.object.annotations.Snippet; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.noise.CNG; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@Snippet("cave-shape") +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Desc("Cave Shape") +@Data +public class IrisCaveShape { + private transient final KMap> cache = new KMap<>(); + + @Desc("Noise used for the shape of the cave") + private IrisGeneratorStyle noise = new IrisGeneratorStyle(); + @RegistryListResource(IrisObject.class) + @Desc("Object used as mask for the shape of the cave") + private String object = null; + @Desc("Rotation to apply to objects before using them as mask") + private IrisObjectRotation objectRotation = new IrisObjectRotation(); + + public CNG getNoise(RNG rng, Engine engine) { + return noise.create(rng, engine.getData()); + } + + public KSet getMasked(RNG rng, Engine engine) { + if (object == null) return null; + return cache.computeIfAbsent(randomRotation(rng), pos -> { + var rotated = new KSet(); + engine.getData().getObjectLoader().load(object).getBlocks().forEach((vector, data) -> { + if (data.getMaterial().isAir()) return; + rotated.add(new IrisPosition(objectRotation.rotate(vector, pos.getX(), pos.getY(), pos.getZ()))); + }); + return rotated; + }); + } + + private IrisPosition randomRotation(RNG rng) { + if (objectRotation == null || !objectRotation.canRotate()) + return new IrisPosition(0,0,0); + return new IrisPosition( + randomDegree(rng, objectRotation.getXAxis()), + randomDegree(rng, objectRotation.getYAxis()), + randomDegree(rng, objectRotation.getZAxis()) + ); + } + + private int randomDegree(RNG rng, IrisAxisRotationClamp clamp) { + if (!clamp.isEnabled()) return 0; + if (clamp.isLocked()) return (int) clamp.getMax(); + double interval = clamp.getInterval(); + if (interval < 1) interval = 1; + + double min = clamp.getMin(), max = clamp.getMax(); + double value = (interval * (Math.ceil(Math.abs(rng.d(0, 360) / interval)))) % 360D; + if (clamp.isUnlimited()) return (int) value; + + if (min > max) { + max = clamp.getMin(); + min = clamp.getMax(); + } + return (int) (double) M.clip(value, min, max); + } +} 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 3ecafab8a..000000000 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisContextInjector.java +++ /dev/null @@ -1,77 +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.misc.ServerProperties; -import lombok.Getter; -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.world.WorldInitEvent; - -import java.util.List; - -import static com.volmit.iris.Iris.instance; - -public class IrisContextInjector implements Listener { - @Getter - private static boolean missingDimensionTypes = false; - private AutoClosing autoClosing = null; - private final int totalWorlds; - private int worldCounter = 0; - - public IrisContextInjector() { - if (!Bukkit.getWorlds().isEmpty()) { - totalWorlds = 0; - return; - } - - String levelName = ServerProperties.LEVEL_NAME; - List irisWorlds = irisWorlds(); - boolean overworld = irisWorlds.contains(levelName); - boolean nether = irisWorlds.contains(levelName + "_nether"); - boolean end = irisWorlds.contains(levelName + "_end"); - - int i = 1; - if (Bukkit.getAllowNether()) i++; - if (Bukkit.getAllowEnd()) i++; - - if (INMS.get().missingDimensionTypes(overworld, nether, end)) { - missingDimensionTypes = true; - totalWorlds = 0; - return; - } - - if (overworld || nether || end) { - var pair = INMS.get().injectUncached(overworld, nether, end); - i += pair.getA() - 3; - autoClosing = pair.getB(); - } - - totalWorlds = i; - instance.registerListener(this); - } - - @EventHandler - public void on(WorldInitEvent event) { - if (++worldCounter < totalWorlds) return; - if (autoClosing != null) { - autoClosing.close(); - autoClosing = null; - } - instance.unregisterListener(this); - } - - private List irisWorlds() { - var config = YamlConfiguration.loadConfiguration(ServerProperties.BUKKIT_YML); - ConfigurationSection section = config.getConfigurationSection("worlds"); - if (section == null) return List.of(); - - return section.getKeys(false) - .stream() - .filter(k -> section.getString(k + ".generator", "").startsWith("Iris")) - .toList(); - } -} diff --git a/core/src/main/java/com/volmit/iris/engine/object/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 f2c5b724c..ef76eac9f 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,13 +25,16 @@ 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.engine.object.annotations.functions.ComponentFlagFunction; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.data.DataProvider; import com.volmit.iris.util.io.IO; import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.mantle.MantleFlag; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.noise.CNG; @@ -45,8 +48,7 @@ import org.bukkit.Material; import org.bukkit.World.Environment; import org.bukkit.block.data.BlockData; -import java.io.File; -import java.io.IOException; +import java.io.*; @Accessors(chain = true) @AllArgsConstructor @@ -74,10 +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; @@ -166,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 = ""; @@ -244,6 +240,10 @@ public class IrisDimension extends IrisRegistrant { @MaxNumber(318) @Desc("The Subterrain Fluid Layer Height") private int caveLavaHeight = 8; + @RegistryListFunction(ComponentFlagFunction.class) + @ArrayType(type = MantleFlag.class) + @Desc("Collection of disabled components") + private KList disabledComponents = new KList<>(); public int getMaxHeight() { return (int) getDimensionHeight().getMax(); @@ -253,12 +253,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) { @@ -410,6 +412,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"; @@ -426,11 +461,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 = """ @@ -455,17 +491,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/IrisEntity.java b/core/src/main/java/com/volmit/iris/engine/object/IrisEntity.java index dfb2508d0..a87c319d0 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisEntity.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisEntity.java @@ -20,8 +20,10 @@ package com.volmit.iris.engine.object; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.loader.IrisRegistrant; import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.service.ExternalDataSVC; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.annotations.*; import com.volmit.iris.util.collection.KList; @@ -455,24 +457,13 @@ public class IrisEntity extends IrisRegistrant { } if (isSpecialType()) { - if (specialType.toLowerCase().startsWith("mythicmobs:")) { - return Iris.linkMythicMobs.spawnMob(specialType.substring(11), at); - } else { - Iris.warn("Invalid mob type to spawn: '" + specialType + "'!"); - return null; - } + return Iris.service(ExternalDataSVC.class).spawnMob(at, Identifier.fromString(specialType)); } return INMS.get().spawnEntity(at, getType(), getReason()); } - public boolean isCitizens() { - return false; - - // TODO: return Iris.linkCitizens.supported() && someType is not empty; - } - public boolean isSpecialType() { return specialType != null && !specialType.equals(""); } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java b/core/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java index a507c342f..a0eb9500d 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java @@ -28,7 +28,6 @@ import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.math.Vector3d; import com.volmit.iris.util.matter.MatterMarker; import com.volmit.iris.util.matter.slices.MarkerMatter; -import io.lumine.mythic.bukkit.adapters.BukkitEntity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -38,9 +37,6 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.LivingEntity; -import org.bukkit.util.BoundingBox; @Snippet("entity-spawn") @Accessors(chain = true) @@ -116,8 +112,8 @@ public class IrisEntitySpawn implements IRare { World world = gen.getWorld().realWorld(); if (spawns > 0) { - if (referenceMarker != null) { - gen.getMantle().getMantle().remove(c.getX(), c.getY(), c.getZ(), MatterMarker.class); + if (referenceMarker != null && referenceMarker.shouldExhaust()) { + gen.getMantle().getMantle().remove(c.getX(), c.getY() - gen.getWorld().minHeight(), c.getZ(), MatterMarker.class); } for (int id = 0; id < spawns; id++) { diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisMarker.java b/core/src/main/java/com/volmit/iris/engine/object/IrisMarker.java index fc0c7eef9..fc5a3c761 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisMarker.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisMarker.java @@ -51,10 +51,10 @@ public class IrisMarker extends IrisRegistrant { private boolean emptyAbove = true; @Desc("If this marker is used, what is the chance it removes itself. For example 25% (0.25) would mean that on average 4 uses will remove a specific marker. Set this below 0 (-1) to never exhaust & set this to 1 or higher to always exhaust on first use.") - private double exhaustionChance = 0.33; + private double exhaustionChance = 0; public boolean shouldExhaust() { - return RNG.r.chance(exhaustionChance); + return exhaustionChance > RNG.r.nextDouble(); } @Override 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 ca6bb7bd4..b7c453bd6 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 @@ -93,14 +93,13 @@ public class IrisRavine extends IrisRegistrant { } public void generate(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z) { - generate(writer, rng, engine, x, y, z, -1); + generate(writer, rng, new RNG(engine.getSeedManager().getCarve()), engine, x, y, z, 0, -1); } - public void generate(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z, int waterHint) { - KList pos = getWorm().generate(rng, engine.getData(), writer, null, x, y, z, (at) -> { - }); - CNG dg = depthStyle.getGenerator().createNoCache(rng, engine.getData()); - CNG bw = baseWidthStyle.getGenerator().createNoCache(rng, engine.getData()); + public void generate(MantleWriter writer, RNG rng, RNG base, Engine engine, int x, int y, int z, int recursion, int waterHint) { + KList pos = getWorm().generate(base.nextParallelRNG(879615), engine.getData(), writer, null, x, y, z, true, 0); + CNG dg = depthStyle.getGenerator().create(base.nextParallelRNG(7894156), engine.getData()); + CNG bw = baseWidthStyle.getGenerator().create(base.nextParallelRNG(15315456), engine.getData()); int highestWater = Math.max(waterHint, -1); boolean water = false; @@ -135,7 +134,7 @@ public class IrisRavine extends IrisRegistrant { int width = (int) Math.round(bw.fitDouble(baseWidthStyle.getMin(), baseWidthStyle.getMax(), p.getX(), p.getZ())); int surface = (int) Math.round(rsurface - depth * 0.45); - fork.doCarving(writer, rng, engine, p.getX(), rng.i(surface - depth, surface), p.getZ(), Math.max(highestWater, waterHint)); + fork.doCarving(writer, rng, base, engine, p.getX(), rng.i(surface - depth, surface), p.getZ(), recursion, highestWater); for (int i = surface + depth; i >= surface; i--) { if (i % ribThickness == 0) { @@ -184,7 +183,7 @@ public class IrisRavine extends IrisRegistrant { } - public int getMaxSize(IrisData data) { - return getWorm().getMaxDistance() + fork.getMaxRange(data); + public int getMaxSize(IrisData data, int depth) { + return getWorm().getMaxDistance() + fork.getMaxRange(data, depth); } } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisRavinePlacer.java b/core/src/main/java/com/volmit/iris/engine/object/IrisRavinePlacer.java index 570859a63..ed9c01841 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisRavinePlacer.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisRavinePlacer.java @@ -50,16 +50,20 @@ public class IrisRavinePlacer implements IRare { @Desc("The ravine to place") @RegistryListResource(IrisRavine.class) private String ravine; + @MinNumber(1) + @MaxNumber(256) + @Desc("The maximum recursion depth") + private int maxRecursion = 100; public IrisRavine getRealRavine(IrisData data) { return ravineCache.aquire(() -> data.getRavineLoader().load(getRavine())); } public void generateRavine(MantleWriter mantle, RNG rng, Engine engine, int x, int y, int z) { - generateRavine(mantle, rng, engine, x, y, z, -1); + generateRavine(mantle, rng, new RNG(engine.getSeedManager().getCarve()), engine, x, y, z, 0, -1); } - public void generateRavine(MantleWriter mantle, RNG rng, Engine engine, int x, int y, int z, int waterHint) { + public void generateRavine(MantleWriter mantle, RNG rng, RNG base, Engine engine, int x, int y, int z, int recursion, int waterHint) { if (fail.get()) { return; } @@ -80,14 +84,14 @@ public class IrisRavinePlacer implements IRare { try { int xx = x + rng.nextInt(15); int zz = z + rng.nextInt(15); - ravine.generate(mantle, rng, engine, xx, y, zz, waterHint); + ravine.generate(mantle, rng, base, engine, xx, y, zz, recursion, waterHint); } catch (Throwable e) { e.printStackTrace(); fail.set(true); } } - public int getSize(IrisData data) { - return getRealRavine(data).getMaxSize(data); + public int getSize(IrisData data, int depth) { + return getRealRavine(data).getMaxSize(data, depth); } } 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..e83fead07 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; @@ -71,13 +71,12 @@ public class IrisWorm { IrisPosition start = new IrisPosition(x, y, z); 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 gx = xStyle.getGenerator().create(rng.nextParallelRNG(14567), data); + CNG gy = yStyle.getGenerator().create(rng.nextParallelRNG(64789), data); + CNG gz = zStyle.getGenerator().create(rng.nextParallelRNG(34790), 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/object/annotations/functions/ComponentFlagFunction.java b/core/src/main/java/com/volmit/iris/engine/object/annotations/functions/ComponentFlagFunction.java new file mode 100644 index 000000000..7a0c01755 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/annotations/functions/ComponentFlagFunction.java @@ -0,0 +1,37 @@ +package com.volmit.iris.engine.object.annotations.functions; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.engine.framework.ListFunction; +import com.volmit.iris.engine.mantle.ComponentFlag; +import com.volmit.iris.engine.mantle.MantleComponent; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.mantle.MantleFlag; + +import java.util.Objects; + +public class ComponentFlagFunction implements ListFunction> { + @Override + public String key() { + return "component-flag"; + } + + @Override + public String fancyName() { + return "Component Flag"; + } + + @Override + public KList apply(IrisData data) { + var engine = data.getEngine(); + if (engine != null) return engine.getMantle().getComponentFlags().toStringList(); + return Iris.getClasses("com.volmit.iris.engine.mantle.components", ComponentFlag.class) + .stream() + .filter(MantleComponent.class::isAssignableFrom) + .map(c -> c.getDeclaredAnnotation(ComponentFlag.class)) + .filter(Objects::nonNull) + .map(ComponentFlag::value) + .map(MantleFlag::toString) + .collect(KList.collector()); + } +} 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 74e711398..36a1852da 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,10 +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.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; @@ -39,6 +41,7 @@ import com.volmit.iris.util.io.ReactiveFolder; import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.Looper; +import io.papermc.lib.PaperLib; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.Setter; @@ -48,6 +51,7 @@ import org.bukkit.block.data.BlockData; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.world.WorldInitEvent; import org.bukkit.generator.BiomeProvider; @@ -58,15 +62,12 @@ 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; -import java.util.concurrent.Semaphore; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Consumer; @EqualsAndHashCode(callSuper = true) @Data @@ -84,14 +85,13 @@ 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 Engine engine; - private Looper hotloader; - private StudioMode lastMode; - private DummyBiomeProvider dummyBiomeProvider; + private final AtomicCache targetCache = new AtomicCache<>(); + private volatile Engine engine; + private volatile Looper hotloader; + private volatile StudioMode lastMode; + private volatile DummyBiomeProvider dummyBiomeProvider; @Setter - private StudioGenerator studioGenerator; - - private boolean initialized = false; + private volatile StudioGenerator studioGenerator; public BukkitChunkGenerator(IrisWorld world, boolean studio, File dataLocation, String dimensionKey) { setup = new AtomicBoolean(false); @@ -108,131 +108,149 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun Bukkit.getServer().getPluginManager().registerEvents(this, Iris.instance); } - private static Field getField(Class clazz, String fieldName) - throws NoSuchFieldException { - try { - return clazz.getDeclaredField(fieldName); - } catch (NoSuchFieldException e) { - Class superClass = clazz.getSuperclass(); - if (superClass == null) { - throw e; - } else { - return getField(superClass, fieldName); + @EventHandler(priority = EventPriority.LOWEST) + public void onWorldInit(WorldInitEvent event) { + if (!world.name().equals(event.getWorld().getName())) return; + Iris.instance.unregisterListener(this); + 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); } - @EventHandler - public void onWorldInit(WorldInitEvent event) { + private boolean initialize(World world) { + Engine engine = getEngine(world); + if (engine == null) return false; try { - if (initialized || !world.name().equals(event.getWorld().getName())) - return; - 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; - } + 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); + IrisWorlds.get().put(world.getName(), dimensionKey); + return true; + } + + @Nullable + @Override + public Location getFixedSpawnLocation(@NotNull World world, @NotNull Random random) { + Location location = new Location(world, 0, 64, 0); + PaperLib.getChunkAtAsync(location) + .thenAccept(c -> { + World w = c.getWorld(); + if (!w.getSpawnLocation().equals(location)) + return; + w.setSpawnLocation(location.add(0, w.getHighestBlockYAt(location) - 64, 0)); + }); + return location; } 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 - public void injectChunkReplacement(World world, int x, int z, Consumer jobs) { + public void injectChunkReplacement(World world, int x, int z, Executor syncExecutor) { try { loadLock.acquire(); IrisBiomeStorage st = new IrisBiomeStorage(); TerrainChunk tc = TerrainChunk.createUnsafe(world, st); - Hunk blocks = Hunk.view(tc); - Hunk biomes = Hunk.view(tc, tc.getMinHeight(), tc.getMaxHeight()); this.world.bind(world); - getEngine().generate(x << 4, z << 4, blocks, biomes, true); - Iris.debug("Regenerated " + x + " " + z); - int t = 0; + getEngine().generate(x << 4, z << 4, tc, false); + + Chunk c = PaperLib.getChunkAtAsync(world, x, z) + .thenApply(d -> { + d.addPluginChunkTicket(Iris.instance); + + for (Entity ee : d.getEntities()) { + if (ee instanceof Player) { + continue; + } + + ee.remove(); + } + + engine.getWorldManager().onChunkLoad(d, false); + return d; + }).get(); + + + KList> futures = new KList<>(1 + getEngine().getHeight() >> 4); for (int i = getEngine().getHeight() >> 4; i >= 0; i--) { - if (!world.isChunkLoaded(x, z)) { - continue; - } - - Chunk c = world.getChunkAt(x, z); - for (Entity ee : c.getEntities()) { - if (ee instanceof Player) { - continue; - } - - J.s(ee::remove); - } - - J.s(() -> engine.getWorldManager().onChunkLoad(c, false)); - - int finalI = i; - jobs.accept(() -> { - + int finalI = i << 4; + futures.add(CompletableFuture.runAsync(() -> { for (int xx = 0; xx < 16; xx++) { for (int yy = 0; yy < 16; yy++) { for (int zz = 0; zz < 16; zz++) { - if (yy + (finalI << 4) >= engine.getHeight() || yy + (finalI << 4) < 0) { + if (yy + finalI >= engine.getHeight() || yy + finalI < 0) { continue; } - c.getBlock(xx, yy + (finalI << 4) + world.getMinHeight(), zz) - .setBlockData(tc.getBlockData(xx, yy + (finalI << 4) + world.getMinHeight(), zz), false); + int y = yy + finalI + world.getMinHeight(); + c.getBlock(xx, y, zz).setBlockData(tc.getBlockData(xx, y, zz), false); } } } - }); - jobs.accept(() -> INMS.get().placeStructures(c)); + }, syncExecutor)); } + futures.add(CompletableFuture.runAsync(() -> INMS.get().placeStructures(c), syncExecutor)); + + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenRunAsync(() -> { + c.removePluginChunkTicket(Iris.instance); + c.unload(); + }, syncExecutor) + .get(); + Iris.debug("Regenerated " + x + " " + z); loadLock.release(); } catch (Throwable e) { @@ -298,7 +316,9 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun hotloader.interrupt(); } - getEngine().close(); + final Engine engine = getEngine(); + if (engine != null && !engine.isClosed()) + engine.close(); folder.clear(); populators.clear(); 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..93066fb62 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,23 +24,25 @@ 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; +import java.util.concurrent.Executor; 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); + void injectChunkReplacement(World world, int x, int z, Executor syncExecutor); void close(); 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/atomics/AtomicAverage.java b/core/src/main/java/com/volmit/iris/util/atomics/AtomicAverage.java index cf9da6d1c..f3574db1d 100644 --- a/core/src/main/java/com/volmit/iris/util/atomics/AtomicAverage.java +++ b/core/src/main/java/com/volmit/iris/util/atomics/AtomicAverage.java @@ -31,11 +31,11 @@ import com.volmit.iris.util.data.DoubleArrayUtils; */ public class AtomicAverage { protected final AtomicDoubleArray values; - protected int cursor; - private double average; - private double lastSum; - private boolean dirty; - private boolean brandNew; + protected transient int cursor; + private transient double average; + private transient double lastSum; + private transient boolean dirty; + private transient boolean brandNew; /** * Create an average holder @@ -57,7 +57,7 @@ public class AtomicAverage { * * @param i the value */ - public void put(double i) { + public synchronized void put(double i) { try { dirty = true; diff --git a/core/src/main/java/com/volmit/iris/util/collection/KList.java b/core/src/main/java/com/volmit/iris/util/collection/KList.java index 9e00ea20d..bf1cf916d 100644 --- a/core/src/main/java/com/volmit/iris/util/collection/KList.java +++ b/core/src/main/java/com/volmit/iris/util/collection/KList.java @@ -26,6 +26,8 @@ import com.volmit.iris.util.math.RNG; import java.util.*; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collector; +import java.util.stream.Collectors; @SuppressWarnings("ALL") public class KList extends ArrayList implements List { @@ -65,6 +67,10 @@ public class KList extends ArrayList implements List { return s; } + public static Collector> collector() { + return Collectors.toCollection(KList::new); + } + public static KList asStringList(List oo) { KList s = new KList(); diff --git a/core/src/main/java/com/volmit/iris/util/collection/KMap.java b/core/src/main/java/com/volmit/iris/util/collection/KMap.java index 01baab32b..4a7f938e0 100644 --- a/core/src/main/java/com/volmit/iris/util/collection/KMap.java +++ b/core/src/main/java/com/volmit/iris/util/collection/KMap.java @@ -23,11 +23,9 @@ import com.volmit.iris.util.function.Consumer2; import com.volmit.iris.util.function.Consumer3; import com.volmit.iris.util.scheduling.Queue; -import java.util.Collections; -import java.util.Comparator; -import java.util.Enumeration; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; import java.util.function.BiFunction; @SuppressWarnings("ALL") @@ -373,6 +371,20 @@ public class KMap extends ConcurrentHashMap { return g; } + public KMap qclear(BiConsumer action) { + final Iterator> it = entrySet().iterator(); + while (it.hasNext()) { + final Map.Entry entry = it.next(); + it.remove(); + try { + action.accept(entry.getKey(), entry.getValue()); + } catch (Throwable e) { + Iris.reportError(e); + } + } + return this; + } + /** * Create a keypair queue * diff --git a/core/src/main/java/com/volmit/iris/util/collection/KSet.java b/core/src/main/java/com/volmit/iris/util/collection/KSet.java index ad96f7085..53b602f9a 100644 --- a/core/src/main/java/com/volmit/iris/util/collection/KSet.java +++ b/core/src/main/java/com/volmit/iris/util/collection/KSet.java @@ -18,29 +18,67 @@ package com.volmit.iris.util.collection; -import java.util.Collection; -import java.util.HashSet; +import org.jetbrains.annotations.NotNull; -public class KSet extends HashSet { +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; + +public class KSet extends AbstractSet implements Serializable { private static final long serialVersionUID = 1L; + private final ConcurrentHashMap map; public KSet() { - super(); + map = new ConcurrentHashMap<>(); } public KSet(Collection c) { - super(c); + this(); + addAll(c); } public KSet(int initialCapacity, float loadFactor) { - super(initialCapacity, loadFactor); + map = new ConcurrentHashMap<>(initialCapacity, loadFactor); } public KSet(int initialCapacity) { - super(initialCapacity); + map = new ConcurrentHashMap<>(initialCapacity); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean contains(Object o) { + return map.containsKey(o); + } + + @Override + public boolean add(T t) { + return map.putIfAbsent(t, Boolean.TRUE) == null; + } + + @Override + public boolean remove(Object o) { + return map.remove(o) != null; + } + + @Override + public void clear() { + map.clear(); + } + + @NotNull + @Override + public Iterator iterator() { + return map.keySet().iterator(); } public KSet copy() { - return new KSet(this); + return new KSet<>(this); } } diff --git a/core/src/main/java/com/volmit/iris/util/context/IrisContext.java b/core/src/main/java/com/volmit/iris/util/context/IrisContext.java index 8b69eb867..73bae0e5e 100644 --- a/core/src/main/java/com/volmit/iris/util/context/IrisContext.java +++ b/core/src/main/java/com/volmit/iris/util/context/IrisContext.java @@ -87,4 +87,21 @@ public class IrisContext { public IrisComplex getComplex() { return engine.getComplex(); } + + public KMap asContext() { + var hash32 = engine.getHash32().getNow(null); + var dimension = engine.getDimension(); + var mantle = engine.getMantle(); + return new KMap() + .qput("studio", engine.isStudio()) + .qput("closed", engine.isClosed()) + .qput("pack", new KMap<>() + .qput("key", dimension == null ? "" : dimension.getLoadKey()) + .qput("version", dimension == null ? "" : dimension.getVersion()) + .qput("hash", hash32 == null ? "" : Long.toHexString(hash32))) + .qput("mantle", new KMap<>() + .qput("idle", mantle.getAdjustedIdleDuration()) + .qput("loaded", mantle.getLoadedRegionCount()) + .qput("queued", mantle.getUnloadRegionCount())); + } } 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..7e71d39f3 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 @@ -21,6 +21,7 @@ package com.volmit.iris.util.data; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.link.Identifier; +import com.volmit.iris.core.link.data.DataType; import com.volmit.iris.core.service.ExternalDataSVC; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; @@ -105,7 +106,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); @@ -665,7 +674,7 @@ public class B { } } - for (Identifier id : Iris.service(ExternalDataSVC.class).getAllBlockIdentifiers()) + for (Identifier id : Iris.service(ExternalDataSVC.class).getAllIdentifiers(DataType.BLOCK)) bt.add(id.toString()); bt.addAll(custom.k()); @@ -680,7 +689,7 @@ public class B { bt.add(v); } - for (Identifier id : Iris.service(ExternalDataSVC.class).getAllItemIdentifiers()) + for (Identifier id : Iris.service(ExternalDataSVC.class).getAllIdentifiers(DataType.ITEM)) bt.add(id.toString()); return bt.toArray(new String[0]); diff --git a/core/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java b/core/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java index e76f62965..d22a8b822 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java +++ b/core/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java @@ -160,7 +160,8 @@ public interface DecreeSystem extends CommandExecutor, TabCompleter { } J.aBukkit(() -> { - if (!call(new VolmitSender(sender), args)) { + var volmit = new VolmitSender(sender); + if (!call(volmit, args)) { if (IrisSettings.get().getGeneral().isCommandSounds()) { if (sender instanceof Player) { @@ -169,7 +170,7 @@ public interface DecreeSystem extends CommandExecutor, TabCompleter { } } - sender.sendMessage(C.RED + "Unknown Iris Command"); + volmit.sendMessage(C.RED + "Unknown Iris Command"); } else { if (IrisSettings.get().getGeneral().isCommandSounds()) { if (sender instanceof Player) { 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/format/C.java b/core/src/main/java/com/volmit/iris/util/format/C.java index de1893c8f..85deb3cac 100644 --- a/core/src/main/java/com/volmit/iris/util/format/C.java +++ b/core/src/main/java/com/volmit/iris/util/format/C.java @@ -376,6 +376,28 @@ public enum C { return "#" + Integer.toHexString(spin(color.awtColor(), h, s, b).getRGB()).substring(2); } + public static String mini(String s) { + String msg = compress(s); + StringBuilder b = new StringBuilder(); + boolean c = false; + + for (char i : msg.toCharArray()) { + if (!c) { + if (i == C.COLOR_CHAR) { + c = true; + continue; + } + b.append(i); + } else { + c = false; + C o = C.getByChar(i); + b.append(o.token); + } + } + + return b.toString(); + } + public static String aura(String s, int hrad, int srad, int vrad) { return aura(s, hrad, srad, vrad, 0.3D); } 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 a8f718885..247dfefb1 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 @@ -18,11 +18,20 @@ package com.volmit.iris.util.io; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.volmit.iris.Iris; import com.volmit.iris.util.format.Form; +import org.apache.commons.io.function.IOConsumer; +import org.apache.commons.io.function.IOFunction; import java.io.*; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -134,8 +143,7 @@ public class IO { continue; } - try (var fin = new FileInputStream(file)) { - var din = new CheckedInputStream(fin, crc); + try (var din = new CheckedInputStream(readDeterministic(file), crc)) { fullTransfer(din, new VoidOutputStream(), 8192); } catch (IOException e) { Iris.reportError(e); @@ -152,10 +160,43 @@ public class IO { return 0; } + public static InputStream readDeterministic(File file) throws IOException { + if (!file.getName().endsWith(".json")) + return new FileInputStream(file); + + JsonElement json; + try (FileReader reader = new FileReader(file)) { + json = JsonParser.parseReader(reader); + } + + var queue = new LinkedList(); + queue.add(json); + + while (!queue.isEmpty()) { + var element = queue.pop(); + Collection add = List.of(); + + if (element instanceof JsonObject obj) { + var map = obj.asMap(); + var sorted = new TreeMap<>(map); + map.clear(); + map.putAll(sorted); + + add = sorted.values(); + } else if (element instanceof JsonArray array) { + add = array.asList(); + } + + add.stream().filter(e -> e.isJsonObject() || e.isJsonArray()).forEach(queue::add); + } + + return toInputStream(json.toString()); + } + public static String hash(File b) { try { MessageDigest d = MessageDigest.getInstance("SHA-256"); - DigestInputStream din = new DigestInputStream(new FileInputStream(b), d); + DigestInputStream din = new DigestInputStream(readDeterministic(b), d); fullTransfer(din, new VoidOutputStream(), 8192); din.close(); return bytesToHex(din.getMessageDigest().digest()); @@ -552,6 +593,25 @@ public class IO { } } + public static void copyDirectory(Path source, Path target) throws IOException { + Files.walk(source).forEach(sourcePath -> { + Path targetPath = target.resolve(source.relativize(sourcePath)); + + try { + if (Files.isDirectory(sourcePath)) { + if (!Files.exists(targetPath)) { + Files.createDirectories(targetPath); + } + } else { + Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); + } + } catch (IOException e) { + Iris.error("Failed to copy " + targetPath); + e.printStackTrace(); + } + }); + } + /** * Unconditionally close an Reader. *

@@ -1602,4 +1662,19 @@ public class IO { int ch2 = input2.read(); return (ch2 == -1); } + + public static void write(File file, IOFunction builder, IOConsumer action) throws IOException { + File dir = new File(file.getParentFile(), ".tmp"); + dir.mkdirs(); + dir.deleteOnExit(); + File temp = File.createTempFile("iris",".bin", dir); + try { + try (var out = builder.apply(new FileOutputStream(temp))) { + action.accept(out); + } + Files.move(temp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); + } finally { + temp.delete(); + } + } } diff --git a/core/src/main/java/com/volmit/iris/util/io/JarScanner.java b/core/src/main/java/com/volmit/iris/util/io/JarScanner.java index 2d8c86e1b..d7dcd31e7 100644 --- a/core/src/main/java/com/volmit/iris/util/io/JarScanner.java +++ b/core/src/main/java/com/volmit/iris/util/io/JarScanner.java @@ -31,16 +31,22 @@ public class JarScanner { private final KSet> classes; private final File jar; private final String superPackage; + private final boolean report; /** * Create a scanner * * @param jar the path to the jar */ - public JarScanner(File jar, String superPackage) { + public JarScanner(File jar, String superPackage, boolean report) { this.jar = jar; this.classes = new KSet<>(); this.superPackage = superPackage; + this.report = report; + } + + public JarScanner(File jar, String superPackage) { + this(jar, superPackage, true); } /** @@ -65,7 +71,8 @@ public class JarScanner { try { Class clazz = Class.forName(c); classes.add(clazz); - } catch (ClassNotFoundException e) { + } catch (Throwable e) { + if (!report) continue; Iris.reportError(e); e.printStackTrace(); } diff --git a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java index 0e89c1162..6e1e56e54 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java @@ -21,20 +21,20 @@ package com.volmit.iris.util.mantle; import com.google.common.util.concurrent.AtomicDouble; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.service.IrisEngineSVC; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.mantle.EngineMantle; import com.volmit.iris.engine.mantle.MantleWriter; -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.documentation.BlockCoordinates; import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.documentation.RegionCoordinates; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.function.Consumer4; +import com.volmit.iris.util.io.IO; import com.volmit.iris.util.math.M; import com.volmit.iris.util.matter.Matter; import com.volmit.iris.util.matter.MatterSlice; @@ -51,8 +51,6 @@ import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.ReentrantLock; /** * The mantle can store any type of data slice anywhere and manage regions & IO on it's own. @@ -60,18 +58,18 @@ import java.util.concurrent.locks.ReentrantLock; */ public class Mantle { - private static final boolean disableClear = System.getProperty("disableClear", "false").equals("true"); private final File dataFolder; @Getter private final int worldHeight; private final Map lastUse; - @Getter private final Map loadedRegions; private final HyperLock hyperLock; private final AtomicBoolean closed; private final MultiBurst ioBurst; private final AtomicBoolean ioTrim; private final AtomicBoolean ioTectonicUnload; + private final AtomicDouble adjustedIdleDuration; + private final KSet toUnload; /** * Create a new mantle @@ -87,10 +85,11 @@ public class Mantle { this.worldHeight = worldHeight; this.ioTrim = new AtomicBoolean(false); this.ioTectonicUnload = new AtomicBoolean(false); - dataFolder.mkdirs(); loadedRegions = new KMap<>(); lastUse = new KMap<>(); ioBurst = MultiBurst.burst; + adjustedIdleDuration = new AtomicDouble(0); + toUnload = new KSet<>(); Iris.debug("Opened The Mantle " + C.DARK_AQUA + dataFolder.getAbsolutePath()); } @@ -103,7 +102,7 @@ public class Mantle { * @return the file */ public static File fileForRegion(File folder, int x, int z) { - return fileForRegion(folder, key(x, z)); + return fileForRegion(folder, key(x, z), true); } /** @@ -113,12 +112,28 @@ public class Mantle { * @param key the region key * @return the file */ - public static File fileForRegion(File folder, Long key) { - File f = new File(folder, "p." + key + ".ttp.lz4b"); - if (!f.getParentFile().exists()) { - f.getParentFile().mkdirs(); + public static File fileForRegion(File folder, Long key, boolean convert) { + File f = oldFileForRegion(folder, key); + File fv = new File(folder, "pv." + key + ".ttp.lz4b"); + if (f.exists() && !fv.exists() && convert) + return f; + + if (!fv.getParentFile().exists()) { + fv.getParentFile().mkdirs(); } - return f; + return fv; + } + + + /** + * Get the old file for the given region + * + * @param folder the data folder + * @param key the region key + * @return the file + */ + public static File oldFileForRegion(File folder, Long key) { + return new File(folder, "p." + key + ".ttp.lz4b"); } /** @@ -210,7 +225,7 @@ public class Mantle { @RegionCoordinates public boolean hasTectonicPlate(int x, int z) { Long k = key(x, z); - return loadedRegions.containsKey(k) || fileForRegion(dataFolder, k).exists(); + return loadedRegions.containsKey(k) || fileForRegion(dataFolder, k, true).exists(); } /** @@ -354,21 +369,24 @@ public class Mantle { */ public synchronized void close() { Iris.debug("Closing The Mantle " + C.DARK_AQUA + dataFolder.getAbsolutePath()); - if (closed.get()) { + if (closed.getAndSet(true)) { return; } - closed.set(true); - BurstExecutor b = ioBurst.burst(loadedRegions.size()); - for (Long i : loadedRegions.keySet()) { - b.queue(() -> { - try { - loadedRegions.get(i).write(fileForRegion(dataFolder, i)); - } catch (IOException e) { - e.printStackTrace(); - } - }); - } + hyperLock.disable(); + BurstExecutor b = ioBurst.burst(toUnload.size()); + loadedRegions.forEach((i, plate) -> b.queue(() -> { + try { + plate.close(); + plate.write(fileForRegion(dataFolder, i, false)); + oldFileForRegion(dataFolder, i).delete(); + } catch (Throwable e) { + Iris.error("Failed to write Tectonic Plate " + C.DARK_GREEN + Cache.keyX(i) + " " + Cache.keyZ(i)); + Iris.reportError(e); + e.printStackTrace(); + } + })); + loadedRegions.clear(); try { b.complete(); @@ -376,7 +394,7 @@ public class Mantle { Iris.reportError(e); } - loadedRegions.clear(); + IO.delete(new File(dataFolder, ".tmp")); Iris.debug("The Mantle has Closed " + C.DARK_AQUA + dataFolder.getAbsolutePath()); } @@ -392,16 +410,6 @@ public class Mantle { return numberOfEntries * bytesPerEntry; } - @Getter - private final AtomicDouble adjustedIdleDuration = new AtomicDouble(0); - @Getter - private final AtomicInteger forceAggressiveThreshold = new AtomicInteger(30); - @Getter - private final AtomicLong oldestTectonicPlate = new AtomicLong(0); - private final ReentrantLock unloadLock = new ReentrantLock(); - @Getter - private final KList toUnload = new KList<>(); - /** * Save & unload regions that have not been used for more than the * specified amount of milliseconds @@ -414,93 +422,81 @@ public class Mantle { } adjustedIdleDuration.set(baseIdleDuration); - - if (loadedRegions != null) { - if (loadedRegions.size() > tectonicLimit) { - // todo update this correctly and maybe do something when its above a 100% - adjustedIdleDuration.set(Math.max(adjustedIdleDuration.get() - (1000 * (((loadedRegions.size() - tectonicLimit) / (double) tectonicLimit) * 100) * 0.4), 4000)); - } + if (loadedRegions.size() > tectonicLimit) { + // todo update this correctly and maybe do something when its above a 100% + adjustedIdleDuration.set(Math.max(adjustedIdleDuration.get() - (1000 * (((loadedRegions.size() - tectonicLimit) / (double) tectonicLimit) * 100) * 0.4), 4000)); } ioTrim.set(true); - unloadLock.lock(); try { - if (lastUse != null && IrisEngineSVC.instance != null) { - if (!lastUse.isEmpty()) { - Iris.debug("Trimming Tectonic Plates older than " + Form.duration(adjustedIdleDuration.get(), 0)); - for (long i : new ArrayList<>(lastUse.keySet())) { - double finalAdjustedIdleDuration = adjustedIdleDuration.get(); - hyperLock.withLong(i, () -> { - Long lastUseTime = lastUse.get(i); - if (lastUseTime != null && M.ms() - lastUseTime >= finalAdjustedIdleDuration) { - toUnload.add(i); - Iris.debug("Tectonic Region added to unload"); - IrisEngineSVC.instance.trimActiveAlive.reset(); - } - }); - } - } - } + double adjustedIdleDuration = this.adjustedIdleDuration.get(); + Iris.debug("Trimming Tectonic Plates older than " + Form.duration(adjustedIdleDuration, 0)); + if (lastUse.isEmpty()) return; + double unloadTime = M.ms() - adjustedIdleDuration; + for (long id : lastUse.keySet()) { + hyperLock.withLong(id, () -> { + Long lastUseTime = lastUse.get(id); + if (lastUseTime != null && lastUseTime < unloadTime) { + toUnload.add(id); + Iris.debug("Tectonic Region added to unload"); + } + }); + } } catch (Throwable e) { Iris.reportError(e); } finally { ioTrim.set(false); - unloadLock.unlock(); } } public synchronized int unloadTectonicPlate(int tectonicLimit) { + if (closed.get()) { + throw new RuntimeException("The Mantle is closed"); + } + AtomicInteger i = new AtomicInteger(); - unloadLock.lock(); - BurstExecutor burst = null; - if (IrisEngineSVC.instance != null) { - try { - KList copy = toUnload.copy(); - if (!disableClear) toUnload.clear(); - burst = MultiBurst.burst.burst(copy.size()); - burst.setMulticore(copy.size() > tectonicLimit); - for (int j = 0; j < copy.size(); j++) { - Long id = copy.get(j); - if (id == null) { - Iris.error("Null id in unloadTectonicPlate at index " + j); - continue; + BurstExecutor burst = ioBurst.burst(toUnload.size()); + burst.setMulticore(toUnload.size() > tectonicLimit); + + ioTectonicUnload.set(true); + try { + for (long id : toUnload) { + burst.queue(() -> hyperLock.withLong(id, () -> { + TectonicPlate m = loadedRegions.get(id); + if (m == null) { + Iris.debug("Tectonic Plate was added to unload while not loaded " + C.DARK_GREEN + Cache.keyX(id) + " " + Cache.keyZ(id)); + toUnload.remove(id); + return; } - burst.queue(() -> - hyperLock.withLong(id, () -> { - TectonicPlate m = loadedRegions.get(id); - if (m != null) { - if (m.inUse()) { - Iris.debug("Tectonic Plate was added to unload while in use " + C.DARK_GREEN + m.getX() + " " + m.getZ()); - if (disableClear) toUnload.remove(id); - lastUse.put(id, M.ms()); - return; - } - try { - m.write(fileForRegion(dataFolder, id)); - loadedRegions.remove(id); - lastUse.remove(id); - if (disableClear) toUnload.remove(id); - i.incrementAndGet(); - Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(id) + " " + Cache.keyZ(id)); - IrisEngineSVC.instance.unloadActiveAlive.reset(); - } catch (IOException e) { - Iris.reportError(e); - } - } - })); - } - burst.complete(); - } catch (Throwable e) { - e.printStackTrace(); - if (burst != null) - burst.complete(); - } finally { - unloadLock.unlock(); - ioTectonicUnload.set(true); + if (m.inUse()) { + Iris.debug("Tectonic Plate was added to unload while in use " + C.DARK_GREEN + m.getX() + " " + m.getZ()); + lastUse.put(id, M.ms()); + toUnload.remove(id); + return; + } + + try { + m.write(fileForRegion(dataFolder, id, false)); + oldFileForRegion(dataFolder, id).delete(); + loadedRegions.remove(id); + lastUse.remove(id); + toUnload.remove(id); + i.incrementAndGet(); + Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(id) + " " + Cache.keyZ(id)); + } catch (IOException e) { + Iris.reportError(e); + } + })); } - return i.get(); + burst.complete(); + } catch (Throwable e) { + Iris.reportError(e); + e.printStackTrace(); + burst.complete(); + } finally { + ioTectonicUnload.set(false); } return i.get(); } @@ -516,7 +512,7 @@ public class Mantle { */ @RegionCoordinates private TectonicPlate get(int x, int z) { - if (ioTrim.get()) { + if (ioTrim.get() || ioTectonicUnload.get()) { try { return getSafe(x, z).get(); } catch (InterruptedException e) { @@ -576,7 +572,7 @@ public class Mantle { if (file.exists()) { try { Iris.addPanic("reading.tectonic-plate", file.getAbsolutePath()); - region = TectonicPlate.read(worldHeight, file); + region = TectonicPlate.read(worldHeight, file, file.getName().startsWith("pv.")); if (region.getX() != x || region.getZ() != z) { Iris.warn("Loaded Tectonic Plate " + x + "," + z + " but read it as " + region.getX() + "," + region.getZ() + "... Assuming " + x + "," + z); @@ -626,6 +622,14 @@ public class Mantle { return loadedRegions.size(); } + public int getUnloadRegionCount() { + return toUnload.size(); + } + + public double getAdjustedIdleDuration() { + return adjustedIdleDuration.get(); + } + public void set(int x, int y, int z, MatterSlice slice) { if (slice.isEmpty()) { return; diff --git a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java index 2d3c2a114..1c50d8c98 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java @@ -19,6 +19,7 @@ package com.volmit.iris.util.mantle; import com.volmit.iris.Iris; +import com.volmit.iris.util.data.Varint; import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.function.Consumer4; import com.volmit.iris.util.io.CountingDataInputStream; @@ -26,11 +27,13 @@ import com.volmit.iris.util.matter.IrisMatter; import com.volmit.iris.util.matter.Matter; import com.volmit.iris.util.matter.MatterSlice; import lombok.Getter; +import lombok.SneakyThrows; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.concurrent.atomic.AtomicReferenceArray; @@ -45,7 +48,8 @@ public class MantleChunk { private final int z; private final AtomicIntegerArray flags; private final AtomicReferenceArray sections; - private final AtomicInteger ref = new AtomicInteger(); + private final Semaphore ref = new Semaphore(Integer.MAX_VALUE, true); + private final AtomicBoolean closed = new AtomicBoolean(false); /** * Create a mantle chunk @@ -72,11 +76,12 @@ public class MantleChunk { * @throws IOException shit happens * @throws ClassNotFoundException shit happens */ - public MantleChunk(int sectionHeight, CountingDataInputStream din) throws IOException { + public MantleChunk(int version, int sectionHeight, CountingDataInputStream din) throws IOException { this(sectionHeight, din.readByte(), din.readByte()); int s = din.readByte(); + int l = version < 0 ? flags.length() : Varint.readUnsignedVarInt(din); - for (int i = 0; i < flags.length(); i++) { + for (int i = 0; i < flags.length() && i < l; i++) { flags.set(i, din.readBoolean() ? 1 : 0); } @@ -85,6 +90,10 @@ public class MantleChunk { long size = din.readInt(); if (size == 0) continue; long start = din.count(); + if (i >= sectionHeight) { + din.skipTo(start + size); + continue; + } try { sections.set(i, Matter.readDin(din)); @@ -103,20 +112,33 @@ public class MantleChunk { } } + @SneakyThrows + public void close() { + closed.set(true); + ref.acquire(Integer.MAX_VALUE); + ref.release(Integer.MAX_VALUE); + } + public boolean inUse() { - return ref.get() > 0; + return ref.availablePermits() < Integer.MAX_VALUE; } public MantleChunk use() { - ref.incrementAndGet(); + if (closed.get()) throw new IllegalStateException("Chunk is closed!"); + ref.acquireUninterruptibly(); + if (closed.get()) { + ref.release(); + throw new IllegalStateException("Chunk is closed!"); + } return this; } public void release() { - ref.decrementAndGet(); + ref.release(); } public void flag(MantleFlag flag, boolean f) { + if (closed.get()) throw new IllegalStateException("Chunk is closed!"); flags.set(flag.ordinal(), f ? 1 : 0); } @@ -198,9 +220,11 @@ public class MantleChunk { * @throws IOException shit happens */ public void write(DataOutputStream dos) throws IOException { + close(); dos.writeByte(x); dos.writeByte(z); dos.writeByte(sections.length()); + Varint.writeUnsignedVarInt(flags.length(), dos); for (int i = 0; i < flags.length(); i++) { dos.writeBoolean(flags.get(i) == 1); diff --git a/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java b/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java index efd724d5e..101019023 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java @@ -19,13 +19,15 @@ package com.volmit.iris.util.mantle; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; import com.volmit.iris.engine.EnginePanic; import com.volmit.iris.engine.data.cache.Cache; -import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.data.Varint; import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.io.CountingDataInputStream; +import com.volmit.iris.util.io.IO; import com.volmit.iris.util.scheduling.PrecisionStopwatch; import lombok.Getter; import net.jpountz.lz4.LZ4BlockInputStream; @@ -44,7 +46,9 @@ import java.util.concurrent.atomic.AtomicReferenceArray; * Tectonic Plates are fully atomic & thread safe */ public class TectonicPlate { - private static final KSet errors = new KSet<>(); + private static final ThreadLocal errors = ThreadLocal.withInitial(() -> false); + public static final int MISSING = -1; + public static final int CURRENT = 0; private final int sectionHeight; private final AtomicReferenceArray chunks; @@ -74,11 +78,12 @@ public class TectonicPlate { * @param din the data input * @throws IOException shit happens yo */ - public TectonicPlate(int worldHeight, CountingDataInputStream din) throws IOException { + public TectonicPlate(int worldHeight, CountingDataInputStream din, boolean versioned) throws IOException { this(worldHeight, din.readInt(), din.readInt()); if (!din.markSupported()) throw new IOException("Mark not supported!"); + int v = versioned ? Varint.readUnsignedVarInt(din) : MISSING; for (int i = 0; i < chunks.length(); i++) { long size = din.readInt(); if (size == 0) continue; @@ -86,7 +91,7 @@ public class TectonicPlate { try { Iris.addPanic("read-chunk", "Chunk[" + i + "]"); - chunks.set(i, new MantleChunk(sectionHeight, din)); + chunks.set(i, new MantleChunk(v, sectionHeight, din)); EnginePanic.saveLast(); } catch (Throwable e) { long end = start + size; @@ -103,7 +108,7 @@ public class TectonicPlate { } } - public static TectonicPlate read(int worldHeight, File file) throws IOException { + public static TectonicPlate read(int worldHeight, File file, boolean versioned) throws IOException { try (FileChannel fc = FileChannel.open(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.SYNC)) { fc.lock(); @@ -111,10 +116,10 @@ public class TectonicPlate { LZ4BlockInputStream lz4 = new LZ4BlockInputStream(fin); BufferedInputStream bis = new BufferedInputStream(lz4); try (CountingDataInputStream din = CountingDataInputStream.wrap(bis)) { - return new TectonicPlate(worldHeight, din); + return new TectonicPlate(worldHeight, din, versioned); } } finally { - if (errors.remove(Thread.currentThread())) { + if (IrisSettings.get().getGeneral().isDumpMantleOnError() && errors.get()) { File dump = Iris.instance.getDataFolder("dump", file.getName() + ".bin"); try (FileChannel fc = FileChannel.open(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.SYNC)) { fc.lock(); @@ -124,6 +129,7 @@ public class TectonicPlate { Files.copy(lz4, dump.toPath(), StandardCopyOption.REPLACE_EXISTING); } } + errors.remove(); } } @@ -136,6 +142,15 @@ public class TectonicPlate { return false; } + public void close() throws InterruptedException { + for (int i = 0; i < chunks.length(); i++) { + MantleChunk chunk = chunks.get(i); + if (chunk != null) { + chunk.close(); + } + } + } + /** * Check if a chunk exists in this plate or not (same as get(x, z) != null) * @@ -208,15 +223,8 @@ public class TectonicPlate { */ public void write(File file) throws IOException { PrecisionStopwatch p = PrecisionStopwatch.start(); - try (FileChannel fc = FileChannel.open(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.SYNC)) { - fc.lock(); - - OutputStream fos = Channels.newOutputStream(fc); - try (DataOutputStream dos = new DataOutputStream(new LZ4BlockOutputStream(fos))) { - write(dos); - Iris.debug("Saved Tectonic Plate " + C.DARK_GREEN + file.getName().split("\\Q.\\E")[0] + C.RED + " in " + Form.duration(p.getMilliseconds(), 2)); - } - } + IO.write(file, out -> new DataOutputStream(new LZ4BlockOutputStream(out)), this::write); + Iris.debug("Saved Tectonic Plate " + C.DARK_GREEN + file.getName() + C.RED + " in " + Form.duration(p.getMilliseconds(), 2)); } /** @@ -228,6 +236,7 @@ public class TectonicPlate { public void write(DataOutputStream dos) throws IOException { dos.writeInt(x); dos.writeInt(z); + Varint.writeUnsignedVarInt(CURRENT, dos); var bytes = new ByteArrayOutputStream(8192); var sub = new DataOutputStream(bytes); @@ -249,6 +258,6 @@ public class TectonicPlate { } public static void addError() { - errors.add(Thread.currentThread()); + errors.set(true); } } diff --git a/core/src/main/java/com/volmit/iris/util/matter/Matter.java b/core/src/main/java/com/volmit/iris/util/matter/Matter.java index c04fe2485..086b397bd 100644 --- a/core/src/main/java/com/volmit/iris/util/matter/Matter.java +++ b/core/src/main/java/com/volmit/iris/util/matter/Matter.java @@ -154,15 +154,16 @@ public interface Matter { matter.putSlice(type, slice); } catch (Throwable e) { long end = start + size; - Iris.error("Failed to read matter slice, skipping it."); - Iris.addPanic("read.byte.range", start + " " + end); - Iris.addPanic("read.byte.current", din.count() + ""); - Iris.reportError(e); - e.printStackTrace(); - Iris.panic(); - + if (!(e instanceof ClassNotFoundException)) { + Iris.error("Failed to read matter slice, skipping it."); + Iris.addPanic("read.byte.range", start + " " + end); + Iris.addPanic("read.byte.current", din.count() + ""); + Iris.reportError(e); + e.printStackTrace(); + Iris.panic(); + TectonicPlate.addError(); + } din.skipTo(end); - TectonicPlate.addError(); } } diff --git a/core/src/main/java/com/volmit/iris/util/matter/slices/BlockMatter.java b/core/src/main/java/com/volmit/iris/util/matter/slices/BlockMatter.java index 5322ca5de..15afb4376 100644 --- a/core/src/main/java/com/volmit/iris/util/matter/slices/BlockMatter.java +++ b/core/src/main/java/com/volmit/iris/util/matter/slices/BlockMatter.java @@ -18,10 +18,10 @@ package com.volmit.iris.util.matter.slices; +import com.volmit.iris.util.data.B; import com.volmit.iris.util.data.IrisCustomData; import com.volmit.iris.util.data.palette.Palette; import com.volmit.iris.util.matter.Sliced; -import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.data.BlockData; @@ -63,6 +63,6 @@ public class BlockMatter extends RawMatter { @Override public BlockData readNode(DataInputStream din) throws IOException { - return Bukkit.createBlockData(din.readUTF()); + return B.get(din.readUTF()); } } diff --git a/core/src/main/java/com/volmit/iris/util/misc/Bindings.java b/core/src/main/java/com/volmit/iris/util/misc/Bindings.java new file mode 100644 index 000000000..308d3d235 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/misc/Bindings.java @@ -0,0 +1,146 @@ +package com.volmit.iris.util.misc; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.safeguard.IrisSafeguard; +import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.engine.platform.PlatformChunkGenerator; +import com.volmit.iris.util.context.IrisContext; +import com.volmit.iris.util.json.JSONException; +import com.volmit.iris.util.reflect.ShadeFix; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.sentry.Attachments; +import com.volmit.iris.util.sentry.IrisLogger; +import com.volmit.iris.util.sentry.ServerID; +import io.sentry.Sentry; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import net.kyori.adventure.text.serializer.ComponentSerializer; +import org.bstats.bukkit.Metrics; +import org.bstats.charts.DrilldownPie; +import org.bstats.charts.SimplePie; +import org.bstats.charts.SingleLineChart; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import oshi.SystemInfo; + +import java.math.RoundingMode; +import java.text.NumberFormat; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +public class Bindings { + + public static void capture(Throwable throwable) { + Sentry.captureException(throwable); + } + + public static void setupSentry() { + var settings = IrisSettings.get().getSentry(); + 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 -> { + options.setDsn("https://b16ecc222e9c1e0c48faecacb906fd89@o4509451052646400.ingest.de.sentry.io/4509452722765904"); + if (settings.debug) { + options.setLogger(new IrisLogger()); + options.setDebug(true); + } + + options.setAttachServerName(false); + options.setEnableUncaughtExceptionHandler(false); + options.setRelease(Iris.instance.getDescription().getVersion()); + options.setBeforeSend((event, hint) -> { + if (suppress(event.getThrowable())) return null; + event.setTag("iris.safeguard", IrisSafeguard.mode()); + 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", IrisSafeguard.asContext()); + return event; + }); + }); + Sentry.configureScope(scope -> { + if (settings.includeServerId) scope.setUser(ServerID.asUser()); + scope.addAttachment(Attachments.PLUGINS); + scope.setTag("server", Bukkit.getVersion()); + scope.setTag("server.type", Bukkit.getName()); + scope.setTag("server.api", Bukkit.getBukkitVersion()); + }); + Runtime.getRuntime().addShutdownHook(new Thread(Sentry::close)); + } + + private static boolean suppress(Throwable e) { + return (e instanceof IllegalStateException ex && "zip file closed".equals(ex.getMessage())) || e instanceof JSONException; + } + + + public static void setupBstats(Iris plugin) { + J.s(() -> { + var metrics = new Metrics(plugin, 24220); + metrics.addCustomChart(new SingleLineChart("custom_dimensions", () -> Bukkit.getWorlds() + .stream() + .filter(IrisToolbelt::isIrisWorld) + .mapToInt(w -> 1) + .sum())); + + metrics.addCustomChart(new DrilldownPie("used_packs", () -> Bukkit.getWorlds().stream() + .map(IrisToolbelt::access) + .filter(Objects::nonNull) + .map(PlatformChunkGenerator::getEngine) + .collect(Collectors.toMap(engine -> engine.getDimension().getLoadKey(), engine -> { + var hash32 = engine.getHash32().getNow(null); + if (hash32 == null) return Map.of(); + int version = engine.getDimension().getVersion(); + String checksum = Long.toHexString(hash32); + + return Map.of("v" + version + " (" + checksum + ")", 1); + }, (a, b) -> { + Map merged = new HashMap<>(a); + b.forEach((k, v) -> merged.merge(k, v, Integer::sum)); + return merged; + })))); + + + var info = new SystemInfo().getHardware(); + var cpu = info.getProcessor().getProcessorIdentifier(); + var mem = info.getMemory(); + metrics.addCustomChart(new SimplePie("cpu_model", cpu::getName)); + + var nf = NumberFormat.getInstance(Locale.ENGLISH); + nf.setMinimumFractionDigits(0); + nf.setMaximumFractionDigits(2); + nf.setRoundingMode(RoundingMode.HALF_UP); + + metrics.addCustomChart(new DrilldownPie("memory", () -> { + double total = mem.getTotal() * 1E-9; + double alloc = Math.min(total, Runtime.getRuntime().maxMemory() * 1E-9); + return Map.of(nf.format(alloc), Map.of(nf.format(total), 1)); + })); + + plugin.postShutdown(metrics::shutdown); + }); + } + + public static class Adventure { + private final BukkitAudiences audiences; + + public Adventure(Iris plugin) { + ShadeFix.fix(ComponentSerializer.class); + this.audiences = BukkitAudiences.create(plugin); + } + + public Audience player(Player player) { + return audiences.player(player); + } + + public Audience sender(CommandSender sender) { + return audiences.sender(sender); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/util/misc/Hastebin.java b/core/src/main/java/com/volmit/iris/util/misc/Hastebin.java deleted file mode 100644 index 11e253b31..000000000 --- a/core/src/main/java/com/volmit/iris/util/misc/Hastebin.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.volmit.iris.util.misc; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.TextComponent; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.command.CommandSender; -import oshi.SystemInfo; - -import java.io.BufferedReader; -import java.io.DataOutputStream; -import java.io.File; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.file.Files; -import java.util.List; - -public class Hastebin { - - public static void enviornment(CommandSender sender) { - // Construct the server information - StringBuilder sb = new StringBuilder(); - SystemInfo systemInfo = new SystemInfo(); - KList disks = new KList<>(getHardware.getDisk()); - KList interfaces = new KList<>(getHardware.getInterfaces()); - KList displays = new KList<>(getHardware.getEDID()); - KList sensors = new KList<>(getHardware.getSensors()); - KList gpus = new KList<>(getHardware.getGraphicsCards()); - KList powersources = new KList<>(getHardware.getPowerSources()); - - KList IrisWorlds = new KList<>(); - KList BukkitWorlds = new KList<>(); - - for (World w : Bukkit.getServer().getWorlds()) { - try { - Engine engine = IrisToolbelt.access(w).getEngine(); - if (engine != null) { - IrisWorlds.add(w); - } - } catch (Exception e) { - BukkitWorlds.add(w); - } - } - - sb.append(" -- == Iris Info == -- \n"); - sb.append("Iris Version Version: ").append(Iris.instance.getDescription().getVersion()).append("\n"); - sb.append("- Iris Worlds"); - for (World w : IrisWorlds.copy()) { - sb.append(" - ").append(w.getName()); - } - sb.append("- Bukkit Worlds"); - for (World w : BukkitWorlds.copy()) { - sb.append(" - ").append(w.getName()); - } - sb.append(" -- == Platform Overview == -- " + "\n"); - sb.append("Server Type: ").append(Bukkit.getVersion()).append("\n"); - sb.append("Server Uptime: ").append(Form.stampTime(systemInfo.getOperatingSystem().getSystemUptime())).append("\n"); - sb.append("Version: ").append(Platform.getVersion()).append(" - Platform: ").append(Platform.getName()).append("\n"); - sb.append("Java Vendor: ").append(Platform.ENVIRONMENT.getJavaVendor()).append(" - Java Version: ").append(Platform.ENVIRONMENT.getJavaVersion()).append("\n"); - sb.append(" -- == Processor Overview == -- " + "\n"); - sb.append("CPU Model: ").append(getHardware.getCPUModel()); - sb.append("CPU Architecture: ").append(Platform.CPU.getArchitecture()).append(" Available Processors: ").append(Platform.CPU.getAvailableProcessors()).append("\n"); - sb.append("CPU Load: ").append(Form.pc(Platform.CPU.getCPULoad())).append(" CPU Live Process Load: ").append(Form.pc(Platform.CPU.getLiveProcessCPULoad())).append("\n"); - sb.append("-=" + " Graphics " + "=- " + "\n"); - for (String gpu : gpus) { - sb.append(" ").append(gpu).append("\n"); - } - sb.append(" -- == Memory Information == -- " + "\n"); - sb.append("Physical Memory - Total: ").append(Form.memSize(Platform.MEMORY.PHYSICAL.getTotalMemory())).append(" Free: ").append(Form.memSize(Platform.MEMORY.PHYSICAL.getFreeMemory())).append(" Used: ").append(Form.memSize(Platform.MEMORY.PHYSICAL.getUsedMemory())).append("\n"); - sb.append("Virtual Memory - Total: ").append(Form.memSize(Platform.MEMORY.VIRTUAL.getTotalMemory())).append(" Free: ").append(Form.memSize(Platform.MEMORY.VIRTUAL.getFreeMemory())).append(" Used: ").append(Form.memSize(Platform.MEMORY.VIRTUAL.getUsedMemory())).append("\n"); - sb.append(" -- == Storage Information == -- " + "\n"); - for (String disk : disks) { - sb.append(" ").append(sb.append(disk)).append("\n"); - } - sb.append(" -- == Interface Information == -- "+ "\n" ); - for (String inter : interfaces) { - sb.append(" ").append(inter).append("\n"); - } - sb.append(" -- == Display Information == -- "+ "\n" ); - for (String display : displays) { - sb.append(display).append("\n"); - } - sb.append(" -- == Sensor Information == -- " + "\n"); - for (String sensor : sensors) { - sb.append(" ").append(sensor).append("\n"); - } - sb.append(" -- == Power Information == -- " + "\n"); - for (String power : powersources) { - sb.append(" ").append(power).append("\n"); - } - - try { - String hastebinUrl = uploadToHastebin(sb.toString()); - - // Create the clickable message - TextComponent message = new TextComponent("[Link]"); - TextComponent link = new TextComponent(hastebinUrl); - link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, hastebinUrl)); - message.addExtra(link); - - // Send the clickable message to the player - sender.spigot().sendMessage(message); - } catch (Exception e) { - sender.sendMessage(C.DARK_RED + "Failed to upload server information to Hastebin."); - } - } - - private static String uploadToHastebin(String content) throws Exception { - URL url = new URL("https://paste.bytecode.ninja/documents"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Content-Type", "text/plain"); - conn.setDoOutput(true); - - DataOutputStream wr = new DataOutputStream(conn.getOutputStream()); - wr.writeBytes(content); - wr.flush(); - wr.close(); - - BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); - String response = br.readLine(); - br.close(); - - return "https://paste.bytecode.ninja/" + response.split("\"")[3]; - } - - -} \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/util/misc/Platform.java b/core/src/main/java/com/volmit/iris/util/misc/Platform.java deleted file mode 100644 index afbf7c8c3..000000000 --- a/core/src/main/java/com/volmit/iris/util/misc/Platform.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.volmit.iris.util.misc; - -import com.sun.management.OperatingSystemMXBean; - -import java.io.File; -import java.lang.management.ManagementFactory; - -@SuppressWarnings("restriction") -public class Platform { - public static String getVersion() { - return getSystem().getVersion(); - } - - public static String getName() { - return getSystem().getName(); - } - - private static OperatingSystemMXBean getSystem() { - return (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); - } - - public static class ENVIRONMENT { - public static boolean canRunBatch() { - return getSystem().getName().toLowerCase().contains("windows"); - } - - public static String getJavaHome() { - return System.getProperty("java.home"); - } - - public static String getJavaVendor() { - return System.getProperty("java.vendor"); - } - - public static String getJavaVersion() { - return System.getProperty("java.version"); - } - } - - public static class STORAGE { - public static long getAbsoluteTotalSpace() { - long t = 0; - - for (File i : getRoots()) { - t += getTotalSpace(i); - } - - return t; - } - - public static long getTotalSpace() { - return getTotalSpace(new File(".")); - } - - public static long getTotalSpace(File root) { - return root.getTotalSpace(); - } - - public static long getAbsoluteFreeSpace() { - long t = 0; - - for (File i : getRoots()) { - t += getFreeSpace(i); - } - - return t; - } - - public static long getFreeSpace() { - return getFreeSpace(new File(".")); - } - - public static long getFreeSpace(File root) { - return root.getFreeSpace(); - } - - public static long getUsedSpace() { - return getTotalSpace() - getFreeSpace(); - } - - public static long getUsedSpace(File root) { - return getTotalSpace(root) - getFreeSpace(root); - } - - public static long getAbsoluteUsedSpace() { - return getAbsoluteTotalSpace() - getAbsoluteFreeSpace(); - } - - public static File[] getRoots() { - return File.listRoots(); - } - } - - public static class MEMORY { - public static class PHYSICAL { - public static long getTotalMemory() { - return getSystem().getTotalPhysicalMemorySize(); - } - - public static long getFreeMemory() { - return getSystem().getFreePhysicalMemorySize(); - } - - public static long getUsedMemory() { - return getTotalMemory() - getFreeMemory(); - } - } - - public static class VIRTUAL { - public static long getTotalMemory() { - return getSystem().getTotalSwapSpaceSize(); - } - - public static long getFreeMemory() { - return getSystem().getFreeSwapSpaceSize(); - } - - public static long getUsedMemory() { - return getTotalMemory() - getFreeMemory(); - } - - public static long getCommittedVirtualMemory() { - return getSystem().getCommittedVirtualMemorySize(); - } - } - } - - public static class CPU { - public static int getAvailableProcessors() { - return getSystem().getAvailableProcessors(); - } - - public static double getCPULoad() { - return getSystem().getSystemCpuLoad(); - } - - public static double getLiveProcessCPULoad() { - return getSystem().getProcessCpuLoad(); - } - - public static String getArchitecture() { - return getSystem().getArch(); - } - } -} \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/util/misc/SlimJar.java b/core/src/main/java/com/volmit/iris/util/misc/SlimJar.java new file mode 100644 index 000000000..fa79b849d --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/misc/SlimJar.java @@ -0,0 +1,58 @@ +package com.volmit.iris.util.misc; + +import io.github.slimjar.app.builder.ApplicationBuilder; +import io.github.slimjar.logging.ProcessLogger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class SlimJar { + private static final Logger LOGGER = Logger.getLogger("Iris"); + private static final ReentrantLock lock = new ReentrantLock(); + private static final AtomicBoolean loaded = new AtomicBoolean(); + + public static void debug(boolean debug) { + LOGGER.setLevel(debug ? Level.FINE : Level.INFO); + } + + public static void load(@Nullable File localRepository) { + if (loaded.get()) return; + lock.lock(); + + try { + if (loaded.getAndSet(true)) return; + if (localRepository == null) { + localRepository = new File(".iris/libraries"); + } + + LOGGER.info("Loading libraries..."); + ApplicationBuilder.appending("Iris") + .downloadDirectoryPath(localRepository.toPath()) + .logger(new ProcessLogger() { + @Override + public void info(@NotNull String message, @Nullable Object... args) { + LOGGER.fine(message.formatted(args)); + } + + @Override + public void error(@NotNull String message, @Nullable Object... args) { + LOGGER.severe(message.formatted(args)); + } + + @Override + public void debug(@NotNull String message, @Nullable Object... args) { + LOGGER.fine(message.formatted(args)); + } + }) + .build(); + LOGGER.info("Libraries loaded successfully!"); + } finally { + lock.unlock(); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/util/parallel/HyperLock.java b/core/src/main/java/com/volmit/iris/util/parallel/HyperLock.java index c40294552..6d0eaaee0 100644 --- a/core/src/main/java/com/volmit/iris/util/parallel/HyperLock.java +++ b/core/src/main/java/com/volmit/iris/util/parallel/HyperLock.java @@ -143,5 +143,6 @@ public class HyperLock { public void disable() { enabled = false; + locks.values().forEach(ReentrantLock::lock); } } diff --git a/core/src/main/java/com/volmit/iris/util/parallel/MultiBurst.java b/core/src/main/java/com/volmit/iris/util/parallel/MultiBurst.java index cf840572e..500a4a0b2 100644 --- a/core/src/main/java/com/volmit/iris/util/parallel/MultiBurst.java +++ b/core/src/main/java/com/volmit/iris/util/parallel/MultiBurst.java @@ -24,12 +24,15 @@ import com.volmit.iris.core.service.PreservationSVC; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.math.M; import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; -public class MultiBurst { +public class MultiBurst implements ExecutorService { + private static final long TIMEOUT = Long.getLong("iris.burst.timeout", 15000); public static final MultiBurst burst = new MultiBurst(); private final AtomicLong last; private final String name; @@ -144,29 +147,106 @@ public class MultiBurst { return getService().submit(o); } + @Override + public void shutdown() { + close(); + } + + @NotNull + @Override + public List shutdownNow() { + close(); + return List.of(); + } + + @Override + public boolean isShutdown() { + return service == null || service.isShutdown(); + } + + @Override + public boolean isTerminated() { + return service == null || service.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException { + return service == null || service.awaitTermination(timeout, unit); + } + + @Override + public void execute(@NotNull Runnable command) { + getService().execute(command); + } + + @NotNull + @Override + public Future submit(@NotNull Callable task) { + return getService().submit(task); + } + + @NotNull + @Override + public Future submit(@NotNull Runnable task, T result) { + return getService().submit(task, result); + } + + @NotNull + @Override + public Future submit(@NotNull Runnable task) { + return getService().submit(task); + } + + @NotNull + @Override + public List> invokeAll(@NotNull Collection> tasks) throws InterruptedException { + return getService().invokeAll(tasks); + } + + @NotNull + @Override + public List> invokeAll(@NotNull Collection> tasks, long timeout, @NotNull TimeUnit unit) throws InterruptedException { + return getService().invokeAll(tasks, timeout, unit); + } + + @NotNull + @Override + public T invokeAny(@NotNull Collection> tasks) throws InterruptedException, ExecutionException { + return getService().invokeAny(tasks); + } + + @Override + public T invokeAny(@NotNull Collection> tasks, long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return getService().invokeAny(tasks, timeout, unit); + } + public void close() { if (service != null) { - service.shutdown(); - PrecisionStopwatch p = PrecisionStopwatch.start(); - try { - while (!service.awaitTermination(1, TimeUnit.SECONDS)) { - Iris.info("Still waiting to shutdown burster..."); - if (p.getMilliseconds() > 7000) { - Iris.warn("Forcing Shutdown..."); + close(service); + } + } - try { - service.shutdownNow(); - } catch (Throwable e) { + public static void close(ExecutorService service) { + service.shutdown(); + PrecisionStopwatch p = PrecisionStopwatch.start(); + try { + while (!service.awaitTermination(1, TimeUnit.SECONDS)) { + Iris.info("Still waiting to shutdown burster..."); + if (p.getMilliseconds() > TIMEOUT) { + Iris.warn("Forcing Shutdown..."); - } + try { + service.shutdownNow(); + } catch (Throwable e) { - break; } + + break; } - } catch (Throwable e) { - e.printStackTrace(); - Iris.reportError(e); } + } catch (Throwable e) { + e.printStackTrace(); + Iris.reportError(e); } } } diff --git a/core/src/main/java/com/volmit/iris/util/parallel/SyncExecutor.java b/core/src/main/java/com/volmit/iris/util/parallel/SyncExecutor.java new file mode 100644 index 000000000..00966b226 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/parallel/SyncExecutor.java @@ -0,0 +1,46 @@ +package com.volmit.iris.util.parallel; + +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.scheduling.SR; +import org.jetbrains.annotations.NotNull; + +import java.util.Queue; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; + +public class SyncExecutor implements Executor, AutoCloseable { + private final CountDownLatch latch = new CountDownLatch(1); + private final Queue queue = new ConcurrentLinkedQueue<>(); + private final AtomicBoolean closed = new AtomicBoolean(false); + + public SyncExecutor(int msPerTick) { + new SR() { + @Override + public void run() { + var time = M.ms() + msPerTick; + while (time > M.ms()) { + Runnable r = queue.poll(); + if (r == null) break; + r.run(); + } + + if (closed.get() && queue.isEmpty()) { + cancel(); + latch.countDown(); + } + } + }; + } + + @Override + public void execute(@NotNull Runnable command) { + if (closed.get()) throw new IllegalStateException("Executor is closed!"); + queue.add(command); + } + + @Override + public void close() throws Exception { + closed.set(true); + latch.await(); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/particle/FastParticle.java b/core/src/main/java/com/volmit/iris/util/particle/FastParticle.java deleted file mode 100644 index 4052dbdd2..000000000 --- a/core/src/main/java/com/volmit/iris/util/particle/FastParticle.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.util.particle; - -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Player; - -/** - * Simple Bukkit Particles API with 1.7 to 1.13.2 support ! - *

- * You can find the project on GitHub - * - * @author MrMicky - */ -public final class FastParticle { - - private static final ParticleSender PARTICLE_SENDER; - - static { - if (FastReflection.optionalClass("org.bukkit.Particle$DustOptions").isPresent()) { - PARTICLE_SENDER = new ParticleSender.ParticleSender1_13(); - } else if (FastReflection.optionalClass("org.bukkit.Particle").isPresent()) { - PARTICLE_SENDER = new ParticleSender.ParticleSenderImpl(); - } else { - PARTICLE_SENDER = new ParticleSenderLegacy(); - } - } - - private FastParticle() { - throw new UnsupportedOperationException(); - } - - /* - * Worlds methods - */ - public static void spawnParticle(World world, ParticleType particle, Location location, int count) { - spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count); - } - - public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count) { - spawnParticle(world, particle, x, y, z, count, null); - } - - public static void spawnParticle(World world, ParticleType particle, Location location, int count, T data) { - spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, data); - } - - public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, - T data) { - spawnParticle(world, particle, x, y, z, count, 0.0D, 0.0D, 0.0D, data); - } - - public static void spawnParticle(World world, ParticleType particle, Location location, int count, double offsetX, - double offsetY, double offsetZ) { - spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ); - } - - public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ) { - spawnParticle(world, particle, x, y, z, count, offsetX, offsetY, offsetZ, null); - } - - public static void spawnParticle(World world, ParticleType particle, Location location, int count, - double offsetX, double offsetY, double offsetZ, T data) { - spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, - offsetZ, data); - } - - public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, T data) { - spawnParticle(world, particle, x, y, z, count, offsetX, offsetY, offsetZ, 1.0D, data); - } - - public static void spawnParticle(World world, ParticleType particle, Location location, int count, double offsetX, - double offsetY, double offsetZ, double extra) { - spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra); - } - - public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, double extra) { - spawnParticle(world, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, null); - } - - public static void spawnParticle(World world, ParticleType particle, Location location, int count, - double offsetX, double offsetY, double offsetZ, double extra, T data) { - spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra, data); - } - - public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, double extra, T data) { - sendParticle(world, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); - } - - /* - * Player methods - */ - public static void spawnParticle(Player player, ParticleType particle, Location location, int count) { - spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count); - } - - public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count) { - spawnParticle(player, particle, x, y, z, count, null); - } - - public static void spawnParticle(Player player, ParticleType particle, Location location, int count, T data) { - spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, data); - } - - public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, - T data) { - spawnParticle(player, particle, x, y, z, count, 0.0D, 0.0D, 0.0D, data); - } - - public static void spawnParticle(Player player, ParticleType particle, Location location, int count, double offsetX, - double offsetY, double offsetZ) { - spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ); - } - - public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ) { - spawnParticle(player, particle, x, y, z, count, offsetX, offsetY, offsetZ, null); - } - - public static void spawnParticle(Player player, ParticleType particle, Location location, int count, - double offsetX, double offsetY, double offsetZ, T data) { - spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, data); - } - - public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, T data) { - spawnParticle(player, particle, x, y, z, count, offsetX, offsetY, offsetZ, 1.0D, data); - } - - public static void spawnParticle(Player player, ParticleType particle, Location location, int count, double offsetX, - double offsetY, double offsetZ, double extra) { - spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra); - } - - public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, double extra) { - spawnParticle(player, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, null); - } - - public static void spawnParticle(Player player, ParticleType particle, Location location, int count, - double offsetX, double offsetY, double offsetZ, double extra, T data) { - spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra, data); - } - - public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, double extra, T data) { - sendParticle(player, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); - } - - private static void sendParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, double extra, Object data) { - if (!particle.isSupported()) { - throw new IllegalArgumentException("The particle '" + particle + "' is not compatible with your server version"); - } - - PARTICLE_SENDER.spawnParticle(receiver, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); - } -} diff --git a/core/src/main/java/com/volmit/iris/util/particle/FastReflection.java b/core/src/main/java/com/volmit/iris/util/particle/FastReflection.java deleted file mode 100644 index ee5994fb6..000000000 --- a/core/src/main/java/com/volmit/iris/util/particle/FastReflection.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.util.particle; - -import com.volmit.iris.Iris; -import org.bukkit.Bukkit; - -import java.util.Optional; - -/** - * Small reflection class to use CraftBukkit and NMS - * - * @author MrMicky - */ -public final class FastReflection { - - public static final String OBC_PACKAGE = "org.bukkit.craftbukkit"; - public static final String NMS_PACKAGE = "net.minecraft.server"; - - public static final String VERSION = Bukkit.getServer().getClass().getPackage().getName().substring(OBC_PACKAGE.length() + 1); - - private FastReflection() { - throw new UnsupportedOperationException(); - } - - public static String nmsClassName(String className) { - return NMS_PACKAGE + '.' + VERSION + '.' + className; - } - - public static Class nmsClass(String className) throws ClassNotFoundException { - return Class.forName(nmsClassName(className)); - } - - public static Optional> nmsOptionalClass(String className) { - return optionalClass(nmsClassName(className)); - } - - public static String obcClassName(String className) { - return OBC_PACKAGE + '.' + VERSION + '.' + className; - } - - public static Class obcClass(String className) throws ClassNotFoundException { - return Class.forName(obcClassName(className)); - } - - public static Optional> obcOptionalClass(String className) { - return optionalClass(obcClassName(className)); - } - - public static Optional> optionalClass(String className) { - try { - return Optional.of(Class.forName(className)); - } catch (ClassNotFoundException e) { - Iris.reportError(e); - return Optional.empty(); - } - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - public static Object enumValueOf(Class enumClass, String enumName) { - return Enum.valueOf((Class) enumClass, enumName.toUpperCase()); - } -} \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/util/particle/ParticleSender.java b/core/src/main/java/com/volmit/iris/util/particle/ParticleSender.java deleted file mode 100644 index d9ac0a401..000000000 --- a/core/src/main/java/com/volmit/iris/util/particle/ParticleSender.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.util.particle; - -import com.volmit.iris.Iris; -import org.bukkit.Bukkit; -import org.bukkit.Color; -import org.bukkit.Particle; -import org.bukkit.World; -import org.bukkit.block.data.BlockData; -import org.bukkit.entity.Player; -import org.bukkit.material.MaterialData; - -/** - * Particle sender using the Bukkit particles API for 1.9+ servers - * - * @author MrMicky - */ -@SuppressWarnings("deprecation") -interface ParticleSender { - - void spawnParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, Object data); - - Object getParticle(ParticleType particle); - - boolean isValidData(Object particle, Object data); - - default double color(double color) { - return color / 255.0; - } - - class ParticleSenderImpl implements ParticleSender { - - @Override - public void spawnParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, Object data) { - Particle bukkitParticle = Particle.valueOf(particle.toString()); - - if (data instanceof Color) { - if (particle.getDataType() == Color.class) { - Color color = (Color) data; - count = 0; - offsetX = color(color.getRed()); - offsetY = color(color.getGreen()); - offsetZ = color(color.getBlue()); - extra = 1.0; - } - data = null; - } - - if (receiver instanceof World) { - ((World) receiver).spawnParticle(bukkitParticle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); - } else if (receiver instanceof Player) { - ((Player) receiver).spawnParticle(bukkitParticle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); - } - } - - @Override - public Particle getParticle(ParticleType particle) { - try { - return Particle.valueOf(particle.toString()); - } catch (IllegalArgumentException e) { - Iris.reportError(e); - return null; - } - } - - @Override - public boolean isValidData(Object particle, Object data) { - return isValidDataBukkit((Particle) particle, data); - } - - public boolean isValidDataBukkit(Particle particle, Object data) { - return particle.getDataType() == Void.class || particle.getDataType().isInstance(data); - } - } - - class ParticleSender1_13 extends ParticleSenderImpl { - @Override - public void spawnParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, Object data) { - Particle bukkitParticle = Particle.valueOf(particle.toString()); - - if (bukkitParticle.getDataType() == Particle.DustOptions.class) { - if (data instanceof Color) { - data = new Particle.DustOptions((Color) data, 1); - } else if (data == null) { - data = new Particle.DustOptions(Color.RED, 1); - } - } else if (bukkitParticle.getDataType() == BlockData.class && data instanceof MaterialData) { - data = Bukkit.createBlockData(((MaterialData) data).getItemType()); - } - - super.spawnParticle(receiver, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); - } - - @Override - public boolean isValidDataBukkit(Particle particle, Object data) { - if (particle.getDataType() == Particle.DustOptions.class && data instanceof Color) { - return true; - } - - if (particle.getDataType() == BlockData.class && data instanceof MaterialData) { - return true; - } - - return super.isValidDataBukkit(particle, data); - } - } -} diff --git a/core/src/main/java/com/volmit/iris/util/particle/ParticleSenderLegacy.java b/core/src/main/java/com/volmit/iris/util/particle/ParticleSenderLegacy.java deleted file mode 100644 index 1668de313..000000000 --- a/core/src/main/java/com/volmit/iris/util/particle/ParticleSenderLegacy.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.util.particle; - -import com.volmit.iris.Iris; -import org.bukkit.Color; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.material.MaterialData; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -/** - * Legacy particle sender with NMS for 1.7/1.8 servers - * - * @author MrMicky - */ -@SuppressWarnings({"deprecation", "JavaReflectionInvocation"}) -class ParticleSenderLegacy implements ParticleSender { - - private static final boolean SERVER_IS_1_8; - - private static final Constructor PACKET_PARTICLE; - private static final Class ENUM_PARTICLE; - - private static final Method WORLD_GET_HANDLE; - private static final Method WORLD_SEND_PARTICLE; - - private static final Method PLAYER_GET_HANDLE; - private static final Field PLAYER_CONNECTION; - private static final Method SEND_PACKET; - private static final int[] EMPTY = new int[0]; - - static { - ENUM_PARTICLE = FastReflection.nmsOptionalClass("EnumParticle").orElse(null); - SERVER_IS_1_8 = ENUM_PARTICLE != null; - - try { - Class packetParticleClass = FastReflection.nmsClass("PacketPlayOutWorldParticles"); - Class playerClass = FastReflection.nmsClass("EntityPlayer"); - Class playerConnectionClass = FastReflection.nmsClass("PlayerConnection"); - Class worldClass = FastReflection.nmsClass("WorldServer"); - Class entityPlayerClass = FastReflection.nmsClass("EntityPlayer"); - - Class craftPlayerClass = FastReflection.obcClass("entity.CraftPlayer"); - Class craftWorldClass = FastReflection.obcClass("CraftWorld"); - - if (SERVER_IS_1_8) { - PACKET_PARTICLE = packetParticleClass.getConstructor(ENUM_PARTICLE, boolean.class, float.class, - float.class, float.class, float.class, float.class, float.class, float.class, int.class, - int[].class); - WORLD_SEND_PARTICLE = worldClass.getDeclaredMethod("sendParticles", entityPlayerClass, ENUM_PARTICLE, - boolean.class, double.class, double.class, double.class, int.class, double.class, double.class, - double.class, double.class, int[].class); - } else { - PACKET_PARTICLE = packetParticleClass.getConstructor(String.class, float.class, float.class, float.class, - float.class, float.class, float.class, float.class, int.class); - WORLD_SEND_PARTICLE = worldClass.getDeclaredMethod("a", String.class, double.class, double.class, - double.class, int.class, double.class, double.class, double.class, double.class); - } - - WORLD_GET_HANDLE = craftWorldClass.getDeclaredMethod("getHandle"); - PLAYER_GET_HANDLE = craftPlayerClass.getDeclaredMethod("getHandle"); - PLAYER_CONNECTION = playerClass.getField("playerConnection"); - SEND_PACKET = playerConnectionClass.getMethod("sendPacket", FastReflection.nmsClass("Packet")); - } catch (ReflectiveOperationException e) { - throw new ExceptionInInitializerError(e); - } - } - - @Override - public void spawnParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, double offsetX, double offsetY, - double offsetZ, double extra, Object data) { - try { - int[] datas = toData(particle, data); - - if (data instanceof Color) { - if (particle.getDataType() == Color.class) { - Color color = (Color) data; - count = 0; - offsetX = color(color.getRed()); - offsetY = color(color.getGreen()); - offsetZ = color(color.getBlue()); - extra = 1.0; - } - } - - if (receiver instanceof World) { - Object worldServer = WORLD_GET_HANDLE.invoke(receiver); - - if (SERVER_IS_1_8) { - WORLD_SEND_PARTICLE.invoke(worldServer, null, getEnumParticle(particle), true, x, y, z, count, offsetX, offsetY, offsetZ, extra, datas); - } else { - String particleName = particle.getLegacyName() + (datas.length != 2 ? "" : "_" + datas[0] + "_" + datas[1]); - WORLD_SEND_PARTICLE.invoke(worldServer, particleName, x, y, z, count, offsetX, offsetY, offsetZ, extra); - } - } else if (receiver instanceof Player) { - Object packet; - - if (SERVER_IS_1_8) { - packet = PACKET_PARTICLE.newInstance(getEnumParticle(particle), true, (float) x, (float) y, - (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count, datas); - } else { - String particleName = particle.getLegacyName() + (datas.length != 2 ? "" : "_" + datas[0] + "_" + datas[1]); - packet = PACKET_PARTICLE.newInstance(particleName, (float) x, (float) y, (float) z, - (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); - } - - Object entityPlayer = PLAYER_GET_HANDLE.invoke(receiver); - Object playerConnection = PLAYER_CONNECTION.get(entityPlayer); - SEND_PACKET.invoke(playerConnection, packet); - } - } catch (ReflectiveOperationException e) { - Iris.reportError(e); - throw new RuntimeException(e); - } - } - - @Override - public boolean isValidData(Object particle, Object data) { - return true; - } - - @Override - public Object getParticle(ParticleType particle) { - if (!SERVER_IS_1_8) { - return particle.getLegacyName(); - } - - try { - return getEnumParticle(particle); - } catch (IllegalArgumentException e) { - Iris.reportError(e); - return null; - } - } - - private Object getEnumParticle(ParticleType particleType) { - return FastReflection.enumValueOf(ENUM_PARTICLE, particleType.toString()); - } - - private int[] toData(ParticleType particle, Object data) { - Class dataType = particle.getDataType(); - if (dataType == ItemStack.class) { - if (!(data instanceof ItemStack itemStack)) { - return SERVER_IS_1_8 ? new int[2] : new int[]{1, 0}; - } - - return new int[]{itemStack.getType().getId(), itemStack.getDurability()}; - } - - if (dataType == MaterialData.class) { - if (!(data instanceof MaterialData materialData)) { - return SERVER_IS_1_8 ? new int[1] : new int[]{1, 0}; - } - - if (SERVER_IS_1_8) { - return new int[]{materialData.getItemType().getId() + (materialData.getData() << 12)}; - } else { - return new int[]{materialData.getItemType().getId(), materialData.getData()}; - } - } - - return EMPTY; - } -} diff --git a/core/src/main/java/com/volmit/iris/util/particle/ParticleType.java b/core/src/main/java/com/volmit/iris/util/particle/ParticleType.java deleted file mode 100644 index 25da1bd75..000000000 --- a/core/src/main/java/com/volmit/iris/util/particle/ParticleType.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.util.particle; - -import com.volmit.iris.Iris; -import org.bukkit.Color; -import org.bukkit.inventory.ItemStack; -import org.bukkit.material.MaterialData; - -/** - * @author MrMicky - */ -@SuppressWarnings("deprecation") -public enum ParticleType { - - // 1.7+ - EXPLOSION_NORMAL("explode", "poof"), - EXPLOSION_LARGE("largeexplode", "explosion"), - EXPLOSION_HUGE("hugeexplosion", "explosion_emitter"), - FIREWORKS_SPARK("fireworksSpark", "firework"), - WATER_BUBBLE("bubble", "bubble"), - WATER_SPLASH("splash", "splash"), - WATER_WAKE("wake", "fishing"), - SUSPENDED("suspended", "underwater"), - SUSPENDED_DEPTH("depthsuspend", "underwater"), - CRIT("crit", "crit"), - CRIT_MAGIC("magicCrit", "enchanted_hit"), - SMOKE_NORMAL("smoke", "smoke"), - SMOKE_LARGE("largesmoke", "large_smoke"), - SPELL("spell", "effect"), - SPELL_INSTANT("instantSpell", "instant_effect"), - SPELL_MOB("mobSpell", "entity_effect"), - SPELL_MOB_AMBIENT("mobSpellAmbient", "ambient_entity_effect"), - SPELL_WITCH("witchMagic", "witch"), - DRIP_WATER("dripWater", "dripping_water"), - DRIP_LAVA("dripLava", "dripping_lava"), - VILLAGER_ANGRY("angryVillager", "angry_villager"), - VILLAGER_HAPPY("happyVillager", "happy_villager"), - TOWN_AURA("townaura", "mycelium"), - NOTE("note", "note"), - PORTAL("portal", "portal"), - ENCHANTMENT_TABLE("enchantmenttable", "enchant"), - FLAME("flame", "flame"), - LAVA("lava", "lava"), - // FOOTSTEP("footstep", null), - CLOUD("cloud", "cloud"), - REDSTONE("reddust", "dust"), - SNOWBALL("snowballpoof", "item_snowball"), - SNOW_SHOVEL("snowshovel", "item_snowball"), - SLIME("slime", "item_slime"), - HEART("heart", "heart"), - ITEM_CRACK("iconcrack", "item"), - BLOCK_CRACK("blockcrack", "block"), - BLOCK_DUST("blockdust", "block"), - - // 1.8+ - BARRIER("barrier", "barrier", 8), - WATER_DROP("droplet", "rain", 8), - MOB_APPEARANCE("mobappearance", "elder_guardian", 8), - // ITEM_TAKE("take", null, 8), - - // 1.9+ - DRAGON_BREATH("dragonbreath", "dragon_breath", 9), - END_ROD("endRod", "end_rod", 9), - DAMAGE_INDICATOR("damageIndicator", "damage_indicator", 9), - SWEEP_ATTACK("sweepAttack", "sweep_attack", 9), - - // 1.10+ - FALLING_DUST("fallingdust", "falling_dust", 10), - - // 1.11+ - TOTEM("totem", "totem_of_undying", 11), - SPIT("spit", "spit", 11), - - // 1.13+ - SQUID_INK(13), - BUBBLE_POP(13), - CURRENT_DOWN(13), - BUBBLE_COLUMN_UP(13), - NAUTILUS(13), - DOLPHIN(13), - - // 1.14+ - SNEEZE(14), - CAMPFIRE_COSY_SMOKE(14), - CAMPFIRE_SIGNAL_SMOKE(14), - COMPOSTER(14), - FLASH(14), - FALLING_LAVA(14), - LANDING_LAVA(14), - FALLING_WATER(14), - - // 1.15+ - DRIPPING_HONEY(15), - FALLING_HONEY(15), - LANDING_HONEY(15), - FALLING_NECTAR(15); - - private static final int SERVER_VERSION_ID; - - static { - String ver = FastReflection.VERSION; - SERVER_VERSION_ID = ver.charAt(4) == '_' ? Character.getNumericValue(ver.charAt(3)) : Integer.parseInt(ver.substring(3, 5)); - } - - private final String legacyName; - private final String name; - private final int minimumVersion; - - // 1.7 particles - ParticleType(String legacyName, String name) { - this(legacyName, name, -1); - } - - // 1.13+ particles - ParticleType(int minimumVersion) { - this.legacyName = null; - this.name = name().toLowerCase(); - this.minimumVersion = minimumVersion; - } - - // 1.8-1.12 particles - ParticleType(String legacyName, String name, int minimumVersion) { - this.legacyName = legacyName; - this.name = name; - this.minimumVersion = minimumVersion; - } - - public static ParticleType getParticle(String particleName) { - try { - return ParticleType.valueOf(particleName.toUpperCase()); - } catch (IllegalArgumentException e) { - Iris.reportError(e); - for (ParticleType particle : values()) { - if (particle.getName().equalsIgnoreCase(particleName)) { - return particle; - } - - if (particle.hasLegacyName() && particle.getLegacyName().equalsIgnoreCase(particleName)) { - return particle; - } - } - } - return null; - } - - public boolean hasLegacyName() { - return legacyName != null; - } - - public String getLegacyName() { - if (!hasLegacyName()) { - throw new IllegalStateException("Particle " + name() + " don't have legacy name"); - } - return legacyName; - } - - public String getName() { - return name; - } - - public boolean isSupported() { - return minimumVersion <= 0 || SERVER_VERSION_ID >= minimumVersion; - } - - public Class getDataType() { - return switch (this) { - case ITEM_CRACK -> ItemStack.class; - case BLOCK_CRACK, BLOCK_DUST, FALLING_DUST -> - //noinspection deprecation - MaterialData.class; - case REDSTONE -> Color.class; - default -> Void.class; - }; - } -} diff --git a/core/src/main/java/com/volmit/iris/util/plugin/VolmitSender.java b/core/src/main/java/com/volmit/iris/util/plugin/VolmitSender.java index 217b44054..dccb95ce7 100644 --- a/core/src/main/java/com/volmit/iris/util/plugin/VolmitSender.java +++ b/core/src/main/java/com/volmit/iris/util/plugin/VolmitSender.java @@ -264,7 +264,7 @@ public class VolmitSender implements CommandSender { private Component createNoPrefixComponent(String message) { if (!IrisSettings.get().getGeneral().canUseCustomColors(this)) { String t = C.translateAlternateColorCodes('&', MiniMessage.miniMessage().stripTags(message)); - return MiniMessage.miniMessage().deserialize(t); + return MiniMessage.miniMessage().deserialize(C.mini(t)); } String t = C.translateAlternateColorCodes('&', message); @@ -273,13 +273,13 @@ public class VolmitSender implements CommandSender { } private Component createNoPrefixComponentNoProcessing(String message) { - return MiniMessage.builder().postProcessor(c -> c).build().deserialize(message); + return MiniMessage.builder().postProcessor(c -> c).build().deserialize(C.mini(message)); } private Component createComponent(String message) { if (!IrisSettings.get().getGeneral().canUseCustomColors(this)) { String t = C.translateAlternateColorCodes('&', MiniMessage.miniMessage().stripTags(getTag() + message)); - return MiniMessage.miniMessage().deserialize(t); + return MiniMessage.miniMessage().deserialize(C.mini(t)); } String t = C.translateAlternateColorCodes('&', getTag() + message); @@ -290,11 +290,11 @@ public class VolmitSender implements CommandSender { private Component createComponentRaw(String message) { if (!IrisSettings.get().getGeneral().canUseCustomColors(this)) { String t = C.translateAlternateColorCodes('&', MiniMessage.miniMessage().stripTags(getTag() + message)); - return MiniMessage.miniMessage().deserialize(t); + return MiniMessage.miniMessage().deserialize(C.mini(t)); } String t = C.translateAlternateColorCodes('&', getTag() + message); - return MiniMessage.miniMessage().deserialize(t); + return MiniMessage.miniMessage().deserialize(C.mini(t)); } public void showWaiting(String passive, CompletableFuture f) { diff --git a/core/src/main/java/com/volmit/iris/util/scheduling/J.java b/core/src/main/java/com/volmit/iris/util/scheduling/J.java index a669317df..550b94092 100644 --- a/core/src/main/java/com/volmit/iris/util/scheduling/J.java +++ b/core/src/main/java/com/volmit/iris/util/scheduling/J.java @@ -240,6 +240,21 @@ public class J { return f; } + public static CompletableFuture sfut(Supplier r) { + CompletableFuture f = new CompletableFuture<>(); + if (!Bukkit.getPluginManager().isPluginEnabled(Iris.instance)) { + return null; + } + Bukkit.getScheduler().scheduleSyncDelayedTask(Iris.instance, () -> { + try { + f.complete(r.get()); + } catch (Throwable e) { + f.completeExceptionally(e); + } + }); + return f; + } + public static CompletableFuture sfut(Runnable r, int delay) { CompletableFuture f = new CompletableFuture(); diff --git a/core/src/main/java/com/volmit/iris/util/scheduling/Looper.java b/core/src/main/java/com/volmit/iris/util/scheduling/Looper.java index e5be20b84..72ba9ff03 100644 --- a/core/src/main/java/com/volmit/iris/util/scheduling/Looper.java +++ b/core/src/main/java/com/volmit/iris/util/scheduling/Looper.java @@ -36,7 +36,6 @@ public abstract class Looper extends Thread { //noinspection BusyWait Thread.sleep(m); } catch (InterruptedException e) { - Iris.reportError(e); break; } catch (Throwable e) { Iris.reportError(e); diff --git a/core/src/main/java/com/volmit/iris/util/sentry/Attachments.java b/core/src/main/java/com/volmit/iris/util/sentry/Attachments.java new file mode 100644 index 000000000..a338ea6ce --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/sentry/Attachments.java @@ -0,0 +1,41 @@ +package com.volmit.iris.util.sentry; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.volmit.iris.util.collection.KMap; +import io.sentry.Attachment; +import org.bukkit.Bukkit; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.Callable; + +public class Attachments { + private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); + public static final Attachment PLUGINS = jsonProvider(Attachments::plugins, "plugins.json"); + + public static Attachment json(Object object, String name) { + return new Attachment(GSON.toJson(object).getBytes(StandardCharsets.UTF_8), name, "application/json", "event.attachment", true); + } + + public static Attachment jsonProvider(Callable object, String name) { + return new Attachment(() -> GSON.toJson(object.call()).getBytes(StandardCharsets.UTF_8), name, "application/json", "event.attachment", true); + } + + private static KMap plugins() { + KMap enabled = new KMap<>(); + KMap disabled = new KMap<>(); + + var pm = Bukkit.getPluginManager(); + for (var plugin : pm.getPlugins()) { + if (plugin.isEnabled()) { + enabled.put(plugin.getName(), plugin.getDescription().getVersion()); + } else { + disabled.put(plugin.getName(), plugin.getDescription().getVersion()); + } + } + + return new KMap() + .qput("enabled", enabled) + .qput("disabled", disabled); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/sentry/IrisLogger.java b/core/src/main/java/com/volmit/iris/util/sentry/IrisLogger.java new file mode 100644 index 000000000..e51d407c5 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/sentry/IrisLogger.java @@ -0,0 +1,47 @@ +package com.volmit.iris.util.sentry; + +import com.volmit.iris.Iris; +import io.sentry.ILogger; +import io.sentry.SentryLevel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class IrisLogger implements ILogger { + @Override + public void log(@NotNull SentryLevel level, @NotNull String message, @Nullable Object... args) { + Iris.msg(String.format("%s: %s", level, String.format(message, args))); + } + + @Override + public void log(@NotNull SentryLevel level, @NotNull String message, @Nullable Throwable throwable) { + if (throwable == null) { + log(level, message); + } else { + Iris.msg(String.format("%s: %s\n%s", level, String.format(message, throwable), captureStackTrace(throwable))); + } + } + + @Override + public void log(@NotNull SentryLevel level, @Nullable Throwable throwable, @NotNull String message, @Nullable Object... args) { + if (throwable == null) { + log(level, message, args); + } else { + Iris.msg(String.format("%s: %s\n%s", level, String.format(message, throwable), captureStackTrace(throwable))); + } + } + + @Override + public boolean isEnabled(@Nullable SentryLevel level) { + return true; + } + + private @NotNull String captureStackTrace(@NotNull Throwable throwable) { + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + throwable.printStackTrace(printWriter); + return stringWriter.toString(); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/sentry/ServerID.java b/core/src/main/java/com/volmit/iris/util/sentry/ServerID.java new file mode 100644 index 000000000..da766574e --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/sentry/ServerID.java @@ -0,0 +1,59 @@ +package com.volmit.iris.util.sentry; + +import com.volmit.iris.util.io.IO; +import io.sentry.protocol.User; +import lombok.SneakyThrows; +import org.bukkit.Bukkit; +import oshi.SystemInfo; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class ServerID { + public static final String ID = calculate(); + + public static User asUser() { + User u = new User(); + u.setId(ID); + return u; + } + + @SneakyThrows + private static String calculate() { + Digest md = new Digest(); + md.update(System.getProperty("java.vm.name")); + md.update(System.getProperty("java.vm.version")); + md.update(new SystemInfo().getHardware().getProcessor().toString()); + md.update(Runtime.getRuntime().maxMemory()); + for (var p : Bukkit.getPluginManager().getPlugins()) { + md.update(p.getName()); + } + + return IO.bytesToHex(md.digest()); + } + + private static final class Digest { + private final MessageDigest md = MessageDigest.getInstance("SHA-256"); + private final byte[] buffer = new byte[8]; + private final ByteBuffer wrapped = ByteBuffer.wrap(buffer); + + private Digest() throws NoSuchAlgorithmException { + } + + public void update(String string) { + if (string == null) return; + md.update(string.getBytes(StandardCharsets.UTF_8)); + } + + public void update(long Long) { + wrapped.putLong(0, Long); + md.update(buffer, 0, 8); + } + + public byte[] digest() { + return md.digest(); + } + } +} diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index a7f3ce21f..99fa326be 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -5,24 +5,7 @@ load: STARTUP authors: [ cyberpwn, NextdoorPsycho, Vatuu ] website: volmit.com description: More than a Dimension! -libraries: - - com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2 - - com.github.ben-manes.caffeine:caffeine:3.0.5 - - org.apache.commons:commons-lang3:3.12.0 - - commons-io:commons-io:2.13.0 - - io.timeandspace:smoothie-map:2.0.2 - - com.google.guava:guava:31.0.1-jre - - com.google.code.gson:gson:2.10.1 - - org.zeroturnaround:zt-zip:1.14 - - it.unimi.dsi:fastutil:8.5.6 - - org.ow2.asm:asm:9.2 - - rhino:js:1.7R2 - - bsf:bsf:2.4.0 - - org.lz4:lz4-java:1.8.0 - - com.github.oshi:oshi-core:6.6.5 - - org.apache.commons:commons-math3:3.6.1 commands: iris: aliases: [ ir, irs ] -api-version: '${apiversion}' -hotload-dependencies: false \ No newline at end of file +api-version: '${apiVersion}' \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000..434bcd870 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,104 @@ +# Version catalog is a central place for you to declare and version dependencies +# https://docs.gradle.org/current/userguide/platforms.html#sub:version-catalog +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format + +[versions] +# Plugins +shadow = "9.0.0-rc1" # https://plugins.gradle.org/plugin/com.gradleup.shadow +slimjar = "2.0.8" # https://plugins.gradle.org/plugin/de.crazydev22.slimjar +download = "5.6.0" # https://plugins.gradle.org/plugin/de.undercouch.download +runPaper = "2.3.1" # https://plugins.gradle.org/plugin/xyz.jpenilla.run-paper +sentryPlugin = "5.8.0" # https://github.com/getsentry/sentry-android-gradle-plugin + +# Core Libraries +lombok = "1.18.38" +spigot = "1.20.1-R0.1-SNAPSHOT" # https://hub.spigotmc.org/nexus/repository/snapshots/org/spigotmc/spigot-api/maven-metadata.xml +log4j = "2.19.0" # https://central.sonatype.com/artifact/org.apache.logging.log4j/log4j-api +adventure-api = "4.23.0" # https://github.com/KyoriPowered/adventure +adventure-platform = "4.4.0" # https://github.com/KyoriPowered/adventure-platform + +annotations = "26.0.2" # https://central.sonatype.com/artifact/org.jetbrains/annotations +paralithic = "0.8.1" # https://github.com/PolyhedralDev/Paralithic/ +paperlib = "1.0.8" # https://github.com/PaperMC/PaperLib/ +bstats = "3.1.0" # https://github.com/Bastian/bstats-metrics/tree/master +sentry = "8.14.0" # https://github.com/getsentry/sentry-java +commons-io = "2.19.0" # https://central.sonatype.com/artifact/commons-io/commons-io +commons-lang = "2.6" # https://central.sonatype.com/artifact/commons-lang/commons-lang +commons-lang3 = "3.17.0" # https://central.sonatype.com/artifact/org.apache.commons/commons-lang3 +commons-math3 = "3.6.1" +oshi = "6.8.2" # https://central.sonatype.com/artifact/com.github.oshi/oshi-core +fastutil = "8.5.16" # https://central.sonatype.com/artifact/it.unimi.dsi/fastutil +lz4 = "1.8.0" # https://central.sonatype.com/artifact/org.lz4/lz4-java +lru = "1.4.2" # https://central.sonatype.com/artifact/com.googlecode.concurrentlinkedhashmap/concurrentlinkedhashmap-lru +zip = "1.17" # https://central.sonatype.com/artifact/org.zeroturnaround/zt-zip +gson = "2.13.1" # https://central.sonatype.com/artifact/com.google.code.gson/gson +asm = "9.8" # https://central.sonatype.com/artifact/org.ow2.asm/asm +bsf = "2.4.0" # https://central.sonatype.com/artifact/bsf/bsf +rhino = "1.7R2" # https://central.sonatype.com/artifact/rhino/js +caffeine = "3.2.1" # https://central.sonatype.com/artifact/com.github.ben-manes.caffeine/caffeine +byte-buddy = "1.17.6" # https://central.sonatype.com/artifact/net.bytebuddy/byte-buddy + +# Third Party Integrations +nexo = "1.8.0" # https://repo.nexomc.com/#/releases/com/nexomc/nexo +itemsadder = "4.0.10" # https://github.com/LoneDev6/API-ItemsAdder +placeholderApi = "2.11.6" # https://repo.extendedclip.com/#/releases/me/clip/placeholderapi +score = "5.25.3.9" # https://github.com/Ssomar-Developement/SCore +mmoitems = "6.9.5-SNAPSHOT" # https://nexus.phoenixdevt.fr/repository/maven-public/net/Indyuce/MMOItems-API/maven-metadata.xml +ecoitems = "5.63.1" # https://github.com/Auxilor/EcoItems/tags +mythic = "5.9.5" +mythic-chrucible = "2.1.0" +kgenerators = "7.3" # https://repo.codemc.io/repository/maven-public/me/kryniowesegryderiusz/kgenerators-core/maven-metadata.xml +multiverseCore = "5.1.0" + +[libraries] +# Core Libraries +lombok = { module = "org.projectlombok:lombok", version.ref ="lombok" } +spigot = { module = "org.spigotmc:spigot-api", version.ref = "spigot" } +log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" } +log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } +annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" } + +# Dynamically Loaded +adventure-api = { module = "net.kyori:adventure-api", version.ref = "adventure-api" } +adventure-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure-api" } +adventure-platform = { module = "net.kyori:adventure-platform-bukkit", version.ref = "adventure-platform" } + +paralithic = { module = "com.dfsek:paralithic", version.ref = "paralithic" } +paperlib = { module = "io.papermc:paperlib", version.ref = "paperlib" } +bstats = { module = "org.bstats:bstats-bukkit", version.ref = "bstats" } +sentry = { module = "io.sentry:sentry", version.ref = "sentry" } +commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" } +commons-lang = { module = "commons-lang:commons-lang", version.ref = "commons-lang" } +commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commons-lang3" } +commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" } +oshi = { module = "com.github.oshi:oshi-core", version.ref = "oshi" } +lz4 = { module = "org.lz4:lz4-java", version.ref = "lz4" } +fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" } +lru = { module = "com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru", version.ref = "lru" } +zip = { module = "org.zeroturnaround:zt-zip", version.ref = "zip" } +gson = { module = "com.google.code.gson:gson", version.ref = "gson" } +asm = { module = "org.ow2.asm:asm", version.ref = "asm" } +bsf = { module = "bsf:bsf", version.ref = "bsf" } +rhino = { module = "rhino:js", version.ref = "rhino" } +caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine" } +byteBuddy-core = { module = "net.bytebuddy:byte-buddy", version.ref = "byte-buddy" } +byteBuddy-agent = { module = "net.bytebuddy:byte-buddy-agent", version.ref = "byte-buddy" } + +# Third Party Integrations +nexo = { module = "com.nexomc:nexo", version.ref = "nexo" } +itemsadder = { module = "dev.lone:api-itemsadder", version.ref = "itemsadder" } +placeholderApi = { module = "me.clip:placeholderapi", version.ref = "placeholderApi" } +score = { module = "com.github.Ssomar-Developement:SCore", version.ref = "score" } +mmoitems = { module = "net.Indyuce:MMOItems-API", version.ref = "mmoitems" } +ecoitems = { module = "com.willfp:EcoItems", version.ref = "ecoitems" } +mythic = { module = "io.lumine:Mythic-Dist", version.ref = "mythic" } +mythicChrucible = { module = "io.lumine:MythicCrucible-Dist", version.ref = "mythic-chrucible" } +kgenerators = { module = "me.kryniowesegryderiusz:kgenerators-core", version.ref = "kgenerators" } +multiverseCore = { module = "org.mvplugins.multiverse.core:multiverse-core", version.ref = "multiverseCore" } + +[plugins] +shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } +slimjar = { id = "de.crazydev22.slimjar", version.ref = "slimjar" } +download = { id = "de.undercouch.download", version.ref = "download" } +runPaper = { id = "xyz.jpenilla.run-paper", version.ref = "runPaper" } +sentry = { id = "io.sentry.jvm.gradle", version.ref = "sentryPlugin" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2..e6441136f 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 48c0a02ca..ff23a68d7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c78733..1aa94a426 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f93..7101f8e46 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/IrisChunkGenerator.java b/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/IrisChunkGenerator.java index 43debf77c..8fcd5c5b9 100644 --- a/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/IrisChunkGenerator.java +++ b/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/IrisChunkGenerator.java @@ -24,6 +24,7 @@ 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.WeightedRandomList; import net.minecraft.world.entity.MobCategory; @@ -129,6 +130,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @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; 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 9b549c77d..c2eef759a 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,45 +2,48 @@ 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.link.Identifier; 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.StructurePlacement; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +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; @@ -50,17 +53,18 @@ 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 net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; 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; @@ -68,31 +72,25 @@ 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.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; import java.util.stream.Collectors; public class NMSBinding implements INMSBinding { @@ -101,11 +99,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; @@ -651,46 +648,40 @@ public class NMSBinding implements INMSBinding { } @Override - public AutoClosing injectLevelStems() { - return inject(this::supplier); + 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 com.volmit.iris.core.nms.container.Pair injectUncached(boolean overworld, boolean nether, boolean end) { - var reg = registry(); - var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); - field.setAccessible(true); + 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()); + } - AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) - ) - ); - - var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM); - var old = (Map>, Registry>) field.get(reg); - var fake = new HashMap<>(old); - fake.put(Registries.LEVEL_STEM, injected); - field.set(reg, fake); - - return new com.volmit.iris.core.nms.container.Pair<>( - injected.size(), - new AutoClosing(() -> { - closing.close(); - field.set(reg, old); - })); - } - - @Override - public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { - var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE); - if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); - if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); - if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); - return overworld || nether || end; + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } @Override @@ -758,76 +749,55 @@ public class NMSBinding implements INMSBinding { .collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new)); } - private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { - return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - )); + 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)); } - @SneakyThrows - private AutoClosing inject(Function transformer) { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, transformer.apply(old)); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); - } - - private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { - var access = registry(); - var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE); - - var settings = new FlatLevelGeneratorSettings( - Optional.empty(), - access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), - List.of() - ); + 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())).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/IrisChunkGenerator.java b/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/IrisChunkGenerator.java index f9b5147e5..597562076 100644 --- a/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/IrisChunkGenerator.java +++ b/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/IrisChunkGenerator.java @@ -24,6 +24,7 @@ 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.WeightedRandomList; import net.minecraft.world.entity.MobCategory; @@ -129,6 +130,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @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; 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 cf12faaac..342ddf015 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,5 +1,6 @@ package com.volmit.iris.core.nms.v1_20_R2; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import java.awt.Color; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -15,33 +16,69 @@ import java.util.function.Function; import java.util.stream.Collectors; import com.mojang.datafixers.util.Pair; +import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMSBinding; import com.mojang.serialization.Lifecycle; import com.volmit.iris.core.link.Identifier; 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.mca.NBTWorld; +import com.volmit.iris.util.nbt.mca.palette.*; +import com.volmit.iris.util.nbt.tag.CompoundTag; import com.volmit.iris.core.nms.container.StructurePlacement; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; 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.levelgen.structure.placement.ConcentricRingsStructurePlacement; import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; +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,58 +88,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; @@ -630,6 +645,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()) { @@ -652,46 +675,32 @@ public class NMSBinding implements INMSBinding { } @Override - public AutoClosing injectLevelStems() { - return inject(this::supplier); - } + 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()); + } - @Override - @SneakyThrows - public com.volmit.iris.core.nms.container.Pair injectUncached(boolean overworld, boolean nether, boolean end) { - var reg = registry(); - var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); - field.setAccessible(true); - - AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) - ) - ); - - var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM); - var old = (Map>, Registry>) field.get(reg); - var fake = new HashMap<>(old); - fake.put(Registries.LEVEL_STEM, injected); - field.set(reg, fake); - - return new com.volmit.iris.core.nms.container.Pair<>( - injected.size(), - new AutoClosing(() -> { - closing.close(); - field.set(reg, old); - })); - } - - @Override - public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { - var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE); - if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); - if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); - if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); - return overworld || nether || end; + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } @Override @@ -759,76 +768,55 @@ public class NMSBinding implements INMSBinding { .collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new)); } - private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { - return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - )); + 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)); } - @SneakyThrows - private AutoClosing inject(Function transformer) { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, transformer.apply(old)); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); - } - - private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { - var access = registry(); - var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE); - - var settings = new FlatLevelGeneratorSettings( - Optional.empty(), - access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), - List.of() - ); + 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())).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/IrisChunkGenerator.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/IrisChunkGenerator.java index 00a7c774a..f5e5252fc 100644 --- a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/IrisChunkGenerator.java +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/IrisChunkGenerator.java @@ -132,6 +132,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @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; 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 2d766197f..ec83517d9 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 @@ -14,34 +14,71 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import java.util.stream.Collectors; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Pair; +import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMSBinding; import com.mojang.serialization.Lifecycle; import com.volmit.iris.core.link.Identifier; 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.mca.NBTWorld; +import com.volmit.iris.util.nbt.mca.palette.*; +import com.volmit.iris.util.nbt.tag.CompoundTag; import com.volmit.iris.core.nms.container.StructurePlacement; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; 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.levelgen.structure.placement.ConcentricRingsStructurePlacement; import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; +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,58 +88,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; @@ -653,46 +668,40 @@ public class NMSBinding implements INMSBinding { } @Override - public AutoClosing injectLevelStems() { - return inject(this::supplier); + 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 com.volmit.iris.core.nms.container.Pair injectUncached(boolean overworld, boolean nether, boolean end) { - var reg = registry(); - var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); - field.setAccessible(true); + 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()); + } - AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) - ) - ); - - var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM); - var old = (Map>, Registry>) field.get(reg); - var fake = new HashMap<>(old); - fake.put(Registries.LEVEL_STEM, injected); - field.set(reg, fake); - - return new com.volmit.iris.core.nms.container.Pair<>( - injected.size(), - new AutoClosing(() -> { - closing.close(); - field.set(reg, old); - })); - } - - @Override - public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { - var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE); - if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); - if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); - if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); - return overworld || nether || end; + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } @Override @@ -760,76 +769,55 @@ public class NMSBinding implements INMSBinding { .collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new)); } - private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { - return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - )); + 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)); } - @SneakyThrows - private AutoClosing inject(Function transformer) { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, transformer.apply(old)); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); - } - - private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { - var access = registry(); - var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE); - - var settings = new FlatLevelGeneratorSettings( - Optional.empty(), - access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), - List.of() - ); + 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())).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/IrisChunkGenerator.java b/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/IrisChunkGenerator.java index 250b810d2..5e424617c 100644 --- a/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/IrisChunkGenerator.java +++ b/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/IrisChunkGenerator.java @@ -24,6 +24,7 @@ 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.WeightedRandomList; import net.minecraft.world.entity.MobCategory; @@ -127,6 +128,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @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; 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 e4ee8c4f5..5ac03cba4 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 @@ -10,50 +10,72 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import java.util.stream.Collectors; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Pair; +import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMSBinding; import com.mojang.serialization.Lifecycle; import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.nms.container.StructurePlacement; 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.engine.object.IrisJigsawStructurePlacement; 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.levelgen.structure.placement.ConcentricRingsStructurePlacement; import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; +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; @@ -62,55 +84,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; @@ -678,46 +682,40 @@ public class NMSBinding implements INMSBinding { } @Override - public AutoClosing injectLevelStems() { - return inject(this::supplier); + 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 com.volmit.iris.core.nms.container.Pair injectUncached(boolean overworld, boolean nether, boolean end) { - var reg = registry(); - var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); - field.setAccessible(true); + 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()); + } - AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) - ) - ); - - var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM); - var old = (Map>, Registry>) field.get(reg); - var fake = new HashMap<>(old); - fake.put(Registries.LEVEL_STEM, injected); - field.set(reg, fake); - - return new com.volmit.iris.core.nms.container.Pair<>( - injected.size(), - new AutoClosing(() -> { - closing.close(); - field.set(reg, old); - })); - } - - @Override - public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { - var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE); - if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); - if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); - if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); - return overworld || nether || end; + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } @Override @@ -785,76 +783,55 @@ public class NMSBinding implements INMSBinding { .collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new)); } - private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { - return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - )); + 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)); } - @SneakyThrows - private AutoClosing inject(Function transformer) { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, transformer.apply(old)); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); - } - - private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { - var access = registry(); - var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE); - - var settings = new FlatLevelGeneratorSettings( - Optional.empty(), - access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), - List.of() - ); + 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())).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/IrisChunkGenerator.java b/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/IrisChunkGenerator.java index 5e690c635..dbba0afe4 100644 --- a/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/IrisChunkGenerator.java +++ b/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/IrisChunkGenerator.java @@ -24,6 +24,7 @@ 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.WeightedRandomList; import net.minecraft.world.entity.MobCategory; @@ -128,6 +129,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @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; 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 d58e5940c..412d2b6e4 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 @@ -14,40 +14,76 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import java.util.stream.Collectors; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Lifecycle; import com.volmit.iris.core.link.Identifier; 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.container.StructurePlacement; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +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.levelgen.structure.placement.ConcentricRingsStructurePlacement; import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; +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; @@ -57,57 +93,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; @@ -682,46 +697,40 @@ public class NMSBinding implements INMSBinding { } @Override - public AutoClosing injectLevelStems() { - return inject(this::supplier); + 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 com.volmit.iris.core.nms.container.Pair injectUncached(boolean overworld, boolean nether, boolean end) { - var reg = registry(); - var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class); - field.setAccessible(true); + 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()); + } - AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) - ) - ); - - var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM); - var old = (Map>, Registry>) field.get(reg); - var fake = new HashMap<>(old); - fake.put(Registries.LEVEL_STEM, injected); - field.set(reg, fake); - - return new com.volmit.iris.core.nms.container.Pair<>( - injected.size(), - new AutoClosing(() -> { - closing.close(); - field.set(reg, old); - })); - } - - @Override - public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { - var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE); - if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); - if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); - if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); - return overworld || nether || end; + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } @Override @@ -789,76 +798,55 @@ public class NMSBinding implements INMSBinding { .collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new)); } - private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { - return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - )); + 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)); } - @SneakyThrows - private AutoClosing inject(Function transformer) { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, transformer.apply(old)); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); - } - - private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { - var access = registry(); - var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE); - - var settings = new FlatLevelGeneratorSettings( - Optional.empty(), - access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), - List.of() - ); + 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())).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/IrisChunkGenerator.java b/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/IrisChunkGenerator.java index c8d41e5e4..3f50012d7 100644 --- a/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/IrisChunkGenerator.java +++ b/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/IrisChunkGenerator.java @@ -24,6 +24,7 @@ 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.WeightedRandomList; import net.minecraft.world.entity.MobCategory; @@ -128,6 +129,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @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; 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 305a77139..1c66cdd79 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 @@ -13,36 +13,71 @@ import java.util.stream.Collectors; import com.mojang.serialization.Lifecycle; import com.volmit.iris.core.link.Identifier; 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.container.StructurePlacement; 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.engine.object.IrisJigsawStructurePlacement; 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.levelgen.structure.placement.ConcentricRingsStructurePlacement; import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; +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,48 +91,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; @@ -672,46 +691,40 @@ public class NMSBinding implements INMSBinding { } @Override - public AutoClosing injectLevelStems() { - return inject(this::supplier); + 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 Pair 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()); + } - AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) - ) - ); - - var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM); - var old = (Map>, Registry>) field.get(reg); - var fake = new HashMap<>(old); - fake.put(Registries.LEVEL_STEM, injected); - field.set(reg, fake); - - return new Pair<>( - injected.size(), - new AutoClosing(() -> { - closing.close(); - field.set(reg, old); - })); - } - - @Override - public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { - var registry = registry().lookupOrThrow(Registries.DIMENSION_TYPE); - if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); - if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); - if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); - return overworld || nether || end; + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } @Override @@ -779,76 +792,55 @@ public class NMSBinding implements INMSBinding { .collect(Collectors.toMap(Pair::getA, Pair::getB, (a, b) -> a, KMap::new)); } - private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { - return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - )); + 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)); } - @SneakyThrows - private AutoClosing inject(Function transformer) { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, transformer.apply(old)); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); - } - - private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { - var access = registry(); - var dimensions = access.lookupOrThrow(Registries.DIMENSION_TYPE); - - var settings = new FlatLevelGeneratorSettings( - Optional.empty(), - access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), - List.of() - ); + 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())).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/IrisChunkGenerator.java b/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/IrisChunkGenerator.java index b5a57945b..7eac84c83 100644 --- a/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/IrisChunkGenerator.java +++ b/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/IrisChunkGenerator.java @@ -24,9 +24,11 @@ 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.WeightedRandomList; import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.item.EnderEyeItem; import net.minecraft.world.level.*; import net.minecraft.world.level.biome.*; import net.minecraft.world.level.block.Blocks; @@ -36,6 +38,7 @@ import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; import net.minecraft.world.level.levelgen.*; import net.minecraft.world.level.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.BuiltinStructures; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureSet; @@ -128,6 +131,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @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; 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 552ccdb6d..f6812ba97 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,11 +1,9 @@ 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.link.Identifier; 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.container.StructurePlacement; @@ -14,8 +12,12 @@ import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisJigsawStructure; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +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; @@ -26,20 +28,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; @@ -51,9 +57,9 @@ 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; @@ -61,6 +67,8 @@ import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; import net.minecraft.world.level.levelgen.structure.StructureSet; import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; +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; @@ -74,6 +82,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; @@ -81,11 +90,14 @@ 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.concurrent.atomic.AtomicBoolean; +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; @@ -97,11 +109,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; @@ -676,46 +687,40 @@ public class NMSBinding implements INMSBinding { } @Override - public AutoClosing injectLevelStems() { - return inject(this::supplier); + 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 Pair 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()); + } - AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end) - ) - ); - - var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM); - var old = (Map>, Registry>) field.get(reg); - var fake = new HashMap<>(old); - fake.put(Registries.LEVEL_STEM, injected); - field.set(reg, fake); - - return new Pair<>( - injected.size(), - new AutoClosing(() -> { - closing.close(); - field.set(reg, old); - })); - } - - @Override - public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) { - var registry = registry().lookupOrThrow(Registries.DIMENSION_TYPE); - if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD)); - if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER)); - if (end) end = !registry.containsKey(createIrisKey(LevelStem.END)); - return overworld || nether || end; + return true; + } catch (Throwable e) { + Iris.error(C.RED + "Failed to inject Bukkit"); + e.printStackTrace(); + } + return false; } @Override @@ -783,76 +788,55 @@ public class NMSBinding implements INMSBinding { .collect(Collectors.toMap(Pair::getA, Pair::getB, (a,b) -> a, KMap::new)); } - private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) { - return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext( - old.resources(), - old.dataConfiguration(), - old.datapackWorldgen(), - createRegistryAccess(old.datapackDimensions(), false, true, true, true) - )); + 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)); } - @SneakyThrows - private AutoClosing inject(Function transformer) { - if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!"); - - var server = ((CraftServer) Bukkit.getServer()); - var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class); - var nmsServer = server.getServer(); - var old = nmsServer.worldLoader; - - field.setAccessible(true); - field.set(nmsServer, transformer.apply(old)); - - return new AutoClosing(() -> { - field.set(nmsServer, old); - dataContextLock.unlock(); - }); - } - - private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) { - var access = registry(); - var dimensions = access.lookupOrThrow(Registries.DIMENSION_TYPE); - - var settings = new FlatLevelGeneratorSettings( - Optional.empty(), - access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), - List.of() - ); + 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())).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/CustomBiomeSource.java b/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/CustomBiomeSource.java new file mode 100644 index 000000000..570eaa0c3 --- /dev/null +++ b/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/CustomBiomeSource.java @@ -0,0 +1,169 @@ +package com.volmit.iris.core.nms.v1_21_R4; + +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_R4.CraftServer; +import org.bukkit.craftbukkit.v1_21_R4.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_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/IrisChunkGenerator.java b/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/IrisChunkGenerator.java new file mode 100644 index 000000000..28513cd77 --- /dev/null +++ b/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/IrisChunkGenerator.java @@ -0,0 +1,311 @@ +package com.volmit.iris.core.nms.v1_21_R4; + +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_R4.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R4.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_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 new file mode 100644 index 000000000..d90830ae3 --- /dev/null +++ b/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/NMSBinding.java @@ -0,0 +1,835 @@ +package com.volmit.iris.core.nms.v1_21_R4; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.volmit.iris.Iris; +import com.volmit.iris.core.link.Identifier; +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.container.StructurePlacement; +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.object.IrisJigsawStructurePlacement; +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.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.levelgen.structure.placement.ConcentricRingsStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; +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_R4.CraftChunk; +import org.bukkit.craftbukkit.v1_21_R4.CraftServer; +import org.bukkit.craftbukkit.v1_21_R4.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R4.block.CraftBlockState; +import org.bukkit.craftbukkit.v1_21_R4.block.CraftBlockStates; +import org.bukkit.craftbukkit.v1_21_R4.block.data.CraftBlockData; +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; +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; +import java.util.stream.Collectors; + +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(net.minecraft.nbt.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 net.minecraft.nbt.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 net.minecraft.nbt.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 = net.minecraft.core.IdMapper.class.getDeclaredField("tToId"); + Field df = net.minecraft.core.IdMapper.class.getDeclaredField("idToT"); + Field bf = net.minecraft.core.IdMapper.class.getDeclaredField("nextId"); + cf.setAccessible(true); + df.setAccessible(true); + bf.setAccessible(true); + net.minecraft.core.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 java.awt.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; + } + + @Override + public void placeStructures(Chunk chunk) { + var craft = ((CraftChunk) chunk); + var level = craft.getCraftWorld().getHandle(); + var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL); + level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager()); + } + + @Override + public KMap collectStructures() { + var structureSets = registry().lookupOrThrow(Registries.STRUCTURE_SET); + var structurePlacements = registry().lookupOrThrow(Registries.STRUCTURE_PLACEMENT); + return structureSets.keySet() + .stream() + .map(structureSets::get) + .filter(Optional::isPresent) + .map(Optional::get) + .map(holder -> { + var set = holder.value(); + var placement = set.placement(); + var key = holder.key().location(); + StructurePlacement.StructurePlacementBuilder builder; + if (placement instanceof RandomSpreadStructurePlacement random) { + builder = StructurePlacement.RandomSpread.builder() + .separation(random.separation()) + .spacing(random.spacing()) + .spreadType(switch (random.spreadType()) { + case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR; + case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR; + }); + } else if (placement instanceof ConcentricRingsStructurePlacement rings) { + builder = StructurePlacement.ConcentricRings.builder() + .distance(rings.distance()) + .spread(rings.spread()) + .count(rings.count()); + } else { + Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type())); + return null; + } + + return new Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder + .salt(placement.salt) + .frequency(placement.frequency) + .structures(set.structures() + .stream() + .map(entry -> new StructurePlacement.Structure( + entry.weight(), + entry.structure() + .unwrapKey() + .map(ResourceKey::location) + .map(ResourceLocation::toString) + .orElse(null), + entry.structure().tags() + .map(TagKey::location) + .map(ResourceLocation::toString) + .toList() + )) + .filter(StructurePlacement.Structure::isValid) + .toList()) + .build()); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(Pair::getA, Pair::getB, (a, b) -> a, KMap::new)); + } + + 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/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..a56ee621d --- /dev/null +++ b/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/NMSBinding.java @@ -0,0 +1,834 @@ +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.link.Identifier; +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.container.StructurePlacement; +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.object.IrisJigsawStructurePlacement; +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.levelgen.structure.placement.ConcentricRingsStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; +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; +import java.util.stream.Collectors; + +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; + } + + @Override + public void placeStructures(Chunk chunk) { + var craft = ((CraftChunk) chunk); + var level = craft.getCraftWorld().getHandle(); + var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL); + level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager()); + } + + @Override + public KMap collectStructures() { + var structureSets = registry().lookupOrThrow(Registries.STRUCTURE_SET); + var structurePlacements = registry().lookupOrThrow(Registries.STRUCTURE_PLACEMENT); + return structureSets.keySet() + .stream() + .map(structureSets::get) + .filter(Optional::isPresent) + .map(Optional::get) + .map(holder -> { + var set = holder.value(); + var placement = set.placement(); + var key = holder.key().location(); + StructurePlacement.StructurePlacementBuilder builder; + if (placement instanceof RandomSpreadStructurePlacement random) { + builder = StructurePlacement.RandomSpread.builder() + .separation(random.separation()) + .spacing(random.spacing()) + .spreadType(switch (random.spreadType()) { + case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR; + case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR; + }); + } else if (placement instanceof ConcentricRingsStructurePlacement rings) { + builder = StructurePlacement.ConcentricRings.builder() + .distance(rings.distance()) + .spread(rings.spread()) + .count(rings.count()); + } else { + Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type())); + return null; + } + + return new Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder + .salt(placement.salt) + .frequency(placement.frequency) + .structures(set.structures() + .stream() + .map(entry -> new StructurePlacement.Structure( + entry.weight(), + entry.structure() + .unwrapKey() + .map(ResourceKey::location) + .map(ResourceLocation::toString) + .orElse(null), + entry.structure().tags() + .map(TagKey::location) + .map(ResourceLocation::toString) + .toList() + )) + .filter(StructurePlacement.Structure::isValid) + .toList()) + .build()); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(Pair::getA, Pair::getB, (a, b) -> a, KMap::new)); + } + + 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 b/settings.gradle.kts similarity index 65% rename from settings.gradle rename to settings.gradle.kts index 57ad35b97..18cbee3fe 100644 --- a/settings.gradle +++ b/settings.gradle.kts @@ -15,26 +15,21 @@ * 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 "0.8.0" + id ("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } -rootProject.name = 'Iris' +rootProject.name = "Iris" -include(':core') +include(":core", ":core:agent") include( - ':nms:v1_21_R3', - ':nms:v1_21_R2', - ':nms:v1_21_R1', - ':nms:v1_20_R4', - ':nms:v1_20_R3', - ':nms:v1_20_R2', - ':nms:v1_20_R1', + ":nms:v1_21_R5", + ":nms:v1_21_R4", + ":nms:v1_21_R3", + ":nms:v1_21_R2", + ":nms:v1_21_R1", + ":nms:v1_20_R4", + ":nms:v1_20_R3", + ":nms:v1_20_R2", + ":nms:v1_20_R1", ) \ No newline at end of file