From a88d389e0f42f95386d4853fab1fa44feaa92374 Mon Sep 17 00:00:00 2001 From: Julian Krings <47589149+CrazyDev05@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:45:27 +0200 Subject: [PATCH 01/22] bump gradle wrapper to 8.14.2 and switch to kotlin dsl (#1203) --- build.gradle | 330 ----------------------- build.gradle.kts | 276 +++++++++++++++++++ core/build.gradle | 95 ------- core/build.gradle.kts | 151 +++++++++++ core/src/main/resources/plugin.yml | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 41 ++- gradlew.bat | 35 +-- settings.gradle => settings.gradle.kts | 22 +- 10 files changed, 489 insertions(+), 467 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 core/build.gradle create mode 100644 core/build.gradle.kts rename settings.gradle => settings.gradle.kts (74%) diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 5036603cd..000000000 --- a/build.gradle +++ /dev/null @@ -1,330 +0,0 @@ -import xyz.jpenilla.runpaper.task.RunServer - -/* - * 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" - id "xyz.jpenilla.run-paper" version "2.3.1" - id "io.sentry.jvm.gradle" version "5.7.0" -} - - -version '3.6.10-1.20.1-1.21.5' - -// 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 MIN_HEAP_SIZE = "2G" -def MAX_HEAP_SIZE = "8G" -//Valid values are: none, truecolor, indexed256, indexed16, indexed8 -def COLOR = "truecolor" - -def NMS_BINDINGS = Map.of( - "v1_21_R4", "1.21.5-R0.1-SNAPSHOT", - "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.forEach { key, value -> - project(":nms:$key") { - apply plugin: 'java' - apply plugin: 'com.volmit.nmstools' - - nmsTools { - it.jvm = JVM_VERSION.getOrDefault(key, 21) - it.version = value - } - - dependencies { - implementation project(":core") - } - } - - tasks.register("runServer-$key", RunServer) { - group("servers") - minecraftVersion(value.split("-")[0]) - minHeapSize(MIN_HEAP_SIZE) - maxHeapSize(MAX_HEAP_SIZE) - pluginJars(tasks.shadowJar.archiveFile) - javaLauncher = javaToolchains.launcherFor { it.languageVersion = JavaLanguageVersion.of(JVM_VERSION.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) - } -} - -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' - relocate "io.sentry", "com.volmit.iris.util.sentry" - archiveFileName.set("Iris-${project.version}.jar") - - dependencies { - exclude(dependency("org.ow2.asm:asm:")) - exclude(dependency("org.jetbrains:")) - } -} - -dependencies { - implementation project(':core') -} - -configurations.configureEach { - resolutionStrategy.cacheChangingModulesFor 60, 'minutes' - resolutionStrategy.cacheDynamicVersionsFor 60, 'minutes' -} - -allprojects { - apply plugin: 'java' - apply plugin: 'io.sentry.jvm.gradle' - - 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/releases/" } - 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' - } - - /** - * 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 - } - - sentry { - includeSourceContext = true - - org = "volmit-software" - projectName = "iris" - authToken = hasProperty("sentry.auth.token") ? - property("sentry.auth.token") : - System.getenv("SENTRY_AUTH_TOKEN") - } -} - -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) - -def cli = file("sentry-cli.exe") -tasks.register("downloadCli", Download) { - group = "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) - } -} - -tasks.register("release") { - group = "sentry" - dependsOn("downloadCli") - doLast { - def authToken = project.hasProperty("sentry.auth.token") ? - project.property("sentry.auth.token") : - System.getenv("SENTRY_AUTH_TOKEN") - def org = "volmit-software" - def projectName = "iris" - exec { - executable(cli) - args("releases", "new", "--auth-token", authToken, "-o", org, "-p", projectName, version) - } - exec { - executable(cli) - args("releases", "set-commits", "--auth-token", authToken, "-o", org, "-p", projectName, version, "--auto") - } - exec { - executable(cli) - args("releases", "finalize", "--auth-token", authToken, "-o", org, "-p", projectName, version) - } - cli.delete() - } -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 000000000..1ccc60382 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,276 @@ +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` + id("com.gradleup.shadow") version "8.3.6" + id("de.undercouch.download") version "5.0.1" + id("xyz.jpenilla.run-paper") version "2.3.1" + id("io.sentry.jvm.gradle") version "5.7.0" +} + +version = "3.6.10-1.20.1-1.21.5" + +// 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 nmsBindings = mapOf( + "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("org.jetbrains:annotations:26.0.2") + } + } + + 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) + } +} + +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) }) + 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 = "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") { + dependsOn("downloadCli") + doLast { + val authToken = project.property("sentry.auth.token") ?: System.getenv("SENTRY_AUTH_TOKEN") + val org = "volmit-software" + val projectName = "iris" + providers.exec { + executable(cli) + args("releases", "new", "--auth-token", authToken, "-o", org, "-p", projectName, version) + }.result.get() + providers.exec { + executable(cli) + args("releases", "set-commits", "--auth-token", authToken, "-o", org, "-p", projectName, version, "--auto") + }.result.get() + providers.exec { + executable(cli) + args("releases", "finalize", "--auth-token", authToken, "-o", org, "-p", projectName, version) + }.result.get() + cli.delete() + } + } +} + +dependencies { + implementation(project(":core")) +} + +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://mvn.lumine.io/repository/maven-public/") + maven("https://jitpack.io") + + maven("https://s01.oss.sonatype.org/content/repositories/snapshots") + maven("https://mvn.lumine.io/repository/maven/") + maven("https://repo.triumphteam.dev/snapshots") + maven("https://repo.mineinabyss.com/releases") + maven("https://hub.jeff-media.com/nexus/repository/jeff-media-public/") + maven("https://repo.nexomc.com/releases/") + } + + dependencies { + // Provided or Classpath + compileOnly("org.projectlombok:lombok:1.18.36") + annotationProcessor("org.projectlombok:lombok:1.18.36") + } + + /** + * 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/core/build.gradle b/core/build.gradle deleted file mode 100644 index 9effb5654..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.6.0' - 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..a6a1a20c5 --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,151 @@ +/* + * 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` + id("com.gradleup.shadow") + id("io.sentry.jvm.gradle") +} + +val apiVersion = "1.19" +val main = "com.volmit.iris.Iris" + +repositories { + maven("https://nexus.phoenixdevt.fr/repository/maven-public/") + maven("https://repo.auxilor.io/repository/maven-public/") +} + +/** + * 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.nexomc:nexo:1.6.0") + 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") + + + // 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") +} + +java { + disableAutoTargetJvm() +} + +sentry { + includeSourceContext = true + + org = "volmit-software" + projectName = "iris" + authToken = property("sentry.auth.token") as String? ?: System.getenv("SENTRY_AUTH_TOKEN") +} + +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() + 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.iris.util.metrics") + relocate("io.sentry", "com.volmit.iris.util.sentry") + + //minimize() + dependencies { + exclude(dependency("org.ow2.asm:asm:")) + exclude(dependency("org.jetbrains:")) + } + } +} + +/** + * 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/resources/plugin.yml b/core/src/main/resources/plugin.yml index 7ff73ff7e..0a58f5d00 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -23,5 +23,5 @@ libraries: commands: iris: aliases: [ ir, irs ] -api-version: '${apiversion}' +api-version: '${apiVersion}' hotload-dependencies: false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL 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/settings.gradle b/settings.gradle.kts similarity index 74% rename from settings.gradle rename to settings.gradle.kts index 9315a1131..c45462895 100644 --- a/settings.gradle +++ b/settings.gradle.kts @@ -23,19 +23,19 @@ pluginManagement { } } 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") include( - ':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', + ":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 From c0d17742e806b715cf4d97da8b3995583f0738e0 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 9 Jun 2025 13:43:00 +0200 Subject: [PATCH 02/22] fix release task for sentry --- build.gradle.kts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1ccc60382..24613a9f5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -134,7 +134,7 @@ tasks { val cli = file("sentry-cli.exe") register("downloadCli") { - group = "sentry" + 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) @@ -144,28 +144,28 @@ tasks { } register("release") { + group = "io.sentry" dependsOn("downloadCli") doLast { val authToken = project.property("sentry.auth.token") ?: System.getenv("SENTRY_AUTH_TOKEN") val org = "volmit-software" val projectName = "iris" - providers.exec { - executable(cli) - args("releases", "new", "--auth-token", authToken, "-o", org, "-p", projectName, version) - }.result.get() - providers.exec { - executable(cli) - args("releases", "set-commits", "--auth-token", authToken, "-o", org, "-p", projectName, version, "--auto") - }.result.get() - providers.exec { - executable(cli) - args("releases", "finalize", "--auth-token", authToken, "-o", org, "-p", projectName, version) - }.result.get() + 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() +} + dependencies { implementation(project(":core")) } From b8ee7561ddcb0e8d044f63e81a16ff9abad82c65 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 9 Jun 2025 13:44:57 +0200 Subject: [PATCH 03/22] fix failing to create temp file when user deleted the temp directory --- .../java/com/volmit/iris/util/mantle/Mantle.java | 1 - .../com/volmit/iris/util/mantle/TectonicPlate.java | 13 +++---------- 2 files changed, 3 insertions(+), 11 deletions(-) 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 9de145a3f..6ec24717d 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 @@ -85,7 +85,6 @@ public class Mantle { this.worldHeight = worldHeight; this.ioTrim = new AtomicBoolean(false); this.ioTectonicUnload = new AtomicBoolean(false); - new File(dataFolder, ".tmp").mkdirs(); loadedRegions = new KMap<>(); lastUse = new KMap<>(); ioBurst = MultiBurst.burst; 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 009993321..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 @@ -27,6 +27,7 @@ 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; @@ -222,16 +223,8 @@ public class TectonicPlate { */ public void write(File file) throws IOException { PrecisionStopwatch p = PrecisionStopwatch.start(); - File temp = File.createTempFile("iris-tectonic-plate", ".bin", new File(file.getParentFile(), ".tmp")); - try { - try (DataOutputStream dos = new DataOutputStream(new LZ4BlockOutputStream(new FileOutputStream(temp)))) { - write(dos); - } - Files.move(temp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); - Iris.debug("Saved Tectonic Plate " + C.DARK_GREEN + file.getName() + C.RED + " in " + Form.duration(p.getMilliseconds(), 2)); - } finally { - temp.delete(); - } + 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)); } /** From 9cf567e1ff75e880b342a88674d894d100a9977c Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 9 Jun 2025 13:47:24 +0200 Subject: [PATCH 04/22] remove unused util to fix class not found exception --- .../iris/util/particle/FastParticle.java | 179 ---------------- .../iris/util/particle/FastReflection.java | 79 ------- .../iris/util/particle/ParticleSender.java | 124 ----------- .../util/particle/ParticleSenderLegacy.java | 185 ----------------- .../iris/util/particle/ParticleType.java | 192 ------------------ 5 files changed, 759 deletions(-) delete mode 100644 core/src/main/java/com/volmit/iris/util/particle/FastParticle.java delete mode 100644 core/src/main/java/com/volmit/iris/util/particle/FastReflection.java delete mode 100644 core/src/main/java/com/volmit/iris/util/particle/ParticleSender.java delete mode 100644 core/src/main/java/com/volmit/iris/util/particle/ParticleSenderLegacy.java delete mode 100644 core/src/main/java/com/volmit/iris/util/particle/ParticleType.java 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; - }; - } -} From eefbbab7ee303ffb39a839ab946771fac641462b Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 9 Jun 2025 18:39:11 +0200 Subject: [PATCH 05/22] remove legacy chars before minimessage deserialization --- .../volmit/iris/util/decree/DecreeSystem.java | 5 +++-- .../java/com/volmit/iris/util/format/C.java | 22 +++++++++++++++++++ .../volmit/iris/util/plugin/VolmitSender.java | 10 ++++----- 3 files changed, 30 insertions(+), 7 deletions(-) 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/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/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) { From f9dac8a3a1ae43684f73e71d7af505f84023abd8 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 9 Jun 2025 18:40:43 +0200 Subject: [PATCH 06/22] fix compile while missing the sentry auth token --- build.gradle.kts | 2 +- core/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 24613a9f5..bb061f2bc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -147,7 +147,7 @@ tasks { group = "io.sentry" dependsOn("downloadCli") doLast { - val authToken = project.property("sentry.auth.token") ?: System.getenv("SENTRY_AUTH_TOKEN") + 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) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index a6a1a20c5..cebfbc6c5 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -100,7 +100,7 @@ sentry { org = "volmit-software" projectName = "iris" - authToken = property("sentry.auth.token") as String? ?: System.getenv("SENTRY_AUTH_TOKEN") + authToken = findProperty("sentry.auth.token") as String? ?: System.getenv("SENTRY_AUTH_TOKEN") } tasks { From 172e23451475b920b8c12ba148a065352b3972e7 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 9 Jun 2025 19:19:00 +0200 Subject: [PATCH 07/22] fix divide by zero in the engine svc --- .../java/com/volmit/iris/core/service/IrisEngineSVC.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 51351f657..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 @@ -187,7 +187,7 @@ public class IrisEngineSVC implements IrisService { return; try { - engine.getMantle().trim(tectonicLimit.get() / worlds.size()); + engine.getMantle().trim(tectonicLimit()); } catch (Throwable e) { Iris.reportError(e); Iris.error("EngineSVC: Failed to trim for " + name); @@ -204,7 +204,7 @@ public class IrisEngineSVC implements IrisService { try { long unloadStart = System.currentTimeMillis(); - int count = engine.getMantle().unloadTectonicPlate(tectonicLimit.get() / worlds.size()); + 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)); } @@ -217,6 +217,10 @@ public class IrisEngineSVC implements IrisService { } } + private int tectonicLimit() { + return tectonicLimit.get() / Math.max(worlds.size(), 1); + } + @Synchronized private void close() { if (closed) return; From 944cc19ebce2b345596dcfa8a9a2c32cac7dff22 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 9 Jun 2025 19:35:43 +0200 Subject: [PATCH 08/22] use proper shuffling algorithm for the loot --- .../com/volmit/iris/engine/framework/Engine.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) 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 9a87d26e9..47aa816d3 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 @@ -78,7 +78,6 @@ 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; @@ -457,14 +456,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); From e2a3f25dcb7516c65135663414a60d00d7f4e70c Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 9 Jun 2025 19:42:03 +0200 Subject: [PATCH 09/22] fix load in parallel method --- .../java/com/volmit/iris/core/loader/ResourceLoader.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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); } }); From 9316ea9e5b514742187c0bf340d386b738513d6c Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 9 Jun 2025 19:49:06 +0200 Subject: [PATCH 10/22] fix npe when creating cuboids --- .../volmit/iris/core/commands/CommandObject.java | 15 ++++++++++++++- .../com/volmit/iris/core/service/WandSVC.java | 4 ++++ 2 files changed, 18 insertions(+), 1 deletion(-) 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/service/WandSVC.java b/core/src/main/java/com/volmit/iris/core/service/WandSVC.java index 53d3800ac..95ea520f0 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()); @@ -357,6 +360,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) { From cd80acdc7d77b4029795ae51282411e02df82a0d Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 9 Jun 2025 19:50:50 +0200 Subject: [PATCH 11/22] fix IndexOutOfBoundsException when getting the selection from the wand --- core/src/main/java/com/volmit/iris/core/service/WandSVC.java | 2 ++ 1 file changed, 2 insertions(+) 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 95ea520f0..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 @@ -201,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); From 113f25dab8c50e89a4f88771070050ddcdb80184 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 9 Jun 2025 23:17:47 +0200 Subject: [PATCH 12/22] more sentry context --- core/src/main/java/com/volmit/iris/Iris.java | 3 +++ .../iris/core/commands/CommandDeveloper.java | 3 +++ .../volmit/iris/util/context/IrisContext.java | 17 +++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index 43384f127..ab9c5dc31 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -44,6 +44,7 @@ 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.context.IrisContext; import com.volmit.iris.util.exceptions.IrisException; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; @@ -963,6 +964,8 @@ public class Iris extends VolmitPlugin implements Listener { options.setBeforeSend((event, hint) -> { 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()); return event; }); }); 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 29d4fd80f..e96eb29ed 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 @@ -26,6 +26,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.util.decree.DecreeExecutor; import com.volmit.iris.util.decree.DecreeOrigin; import com.volmit.iris.util.decree.annotations.Decree; @@ -73,6 +74,8 @@ public class CommandDeveloper implements DecreeExecutor { @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")); } 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..b076b5eef 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.getLoadKey()) + .qput("version", 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())); + } } From ccc3bab8e06e5e554fbd8068916f718a239d5cf4 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 9 Jun 2025 23:25:33 +0200 Subject: [PATCH 13/22] allow carving recursion --- .../components/MantleCarvingComponent.java | 8 +++---- .../iris/engine/object/IrisCarving.java | 23 ++++++++++++------- .../volmit/iris/engine/object/IrisCave.java | 10 ++++---- .../iris/engine/object/IrisCavePlacer.java | 14 +++++++---- .../volmit/iris/engine/object/IrisRavine.java | 10 ++++---- .../iris/engine/object/IrisRavinePlacer.java | 14 +++++++---- 6 files changed, 47 insertions(+), 32 deletions(-) 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..8d067abfd 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 @@ -58,21 +58,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/object/IrisCarving.java b/core/src/main/java/com/volmit/iris/engine/object/IrisCarving.java index bf7efa6af..1da8b1a31 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,21 +61,25 @@ 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, 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, 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, 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, engine, x, y, z, nextRecursion, waterHint); } } @@ -104,15 +108,18 @@ public class IrisCarving { } } - 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..e0e818403 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisCave.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisCave.java @@ -66,10 +66,10 @@ 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, engine, x, y, z, 0, -1); } - public void generate(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z, int waterHint) { + public void generate(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z, int recursion, 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) -> { @@ -92,7 +92,7 @@ public class IrisCave extends IrisRegistrant { int h = Math.min(Math.max(highestWater, waterHint), engine.getDimension().getFluidHeight()); for (IrisPosition i : points) { - fork.doCarving(writer, rng, engine, i.getX(), i.getY(), i.getZ(), h); + fork.doCarving(writer, rng, engine, i.getX(), i.getY(), i.getZ(), recursion, h); } MatterCavern c = new MatterCavern(true, customBiome, (byte) 0); @@ -108,7 +108,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 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..1a95493a1 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, 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, Engine engine, int x, int y, int z, int recursion, int waterHint) { if (fail.get()) { return; } @@ -92,18 +96,18 @@ public class IrisCavePlacer implements IRare { } try { - cave.generate(mantle, rng, engine, x + rng.nextInt(15), y, z + rng.nextInt(15), waterHint); + cave.generate(mantle, rng, engine, x + rng.nextInt(15), y, z + rng.nextInt(15), recursion, waterHint); } 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/IrisRavine.java b/core/src/main/java/com/volmit/iris/engine/object/IrisRavine.java index ca6bb7bd4..e2d9a79e4 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,10 +93,10 @@ 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, engine, x, y, z, 0, -1); } - public void generate(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z, int waterHint) { + public void generate(MantleWriter writer, RNG rng, Engine engine, int x, int y, int z, int recursion, int waterHint) { KList pos = getWorm().generate(rng, engine.getData(), writer, null, x, y, z, (at) -> { }); CNG dg = depthStyle.getGenerator().createNoCache(rng, engine.getData()); @@ -135,7 +135,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, engine, p.getX(), rng.i(surface - depth, surface), p.getZ(), recursion, Math.max(highestWater, waterHint)); for (int i = surface + depth; i >= surface; i--) { if (i % ribThickness == 0) { @@ -184,7 +184,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..fa65c5c79 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, 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, 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, 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); } } From 8712c8874ccd417cd199ca123bbbe119aa78a53c Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 9 Jun 2025 23:27:12 +0200 Subject: [PATCH 14/22] disable global pregen cache by default for now --- core/src/main/java/com/volmit/iris/core/IrisSettings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 20c0fa265..7804b3eac 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -132,7 +132,7 @@ public class IrisSettings { public boolean markerEntitySpawningSystem = true; public boolean effectSystem = true; public boolean worldEditWandCUI = true; - public boolean globalPregenCache = true; + public boolean globalPregenCache = false; } @Data From ed67b4d3c20cf2213ef2a831f601b1dc966b810c Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 11 Jun 2025 13:03:31 +0200 Subject: [PATCH 15/22] fix spawners not having entities due to using old format --- .../com/volmit/iris/engine/object/IrisBlockData.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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); From 37be7ca847290ee26e50ac3943556e6aeca614b8 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 11 Jun 2025 13:14:28 +0200 Subject: [PATCH 16/22] don't send json and zip file closed exceptions to sentry --- core/src/main/java/com/volmit/iris/Iris.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index ab9c5dc31..3192a2a61 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -53,6 +53,7 @@ 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.json.JSONException; import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.misc.getHardware; @@ -947,6 +948,10 @@ public class Iris extends VolmitPlugin implements Listener { } } + private static boolean suppress(Throwable e) { + return (e instanceof IllegalStateException ex && "zip file closed".equals(ex.getMessage())) || e instanceof JSONException; + } + private static void setupSentry() { var settings = IrisSettings.get().getSentry(); if (settings.disableAutoReporting || Sentry.isEnabled()) return; @@ -962,6 +967,7 @@ public class Iris extends VolmitPlugin implements Listener { 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(); From 851ac18f0d29ed22d54a340867d8416f7cbb20f4 Mon Sep 17 00:00:00 2001 From: Julian Krings <47589149+CrazyDev05@users.noreply.github.com> Date: Wed, 11 Jun 2025 13:23:09 +0200 Subject: [PATCH 17/22] implement custom conditions for mythic mobs (#1204) --- .../volmit/iris/core/link/MythicMobsLink.java | 81 +++++++++++++++++-- .../com/volmit/iris/engine/IrisEngine.java | 4 +- .../volmit/iris/engine/framework/Engine.java | 4 +- 3 files changed, 80 insertions(+), 9 deletions(-) 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 index 8a7d1d7be..052952346 100644 --- a/core/src/main/java/com/volmit/iris/core/link/MythicMobsLink.java +++ b/core/src/main/java/com/volmit/iris/core/link/MythicMobsLink.java @@ -18,20 +18,33 @@ package com.volmit.iris.core.link; +import com.google.common.collect.Sets; +import com.volmit.iris.Iris; +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.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Entity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; import org.bukkit.plugin.Plugin; import javax.annotation.Nullable; -import java.util.Collection; -import java.util.List; +import java.util.*; public class MythicMobsLink { public MythicMobsLink() { - + if (getPlugin() == null) return; + Iris.instance.registerListener(new ConditionListener()); } public boolean isEnabled() { @@ -49,12 +62,70 @@ public class MythicMobsLink { * @param location The location * @return The mob, or null if it can't be spawned */ - public @Nullable - Entity spawnMob(String mob, Location location) { + 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(); } + + private static class ConditionListener implements Listener { + @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 = Sets.newConcurrentHashSet(); + @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 = "irisbiome", 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 = Sets.newConcurrentHashSet(); + + 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/engine/IrisEngine.java b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java index 7224ea295..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); @@ -475,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/framework/Engine.java b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java index 47aa816d3..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 @@ -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 { From 7b7118fe0dd095aa844d579bc19f4db61c1d5916 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Fri, 13 Jun 2025 12:26:56 +0200 Subject: [PATCH 18/22] add id for servers hash of jvm name & version, processor info, memory size and plugins --- core/src/main/java/com/volmit/iris/Iris.java | 1975 +++++++++-------- .../com/volmit/iris/core/IrisSettings.java | 1 + .../com/volmit/iris/util/sentry/ServerID.java | 59 + 3 files changed, 1049 insertions(+), 986 deletions(-) create mode 100644 core/src/main/java/com/volmit/iris/util/sentry/ServerID.java diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index 3192a2a61..08d5c9736 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -1,986 +1,989 @@ -/* - * 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.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.context.IrisContext; -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.json.JSONException; -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 com.volmit.iris.util.sentry.Attachments; -import com.volmit.iris.util.sentry.IrisLogger; -import io.papermc.lib.PaperLib; -import io.sentry.Sentry; -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) { - Sentry.captureException(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(); - setupSentry(); - initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class) i.getClass(), (IrisService) i)); - INMS.get(); - IO.delete(new File("iris")); - compat = IrisCompat.configured(getDataFile("compat.json")); - ServerConfigurator.configure(); - new IrisContextInjector(); - IrisSafeguard.IrisSafeguardSystem(); - getSender().setTag(getTag()); - IrisSafeguard.earlySplash(); - 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(); - 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 = 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); - 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()) { - 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 (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; - } - } - - private static boolean suppress(Throwable e) { - return (e instanceof IllegalStateException ex && "zip file closed".equals(ex.getMessage())) || e instanceof JSONException; - } - - private static void setupSentry() { - var settings = IrisSettings.get().getSentry(); - if (settings.disableAutoReporting || Sentry.isEnabled()) return; - Iris.info("Enabling Sentry for anonymous error reporting. You can disable this in the settings."); - 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()); - return event; - }); - }); - Sentry.configureScope(scope -> { - 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)); - } -} +/* + * 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.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.context.IrisContext; +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.json.JSONException; +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 com.volmit.iris.util.sentry.Attachments; +import com.volmit.iris.util.sentry.IrisLogger; +import com.volmit.iris.util.sentry.ServerID; +import io.papermc.lib.PaperLib; +import io.sentry.Sentry; +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) { + Sentry.captureException(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(); + setupSentry(); + initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class) i.getClass(), (IrisService) i)); + INMS.get(); + IO.delete(new File("iris")); + compat = IrisCompat.configured(getDataFile("compat.json")); + ServerConfigurator.configure(); + new IrisContextInjector(); + IrisSafeguard.IrisSafeguardSystem(); + getSender().setTag(getTag()); + IrisSafeguard.earlySplash(); + 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(); + 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 = 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); + 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()) { + 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 (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; + } + } + + private static boolean suppress(Throwable e) { + return (e instanceof IllegalStateException ex && "zip file closed".equals(ex.getMessage())) || e instanceof JSONException; + } + + private static void setupSentry() { + var settings = IrisSettings.get().getSentry(); + if (settings.disableAutoReporting || Sentry.isEnabled()) 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()); + 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)); + } +} 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 7804b3eac..c409c1f9d 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -225,6 +225,7 @@ public class IrisSettings { @Data public static class IrisSettingsSentry { + public boolean includeServerId = true; public boolean disableAutoReporting = false; public boolean debug = false; } 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(); + } + } +} From f32f73e65af9717561865d8ac7f3ca8adf87a391 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Fri, 13 Jun 2025 12:27:03 +0200 Subject: [PATCH 19/22] disable error reporting in dev environment --- build.gradle.kts | 2 ++ core/src/main/java/com/volmit/iris/Iris.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index bb061f2bc..86a2170c1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -60,6 +60,7 @@ 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_R4" to "1.21.5-R0.1-SNAPSHOT", @@ -103,6 +104,7 @@ nmsBindings.forEach { key, value -> systemProperty("disable.watchdog", "") systemProperty("net.kyori.ansi.colorLevel", color) systemProperty("com.mojang.eula.agree", true) + systemProperty("iris.errorReporting", errorReporting) } } diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index 08d5c9736..a33f4cbfa 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -955,7 +955,7 @@ public class Iris extends VolmitPlugin implements Listener { private static void setupSentry() { var settings = IrisSettings.get().getSentry(); - if (settings.disableAutoReporting || Sentry.isEnabled()) return; + if (settings.disableAutoReporting || Sentry.isEnabled() || !Boolean.getBoolean("iris.errorReporting")) 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 -> { From 01b62c13b60d793b0cb2fecd32117701e1f50dd3 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 14 Jun 2025 11:16:47 +0200 Subject: [PATCH 20/22] fix deleting mantle temp files before they are fully written --- core/src/main/java/com/volmit/iris/util/mantle/Mantle.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 6ec24717d..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 @@ -369,11 +369,10 @@ 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); hyperLock.disable(); BurstExecutor b = ioBurst.burst(toUnload.size()); loadedRegions.forEach((i, plate) -> b.queue(() -> { @@ -383,11 +382,11 @@ public class Mantle { 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(); - IO.delete(new File(dataFolder, ".tmp")); try { b.complete(); @@ -395,6 +394,7 @@ public class Mantle { Iris.reportError(e); } + IO.delete(new File(dataFolder, ".tmp")); Iris.debug("The Mantle has Closed " + C.DARK_AQUA + dataFolder.getAbsolutePath()); } From 8df6253604e905dbbe91b425497d0c664da79939 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 14 Jun 2025 11:44:30 +0200 Subject: [PATCH 21/22] add safeguard info to sentry reports --- core/src/main/java/com/volmit/iris/Iris.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index a33f4cbfa..65b78aa43 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -30,6 +30,7 @@ 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.safeguard.ServerBootSFG; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.EnginePanic; @@ -974,6 +975,7 @@ public class Iris extends VolmitPlugin implements Listener { event.setTag("iris.nms", INMS.get().getClass().getCanonicalName()); var context = IrisContext.get(); if (context != null) event.getContexts().set("engine", context.asContext()); + event.getContexts().set("safeguard", ServerBootSFG.allIncompatibilities); return event; }); }); From 32d9a5e40ad775118418effb726a4e04438f915d Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 14 Jun 2025 11:46:51 +0200 Subject: [PATCH 22/22] make sentry engine context hotload safe --- .../main/java/com/volmit/iris/util/context/IrisContext.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b076b5eef..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 @@ -96,8 +96,8 @@ public class IrisContext { .qput("studio", engine.isStudio()) .qput("closed", engine.isClosed()) .qput("pack", new KMap<>() - .qput("key", dimension.getLoadKey()) - .qput("version", dimension.getVersion()) + .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())