diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..3ffcddd8e
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,268 @@
+import de.undercouch.gradle.tasks.download.Download
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.compile.JavaCompile
+import org.gradle.jvm.tasks.Jar
+import org.gradle.jvm.toolchain.JavaLanguageVersion
+
+/*
+ * 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 = uri('https://jitpack.io')
+ }
+ }
+ dependencies {
+ classpath('com.github.VolmitSoftware:NMSTools:c88961416f')
+ }
+}
+
+plugins {
+ id 'java'
+ id 'java-library'
+ alias(libs.plugins.download)
+}
+
+group = 'art.arcane'
+version = '4.0.0-1.21.11'
+String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
+ .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT')
+ .get()
+
+apply plugin: ApiGenerator
+
+// 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/[Minecraft Server]/consumers/plugin-consumers/dropins/plugins')
+registerCustomOutputTaskUnix('PixelMac', '/Users/test/Desktop/mcserver/plugins')
+registerCustomOutputTaskUnix('CrazyDev22LT', '/home/julian/Desktop/server/plugins')
+// ==============================================================
+
+def nmsBindings = [
+ v1_21_R7: '1.21.11-R0.1-SNAPSHOT',
+]
+Class nmsTypeClass = Class.forName('NMSBinding$Type')
+nmsBindings.each { key, value ->
+ project(":nms:${key}") {
+ apply plugin: JavaPlugin
+
+ def nmsConfig = new Config()
+ nmsConfig.jvm = 21
+ nmsConfig.version = value
+ nmsConfig.type = Enum.valueOf(nmsTypeClass, 'DIRECT')
+ extensions.extraProperties.set('nms', nmsConfig)
+ plugins.apply(NMSBinding)
+
+ dependencies {
+ compileOnly(project(':core'))
+ compileOnly(volmLibCoordinate) {
+ changing = true
+ transitive = false
+ }
+ compileOnly(rootProject.libs.annotations)
+ compileOnly(rootProject.libs.byteBuddy.core)
+ }
+ }
+}
+
+def included = configurations.create('included')
+def jarJar = configurations.create('jarJar')
+dependencies {
+ nmsBindings.keySet().each { key ->
+ add('included', project(path: ":nms:${key}", configuration: 'reobf'))
+ }
+ add('included', project(path: ':core', configuration: 'shadow'))
+ add('jarJar', project(':core:agent'))
+}
+
+tasks.named('jar', Jar).configure {
+ inputs.files(included)
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+ from(jarJar, provider { included.resolve().collect { zipTree(it) } })
+ archiveFileName.set("Iris-${project.version}.jar")
+}
+
+tasks.register('iris', Copy) {
+ group = 'iris'
+ dependsOn('jar')
+ from(layout.buildDirectory.file("libs/Iris-${project.version}.jar"))
+ into(layout.buildDirectory)
+}
+
+tasks.register('irisDev', Copy) {
+ group = 'iris'
+ from(project(':core').layout.buildDirectory.files('libs/core-javadoc.jar', 'libs/core-sources.jar'))
+ rename { String fileName -> fileName.replace('core', "Iris-${project.version}") }
+ into(layout.buildDirectory)
+ dependsOn(':core:sourcesJar')
+ dependsOn(':core:javadocJar')
+}
+
+def cli = file('sentry-cli.exe')
+tasks.register('downloadCli', Download) {
+ group = 'io.sentry'
+ src("https://release-registry.services.sentry.io/apps/sentry-cli/latest?response=download&arch=x86_64&platform=${System.getProperty('os.name')}&package=sentry-cli")
+ dest(cli)
+
+ doLast {
+ cli.setExecutable(true)
+ }
+}
+
+tasks.register('release') {
+ group = 'io.sentry'
+ dependsOn('downloadCli')
+ doLast {
+ String url = 'http://sentry.volmit.com:8080'
+ def authToken = project.findProperty('sentry.auth.token') ?: System.getenv('SENTRY_AUTH_TOKEN')
+ String org = 'sentry'
+ String projectName = 'iris'
+ runCommand(cli, '--url', url, '--auth-token', authToken, 'releases', 'new', '-o', org, '-p', projectName, version)
+ runCommand(cli, '--url', url, '--auth-token', authToken, 'releases', 'set-commits', '-o', org, '-p', projectName, version, '--auto', '--ignore-missing')
+ //exec(cli, "--url", url, "--auth-token", authToken, "releases", "finalize", "-o", org, "-p", projectName, version)
+ cli.delete()
+ }
+}
+
+void runCommand(Object... command) {
+ Process process = new ProcessBuilder(command.collect { it.toString() }).start()
+ process.inputStream.readLines().each { println(it) }
+ process.errorStream.readLines().each { println(it) }
+ process.waitFor()
+}
+
+configurations.configureEach {
+ resolutionStrategy.cacheChangingModulesFor(0, 'seconds')
+ resolutionStrategy.cacheDynamicVersionsFor(0, 'seconds')
+}
+
+allprojects {
+ apply plugin: 'java'
+
+ java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+ }
+
+ repositories {
+ mavenCentral()
+ maven { url = uri('https://repo.papermc.io/repository/maven-public/') }
+ maven { url = uri('https://repo.codemc.org/repository/maven-public/') }
+
+ maven { url = uri('https://jitpack.io') } // EcoItems, score
+ maven { url = uri('https://repo.nexomc.com/releases/') } // nexo
+ maven { url = uri('https://maven.devs.beer/') } // itemsadder
+ maven { url = uri('https://repo.extendedclip.com/releases/') } // placeholderapi
+ maven { url = uri('https://mvn.lumine.io/repository/maven-public/') } // mythic
+ maven { url = uri('https://nexus.phoenixdevt.fr/repository/maven-public/') } //MMOItems
+ maven { url = uri('https://repo.onarandombox.com/content/groups/public/') } //Multiverse Core
+ }
+
+ dependencies {
+ // Provided or Classpath
+ compileOnly(rootProject.libs.lombok)
+ annotationProcessor(rootProject.libs.lombok)
+ }
+
+ /**
+ * We need parameter meta for the decree command system
+ */
+ tasks.named('compileJava', JavaCompile).configure {
+ options.compilerArgs.add('-parameters')
+ options.encoding = 'UTF-8'
+ options.debugOptions.debugLevel = 'none'
+ }
+
+ tasks.named('javadoc').configure {
+ options.encoding = 'UTF-8'
+ options.quiet()
+ //options.addStringOption("Xdoclint:none") // TODO: Re-enable this
+ }
+
+ tasks.register('sourcesJar', Jar) {
+ archiveClassifier.set('sources')
+ from(sourceSets.main.allSource)
+ }
+
+ tasks.register('javadocJar', Jar) {
+ archiveClassifier.set('javadoc')
+ from(tasks.named('javadoc').map { it.destinationDir })
+ }
+}
+
+if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_21)) {
+ System.err.println()
+ System.err.println('=========================================================================================================')
+ System.err.println('You must run gradle on Java 21 or newer. You are using ' + JavaVersion.current())
+ System.err.println()
+ System.err.println('=== For IDEs ===')
+ System.err.println('1. Configure the project for Java 21 toolchain')
+ 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)
+}
+
+void registerCustomOutputTask(String name, String path) {
+ if (!System.getProperty('os.name').toLowerCase().contains('windows')) {
+ return
+ }
+
+ tasks.register("build${name}", Copy) {
+ group = 'development'
+ outputs.upToDateWhen { false }
+ dependsOn('iris')
+ from(layout.buildDirectory.file("Iris-${project.version}.jar"))
+ into(file(path))
+ rename { String ignored -> 'Iris.jar' }
+ }
+}
+
+void registerCustomOutputTaskUnix(String name, String path) {
+ if (System.getProperty('os.name').toLowerCase().contains('windows')) {
+ return
+ }
+
+ tasks.register("build${name}", Copy) {
+ group = 'development'
+ outputs.upToDateWhen { false }
+ dependsOn('iris')
+ from(layout.buildDirectory.file("Iris-${project.version}.jar"))
+ into(file(path))
+ rename { String ignored -> 'Iris.jar' }
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
deleted file mode 100644
index beb8f6c2e..000000000
--- a/build.gradle.kts
+++ /dev/null
@@ -1,263 +0,0 @@
-import de.undercouch.gradle.tasks.download.Download
-import org.gradle.jvm.toolchain.JavaLanguageVersion
-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:c88961416f")
-}
-
-plugins {
- java
- `java-library`
- alias(libs.plugins.download)
-}
-
-group = "art.arcane"
-version = "4.0.0-1.21.11"
-val volmLibCoordinate: String = providers.gradleProperty("volmLibCoordinate")
- .orElse("com.github.VolmitSoftware:VolmLib:master-SNAPSHOT")
- .get()
-
-apply()
-
-// ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED
-// ======================== WINDOWS =============================
-registerCustomOutputTask("Cyberpwn", "C://Users/cyberpwn/Documents/development/server/plugins")
-registerCustomOutputTask("Psycho", "C://Dan/MinecraftDevelopment/Server/plugins")
-registerCustomOutputTask("ArcaneArts", "C://Users/arcane/Documents/development/server/plugins")
-registerCustomOutputTask("Coco", "D://mcsm/plugins")
-registerCustomOutputTask("Strange", "D://Servers/1.17 Test Server/plugins")
-registerCustomOutputTask("Vatuu", "D://Minecraft/Servers/1.19.4/plugins")
-registerCustomOutputTask("CrazyDev22", "C://Users/Julian/Desktop/server/plugins")
-registerCustomOutputTask("PixelFury", "C://Users/repix/workplace/Iris/1.21.3 - Development-Public-v3/plugins")
-registerCustomOutputTask("PixelFuryDev", "C://Users/repix/workplace/Iris/1.21 - Development-v3/plugins")
-// ========================== UNIX ==============================
-registerCustomOutputTaskUnix("CyberpwnLT", "/Users/danielmills/development/server/plugins")
-registerCustomOutputTaskUnix("PsychoLT", "/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/consumers/plugin-consumers/dropins/plugins")
-registerCustomOutputTaskUnix("PixelMac", "/Users/test/Desktop/mcserver/plugins")
-registerCustomOutputTaskUnix("CrazyDev22LT", "/home/julian/Desktop/server/plugins")
-// ==============================================================
-
-val nmsBindings = mapOf(
- "v1_21_R7" to "1.21.11-R0.1-SNAPSHOT",
-)
-nmsBindings.forEach { (key, value) ->
- project(":nms:$key") {
- apply()
-
- nmsBinding {
- jvm = 21
- version = value
- type = NMSBinding.Type.DIRECT
- }
-
- dependencies {
- compileOnly(project(":core"))
- compileOnly(volmLibCoordinate) {
- isChanging = true
- isTransitive = false
- }
- compileOnly(rootProject.libs.annotations)
- compileOnly(rootProject.libs.byteBuddy.core)
- }
- }
-}
-
-val included: Configuration by configurations.creating
-val jarJar: Configuration by configurations.creating
-dependencies {
- for (key in nmsBindings.keys) {
- included(project(":nms:$key", "reobf"))
- }
- included(project(":core", "shadow"))
- jarJar(project(":core:agent"))
-}
-
-tasks {
- jar {
- inputs.files(included)
- duplicatesStrategy = DuplicatesStrategy.EXCLUDE
- from(jarJar, provider { included.resolve().map(::zipTree) })
- archiveFileName.set("Iris-${project.version}.jar")
- }
-
- register("iris") {
- group = "iris"
- dependsOn("jar")
- from(layout.buildDirectory.file("libs/Iris-${project.version}.jar"))
- into(layout.buildDirectory)
- }
-
- register("irisDev") {
- group = "iris"
- from(project(":core").layout.buildDirectory.files("libs/core-javadoc.jar", "libs/core-sources.jar"))
- rename { it.replace("core", "Iris-${project.version}") }
- into(layout.buildDirectory)
- dependsOn(":core:sourcesJar")
- dependsOn(":core:javadocJar")
- }
-
- val cli = file("sentry-cli.exe")
- register("downloadCli") {
- group = "io.sentry"
- src("https://release-registry.services.sentry.io/apps/sentry-cli/latest?response=download&arch=x86_64&platform=${System.getProperty("os.name")}&package=sentry-cli")
- dest(cli)
-
- doLast {
- cli.setExecutable(true)
- }
- }
-
- register("release") {
- group = "io.sentry"
- dependsOn("downloadCli")
- doLast {
- val url = "http://sentry.volmit.com:8080"
- val authToken = project.findProperty("sentry.auth.token") ?: System.getenv("SENTRY_AUTH_TOKEN")
- val org = "sentry"
- val projectName = "iris"
- exec(cli, "--url", url , "--auth-token", authToken, "releases", "new", "-o", org, "-p", projectName, version)
- exec(cli, "--url", url , "--auth-token", authToken, "releases", "set-commits", "-o", org, "-p", projectName, version, "--auto", "--ignore-missing")
- //exec(cli, "--url", url, "--auth-token", authToken, "releases", "finalize", "-o", org, "-p", projectName, version)
- cli.delete()
- }
- }
-}
-
-fun exec(vararg command: Any) {
- val p = ProcessBuilder(command.map { it.toString() })
- .start()
- p.inputStream.reader().useLines { it.forEach(::println) }
- p.errorStream.reader().useLines { it.forEach(::println) }
- p.waitFor()
-}
-
-configurations.configureEach {
- resolutionStrategy.cacheChangingModulesFor(0, "seconds")
- resolutionStrategy.cacheDynamicVersionsFor(0, "seconds")
-}
-
-allprojects {
- apply()
-
- java {
- toolchain {
- languageVersion.set(JavaLanguageVersion.of(21))
- }
- }
-
- repositories {
- mavenCentral()
- maven("https://repo.papermc.io/repository/maven-public/")
- maven("https://repo.codemc.org/repository/maven-public/")
-
- maven("https://jitpack.io") // EcoItems, score
- maven("https://repo.nexomc.com/releases/") // nexo
- maven("https://maven.devs.beer/") // itemsadder
- maven("https://repo.extendedclip.com/releases/") // placeholderapi
- maven("https://mvn.lumine.io/repository/maven-public/") // mythic
- maven("https://nexus.phoenixdevt.fr/repository/maven-public/") //MMOItems
- maven("https://repo.onarandombox.com/content/groups/public/") //Multiverse Core
- }
-
- dependencies {
- // Provided or Classpath
- compileOnly(rootProject.libs.lombok)
- annotationProcessor(rootProject.libs.lombok)
- }
-
- /**
- * We need parameter meta for the decree command system
- */
- tasks {
- compileJava {
- options.compilerArgs.add("-parameters")
- options.encoding = "UTF-8"
- options.debugOptions.debugLevel = "none"
- }
-
- 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().isCompatibleWith(JavaVersion.VERSION_21)) {
- System.err.println()
- System.err.println("=========================================================================================================")
- System.err.println("You must run gradle on Java 21 or newer. You are using " + JavaVersion.current())
- System.err.println()
- System.err.println("=== For IDEs ===")
- System.err.println("1. Configure the project for Java 21 toolchain")
- 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" }
- }
-}
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
new file mode 100644
index 000000000..add9425c9
--- /dev/null
+++ b/buildSrc/build.gradle
@@ -0,0 +1,25 @@
+import org.gradle.jvm.toolchain.JavaLanguageVersion
+
+plugins {
+ id 'java'
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+}
+
+repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ maven {
+ url = uri('https://jitpack.io')
+ }
+}
+
+dependencies {
+ implementation('org.ow2.asm:asm:9.8')
+ implementation('com.github.VolmitSoftware:NMSTools:c88961416f')
+ implementation('io.papermc.paperweight:paperweight-userdev:2.0.0-beta.18')
+}
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
deleted file mode 100644
index 983ee94c1..000000000
--- a/buildSrc/build.gradle.kts
+++ /dev/null
@@ -1,32 +0,0 @@
-import org.gradle.jvm.toolchain.JavaLanguageVersion
-import org.jetbrains.kotlin.gradle.dsl.JvmTarget
-
-plugins {
- kotlin("jvm") version embeddedKotlinVersion
-}
-
-java {
- toolchain {
- languageVersion.set(JavaLanguageVersion.of(21))
- }
-}
-
-kotlin {
- jvmToolchain(21)
- compilerOptions {
- jvmTarget.set(JvmTarget.JVM_21)
- }
-}
-
-repositories {
- mavenCentral()
- gradlePluginPortal()
- maven("https://jitpack.io")
-}
-
-dependencies {
- implementation("org.ow2.asm:asm:9.8")
- implementation("com.github.VolmitSoftware:NMSTools:c88961416f")
- implementation("io.papermc.paperweight:paperweight-userdev:2.0.0-beta.18")
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
-}
diff --git a/buildSrc/src/main/java/ApiGenerator.java b/buildSrc/src/main/java/ApiGenerator.java
new file mode 100644
index 000000000..4a90df4e7
--- /dev/null
+++ b/buildSrc/src/main/java/ApiGenerator.java
@@ -0,0 +1,162 @@
+import org.gradle.api.DefaultTask;
+import org.gradle.api.GradleException;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.maven.MavenPublication;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.TaskProvider;
+import org.gradle.jvm.tasks.Jar;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+
+public class ApiGenerator implements Plugin {
+ @Override
+ public void apply(Project target) {
+ target.getPlugins().apply("maven-publish");
+ TaskProvider task = target.getTasks().register("irisApi", GenerateApiTask.class);
+
+ PublishingExtension publishing = target.getExtensions().findByType(PublishingExtension.class);
+ if (publishing == null) {
+ throw new GradleException("Publishing extension not found");
+ }
+
+ publishing.getRepositories().maven(repository -> {
+ repository.setName("deployDir");
+ repository.setUrl(targetDirectory(target).toURI());
+ });
+
+ publishing.getPublications().create("maven", MavenPublication.class, publication -> {
+ publication.setGroupId(target.getName());
+ publication.setVersion(target.getVersion().toString());
+ publication.artifact(task);
+ });
+ }
+
+ public static File targetDirectory(Project project) {
+ String dir = System.getenv("DEPLOY_DIR");
+ if (dir == null) {
+ return project.getLayout().getBuildDirectory().dir("api").get().getAsFile();
+ }
+ return new File(dir);
+ }
+}
+
+abstract class GenerateApiTask extends DefaultTask {
+ private final File inputFile;
+ private final File outputFile;
+
+ public GenerateApiTask() {
+ setGroup("iris");
+ dependsOn("jar");
+ finalizedBy("publishMavenPublicationToDeployDirRepository");
+ doLast(task -> getLogger().lifecycle("The API is located at " + getOutputFile().getAbsolutePath()));
+
+ TaskProvider jarTask = getProject().getTasks().named("jar", Jar.class);
+ this.inputFile = jarTask.get().getArchiveFile().get().getAsFile();
+ this.outputFile = ApiGenerator.targetDirectory(getProject()).toPath().resolve(this.inputFile.getName()).toFile();
+ }
+
+ @InputFile
+ public File getInputFile() {
+ return inputFile;
+ }
+
+ @OutputFile
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+ @TaskAction
+ public void generate() throws IOException {
+ File parent = outputFile.getParentFile();
+ if (parent != null) {
+ parent.mkdirs();
+ }
+
+ try (JarFile jar = new JarFile(inputFile);
+ JarOutputStream out = new JarOutputStream(new FileOutputStream(outputFile))) {
+ jar.stream()
+ .parallel()
+ .filter(entry -> !entry.isDirectory())
+ .filter(entry -> entry.getName().endsWith(".class"))
+ .forEach(entry -> writeStrippedClass(jar, out, entry));
+ }
+ }
+
+ private static void writeStrippedClass(JarFile jar, JarOutputStream out, JarEntry entry) {
+ byte[] bytes;
+ try (InputStream input = jar.getInputStream(entry)) {
+ ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+ ClassVisitor visitor = new MethodClearingVisitor(writer);
+ ClassReader reader = new ClassReader(input);
+ reader.accept(visitor, 0);
+ bytes = writer.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ synchronized (out) {
+ try {
+ JarEntry outputEntry = new JarEntry(entry.getName());
+ out.putNextEntry(outputEntry);
+ out.write(bytes);
+ out.closeEntry();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
+
+class MethodClearingVisitor extends ClassVisitor {
+ public MethodClearingVisitor(ClassVisitor cv) {
+ super(Opcodes.ASM9, cv);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ return new ExceptionThrowingMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions));
+ }
+}
+
+class ExceptionThrowingMethodVisitor extends MethodVisitor {
+ public ExceptionThrowingMethodVisitor(MethodVisitor mv) {
+ super(Opcodes.ASM9, mv);
+ }
+
+ @Override
+ public void visitCode() {
+ if (mv == null) {
+ return;
+ }
+
+ mv.visitCode();
+ mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException");
+ mv.visitInsn(Opcodes.DUP);
+ mv.visitLdcInsn("Only API");
+ mv.visitMethodInsn(
+ Opcodes.INVOKESPECIAL,
+ "java/lang/IllegalStateException",
+ "",
+ "(Ljava/lang/String;)V",
+ false
+ );
+ mv.visitInsn(Opcodes.ATHROW);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+}
diff --git a/buildSrc/src/main/java/Config.java b/buildSrc/src/main/java/Config.java
new file mode 100644
index 000000000..92d71b086
--- /dev/null
+++ b/buildSrc/src/main/java/Config.java
@@ -0,0 +1,5 @@
+public class Config {
+ public int jvm = 21;
+ public NMSBinding.Type type = NMSBinding.Type.DIRECT;
+ public String version;
+}
diff --git a/buildSrc/src/main/java/NMSBinding.java b/buildSrc/src/main/java/NMSBinding.java
new file mode 100644
index 000000000..61016f589
--- /dev/null
+++ b/buildSrc/src/main/java/NMSBinding.java
@@ -0,0 +1,274 @@
+import com.volmit.nmstools.NMSToolsExtension;
+import com.volmit.nmstools.NMSToolsPlugin;
+import io.papermc.paperweight.userdev.PaperweightUser;
+import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension;
+import io.papermc.paperweight.userdev.PaperweightUserExtension;
+import io.papermc.paperweight.userdev.attribute.Obfuscation;
+import org.gradle.api.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.GradleException;
+import org.gradle.api.Named;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.attributes.Bundling;
+import org.gradle.api.attributes.Category;
+import org.gradle.api.attributes.LibraryElements;
+import org.gradle.api.attributes.Usage;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.model.ObjectFactory;
+import org.gradle.api.plugins.JavaPluginExtension;
+import org.gradle.api.plugins.ExtraPropertiesExtension;
+import org.gradle.api.provider.Provider;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.jvm.toolchain.JavaLanguageVersion;
+import org.gradle.jvm.toolchain.JavaToolchainService;
+import org.gradle.work.DisableCachingByDefault;
+
+import javax.inject.Inject;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static io.papermc.paperweight.util.constants.ConstantsKt.REOBF_CONFIG;
+
+public class NMSBinding implements Plugin {
+ private static final String NEW_LINE = System.lineSeparator();
+ private static final byte[] NEW_LINE_BYTES = NEW_LINE.getBytes(StandardCharsets.UTF_8);
+
+ @Override
+ public void apply(Project target) {
+ ExtraPropertiesExtension extra = target.getExtensions().getExtraProperties();
+ Object configValue = extra.has("nms") ? extra.get("nms") : null;
+ if (!(configValue instanceof Config)) {
+ throw new GradleException("No NMS binding configuration found");
+ }
+
+ Config config = (Config) configValue;
+ int jvm = config.jvm;
+ Type type = config.type;
+
+ if (type == Type.USER_DEV) {
+ target.getPlugins().apply(PaperweightUser.class);
+
+ PaperweightUserDependenciesExtension dependenciesExtension =
+ target.getDependencies().getExtensions().findByType(PaperweightUserDependenciesExtension.class);
+ if (dependenciesExtension != null) {
+ dependenciesExtension.paperDevBundle(config.version);
+ }
+
+ JavaPluginExtension java = target.getExtensions().findByType(JavaPluginExtension.class);
+ if (java == null) {
+ throw new GradleException("Java plugin not found");
+ }
+
+ java.getToolchain().getLanguageVersion().set(JavaLanguageVersion.of(jvm));
+ JavaToolchainService javaToolchains = target.getExtensions().getByType(JavaToolchainService.class);
+ target.getExtensions().configure(PaperweightUserExtension.class,
+ extension -> extension.getJavaLauncher().set(javaToolchains.launcherFor(java.getToolchain())));
+ } else {
+ extra.set("nmsTools.useBuildTools", type == Type.BUILD_TOOLS);
+ target.getPlugins().apply(NMSToolsPlugin.class);
+ target.getExtensions().configure(NMSToolsExtension.class, extension -> {
+ extension.getJvm().set(jvm);
+ extension.getVersion().set(config.version);
+ });
+
+ ObjectFactory objects = target.getObjects();
+ target.getConfigurations().register(REOBF_CONFIG, configuration -> {
+ configuration.setCanBeConsumed(true);
+ configuration.setCanBeResolved(false);
+ configuration.getAttributes().attribute(Usage.USAGE_ATTRIBUTE, named(objects, Usage.class, Usage.JAVA_RUNTIME));
+ configuration.getAttributes().attribute(Category.CATEGORY_ATTRIBUTE, named(objects, Category.class, Category.LIBRARY));
+ configuration.getAttributes().attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, named(objects, LibraryElements.class, LibraryElements.JAR));
+ configuration.getAttributes().attribute(Bundling.BUNDLING_ATTRIBUTE, named(objects, Bundling.class, Bundling.EXTERNAL));
+ configuration.getAttributes().attribute(Obfuscation.Companion.getOBFUSCATION_ATTRIBUTE(), named(objects, Obfuscation.class, Obfuscation.OBFUSCATED));
+ configuration.getOutgoing().artifact(target.getTasks().named("remap"));
+ });
+ }
+
+ int[] version = parseVersion(config.version);
+ int major = version[0];
+ int minor = version[1];
+ if (major <= 20 && minor <= 4) {
+ return;
+ }
+
+ target.getTasks().register("convert", ConversionTask.class, type);
+ target.getTasks().named("compileJava").configure(task -> task.dependsOn("convert"));
+ target.getRootProject().getTasks()
+ .matching(task -> task.getName().equals("prepareKotlinBuildScriptModel"))
+ .configureEach(task -> task.dependsOn(target.getPath() + ":convert"));
+ }
+
+ public static void nmsBinding(Project project, Action action) {
+ Config config = new Config();
+ action.execute(config);
+ project.getExtensions().getExtraProperties().set("nms", config);
+ project.getPlugins().apply(NMSBinding.class);
+ }
+
+ private static int[] parseVersion(String version) {
+ String trimmed = version;
+ int suffix = trimmed.indexOf('-');
+ if (suffix >= 0) {
+ trimmed = trimmed.substring(0, suffix);
+ }
+
+ String[] parts = trimmed.split("\\.");
+ return new int[]{Integer.parseInt(parts[1]), Integer.parseInt(parts[2])};
+ }
+
+ private static T named(ObjectFactory objects, Class type, String name) {
+ return objects.named(type, name);
+ }
+
+ @DisableCachingByDefault
+ public abstract static class ConversionTask extends DefaultTask {
+ private final Pattern pattern;
+ private final String replacement;
+
+ @Inject
+ public ConversionTask(Type type) {
+ setGroup("nms");
+ getInputs().property("type", type);
+
+ JavaPluginExtension java = getProject().getExtensions().findByType(JavaPluginExtension.class);
+ if (java == null) {
+ throw new GradleException("Java plugin not found");
+ }
+
+ Provider source = java.getSourceSets().named(SourceSet.MAIN_SOURCE_SET_NAME).map(SourceSet::getAllJava);
+ getInputs().files(source);
+ getOutputs().files(source);
+
+ if (type == Type.USER_DEV) {
+ this.pattern = Pattern.compile("org\\.bukkit\\.craftbukkit\\." + getProject().getName());
+ this.replacement = "org.bukkit.craftbukkit";
+ } else {
+ this.pattern = Pattern.compile("org\\.bukkit\\.craftbukkit\\.(?!" + getProject().getName() + ")");
+ this.replacement = "org.bukkit.craftbukkit." + getProject().getName() + ".";
+ }
+ }
+
+ @TaskAction
+ public void process() {
+ ExecutorService executor = Executors.newFixedThreadPool(16);
+ try {
+ Set files = getInputs().getFiles().getFiles();
+ List> futures = new ArrayList<>(files.size());
+ for (File file : files) {
+ if (!file.getName().endsWith(".java")) {
+ continue;
+ }
+ futures.add(executor.submit(() -> processFile(file)));
+ }
+
+ for (Future> future : futures) {
+ future.get();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e.getCause());
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ private void processFile(File file) {
+ List output = new ArrayList<>();
+ boolean changed = false;
+
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith("package") || line.isBlank()) {
+ output.add(line);
+ continue;
+ }
+
+ if (!line.startsWith("import")) {
+ if (!changed) {
+ return;
+ }
+
+ output.add(line);
+ continue;
+ }
+
+ Matcher matcher = pattern.matcher(line);
+ if (!matcher.find()) {
+ output.add(line);
+ continue;
+ }
+
+ output.add(matcher.replaceAll(replacement));
+ changed = true;
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ if (!changed) {
+ return;
+ }
+
+ try {
+ if (hasTrailingNewLine(file)) {
+ output.add("");
+ }
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
+ for (int i = 0; i < output.size(); i++) {
+ writer.append(output.get(i));
+ if (i + 1 < output.size()) {
+ writer.append(NEW_LINE);
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private boolean hasTrailingNewLine(File file) throws IOException {
+ if (NEW_LINE_BYTES.length == 0) {
+ return false;
+ }
+
+ try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
+ if (raf.length() < NEW_LINE_BYTES.length) {
+ return false;
+ }
+
+ byte[] bytes = new byte[NEW_LINE_BYTES.length];
+ raf.seek(raf.length() - bytes.length);
+ raf.readFully(bytes);
+ return Arrays.equals(bytes, NEW_LINE_BYTES);
+ }
+ }
+ }
+
+ public enum Type {
+ USER_DEV,
+ BUILD_TOOLS,
+ DIRECT
+ }
+}
diff --git a/buildSrc/src/main/kotlin/ApiGenerator.kt b/buildSrc/src/main/kotlin/ApiGenerator.kt
deleted file mode 100644
index 530d4c6eb..000000000
--- a/buildSrc/src/main/kotlin/ApiGenerator.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-import org.gradle.api.DefaultTask
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.publish.PublishingExtension
-import org.gradle.api.publish.maven.MavenPublication
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-import org.gradle.jvm.tasks.Jar
-import org.objectweb.asm.*
-import java.io.File
-import java.util.jar.JarFile
-import java.util.jar.JarOutputStream
-
-class ApiGenerator : Plugin {
- override fun apply(target: Project): Unit = with(target) {
- plugins.apply("maven-publish")
- val task = tasks.register("irisApi", GenerateApiTask::class.java)
- extensions.findByType(PublishingExtension::class.java)!!.apply {
- repositories.maven {
- it.name = "deployDir"
- it.url = targetDirectory.toURI()
- }
-
- publications.create("maven", MavenPublication::class.java) {
- it.groupId = name
- it.version = version.toString()
- it.artifact(task)
- }
- }
- }
-}
-
-abstract class GenerateApiTask : DefaultTask() {
- init {
- group = "iris"
- dependsOn("jar")
- finalizedBy("publishMavenPublicationToDeployDirRepository")
- doLast {
- logger.lifecycle("The API is located at ${outputFile.absolutePath}")
- }
- }
-
- @InputFile
- val inputFile: File = project.tasks
- .named("jar", Jar::class.java)
- .get()
- .archiveFile
- .get()
- .asFile
-
- @OutputFile
- val outputFile: File = project.targetDirectory.resolve(inputFile.name)
-
- @TaskAction
- fun generate() {
- JarFile(inputFile).use { jar ->
- JarOutputStream(outputFile.apply { parentFile?.mkdirs() }.outputStream()).use { out ->
- jar.stream()
- .parallel()
- .filter { !it.isDirectory }
- .filter { it.name.endsWith(".class") }
- .forEach {
- val bytes = jar.getInputStream(it).use { input ->
- val writer = ClassWriter(ClassWriter.COMPUTE_MAXS)
- val visitor = MethodClearingVisitor(writer)
- ClassReader(input).accept(visitor, 0)
- writer.toByteArray()
- }
-
- synchronized(out) {
- out.putNextEntry(it)
- out.write(bytes)
- out.closeEntry()
- }
- }
- }
- }
- }
-}
-
-val Project.targetDirectory: File get() {
- val dir = System.getenv("DEPLOY_DIR") ?: return project.layout.buildDirectory.dir("api").get().asFile
- return File(dir)
-}
-
-private class MethodClearingVisitor(
- cv: ClassVisitor
-) : ClassVisitor(Opcodes.ASM9, cv) {
-
- override fun visitMethod(
- access: Int,
- name: String?,
- descriptor: String?,
- signature: String?,
- exceptions: Array?
- ) = ExceptionThrowingMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions))
-}
-
-private class ExceptionThrowingMethodVisitor(
- mv: MethodVisitor
-) : MethodVisitor(Opcodes.ASM9, mv) {
-
- override fun visitCode() {
- if (mv == null) return
- mv.visitCode()
-
- mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException")
- mv.visitInsn(Opcodes.DUP)
- mv.visitLdcInsn("Only API")
- mv.visitMethodInsn(
- Opcodes.INVOKESPECIAL,
- "java/lang/IllegalStateException",
- "", "(Ljava/lang/String;)V", false
- )
- mv.visitInsn(Opcodes.ATHROW)
-
- mv.visitMaxs(0, 0)
- mv.visitEnd()
- }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/NMSBinding.kt b/buildSrc/src/main/kotlin/NMSBinding.kt
deleted file mode 100644
index 14a7c3cca..000000000
--- a/buildSrc/src/main/kotlin/NMSBinding.kt
+++ /dev/null
@@ -1,182 +0,0 @@
-import NMSBinding.Type
-import com.volmit.nmstools.NMSToolsExtension
-import com.volmit.nmstools.NMSToolsPlugin
-import io.papermc.paperweight.userdev.PaperweightUser
-import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension
-import io.papermc.paperweight.userdev.PaperweightUserExtension
-import io.papermc.paperweight.userdev.attribute.Obfuscation
-import io.papermc.paperweight.util.constants.REOBF_CONFIG
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import org.gradle.api.*
-import org.gradle.api.attributes.Bundling
-import org.gradle.api.attributes.Category
-import org.gradle.api.attributes.LibraryElements
-import org.gradle.api.attributes.Usage
-import org.gradle.api.model.ObjectFactory
-import org.gradle.api.plugins.JavaPluginExtension
-import org.gradle.api.tasks.TaskAction
-import org.gradle.internal.extensions.core.extra
-import org.gradle.jvm.toolchain.JavaLanguageVersion
-import org.gradle.jvm.toolchain.JavaToolchainService
-import org.gradle.work.DisableCachingByDefault
-import java.io.RandomAccessFile
-import javax.inject.Inject
-
-class NMSBinding : Plugin {
- override fun apply(target: Project): Unit = with(target) {
- val config = extra["nms"] as? Config ?: throw GradleException("No NMS binding configuration found")
- val jvm = config.jvm
- val type = config.type
-
- if (type == Type.USER_DEV) {
- plugins.apply(PaperweightUser::class.java)
- dependencies.extensions.findByType(PaperweightUserDependenciesExtension::class.java)
- ?.paperDevBundle(config.version)
-
- val java = extensions.findByType(JavaPluginExtension::class.java) ?: throw GradleException("Java plugin not found")
- java.toolchain.languageVersion.set(JavaLanguageVersion.of(jvm))
-
- val javaToolchains = project.extensions.getByType(JavaToolchainService::class.java) ?: throw GradleException("Java toolchain service not found")
- extensions.configure(PaperweightUserExtension::class.java) {
- it.javaLauncher.set(javaToolchains.launcherFor(java.toolchain))
- }
- } else {
- extra["nmsTools.useBuildTools"] = type == Type.BUILD_TOOLS
- plugins.apply(NMSToolsPlugin::class.java)
- extensions.configure(NMSToolsExtension::class.java) {
- it.jvm.set(jvm)
- it.version.set(config.version)
- }
-
- configurations.register(REOBF_CONFIG) { conf ->
- conf.isCanBeConsumed = true
- conf.isCanBeResolved = false
- conf.attributes {
- it.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
- it.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
- it.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
- it.attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
- it.attribute(Obfuscation.OBFUSCATION_ATTRIBUTE, objects.named(Obfuscation.OBFUSCATED))
- }
- conf.outgoing.artifact(tasks.named("remap"))
- }
- }
-
- val (major, minor) = config.version.parseVersion()
- if (major <= 20 && minor <= 4) return@with
- tasks.register("convert", ConversionTask::class.java, type)
- tasks.named("compileJava") { it.dependsOn("convert") }
- rootProject.tasks.named("prepareKotlinBuildScriptModel") { it.dependsOn("$path:convert") }
- }
-
- @DisableCachingByDefault
- abstract class ConversionTask @Inject constructor(type: Type) : DefaultTask() {
- private val pattern: Regex
- private val replacement: String
-
- init {
- group = "nms"
- inputs.property("type", type)
- val java = project.extensions.findByType(JavaPluginExtension::class.java) ?: throw GradleException("Java plugin not found")
- val source = java.sourceSets.named("main").map { it.allJava }
- inputs.files(source)
- outputs.files(source)
-
- if (type == Type.USER_DEV) {
- pattern = "org\\.bukkit\\.craftbukkit\\.${project.name}".toRegex()
- replacement = "org.bukkit.craftbukkit"
- } else {
- pattern = "org\\.bukkit\\.craftbukkit\\.(?!${project.name})".toRegex()
- replacement = "org.bukkit.craftbukkit.${project.name}."
- }
- }
-
- @TaskAction
- fun process() {
- val dispatcher = Dispatchers.IO.limitedParallelism(16)
- runBlocking {
- for (file in inputs.files) {
- if (file.extension !in listOf("java"))
- continue
-
- launch(dispatcher) {
- val output = ArrayList()
- var changed = false
-
- file.bufferedReader().use {
- for (line in it.lines()) {
- if (line.startsWith("package") || line.isBlank()) {
- output += line
- continue
- }
-
- if (!line.startsWith("import")) {
- if (!changed) return@launch
- else {
- output += line
- continue
- }
- }
-
- if (!line.contains(pattern)) {
- output += line
- continue
- }
-
- output += line.replace(pattern, replacement)
- changed = true
- }
- }
-
- if (changed) {
- RandomAccessFile(file, "r").use { raf ->
- val bytes = ByteArray(NEW_LINE_BYTES.size)
- raf.seek(raf.length() - bytes.size)
- raf.readFully(bytes)
- if (bytes.contentEquals(NEW_LINE_BYTES))
- output += ""
- }
-
- file.writer().use {
- val iterator = output.iterator()
- while (iterator.hasNext()) {
- it.append(iterator.next())
- if (iterator.hasNext())
- it.append(NEW_LINE)
- }
- }
- }
- }
- }
- }
- }
- }
-
- enum class Type {
- USER_DEV,
- BUILD_TOOLS,
- DIRECT,
- }
-}
-
-private val NEW_LINE = System.lineSeparator()
-private val NEW_LINE_BYTES = NEW_LINE.encodeToByteArray()
-private fun String.parseVersion() = substringBefore('-').split(".").let {
- it[1].toInt() to it[2].toInt()
-}
-
-class Config(
- var jvm: Int = 21,
- var type: Type = Type.DIRECT
-) {
- lateinit var version: String
-}
-
-fun Project.nmsBinding(action: Config.() -> Unit) {
- extra["nms"] = Config().apply(action)
- plugins.apply(NMSBinding::class.java)
-}
-
-private inline fun ObjectFactory.named(name: String): T = named(T::class.java, name)
diff --git a/core/agent/build.gradle b/core/agent/build.gradle
new file mode 100644
index 000000000..baff85547
--- /dev/null
+++ b/core/agent/build.gradle
@@ -0,0 +1,14 @@
+import org.gradle.jvm.tasks.Jar
+
+plugins {
+ id 'java'
+}
+
+tasks.named('jar', Jar).configure {
+ manifest.attributes(
+ 'Agent-Class': 'art.arcane.iris.util.project.agent.Installer',
+ 'Premain-Class': 'art.arcane.iris.util.project.agent.Installer',
+ 'Can-Redefine-Classes': true,
+ 'Can-Retransform-Classes': true
+ )
+}
diff --git a/core/agent/build.gradle.kts b/core/agent/build.gradle.kts
deleted file mode 100644
index 0421e533b..000000000
--- a/core/agent/build.gradle.kts
+++ /dev/null
@@ -1,12 +0,0 @@
-plugins {
- java
-}
-
-tasks.jar {
- manifest.attributes(
- "Agent-Class" to "art.arcane.iris.util.project.agent.Installer",
- "Premain-Class" to "art.arcane.iris.util.project.agent.Installer",
- "Can-Redefine-Classes" to true,
- "Can-Retransform-Classes" to true
- )
-}
\ No newline at end of file
diff --git a/core/build.gradle b/core/build.gradle
new file mode 100644
index 000000000..47b946d91
--- /dev/null
+++ b/core/build.gradle
@@ -0,0 +1,257 @@
+import io.github.slimjar.resolver.data.Mirror
+import org.ajoberstar.grgit.Grgit
+import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.compile.JavaCompile
+import org.gradle.jvm.tasks.Jar
+import org.gradle.jvm.toolchain.JavaLanguageVersion
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
+import java.net.URI
+
+/*
+ * Iris is a World Generator for Minecraft Bukkit Servers
+ * Copyright (c) 2021 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+plugins {
+ id 'java'
+ id 'java-library'
+ alias(libs.plugins.shadow)
+ alias(libs.plugins.sentry)
+ alias(libs.plugins.slimjar)
+ alias(libs.plugins.grgit)
+ alias(libs.plugins.kotlin.jvm)
+ alias(libs.plugins.kotlin.lombok)
+}
+
+def apiVersion = '1.21'
+def mainClass = 'art.arcane.iris.Iris'
+def lib = 'art.arcane.iris.util'
+String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
+ .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT')
+ .get()
+
+/**
+ * Dependencies.
+ *
+ * Provided or classpath dependencies are not shaded and are available on the runtime classpath
+ *
+ * Shaded dependencies are not available at runtime, nor are they available on mvn central so they
+ * need to be shaded into the jar (increasing binary size)
+ *
+ * Dynamically loaded dependencies are defined in the plugin.yml (updating these must be updated in the
+ * plugin.yml also, otherwise they wont be available). These do not increase binary size). Only declare
+ * these dependencies if they are available on mvn central.
+ */
+dependencies {
+ // Provided or Classpath
+ compileOnly(libs.spigot)
+ compileOnly(libs.log4j.api)
+ compileOnly(libs.log4j.core)
+
+ // Third Party Integrations
+ compileOnly(libs.nexo)
+ compileOnly(libs.itemsadder)
+ compileOnly(libs.placeholderApi)
+ compileOnly(libs.score)
+ compileOnly(libs.mmoitems)
+ compileOnly(libs.ecoitems)
+ compileOnly(libs.mythic)
+ compileOnly(libs.mythicChrucible)
+ compileOnly(libs.kgenerators) {
+ transitive = false
+ }
+ compileOnly(libs.multiverseCore)
+
+ // Shaded
+ implementation('de.crazydev22.slimjar.helper:spigot:2.1.5')
+ implementation(volmLibCoordinate) {
+ changing = true
+ transitive = false
+ }
+
+ // Dynamically Loaded
+ slim(libs.paralithic)
+ slim(libs.paperlib)
+ slim(libs.adventure.api)
+ slim(libs.adventure.minimessage)
+ slim(libs.adventure.platform)
+ slim(libs.bstats)
+ slim(libs.sentry)
+
+ slim(libs.commons.io)
+ slim(libs.commons.lang)
+ slim(libs.commons.lang3)
+ slim(libs.commons.math3)
+ slim(libs.oshi)
+ slim(libs.lz4)
+ slim(libs.fastutil)
+ slim(libs.lru)
+ slim(libs.zip)
+ slim(libs.gson)
+ slim(libs.asm)
+ slim(libs.caffeine)
+ slim(libs.byteBuddy.core)
+ slim(libs.byteBuddy.agent)
+ slim(libs.dom4j)
+ slim(libs.jaxen)
+
+ // Script Engine
+ slim(libs.kotlin.stdlib)
+ slim(libs.kotlin.coroutines)
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+}
+
+kotlin {
+ jvmToolchain(21)
+ compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_21)
+ }
+}
+
+sentry {
+ url = 'http://sentry.volmit.com:8080'
+ autoInstallation.enabled = false
+ includeSourceContext = true
+
+ org = 'sentry'
+ projectName = 'iris'
+ authToken = findProperty('sentry.auth.token') as String ?: System.getenv('SENTRY_AUTH_TOKEN')
+}
+
+slimJar {
+ mirrors = [
+ new Mirror(
+ URI.create('https://maven-central.storage-download.googleapis.com/maven2').toURL(),
+ URI.create('https://repo.maven.apache.org/maven2/').toURL()
+ )
+ ]
+
+ relocate('com.dfsek.paralithic', "${lib}.paralithic")
+ relocate('io.papermc.lib', "${lib}.paper")
+ relocate('net.kyori', "${lib}.kyori")
+ relocate('org.bstats', "${lib}.metrics")
+ relocate('io.sentry', "${lib}.sentry")
+ relocate('org.apache.maven', "${lib}.maven")
+ relocate('org.codehaus.plexus', "${lib}.plexus")
+ relocate('org.eclipse.sisu', "${lib}.sisu")
+ relocate('org.eclipse.aether', "${lib}.aether")
+ relocate('com.google.inject', "${lib}.guice")
+ relocate('org.dom4j', "${lib}.dom4j")
+ relocate('org.jaxen', "${lib}.jaxen")
+ relocate('com.github.benmanes.caffeine', "${lib}.caffeine")
+}
+
+def embeddedAgentJar = project(':core:agent').tasks.named('jar', Jar)
+def templateSource = file('src/main/templates')
+def templateDest = layout.buildDirectory.dir('generated/sources/templates')
+def generateTemplates = tasks.register('generateTemplates', Copy) {
+ inputs.properties([
+ environment: providers.provider {
+ if (project.hasProperty('release')) {
+ return 'production'
+ }
+ if (project.hasProperty('argghh')) {
+ return 'Argghh!'
+ }
+ return 'development'
+ },
+ commit: providers.provider {
+ String commitId = null
+ Exception failure = null
+ try {
+ commitId = project.extensions.getByType(Grgit).head().id
+ } catch (Exception ex) {
+ failure = ex
+ }
+
+ if (commitId != null && commitId.length() == 40) {
+ return commitId
+ }
+
+ logger.error('Git commit hash not found', failure)
+ return 'unknown'
+ },
+ ])
+
+ from(templateSource)
+ into(templateDest)
+ rename { String fileName -> "art/arcane/iris/${fileName}" }
+ expand(inputs.properties)
+}
+
+tasks.named('compileJava', JavaCompile).configure {
+ /**
+ * We need parameter meta for the decree command system
+ */
+ options.compilerArgs.add('-parameters')
+ options.encoding = 'UTF-8'
+ options.debugOptions.debugLevel = 'none'
+}
+
+tasks.named('processResources').configure {
+ /**
+ * Expand properties into plugin yml
+ */
+ def pluginProperties = [
+ name : rootProject.name,
+ version : rootProject.version,
+ apiVersion: apiVersion,
+ main : mainClass,
+ ]
+ inputs.properties(pluginProperties)
+ filesMatching('**/plugin.yml') {
+ expand(pluginProperties)
+ }
+}
+
+tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar).configure {
+ dependsOn(embeddedAgentJar)
+ mergeServiceFiles()
+ //minimize()
+ relocate('io.github.slimjar', "${lib}.slimjar")
+ exclude('modules/loader-agent.isolated-jar')
+ from(embeddedAgentJar.map { it.archiveFile }) {
+ rename { String ignored -> 'agent.jar' }
+ }
+}
+
+tasks.named('sentryCollectSourcesJava').configure {
+ dependsOn(generateTemplates)
+}
+
+tasks.named('generateSentryBundleIdJava').configure {
+ dependsOn(generateTemplates)
+}
+
+rootProject.tasks.matching {
+ it.name == 'prepareKotlinBuildScriptModel'
+}.configureEach {
+ dependsOn(generateTemplates)
+}
+
+sourceSets {
+ main {
+ java {
+ srcDir(generateTemplates.map { it.outputs })
+ }
+ }
+}
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
deleted file mode 100644
index e38516e62..000000000
--- a/core/build.gradle.kts
+++ /dev/null
@@ -1,246 +0,0 @@
-import io.github.slimjar.func.slimjarHelper
-import io.github.slimjar.resolver.data.Mirror
-import org.ajoberstar.grgit.Grgit
-import org.gradle.jvm.toolchain.JavaLanguageVersion
-import org.gradle.jvm.tasks.Jar
-import org.jetbrains.kotlin.gradle.dsl.JvmTarget
-import java.net.URI
-
-/*
- * Iris is a World Generator for Minecraft Bukkit Servers
- * Copyright (c) 2021 Arcane Arts (Volmit Software)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-plugins {
- java
- `java-library`
- alias(libs.plugins.shadow)
- alias(libs.plugins.sentry)
- alias(libs.plugins.slimjar)
- alias(libs.plugins.grgit)
- alias(libs.plugins.kotlin.jvm)
- alias(libs.plugins.kotlin.lombok)
-}
-
-val apiVersion = "1.21"
-val main = "art.arcane.iris.Iris"
-val lib = "art.arcane.iris.util"
-val volmLibCoordinate: String = providers.gradleProperty("volmLibCoordinate")
- .orElse("com.github.VolmitSoftware:VolmLib:master-SNAPSHOT")
- .get()
-
-/**
- * Dependencies.
- *
- * Provided or classpath dependencies are not shaded and are available on the runtime classpath
- *
- * Shaded dependencies are not available at runtime, nor are they available on mvn central so they
- * need to be shaded into the jar (increasing binary size)
- *
- * Dynamically loaded dependencies are defined in the plugin.yml (updating these must be updated in the
- * plugin.yml also, otherwise they wont be available). These do not increase binary size). Only declare
- * these dependencies if they are available on mvn central.
- */
-dependencies {
- // Provided or Classpath
- compileOnly(libs.spigot)
- compileOnly(libs.log4j.api)
- compileOnly(libs.log4j.core)
-
- // Third Party Integrations
- compileOnly(libs.nexo)
- compileOnly(libs.itemsadder)
- compileOnly(libs.placeholderApi)
- compileOnly(libs.score)
- compileOnly(libs.mmoitems)
- compileOnly(libs.ecoitems)
- compileOnly(libs.mythic)
- compileOnly(libs.mythicChrucible)
- compileOnly(libs.kgenerators) {
- isTransitive = false
- }
- compileOnly(libs.multiverseCore)
-
- // Shaded
- implementation(slimjarHelper("spigot"))
- implementation(volmLibCoordinate) {
- isChanging = true
- isTransitive = false
- }
-
- // Dynamically Loaded
- slim(libs.paralithic)
- slim(libs.paperlib)
- slim(libs.adventure.api)
- slim(libs.adventure.minimessage)
- slim(libs.adventure.platform)
- slim(libs.bstats)
- slim(libs.sentry)
-
- slim(libs.commons.io)
- slim(libs.commons.lang)
- slim(libs.commons.lang3)
- slim(libs.commons.math3)
- slim(libs.oshi)
- slim(libs.lz4)
- slim(libs.fastutil)
- slim(libs.lru)
- slim(libs.zip)
- slim(libs.gson)
- slim(libs.asm)
- slim(libs.caffeine)
- slim(libs.byteBuddy.core)
- slim(libs.byteBuddy.agent)
- slim(libs.dom4j)
- slim(libs.jaxen)
-
- // Script Engine
- slim(libs.kotlin.stdlib)
- slim(libs.kotlin.coroutines)
- slim(libs.kotlin.scripting.common)
- slim(libs.kotlin.scripting.jvm)
- slim(libs.kotlin.scripting.jvm.host)
- slim(libs.kotlin.scripting.dependencies.maven) {
- constraints {
- slim(libs.mavenCore)
- }
- }
-}
-
-java {
- disableAutoTargetJvm()
- toolchain {
- languageVersion.set(JavaLanguageVersion.of(21))
- }
-}
-
-kotlin {
- jvmToolchain(21)
- compilerOptions {
- jvmTarget.set(JvmTarget.JVM_21)
- }
-}
-
-sentry {
- url = "http://sentry.volmit.com:8080"
- autoInstallation.enabled = false
- includeSourceContext = true
-
- org = "sentry"
- projectName = "iris"
- authToken = findProperty("sentry.auth.token") as String? ?: System.getenv("SENTRY_AUTH_TOKEN")
-}
-
-slimJar {
- mirrors = listOf(Mirror(
- URI.create("https://maven-central.storage-download.googleapis.com/maven2").toURL(),
- URI.create("https://repo.maven.apache.org/maven2/").toURL()
- ))
-
- relocate("com.dfsek.paralithic", "$lib.paralithic")
- relocate("io.papermc.lib", "$lib.paper")
- relocate("net.kyori", "$lib.kyori")
- relocate("org.bstats", "$lib.metrics")
- relocate("io.sentry", "$lib.sentry")
- relocate("org.apache.maven", "$lib.maven")
- relocate("org.codehaus.plexus", "$lib.plexus")
- relocate("org.eclipse.sisu", "$lib.sisu")
- relocate("org.eclipse.aether", "$lib.aether")
- relocate("com.google.inject", "$lib.guice")
- relocate("org.dom4j", "$lib.dom4j")
- relocate("org.jaxen", "$lib.jaxen")
- relocate("com.github.benmanes.caffeine", "$lib.caffeine")
-}
-
-val embeddedAgentJar = project(":core:agent").tasks.named("jar")
-
-tasks {
- /**
- * We need parameter meta for the decree command system
- */
- compileJava {
- options.compilerArgs.add("-parameters")
- options.encoding = "UTF-8"
- options.debugOptions.debugLevel = "none"
- }
-
- /**
- * 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 {
- dependsOn(embeddedAgentJar)
- mergeServiceFiles()
- //minimize()
- relocate("io.github.slimjar", "$lib.slimjar")
- exclude("modules/loader-agent.isolated-jar")
- from(embeddedAgentJar.map { it.archiveFile }) {
- rename { "agent.jar" }
- }
- }
-
- sentryCollectSourcesJava {
- dependsOn(generateTemplates)
- }
-}
-
-val templateSource = file("src/main/templates")
-val templateDest = layout.buildDirectory.dir("generated/sources/templates")!!
-val generateTemplates = tasks.register("generateTemplates") {
- inputs.properties(
- "environment" to when {
- project.hasProperty("release") -> "production"
- project.hasProperty("argghh") -> "Argghh!"
- else -> "development"
- },
- "commit" to provider {
- val res = runCatching { project.extensions.getByType().head().id }
- res.getOrDefault("")
- .takeIf { it.length == 40 } ?: run {
- this.logger.error("Git commit hash not found", res.exceptionOrNull())
- "unknown"
- }
- },
- )
-
- from(templateSource)
- into(templateDest)
- rename { "art/arcane/iris/$it" }
- expand(inputs.properties)
-}
-
-tasks.generateSentryBundleIdJava {
- dependsOn(generateTemplates)
-}
-
-rootProject.tasks.named("prepareKotlinBuildScriptModel") {
- dependsOn(generateTemplates)
-}
-
-sourceSets.main {
- java.srcDir(generateTemplates.map { it.outputs })
-}
diff --git a/core/src/main/java/art/arcane/iris/core/ExternalDataPackPipeline.java b/core/src/main/java/art/arcane/iris/core/ExternalDataPackPipeline.java
index f707afbc9..753c167c9 100644
--- a/core/src/main/java/art/arcane/iris/core/ExternalDataPackPipeline.java
+++ b/core/src/main/java/art/arcane/iris/core/ExternalDataPackPipeline.java
@@ -12,6 +12,7 @@ import art.arcane.iris.engine.object.IrisExternalDatapackReplaceTargets;
import art.arcane.iris.engine.object.IrisExternalDatapackStructureAlias;
import art.arcane.iris.engine.object.IrisExternalDatapackStructureSetAlias;
import art.arcane.iris.engine.object.IrisExternalDatapackStructurePatch;
+import art.arcane.iris.engine.object.IrisExternalDatapackTemplateAlias;
import art.arcane.iris.engine.object.TileData;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.common.math.Vector3i;
@@ -436,6 +437,9 @@ public final class ExternalDataPackPipeline {
+ ", installedDatapacks=" + projectionResult.installedDatapacks()
+ ", installedAssets=" + projectionResult.installedAssets()
+ ", syntheticStructureSets=" + projectionResult.syntheticStructureSets()
+ + ", templateAliasesApplied=" + projectionResult.templateAliasesApplied()
+ + ", emptyElementConversions=" + projectionResult.emptyElementConversions()
+ + ", unresolvedTemplateRefs=" + projectionResult.unresolvedTemplateRefs()
+ ", success=true");
} else {
Iris.warn("External datapack projection: id=" + request.id()
@@ -445,6 +449,9 @@ public final class ExternalDataPackPipeline {
+ ", installedDatapacks=" + projectionResult.installedDatapacks()
+ ", installedAssets=" + projectionResult.installedAssets()
+ ", syntheticStructureSets=" + projectionResult.syntheticStructureSets()
+ + ", templateAliasesApplied=" + projectionResult.templateAliasesApplied()
+ + ", emptyElementConversions=" + projectionResult.emptyElementConversions()
+ + ", unresolvedTemplateRefs=" + projectionResult.unresolvedTemplateRefs()
+ ", success=false"
+ ", reason=" + projectionResult.error());
}
@@ -1184,7 +1191,7 @@ public final class ExternalDataPackPipeline {
if (request.required()) {
return ProjectionResult.failure(managedName, "no target world datapack folder is available for required external datapack request");
}
- return ProjectionResult.success(managedName, 0, 0, Set.copyOf(request.resolvedLocateStructures()), 0, Set.of());
+ return ProjectionResult.success(managedName, 0, 0, Set.copyOf(request.resolvedLocateStructures()), 0, Set.of(), 0, 0, 0);
}
ProjectionAssetSummary projectionAssetSummary;
@@ -1203,7 +1210,10 @@ public final class ExternalDataPackPipeline {
0,
projectionAssetSummary.resolvedLocateStructures(),
projectionAssetSummary.syntheticStructureSets(),
- projectionAssetSummary.projectedStructureKeys()
+ projectionAssetSummary.projectedStructureKeys(),
+ projectionAssetSummary.templateAliasesApplied(),
+ projectionAssetSummary.emptyElementConversions(),
+ projectionAssetSummary.unresolvedTemplateRefs()
);
}
@@ -1245,7 +1255,10 @@ public final class ExternalDataPackPipeline {
installedAssets,
projectionAssetSummary.resolvedLocateStructures(),
projectionAssetSummary.syntheticStructureSets(),
- projectionAssetSummary.projectedStructureKeys()
+ projectionAssetSummary.projectedStructureKeys(),
+ projectionAssetSummary.templateAliasesApplied(),
+ projectionAssetSummary.emptyElementConversions(),
+ projectionAssetSummary.unresolvedTemplateRefs()
);
}
@@ -1257,7 +1270,7 @@ public final class ExternalDataPackPipeline {
List inputAssets = projectionSelection.assets();
if (inputAssets.isEmpty()) {
- return new ProjectionAssetSummary(List.of(), Set.copyOf(request.resolvedLocateStructures()), 0, Set.of());
+ return new ProjectionAssetSummary(List.of(), Set.copyOf(request.resolvedLocateStructures()), 0, Set.of(), 0, 0, 0);
}
int selectedStructureNbtCount = 0;
for (ProjectionInputAsset inputAsset : inputAssets) {
@@ -1290,6 +1303,9 @@ public final class ExternalDataPackPipeline {
LinkedHashSet remappedStructureKeys = new LinkedHashSet<>();
LinkedHashSet projectedStructureKeys = new LinkedHashSet<>();
LinkedHashSet structureSetReferences = new LinkedHashSet<>();
+ int templateAliasesApplied = 0;
+ int emptyElementConversions = 0;
+ LinkedHashSet unresolvedTemplateRefs = new LinkedHashSet<>();
LinkedHashSet writtenPaths = new LinkedHashSet<>();
ArrayList outputAssets = new ArrayList<>();
int projectedCanonicalStructureNbtCount = 0;
@@ -1318,6 +1334,13 @@ public final class ExternalDataPackPipeline {
JSONObject root = new JSONObject(new String(outputBytes, StandardCharsets.UTF_8));
rewriteJsonValues(root, remapStringValues);
+ if (projectedEntry.type() == ProjectedEntryType.TEMPLATE_POOL && !request.templateAliases().isEmpty()) {
+ TemplateAliasRewriteResult templateAliasRewriteResult = applyTemplateAliasesToTemplatePool(root, request.templateAliases());
+ templateAliasesApplied += templateAliasRewriteResult.appliedCount();
+ emptyElementConversions += templateAliasRewriteResult.emptyConversions();
+ unresolvedTemplateRefs.addAll(templateAliasRewriteResult.unresolvedReferences());
+ }
+
if (projectedEntry.type() == ProjectedEntryType.STRUCTURE) {
Integer startHeightAbsolute = getPatchedStartHeightAbsolute(projectedEntry, request);
if (startHeightAbsolute == null && remappedKey != null) {
@@ -1395,17 +1418,26 @@ public final class ExternalDataPackPipeline {
throw new IOException("Required external datapack projection produced no canonical structure template outputs (data/*/structure/*.nbt).");
}
+ if (request.required() && !unresolvedTemplateRefs.isEmpty()) {
+ throw new IOException("Required external datapack template alias rewrite unresolved reference(s): "
+ + summarizeMissingSeededTargets(unresolvedTemplateRefs));
+ }
+
if (request.replaceVanilla() && !request.alongsideMode()) {
int directTargetCount = projectionSelection.directResolvedTargets().size();
int aliasTargetCount = projectionSelection.aliasResolvedTargets().size();
int projectedStructureCount = projectedStructureKeys.size();
int projectedTemplateCount = projectedCanonicalStructureNbtCount;
+ int unresolvedTemplateRefCount = unresolvedTemplateRefs.size();
if (request.required()) {
Iris.info("External datapack strict replace validation: id=" + request.id()
+ ", directTargets=" + directTargetCount
+ ", aliasTargets=" + aliasTargetCount
+ ", projectedStructures=" + projectedStructureCount
+ ", templates=" + projectedTemplateCount
+ + ", templateAliasesApplied=" + templateAliasesApplied
+ + ", emptyElementConversions=" + emptyElementConversions
+ + ", unresolvedTemplateRefs=" + unresolvedTemplateRefCount
+ ", missingTargets=0");
} else {
Iris.verbose("External datapack strict replace validation: id=" + request.id()
@@ -1413,11 +1445,22 @@ public final class ExternalDataPackPipeline {
+ ", aliasTargets=" + aliasTargetCount
+ ", projectedStructures=" + projectedStructureCount
+ ", templates=" + projectedTemplateCount
+ + ", templateAliasesApplied=" + templateAliasesApplied
+ + ", emptyElementConversions=" + emptyElementConversions
+ + ", unresolvedTemplateRefs=" + unresolvedTemplateRefCount
+ ", missingTargets=0");
}
}
- return new ProjectionAssetSummary(outputAssets, Set.copyOf(resolvedLocateStructures), syntheticStructureSets, Set.copyOf(projectedStructureKeys));
+ return new ProjectionAssetSummary(
+ outputAssets,
+ Set.copyOf(resolvedLocateStructures),
+ syntheticStructureSets,
+ Set.copyOf(projectedStructureKeys),
+ templateAliasesApplied,
+ emptyElementConversions,
+ unresolvedTemplateRefs.size()
+ );
}
private static ProjectionSelection readProjectedEntries(File source, DatapackRequest request) throws IOException {
@@ -2492,6 +2535,75 @@ public final class ExternalDataPackPipeline {
return value;
}
+ private static TemplateAliasRewriteResult applyTemplateAliasesToTemplatePool(JSONObject root, Map templateAliases) {
+ if (root == null || templateAliases == null || templateAliases.isEmpty()) {
+ return TemplateAliasRewriteResult.empty();
+ }
+
+ JSONArray elements = root.optJSONArray("elements");
+ if (elements == null || elements.length() <= 0) {
+ return TemplateAliasRewriteResult.empty();
+ }
+
+ LinkedHashSet referenced = new LinkedHashSet<>();
+ LinkedHashSet rewritten = new LinkedHashSet<>();
+ LinkedHashSet unresolved = new LinkedHashSet<>();
+ int applied = 0;
+ int emptyConversions = 0;
+
+ for (int i = 0; i < elements.length(); i++) {
+ JSONObject poolElement = elements.optJSONObject(i);
+ if (poolElement == null) {
+ continue;
+ }
+
+ JSONObject elementData = poolElement.optJSONObject("element");
+ if (elementData == null) {
+ continue;
+ }
+
+ String location = elementData.optString("location", "");
+ if (location.isBlank()) {
+ continue;
+ }
+
+ String normalizedLocation = normalizeResourceKey("minecraft", location, "structure/", "structures/");
+ if (normalizedLocation == null || normalizedLocation.isBlank()) {
+ continue;
+ }
+
+ String aliasTarget = templateAliases.get(normalizedLocation);
+ if (aliasTarget == null || aliasTarget.isBlank()) {
+ continue;
+ }
+
+ referenced.add(normalizedLocation);
+ try {
+ if ("minecraft:empty".equalsIgnoreCase(aliasTarget)) {
+ JSONObject emptyElement = new JSONObject();
+ emptyElement.put("element_type", "minecraft:empty_pool_element");
+ poolElement.put("element", emptyElement);
+ emptyConversions++;
+ } else {
+ elementData.put("location", aliasTarget);
+ poolElement.put("element", elementData);
+ }
+ applied++;
+ rewritten.add(normalizedLocation);
+ } catch (Throwable ignored) {
+ unresolved.add(normalizedLocation);
+ }
+ }
+
+ for (String reference : referenced) {
+ if (!rewritten.contains(reference)) {
+ unresolved.add(reference);
+ }
+ }
+
+ return new TemplateAliasRewriteResult(applied, emptyConversions, unresolved);
+ }
+
private static Set readStructureSetReferences(JSONObject root) {
LinkedHashSet references = new LinkedHashSet<>();
if (root == null) {
@@ -4002,6 +4114,7 @@ public final class ExternalDataPackPipeline {
Set structureSets,
Map> structureAliases,
Map structureSetAliases,
+ Map templateAliases,
Set configuredFeatures,
Set placedFeatures,
Set templatePools,
@@ -4024,6 +4137,7 @@ public final class ExternalDataPackPipeline {
IrisExternalDatapackReplaceTargets replaceTargets,
KList structureAliases,
KList structureSetAliases,
+ KList templateAliases,
KList structurePatches
) {
this(
@@ -4037,6 +4151,7 @@ public final class ExternalDataPackPipeline {
replaceTargets,
structureAliases,
structureSetAliases,
+ templateAliases,
structurePatches,
Set.of(),
"dimension-root",
@@ -4056,6 +4171,7 @@ public final class ExternalDataPackPipeline {
IrisExternalDatapackReplaceTargets replaceTargets,
KList structureAliases,
KList structureSetAliases,
+ KList templateAliases,
KList structurePatches,
Set forcedBiomeKeys,
String scopeKey,
@@ -4074,6 +4190,7 @@ public final class ExternalDataPackPipeline {
normalizeTargets(replaceTargets == null ? null : replaceTargets.getStructureSets(), "worldgen/structure_set/"),
normalizeStructureAliases(structureAliases),
normalizeStructureSetAliases(structureSetAliases),
+ normalizeTemplateAliases(templateAliases),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getConfiguredFeatures(), "worldgen/configured_feature/"),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getPlacedFeatures(), "worldgen/placed_feature/"),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getTemplatePools(), "worldgen/template_pool/"),
@@ -4099,6 +4216,7 @@ public final class ExternalDataPackPipeline {
structureSets = immutableSet(structureSets);
structureAliases = immutableStructureAliasMap(structureAliases);
structureSetAliases = immutableAliasMap(structureSetAliases);
+ templateAliases = immutableAliasMap(templateAliases);
configuredFeatures = immutableSet(configuredFeatures);
placedFeatures = immutableSet(placedFeatures);
templatePools = immutableSet(templatePools);
@@ -4145,6 +4263,7 @@ public final class ExternalDataPackPipeline {
union(structureSets, other.structureSets),
unionStructureAliases(structureAliases, other.structureAliases),
unionAliases(structureSetAliases, other.structureSetAliases),
+ unionAliases(templateAliases, other.templateAliases),
union(configuredFeatures, other.configuredFeatures),
union(placedFeatures, other.placedFeatures),
union(templatePools, other.templatePools),
@@ -4274,6 +4393,40 @@ public final class ExternalDataPackPipeline {
return normalized;
}
+ private static Map normalizeTemplateAliases(KList aliases) {
+ LinkedHashMap normalized = new LinkedHashMap<>();
+ if (aliases == null) {
+ return normalized;
+ }
+
+ for (IrisExternalDatapackTemplateAlias alias : aliases) {
+ if (alias == null || !alias.isEnabled()) {
+ continue;
+ }
+
+ String from = normalizeResourceKey("minecraft", alias.getFrom(), "structure/", "structures/");
+ if (from == null || from.isBlank()) {
+ continue;
+ }
+
+ String toRaw = alias.getTo() == null ? "" : alias.getTo().trim();
+ String to;
+ if ("minecraft:empty".equalsIgnoreCase(toRaw)) {
+ to = "minecraft:empty";
+ } else {
+ to = normalizeResourceKey("minecraft", toRaw, "structure/", "structures/");
+ }
+
+ if (to == null || to.isBlank()) {
+ continue;
+ }
+
+ normalized.put(from, to);
+ }
+
+ return normalized;
+ }
+
private static Set immutableSet(Set values) {
LinkedHashSet copy = new LinkedHashSet<>();
if (values != null) {
@@ -4666,6 +4819,9 @@ public final class ExternalDataPackPipeline {
Set resolvedLocateStructures,
int syntheticStructureSets,
Set projectedStructureKeys,
+ int templateAliasesApplied,
+ int emptyElementConversions,
+ int unresolvedTemplateRefs,
String managedName,
String error
) {
@@ -4691,6 +4847,9 @@ public final class ExternalDataPackPipeline {
}
projectedStructureKeys = Set.copyOf(normalizedProjected);
syntheticStructureSets = Math.max(0, syntheticStructureSets);
+ templateAliasesApplied = Math.max(0, templateAliasesApplied);
+ emptyElementConversions = Math.max(0, emptyElementConversions);
+ unresolvedTemplateRefs = Math.max(0, unresolvedTemplateRefs);
if (error == null) {
error = "";
}
@@ -4702,14 +4861,29 @@ public final class ExternalDataPackPipeline {
int installedAssets,
Set resolvedLocateStructures,
int syntheticStructureSets,
- Set projectedStructureKeys
+ Set projectedStructureKeys,
+ int templateAliasesApplied,
+ int emptyElementConversions,
+ int unresolvedTemplateRefs
) {
- return new ProjectionResult(true, installedDatapacks, installedAssets, resolvedLocateStructures, syntheticStructureSets, projectedStructureKeys, managedName, "");
+ return new ProjectionResult(
+ true,
+ installedDatapacks,
+ installedAssets,
+ resolvedLocateStructures,
+ syntheticStructureSets,
+ projectedStructureKeys,
+ templateAliasesApplied,
+ emptyElementConversions,
+ unresolvedTemplateRefs,
+ managedName,
+ ""
+ );
}
private static ProjectionResult failure(String managedName, String error) {
String message = error == null || error.isBlank() ? "projection failed" : error;
- return new ProjectionResult(false, 0, 0, Set.of(), 0, Set.of(), managedName, message);
+ return new ProjectionResult(false, 0, 0, Set.of(), 0, Set.of(), 0, 0, 0, managedName, message);
}
}
@@ -4725,11 +4899,30 @@ public final class ExternalDataPackPipeline {
}
}
+ private record TemplateAliasRewriteResult(
+ int appliedCount,
+ int emptyConversions,
+ Set unresolvedReferences
+ ) {
+ private TemplateAliasRewriteResult {
+ appliedCount = Math.max(0, appliedCount);
+ emptyConversions = Math.max(0, emptyConversions);
+ unresolvedReferences = unresolvedReferences == null ? Set.of() : Set.copyOf(unresolvedReferences);
+ }
+
+ private static TemplateAliasRewriteResult empty() {
+ return new TemplateAliasRewriteResult(0, 0, Set.of());
+ }
+ }
+
private record ProjectionAssetSummary(
List assets,
Set resolvedLocateStructures,
int syntheticStructureSets,
- Set projectedStructureKeys
+ Set projectedStructureKeys,
+ int templateAliasesApplied,
+ int emptyElementConversions,
+ int unresolvedTemplateRefs
) {
private ProjectionAssetSummary {
assets = assets == null ? List.of() : List.copyOf(assets);
@@ -4754,6 +4947,9 @@ public final class ExternalDataPackPipeline {
}
projectedStructureKeys = Set.copyOf(normalizedProjected);
syntheticStructureSets = Math.max(0, syntheticStructureSets);
+ templateAliasesApplied = Math.max(0, templateAliasesApplied);
+ emptyElementConversions = Math.max(0, emptyElementConversions);
+ unresolvedTemplateRefs = Math.max(0, unresolvedTemplateRefs);
}
}
diff --git a/core/src/main/java/art/arcane/iris/core/IrisSettings.java b/core/src/main/java/art/arcane/iris/core/IrisSettings.java
index bbb9fd6bb..307b9522e 100644
--- a/core/src/main/java/art/arcane/iris/core/IrisSettings.java
+++ b/core/src/main/java/art/arcane/iris/core/IrisSettings.java
@@ -167,7 +167,6 @@ public class IrisSettings {
public int noiseCacheSize = 1_024;
public int resourceLoaderCacheSize = 1_024;
public int objectLoaderCacheSize = 4_096;
- public int scriptLoaderCacheSize = 512;
public int tectonicPlateSize = -1;
public int mantleCleanupDelay = 200;
diff --git a/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java b/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java
index 9b6164058..5912ed131 100644
--- a/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java
+++ b/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java
@@ -343,6 +343,7 @@ public class ServerConfigurator {
definition.getReplaceTargets(),
definition.getStructureAliases(),
definition.getStructureSetAliases(),
+ definition.getTemplateAliases(),
definition.getStructurePatches(),
Set.of(),
scopeKey,
@@ -374,6 +375,7 @@ public class ServerConfigurator {
definition.getReplaceTargets(),
definition.getStructureAliases(),
definition.getStructureSetAliases(),
+ definition.getTemplateAliases(),
definition.getStructurePatches(),
group.forcedBiomeKeys(),
group.scopeKey(),
diff --git a/core/src/main/java/art/arcane/iris/core/commands/CommandStudio.java b/core/src/main/java/art/arcane/iris/core/commands/CommandStudio.java
index 9ee64970c..0eb013dfc 100644
--- a/core/src/main/java/art/arcane/iris/core/commands/CommandStudio.java
+++ b/core/src/main/java/art/arcane/iris/core/commands/CommandStudio.java
@@ -277,14 +277,6 @@ public class CommandStudio implements DirectorExecutor {
}
}
- @Director(description = "Execute a script", aliases = "run", origin = DirectorOrigin.PLAYER)
- public void execute(
- @Param(description = "The script to run")
- IrisScript script
- ) {
- engine().getExecution().execute(script.getLoadKey());
- }
-
@Director(description = "Open the noise explorer (External GUI)", aliases = {"nmap", "n"})
public void noise() {
if (noGUI()) return;
diff --git a/core/src/main/java/art/arcane/iris/core/loader/IrisData.java b/core/src/main/java/art/arcane/iris/core/loader/IrisData.java
index 12afee310..d777d9429 100644
--- a/core/src/main/java/art/arcane/iris/core/loader/IrisData.java
+++ b/core/src/main/java/art/arcane/iris/core/loader/IrisData.java
@@ -24,7 +24,6 @@ import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import art.arcane.iris.Iris;
-import art.arcane.iris.core.scripting.environment.PackEnvironment;
import art.arcane.iris.engine.data.cache.AtomicCache;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.*;
@@ -32,8 +31,6 @@ import art.arcane.iris.engine.object.annotations.Snippet;
import art.arcane.iris.engine.object.matter.IrisMatterObject;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
-import art.arcane.iris.util.project.context.IrisContext;
-import art.arcane.iris.util.common.format.C;
import art.arcane.volmlib.util.mantle.flag.MantleFlagAdapter;
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
import art.arcane.volmlib.util.math.RNG;
@@ -58,7 +55,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
private final File dataFolder;
private final int id;
private boolean closed = false;
- private PackEnvironment environment;
private ResourceLoader biomeLoader;
private ResourceLoader lootLoader;
private ResourceLoader regionLoader;
@@ -73,7 +69,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
private ResourceLoader objectLoader;
private ResourceLoader matterLoader;
private ResourceLoader imageLoader;
- private ResourceLoader scriptLoader;
private ResourceLoader matterObjectLoader;
private KMap> possibleSnippets;
private Gson gson;
@@ -152,10 +147,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
return loadAny(IrisSpawner.class, key, nearest);
}
- public static IrisScript loadAnyScript(String key, @Nullable IrisData nearest) {
- return loadAny(IrisScript.class, key, nearest);
- }
-
public static IrisRegion loadAnyRegion(String key, @Nullable IrisData nearest) {
return loadAny(IrisRegion.class, key, nearest);
}
@@ -237,45 +228,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
}
public void preprocessObject(IrisRegistrant t) {
- try {
- IrisContext ctx = IrisContext.get();
- Engine engine = this.engine;
-
- if (engine == null && ctx != null && ctx.getEngine() != null) {
- engine = ctx.getEngine();
- }
-
- if (engine == null && t.getPreprocessors().isNotEmpty()) {
- Iris.error("Failed to preprocess object " + t.getLoadKey() + " because there is no engine context here. (See stack below)");
- try {
- throw new RuntimeException();
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- }
-
- if (engine == null) return;
- var global = engine.getDimension().getPreProcessors(t.getFolderName());
- var local = t.getPreprocessors();
- if ((global != null && global.isNotEmpty()) || local.isNotEmpty()) {
- synchronized (this) {
- if (global != null) {
- for (String i : global) {
- engine.getExecution().preprocessObject(i, t);
- Iris.debug("Loader<" + C.GREEN + t.getTypeName() + C.LIGHT_PURPLE + "> iprocess " + C.YELLOW + t.getLoadKey() + C.LIGHT_PURPLE + " in " + i);
- }
- }
-
- for (String i : local) {
- engine.getExecution().preprocessObject(i, t);
- Iris.debug("Loader<" + C.GREEN + t.getTypeName() + C.LIGHT_PURPLE + "> iprocess " + C.YELLOW + t.getLoadKey() + C.LIGHT_PURPLE + " in " + i);
- }
- }
- }
- } catch (Throwable e) {
- Iris.error("Failed to preprocess object!");
- e.printStackTrace();
- }
}
public void close() {
@@ -298,9 +250,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
} else if (registrant.equals(IrisMatterObject.class)) {
r = (ResourceLoader) new MatterObjectResourceLoader(dataFolder, this, rr.getFolderName(),
rr.getTypeName());
- } else if (registrant.equals(IrisScript.class)) {
- r = (ResourceLoader) new ScriptResourceLoader(dataFolder, this, rr.getFolderName(),
- rr.getTypeName());
} else if (registrant.equals(IrisImage.class)) {
r = (ResourceLoader) new ImageResourceLoader(dataFolder, this, rr.getFolderName(),
rr.getTypeName());
@@ -347,16 +296,10 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
this.expressionLoader = registerLoader(IrisExpression.class);
this.objectLoader = registerLoader(IrisObject.class);
this.imageLoader = registerLoader(IrisImage.class);
- this.scriptLoader = registerLoader(IrisScript.class);
this.matterObjectLoader = registerLoader(IrisMatterObject.class);
- this.environment = PackEnvironment.create(this);
builder.registerTypeAdapterFactory(KeyedType::createTypeAdapter);
gson = builder.create();
- dimensionLoader.streamAll()
- .map(IrisDimension::getDataScripts)
- .flatMap(KList::stream)
- .forEach(environment::execute);
if (engine != null) {
engine.hotload();
diff --git a/core/src/main/java/art/arcane/iris/core/loader/IrisRegistrant.java b/core/src/main/java/art/arcane/iris/core/loader/IrisRegistrant.java
index 63a689a9d..502e16a50 100644
--- a/core/src/main/java/art/arcane/iris/core/loader/IrisRegistrant.java
+++ b/core/src/main/java/art/arcane/iris/core/loader/IrisRegistrant.java
@@ -20,11 +20,6 @@ package art.arcane.iris.core.loader;
import com.google.gson.GsonBuilder;
import art.arcane.iris.Iris;
-import art.arcane.iris.engine.object.IrisScript;
-import art.arcane.iris.engine.object.annotations.ArrayType;
-import art.arcane.iris.engine.object.annotations.Desc;
-import art.arcane.iris.engine.object.annotations.RegistryListResource;
-import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.json.JSONObject;
import art.arcane.iris.util.common.plugin.VolmitSender;
import lombok.Data;
@@ -35,11 +30,6 @@ import java.io.File;
@Data
public abstract class IrisRegistrant {
- @Desc("Preprocess this object in-memory when it's loaded, run scripts using the variable 'object' and modify properties about this object before it's used.\nFile extension: .proc.kts")
- @RegistryListResource(IrisScript.class)
- @ArrayType(min = 1, type = String.class)
- private KList preprocessors = new KList<>();
-
@EqualsAndHashCode.Exclude
private transient IrisData loader;
diff --git a/core/src/main/java/art/arcane/iris/core/loader/ScriptResourceLoader.java b/core/src/main/java/art/arcane/iris/core/loader/ScriptResourceLoader.java
deleted file mode 100644
index efcfe2219..000000000
--- a/core/src/main/java/art/arcane/iris/core/loader/ScriptResourceLoader.java
+++ /dev/null
@@ -1,170 +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 art.arcane.iris.core.loader;
-
-import art.arcane.iris.Iris;
-import art.arcane.iris.core.IrisSettings;
-import art.arcane.iris.engine.object.IrisScript;
-import art.arcane.volmlib.util.data.KCache;
-import art.arcane.volmlib.util.io.IO;
-import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Set;
-
-public class ScriptResourceLoader extends ResourceLoader {
- public ScriptResourceLoader(File root, IrisData idm, String folderName, String resourceTypeName) {
- super(root, idm, folderName, resourceTypeName, IrisScript.class);
- loadCache = new KCache<>(this::loadRaw, IrisSettings.get().getPerformance().getScriptLoaderCacheSize());
- }
-
- public boolean supportsSchemas() {
- return false;
- }
-
- public long getSize() {
- return loadCache.getSize();
- }
-
- protected IrisScript loadFile(File j, String name) {
- try {
- PrecisionStopwatch p = PrecisionStopwatch.start();
- IrisScript t = new IrisScript(IO.readAll(j));
- t.setLoadKey(name);
- t.setLoader(manager);
- t.setLoadFile(j);
- logLoad(j, t);
- tlt.addAndGet(p.getMilliseconds());
- return t;
- } catch (Throwable e) {
- Iris.reportError(e);
- Iris.warn("Couldn't read " + resourceTypeName + " file: " + j.getPath() + ": " + e.getMessage());
- return null;
- }
- }
-
-
- public String[] getPossibleKeys() {
- if (possibleKeys != null) {
- return possibleKeys;
- }
-
- Iris.debug("Building " + resourceTypeName + " Possibility Lists");
- Set keys = new HashSet<>();
-
- for (File i : getFolders()) {
- if (i.isDirectory()) {
- keys.addAll(getKeysInDirectory(i));
- }
- }
-
- possibleKeys = keys.toArray(new String[0]);
- return possibleKeys;
- }
-
- private Set getKeysInDirectory(File directory) {
- Set keys = new HashSet<>();
- for (File file : directory.listFiles()) {
- if (file.isFile() && file.getName().endsWith(".kts")) {
- keys.add(file.getName().replaceAll("\\Q.kts\\E", ""));
- } else if (file.isDirectory()) {
- keys.addAll(getKeysInDirectory(file));
- }
- }
- return keys;
- }
-
-// public String[] getPossibleKeys() {
-// if (possibleKeys != null) {
-// return possibleKeys;
-// }
-//
-// Iris.debug("Building " + resourceTypeName + " Possibility Lists");
-// KSet m = new KSet<>();
-//
-// for (File i : getFolders()) {
-// for (File j : i.listFiles()) {
-// if (j.isFile() && j.getName().endsWith(".js")) {
-// m.add(j.getName().replaceAll("\\Q.js\\E", ""));
-// } else if (j.isDirectory()) {
-// for (File k : j.listFiles()) {
-// if (k.isFile() && k.getName().endsWith(".js")) {
-// m.add(j.getName() + "/" + k.getName().replaceAll("\\Q.js\\E", ""));
-// } else if (k.isDirectory()) {
-// for (File l : k.listFiles()) {
-// if (l.isFile() && l.getName().endsWith(".js")) {
-// m.add(j.getName() + "/" + k.getName() + "/" + l.getName().replaceAll("\\Q.js\\E", ""));
-// }
-// }
-// }
-// }
-// }
-// }
-// }
-//
-// KList v = new KList<>(m);
-// possibleKeys = v.toArray(new String[0]);
-// return possibleKeys;
-// }
-
- public File findFile(String name) {
- for (File i : getFolders(name)) {
- for (File j : i.listFiles()) {
- if (j.isFile() && j.getName().endsWith(".kts") && j.getName().split("\\Q.\\E")[0].equals(name)) {
- return j;
- }
- }
-
- File file = new File(i, name + ".kts");
-
- if (file.exists()) {
- return file;
- }
- }
-
- Iris.warn("Couldn't find " + resourceTypeName + ": " + name);
-
- return null;
- }
-
- private IrisScript loadRaw(String name) {
- for (File i : getFolders(name)) {
- for (File j : i.listFiles()) {
- if (j.isFile() && j.getName().endsWith(".kts") && j.getName().split("\\Q.\\E")[0].equals(name)) {
- return loadFile(j, name);
- }
- }
-
- File file = new File(i, name + ".kts");
-
- if (file.exists()) {
- return loadFile(file, name);
- }
- }
-
- Iris.warn("Couldn't find " + resourceTypeName + ": " + name);
-
- return null;
- }
-
- public IrisScript load(String name, boolean warn) {
- return loadCache.get(name);
- }
-}
diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java b/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java
new file mode 100644
index 000000000..f96d0e996
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java
@@ -0,0 +1,369 @@
+package art.arcane.iris.core.pregenerator.cache;
+
+import art.arcane.iris.Iris;
+import art.arcane.volmlib.util.data.Varint;
+import art.arcane.volmlib.util.documentation.ChunkCoordinates;
+import art.arcane.volmlib.util.documentation.RegionCoordinates;
+import art.arcane.volmlib.util.io.IO;
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
+import net.jpountz.lz4.LZ4BlockInputStream;
+import net.jpountz.lz4.LZ4BlockOutputStream;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class PregenCacheImpl implements PregenCache {
+ private static final ExecutorService DISPATCHER = Executors.newFixedThreadPool(4);
+ private static final short SIZE = 1024;
+
+ private final File directory;
+ private final int maxSize;
+ private final Long2ObjectLinkedOpenHashMap cache = new Long2ObjectLinkedOpenHashMap<>();
+
+ public PregenCacheImpl(File directory, int maxSize) {
+ this.directory = directory;
+ this.maxSize = maxSize;
+ }
+
+ @Override
+ @ChunkCoordinates
+ public boolean isChunkCached(int x, int z) {
+ return getPlate(x >> 10, z >> 10).isCached(
+ (x >> 5) & 31,
+ (z >> 5) & 31,
+ region -> region.isCached(x & 31, z & 31)
+ );
+ }
+
+ @Override
+ @RegionCoordinates
+ public boolean isRegionCached(int x, int z) {
+ return getPlate(x >> 5, z >> 5).isCached(
+ x & 31,
+ z & 31,
+ Region::isCached
+ );
+ }
+
+ @Override
+ @ChunkCoordinates
+ public void cacheChunk(int x, int z) {
+ getPlate(x >> 10, z >> 10).cache(
+ (x >> 5) & 31,
+ (z >> 5) & 31,
+ region -> region.cache(x & 31, z & 31)
+ );
+ }
+
+ @Override
+ @RegionCoordinates
+ public void cacheRegion(int x, int z) {
+ getPlate(x >> 5, z >> 5).cache(
+ x & 31,
+ z & 31,
+ Region::cache
+ );
+ }
+
+ @Override
+ public void write() {
+ if (cache.isEmpty()) {
+ return;
+ }
+
+ List> futures = new ArrayList<>(cache.size());
+ for (Plate plate : cache.values()) {
+ if (!plate.dirty) {
+ continue;
+ }
+ futures.add(CompletableFuture.runAsync(() -> writePlate(plate), DISPATCHER));
+ }
+
+ for (CompletableFuture future : futures) {
+ future.join();
+ }
+ }
+
+ @Override
+ public void trim(long unloadDuration) {
+ if (cache.isEmpty()) {
+ return;
+ }
+
+ long threshold = System.currentTimeMillis() - unloadDuration;
+ List> futures = new ArrayList<>(cache.size());
+ Iterator iterator = cache.values().iterator();
+ while (iterator.hasNext()) {
+ Plate plate = iterator.next();
+ if (plate.lastAccess < threshold) {
+ iterator.remove();
+ }
+ futures.add(CompletableFuture.runAsync(() -> writePlate(plate), DISPATCHER));
+ }
+
+ for (CompletableFuture future : futures) {
+ future.join();
+ }
+ }
+
+ private Plate getPlate(int x, int z) {
+ long key = key(x, z);
+ Plate plate = cache.getAndMoveToFirst(key);
+ if (plate != null) {
+ return plate;
+ }
+
+ Plate loaded = readPlate(x, z);
+ cache.putAndMoveToFirst(key, loaded);
+
+ if (cache.size() > maxSize) {
+ List> futures = new ArrayList<>(cache.size() - maxSize);
+ while (cache.size() > maxSize) {
+ Plate evicted = cache.removeLast();
+ futures.add(CompletableFuture.runAsync(() -> writePlate(evicted), DISPATCHER));
+ }
+ for (CompletableFuture future : futures) {
+ future.join();
+ }
+ }
+
+ return loaded;
+ }
+
+ private Plate readPlate(int x, int z) {
+ File file = fileForPlate(x, z);
+ if (!file.exists()) {
+ return new Plate(x, z);
+ }
+
+ try (DataInputStream input = new DataInputStream(new LZ4BlockInputStream(new FileInputStream(file)))) {
+ return readPlate(x, z, input);
+ } catch (IOException e) {
+ Iris.error("Failed to read pregen cache " + file);
+ e.printStackTrace();
+ Iris.reportError(e);
+ }
+
+ return new Plate(x, z);
+ }
+
+ private void writePlate(Plate plate) {
+ if (!plate.dirty) {
+ return;
+ }
+
+ File file = fileForPlate(plate.x, plate.z);
+ try {
+ IO.write(file, output -> new DataOutputStream(new LZ4BlockOutputStream(output)), plate::write);
+ plate.dirty = false;
+ } catch (IOException e) {
+ Iris.error("Failed to write preen cache " + file);
+ e.printStackTrace();
+ Iris.reportError(e);
+ }
+ }
+
+ private File fileForPlate(int x, int z) {
+ if (!directory.exists() && !directory.mkdirs()) {
+ throw new IllegalStateException("Cannot create directory: " + directory.getAbsolutePath());
+ }
+ return new File(directory, "c." + x + "." + z + ".lz4b");
+ }
+
+ private static long key(int x, int z) {
+ return (((long) x) << 32) ^ (z & 0xffffffffL);
+ }
+
+ private interface RegionPredicate {
+ boolean test(Region region);
+ }
+
+ private static class Plate {
+ private final int x;
+ private final int z;
+ private short count;
+ private Region[] regions;
+ private boolean dirty;
+ private long lastAccess;
+
+ private Plate(int x, int z) {
+ this(x, z, (short) 0, new Region[1024]);
+ }
+
+ private Plate(int x, int z, short count, Region[] regions) {
+ this.x = x;
+ this.z = z;
+ this.count = count;
+ this.regions = regions;
+ this.lastAccess = System.currentTimeMillis();
+ }
+
+ private boolean cache(int x, int z, RegionPredicate predicate) {
+ lastAccess = System.currentTimeMillis();
+ if (count == SIZE) {
+ return false;
+ }
+
+ int index = x * 32 + z;
+ Region region = regions[index];
+ if (region == null) {
+ region = new Region();
+ regions[index] = region;
+ }
+
+ if (!predicate.test(region)) {
+ return false;
+ }
+
+ count++;
+ if (count == SIZE) {
+ regions = null;
+ }
+ dirty = true;
+ return true;
+ }
+
+ private boolean isCached(int x, int z, RegionPredicate predicate) {
+ lastAccess = System.currentTimeMillis();
+ if (count == SIZE) {
+ return true;
+ }
+
+ Region region = regions[x * 32 + z];
+ if (region == null) {
+ return false;
+ }
+ return predicate.test(region);
+ }
+
+ private void write(DataOutput output) throws IOException {
+ Varint.writeSignedVarInt(count, output);
+ if (regions == null) {
+ return;
+ }
+
+ for (Region region : regions) {
+ output.writeBoolean(region == null);
+ if (region != null) {
+ region.write(output);
+ }
+ }
+ }
+ }
+
+ private static class Region {
+ private short count;
+ private long[] words;
+
+ private Region() {
+ this((short) 0, new long[64]);
+ }
+
+ private Region(short count, long[] words) {
+ this.count = count;
+ this.words = words;
+ }
+
+ private boolean cache() {
+ if (count == SIZE) {
+ return false;
+ }
+ count = SIZE;
+ words = null;
+ return true;
+ }
+
+ private boolean cache(int x, int z) {
+ if (count == SIZE) {
+ return false;
+ }
+
+ long[] value = words;
+ if (value == null) {
+ return false;
+ }
+
+ int index = x * 32 + z;
+ int wordIndex = index >> 6;
+ long bit = 1L << (index & 63);
+ boolean current = (value[wordIndex] & bit) != 0L;
+ if (current) {
+ return false;
+ }
+
+ count++;
+ if (count == SIZE) {
+ words = null;
+ return true;
+ }
+
+ value[wordIndex] = value[wordIndex] | bit;
+ return false;
+ }
+
+ private boolean isCached() {
+ return count == SIZE;
+ }
+
+ private boolean isCached(int x, int z) {
+ int index = x * 32 + z;
+ if (count == SIZE) {
+ return true;
+ }
+ return (words[index >> 6] & (1L << (index & 63))) != 0L;
+ }
+
+ private void write(DataOutput output) throws IOException {
+ Varint.writeSignedVarInt(count, output);
+ if (words == null) {
+ return;
+ }
+
+ for (long word : words) {
+ Varint.writeUnsignedVarLong(word, output);
+ }
+ }
+ }
+
+ private static Plate readPlate(int x, int z, DataInput input) throws IOException {
+ int count = Varint.readSignedVarInt(input);
+ if (count == 1024) {
+ return new Plate(x, z, SIZE, null);
+ }
+
+ Region[] regions = new Region[1024];
+ for (int i = 0; i < regions.length; i++) {
+ boolean isNull = input.readBoolean();
+ if (!isNull) {
+ regions[i] = readRegion(input);
+ }
+ }
+
+ return new Plate(x, z, (short) count, regions);
+ }
+
+ private static Region readRegion(DataInput input) throws IOException {
+ int count = Varint.readSignedVarInt(input);
+ if (count == 1024) {
+ return new Region(SIZE, null);
+ }
+
+ long[] words = new long[64];
+ for (int i = 0; i < words.length; i++) {
+ words[i] = Varint.readUnsignedVarLong(input);
+ }
+
+ return new Region((short) count, words);
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/core/project/Gradle.java b/core/src/main/java/art/arcane/iris/core/project/Gradle.java
index 1d75418af..3940d9375 100644
--- a/core/src/main/java/art/arcane/iris/core/project/Gradle.java
+++ b/core/src/main/java/art/arcane/iris/core/project/Gradle.java
@@ -20,7 +20,7 @@ public class Gradle {
public static synchronized void wrapper(File projectDir) {
try {
- File settings = new File(projectDir, "settings.gradle.kts");
+ File settings = new File(projectDir, "settings.gradle");
if (!settings.exists()) settings.createNewFile();
runGradle(projectDir, "wrapper");
} catch (Throwable e) {
diff --git a/core/src/main/java/art/arcane/iris/core/project/IrisProject.java b/core/src/main/java/art/arcane/iris/core/project/IrisProject.java
index 1437a82a3..82739ab1a 100644
--- a/core/src/main/java/art/arcane/iris/core/project/IrisProject.java
+++ b/core/src/main/java/art/arcane/iris/core/project/IrisProject.java
@@ -428,7 +428,6 @@ public class IrisProject {
settings.put("json.schemas", schemas);
ws.put("settings", settings);
- dm.getEnvironment().configureProject();
File schemasFile = new File(path, ".idea" + File.separator + "jsonSchemas.xml");
Document doc = IO.read(schemasFile);
Element mappings = (Element) doc.selectSingleNode("//component[@name='JsonSchemaMappingsProjectConfiguration']");
diff --git a/core/src/main/java/art/arcane/iris/core/safeguard/IrisSafeguard.java b/core/src/main/java/art/arcane/iris/core/safeguard/IrisSafeguard.java
new file mode 100644
index 000000000..77d7a96c4
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/core/safeguard/IrisSafeguard.java
@@ -0,0 +1,129 @@
+package art.arcane.iris.core.safeguard;
+
+import art.arcane.iris.Iris;
+import art.arcane.iris.core.safeguard.task.Diagnostic;
+import art.arcane.iris.core.safeguard.task.Task;
+import art.arcane.iris.core.safeguard.task.Tasks;
+import art.arcane.iris.core.safeguard.task.ValueWithDiagnostics;
+import art.arcane.iris.util.common.format.C;
+import art.arcane.iris.util.common.scheduling.J;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class IrisSafeguard {
+ private static volatile boolean forceShutdown = false;
+ private static Map> results = Collections.emptyMap();
+ private static Map context = Collections.emptyMap();
+ private static Map> attachment = Collections.emptyMap();
+ private static Mode mode = Mode.STABLE;
+ private static int count = 0;
+
+ private IrisSafeguard() {
+ }
+
+ public static void execute() {
+ List tasks = Tasks.getTasks();
+ LinkedHashMap> resultValues = new LinkedHashMap<>(tasks.size());
+ LinkedHashMap contextValues = new LinkedHashMap<>(tasks.size());
+ LinkedHashMap> attachmentValues = new LinkedHashMap<>(tasks.size());
+ Mode currentMode = Mode.STABLE;
+ int issueCount = 0;
+
+ for (Task task : tasks) {
+ ValueWithDiagnostics result;
+ try {
+ result = task.run();
+ } catch (Throwable e) {
+ Iris.reportError(e);
+ result = new ValueWithDiagnostics<>(
+ Mode.WARNING,
+ new Diagnostic(Diagnostic.Logger.ERROR, "Error while running task " + task.getId(), e)
+ );
+ }
+
+ currentMode = currentMode.highest(result.getValue());
+ resultValues.put(task, result);
+ contextValues.put(task.getId(), result.getValue().getId());
+
+ List lines = new ArrayList<>();
+ for (Diagnostic diagnostic : result.getDiagnostics()) {
+ String[] split = diagnostic.toString().split("\\n");
+ Collections.addAll(lines, split);
+ }
+ attachmentValues.put(task.getId(), lines);
+
+ if (result.getValue() != Mode.STABLE) {
+ issueCount++;
+ }
+ }
+
+ results = Collections.unmodifiableMap(resultValues);
+ context = Collections.unmodifiableMap(contextValues);
+ attachment = Collections.unmodifiableMap(attachmentValues);
+ mode = currentMode;
+ count = issueCount;
+ }
+
+ public static Mode mode() {
+ return mode;
+ }
+
+ public static Map asContext() {
+ return context;
+ }
+
+ public static Map> asAttachment() {
+ return attachment;
+ }
+
+ public static void splash() {
+ Iris.instance.splash();
+ printReports();
+ printFooter();
+ }
+
+ public static void printReports() {
+ switch (mode) {
+ case STABLE -> Iris.info(C.BLUE + "0 Conflicts found");
+ case WARNING -> Iris.warn(C.GOLD + "%s Issues found", count);
+ case UNSTABLE -> Iris.error(C.DARK_RED + "%s Issues found", count);
+ }
+
+ for (ValueWithDiagnostics value : results.values()) {
+ value.log(true, true);
+ }
+ }
+
+ public static void printFooter() {
+ switch (mode) {
+ case STABLE -> Iris.info(C.BLUE + "Iris is running Stable");
+ case WARNING -> warning();
+ case UNSTABLE -> unstable();
+ }
+ }
+
+ public static boolean isForceShutdown() {
+ return forceShutdown;
+ }
+
+ private static void warning() {
+ Iris.warn(C.GOLD + "Iris is running in Warning Mode");
+ Iris.warn(C.GRAY + "Some startup checks need attention. Review the messages above for tuning suggestions.");
+ Iris.warn(C.GRAY + "Iris will continue startup normally.");
+ Iris.warn("");
+ }
+
+ private static void unstable() {
+ Iris.error(C.DARK_RED + "Iris is running in Danger Mode");
+ Iris.error("");
+ Iris.error(C.DARK_GRAY + "--==<" + C.RED + " IMPORTANT " + C.DARK_GRAY + ">==--");
+ Iris.error("Critical startup checks failed. Iris will continue startup in 10 seconds.");
+ Iris.error("Review and resolve the errors above as soon as possible.");
+ J.sleep(10000L);
+ Iris.info("");
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/core/safeguard/Mode.java b/core/src/main/java/art/arcane/iris/core/safeguard/Mode.java
new file mode 100644
index 000000000..a7fad6cfe
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/core/safeguard/Mode.java
@@ -0,0 +1,143 @@
+package art.arcane.iris.core.safeguard;
+
+import art.arcane.iris.BuildConstants;
+import art.arcane.iris.Iris;
+import art.arcane.iris.core.IrisSettings;
+import art.arcane.iris.util.common.format.C;
+import art.arcane.volmlib.util.format.Form;
+import org.bukkit.Bukkit;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+public enum Mode {
+ STABLE(C.IRIS),
+ WARNING(C.GOLD),
+ UNSTABLE(C.RED);
+
+ private final C color;
+ private final String id;
+
+ Mode(C color) {
+ this.color = color;
+ this.id = name().toLowerCase(Locale.ROOT);
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public Mode highest(Mode mode) {
+ if (mode.ordinal() > ordinal()) {
+ return mode;
+ }
+ return this;
+ }
+
+ public String tag(String subTag) {
+ if (subTag == null || subTag.isBlank()) {
+ return wrap("Iris") + C.GRAY + ": ";
+ }
+ return wrap("Iris") + " " + wrap(subTag) + C.GRAY + ": ";
+ }
+
+ public void trySplash() {
+ if (!IrisSettings.get().getGeneral().isSplashLogoStartup()) {
+ return;
+ }
+ splash();
+ }
+
+ public void splash() {
+ String padd = Form.repeat(" ", 8);
+ String padd2 = Form.repeat(" ", 4);
+ String version = Iris.instance.getDescription().getVersion();
+ String releaseTrain = getReleaseTrain(version);
+ String serverVersion = getServerVersion();
+ String startupDate = getStartupDate();
+ int javaVersion = getJavaVersion();
+
+ String[] splash = new String[]{
+ padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@",
+ padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + color + " .(((()))). ",
+ padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + color + " .((((((())))))). ",
+ padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + color + " ((((((((())))))))) " + C.GRAY + " @",
+ padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + color + " ((((((((-))))))))) " + C.GRAY + " @@",
+ padd + C.GRAY + "@@@&&" + color + " ((((((({ })))))))) " + C.GRAY + " &&@@@",
+ padd + C.GRAY + "@@" + color + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@",
+ padd + C.GRAY + "@" + color + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@",
+ padd + C.GRAY + "" + color + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@",
+ padd + C.GRAY + "" + color + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@",
+ padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@"
+ };
+
+ String[] info = new String[]{
+ "",
+ padd2 + color + " Iris, " + C.AQUA + "Dimension Engine " + C.RED + "[" + releaseTrain + " RELEASE]",
+ padd2 + C.GRAY + " Version: " + color + version,
+ padd2 + C.GRAY + " By: " + color + "Volmit Software (Arcane Arts)",
+ padd2 + C.GRAY + " Server: " + color + serverVersion,
+ padd2 + C.GRAY + " Java: " + color + javaVersion + C.GRAY + " | Date: " + color + startupDate,
+ padd2 + C.GRAY + " Commit: " + color + BuildConstants.COMMIT + C.GRAY + "/" + color + BuildConstants.ENVIRONMENT,
+ "",
+ "",
+ "",
+ ""
+ };
+
+ StringBuilder builder = new StringBuilder("\n\n");
+ for (int i = 0; i < splash.length; i++) {
+ builder.append(splash[i]);
+ if (i < info.length) {
+ builder.append(info[i]);
+ }
+ builder.append("\n");
+ }
+
+ Iris.info(builder.toString());
+ }
+
+ private String wrap(String tag) {
+ return C.BOLD.toString() + C.DARK_GRAY + "[" + C.BOLD + color + tag + C.BOLD + C.DARK_GRAY + "]" + C.RESET;
+ }
+
+ private String getServerVersion() {
+ String version = Bukkit.getVersion();
+ int marker = version.indexOf(" (MC:");
+ if (marker != -1) {
+ return version.substring(0, marker);
+ }
+ return version;
+ }
+
+ private 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);
+ }
+
+ private String getStartupDate() {
+ return LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);
+ }
+
+ private String getReleaseTrain(String version) {
+ String value = version;
+ int suffixIndex = value.indexOf('-');
+ if (suffixIndex >= 0) {
+ value = value.substring(0, suffixIndex);
+ }
+ String[] split = value.split("\\.");
+ if (split.length >= 2) {
+ return split[0] + "." + split[1];
+ }
+ return value;
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/core/safeguard/task/Diagnostic.java b/core/src/main/java/art/arcane/iris/core/safeguard/task/Diagnostic.java
new file mode 100644
index 000000000..132a6310f
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/core/safeguard/task/Diagnostic.java
@@ -0,0 +1,112 @@
+package art.arcane.iris.core.safeguard.task;
+
+import art.arcane.iris.Iris;
+import art.arcane.iris.util.common.format.C;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.function.Consumer;
+
+public class Diagnostic {
+ private final Logger logger;
+ private final String message;
+ private final Throwable exception;
+
+ public Diagnostic(String message) {
+ this(Logger.ERROR, message, null);
+ }
+
+ public Diagnostic(Logger logger, String message) {
+ this(logger, message, null);
+ }
+
+ public Diagnostic(Logger logger, String message, Throwable exception) {
+ this.logger = logger;
+ this.message = message;
+ this.exception = exception;
+ }
+
+ public Logger getLogger() {
+ return logger;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public Throwable getException() {
+ return exception;
+ }
+
+ public void log() {
+ log(true, false);
+ }
+
+ public void log(boolean withException) {
+ log(withException, false);
+ }
+
+ public void log(boolean withException, boolean withStackTrace) {
+ logger.print(render(withException, withStackTrace));
+ }
+
+ public String render() {
+ return render(true, false);
+ }
+
+ public String render(boolean withException) {
+ return render(withException, false);
+ }
+
+ public String render(boolean withException, boolean withStackTrace) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(message);
+ if (withException && exception != null) {
+ builder.append(": ");
+ builder.append(exception);
+ if (withStackTrace) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(os);
+ exception.printStackTrace(ps);
+ ps.flush();
+ builder.append("\n");
+ builder.append(os);
+ }
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public String toString() {
+ return C.strip(render());
+ }
+
+ public enum Logger {
+ DEBUG(Iris::debug),
+ RAW(Iris::msg),
+ INFO(Iris::info),
+ WARN(Iris::warn),
+ ERROR(Iris::error);
+
+ private final Consumer logger;
+
+ Logger(Consumer logger) {
+ this.logger = logger;
+ }
+
+ public void print(String message) {
+ String[] lines = message.split("\\n");
+ for (String line : lines) {
+ logger.accept(line);
+ }
+ }
+
+ public Diagnostic create(String message) {
+ return create(message, null);
+ }
+
+ public Diagnostic create(String message, Throwable exception) {
+ return new Diagnostic(this, message, exception);
+ }
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/core/safeguard/task/Task.java b/core/src/main/java/art/arcane/iris/core/safeguard/task/Task.java
new file mode 100644
index 000000000..adfc428d3
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/core/safeguard/task/Task.java
@@ -0,0 +1,49 @@
+package art.arcane.iris.core.safeguard.task;
+
+import art.arcane.iris.core.safeguard.Mode;
+import art.arcane.volmlib.util.format.Form;
+
+import java.util.Locale;
+import java.util.function.Supplier;
+
+public abstract class Task {
+ private final String id;
+ private final String name;
+
+ public Task(String id) {
+ this(id, Form.capitalizeWords(id.replace(" ", "_").toLowerCase(Locale.ROOT)));
+ }
+
+ public Task(String id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public abstract ValueWithDiagnostics run();
+
+ public static Task of(String id, String name, Supplier> action) {
+ return new Task(id, name) {
+ @Override
+ public ValueWithDiagnostics run() {
+ return action.get();
+ }
+ };
+ }
+
+ public static Task of(String id, Supplier> action) {
+ return new Task(id) {
+ @Override
+ public ValueWithDiagnostics run() {
+ return action.get();
+ }
+ };
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/core/safeguard/task/Tasks.java b/core/src/main/java/art/arcane/iris/core/safeguard/task/Tasks.java
new file mode 100644
index 000000000..5061ee3c4
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/core/safeguard/task/Tasks.java
@@ -0,0 +1,228 @@
+package art.arcane.iris.core.safeguard.task;
+
+import art.arcane.iris.Iris;
+import art.arcane.iris.core.IrisWorlds;
+import art.arcane.iris.core.nms.INMS;
+import art.arcane.iris.core.nms.v1X.NMSBinding1X;
+import art.arcane.iris.core.safeguard.Mode;
+import art.arcane.iris.engine.object.IrisDimension;
+import art.arcane.iris.util.common.misc.getHardware;
+import art.arcane.iris.util.project.agent.Agent;
+import org.bukkit.Bukkit;
+import org.bukkit.Server;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class Tasks {
+ private static final Task MEMORY = Task.of("memory", () -> {
+ long mem = getHardware.getProcessMemory();
+ if (mem >= 3072L) {
+ return withDiagnostics(Mode.STABLE);
+ }
+
+ if (mem > 2048L) {
+ return withDiagnostics(Mode.STABLE,
+ Diagnostic.Logger.INFO.create("Memory Recommendation"),
+ Diagnostic.Logger.INFO.create("- 3GB+ process memory is recommended for Iris."),
+ Diagnostic.Logger.INFO.create("- Process Memory: " + mem + " MB"));
+ }
+
+ return withDiagnostics(Mode.WARNING,
+ Diagnostic.Logger.WARN.create("Low Memory"),
+ Diagnostic.Logger.WARN.create("- Iris is running with 2GB or less process memory."),
+ Diagnostic.Logger.WARN.create("- 3GB+ process memory is recommended for Iris."),
+ Diagnostic.Logger.WARN.create("- Process Memory: " + mem + " MB"));
+ });
+
+ private static final Task INCOMPATIBILITIES = Task.of("incompatibilities", () -> {
+ Set plugins = new HashSet<>(Set.of("dynmap", "Stratos"));
+ plugins.removeIf(name -> server().getPluginManager().getPlugin(name) == null);
+
+ if (plugins.isEmpty()) {
+ return withDiagnostics(Mode.STABLE);
+ }
+
+ List diagnostics = new ArrayList<>();
+ if (plugins.contains("dynmap")) {
+ addAllDiagnostics(diagnostics,
+ Diagnostic.Logger.ERROR.create("Dynmap"),
+ Diagnostic.Logger.ERROR.create("- The plugin Dynmap is not compatible with the server."),
+ Diagnostic.Logger.ERROR.create("- If you want to have a map plugin like Dynmap, consider Bluemap."));
+ }
+ if (plugins.contains("Stratos")) {
+ addAllDiagnostics(diagnostics,
+ Diagnostic.Logger.ERROR.create("Stratos"),
+ Diagnostic.Logger.ERROR.create("- Iris is not compatible with other worldgen plugins."));
+ }
+ return withDiagnostics(Mode.WARNING, diagnostics);
+ });
+
+ private static final Task SOFTWARE = Task.of("software", () -> {
+ Set supported = Set.of("canvas", "folia", "purpur", "pufferfish", "paper", "spigot", "bukkit");
+ String serverName = server().getName().toLowerCase(Locale.ROOT);
+ boolean supportedServer = isCanvasServer();
+ if (!supportedServer) {
+ for (String candidate : supported) {
+ if (serverName.contains(candidate)) {
+ supportedServer = true;
+ break;
+ }
+ }
+ }
+
+ if (supportedServer) {
+ return withDiagnostics(Mode.STABLE);
+ }
+
+ return withDiagnostics(Mode.WARNING,
+ Diagnostic.Logger.WARN.create("Unsupported Server Software"),
+ Diagnostic.Logger.WARN.create("- Please consider using Canvas, Folia, Paper, or Purpur instead."));
+ });
+
+ private static final Task VERSION = Task.of("version", () -> {
+ String[] parts = Iris.instance.getDescription().getVersion().split("-");
+ String supportedVersions;
+ if (parts.length >= 3) {
+ String minVersion = parts[1];
+ String maxVersion = parts[2];
+ supportedVersions = minVersion.equals(maxVersion) ? minVersion : minVersion + " - " + maxVersion;
+ } else if (parts.length >= 2) {
+ supportedVersions = parts[1];
+ } else {
+ supportedVersions = "1.21.11";
+ }
+
+ if (!(INMS.get() instanceof NMSBinding1X)) {
+ return withDiagnostics(Mode.STABLE);
+ }
+
+ return withDiagnostics(Mode.UNSTABLE,
+ Diagnostic.Logger.ERROR.create("Server Version"),
+ Diagnostic.Logger.ERROR.create("- Iris only supports " + supportedVersions));
+ });
+
+ private static final Task INJECTION = Task.of("injection", () -> {
+ if (!isPaperPreferredServer() && !Agent.isInstalled()) {
+ return withDiagnostics(Mode.WARNING,
+ Diagnostic.Logger.WARN.create("Java Agent"),
+ Diagnostic.Logger.WARN.create("- Skipping dynamic Java agent attach on Spigot/Bukkit to avoid runtime agent warnings."),
+ Diagnostic.Logger.WARN.create("- For full runtime injection support, run with -javaagent:"
+ + Agent.AGENT_JAR.getPath() + " or use Canvas/Folia/Paper/Purpur."));
+ }
+
+ if (!Agent.install()) {
+ return withDiagnostics(Mode.UNSTABLE,
+ Diagnostic.Logger.ERROR.create("Java Agent"),
+ Diagnostic.Logger.ERROR.create("- Please enable dynamic agent loading by adding -XX:+EnableDynamicAgentLoading to your jvm arguments."),
+ Diagnostic.Logger.ERROR.create("- or add the jvm argument -javaagent:" + Agent.AGENT_JAR.getPath()));
+ }
+
+ if (!INMS.get().injectBukkit()) {
+ return withDiagnostics(Mode.UNSTABLE,
+ Diagnostic.Logger.ERROR.create("Code Injection"),
+ Diagnostic.Logger.ERROR.create("- Failed to inject code. Please contact support"));
+ }
+
+ return withDiagnostics(Mode.STABLE);
+ });
+
+ private static final Task DIMENSION_TYPES = Task.of("dimensionTypes", () -> {
+ Set keys = IrisWorlds.get().getDimensions().map(IrisDimension::getDimensionTypeKey).collect(Collectors.toSet());
+ if (!INMS.get().missingDimensionTypes(keys.toArray(String[]::new))) {
+ return withDiagnostics(Mode.STABLE);
+ }
+
+ return withDiagnostics(Mode.UNSTABLE,
+ Diagnostic.Logger.ERROR.create("Dimension Types"),
+ Diagnostic.Logger.ERROR.create("- Required Iris dimension types were not loaded."),
+ Diagnostic.Logger.ERROR.create("- If this still happens after a restart please contact support."));
+ });
+
+ private static final Task DISK_SPACE = Task.of("diskSpace", () -> {
+ double freeGiB = server().getWorldContainer().getFreeSpace() / (double) 0x4000_0000;
+ if (freeGiB > 3.0) {
+ return withDiagnostics(Mode.STABLE);
+ }
+
+ return withDiagnostics(Mode.WARNING,
+ Diagnostic.Logger.WARN.create("Insufficient Disk Space"),
+ Diagnostic.Logger.WARN.create("- 3GB of free space is required for Iris to function."));
+ });
+
+ private static final Task JAVA = Task.of("java", () -> {
+ int version = Iris.getJavaVersion();
+ if (version == 21) {
+ return withDiagnostics(Mode.STABLE);
+ }
+
+ if (version > 21) {
+ return withDiagnostics(Mode.STABLE,
+ Diagnostic.Logger.INFO.create("Java Runtime"),
+ Diagnostic.Logger.INFO.create("- Running Java " + version + ". Iris is tested primarily on Java 21."));
+ }
+
+ return withDiagnostics(Mode.WARNING,
+ Diagnostic.Logger.WARN.create("Unsupported Java version"),
+ Diagnostic.Logger.WARN.create("- Java 21+ is recommended. Current runtime: Java " + version));
+ });
+
+ private static final List TASKS = List.of(
+ MEMORY,
+ INCOMPATIBILITIES,
+ SOFTWARE,
+ VERSION,
+ INJECTION,
+ DIMENSION_TYPES,
+ DISK_SPACE,
+ JAVA
+ );
+
+ private Tasks() {
+ }
+
+ public static List getTasks() {
+ return TASKS;
+ }
+
+ private static Server server() {
+ return Bukkit.getServer();
+ }
+
+ private static boolean isPaperPreferredServer() {
+ String name = server().getName().toLowerCase(Locale.ROOT);
+ return isCanvasServer()
+ || name.contains("folia")
+ || name.contains("paper")
+ || name.contains("purpur")
+ || name.contains("pufferfish");
+ }
+
+ private static boolean isCanvasServer() {
+ ClassLoader loader = server().getClass().getClassLoader();
+ try {
+ Class.forName("io.canvasmc.canvas.region.WorldRegionizer", false, loader);
+ return true;
+ } catch (Throwable ignored) {
+ return server().getName().toLowerCase(Locale.ROOT).contains("canvas");
+ }
+ }
+
+ private static void addAllDiagnostics(List diagnostics, Diagnostic... values) {
+ for (Diagnostic value : values) {
+ diagnostics.add(value);
+ }
+ }
+
+ private static ValueWithDiagnostics withDiagnostics(Mode mode, Diagnostic... diagnostics) {
+ return new ValueWithDiagnostics<>(mode, diagnostics);
+ }
+
+ private static ValueWithDiagnostics withDiagnostics(Mode mode, List diagnostics) {
+ return new ValueWithDiagnostics<>(mode, diagnostics);
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/core/safeguard/task/ValueWithDiagnostics.java b/core/src/main/java/art/arcane/iris/core/safeguard/task/ValueWithDiagnostics.java
new file mode 100644
index 000000000..6650cc4a0
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/core/safeguard/task/ValueWithDiagnostics.java
@@ -0,0 +1,40 @@
+package art.arcane.iris.core.safeguard.task;
+
+import java.util.List;
+
+public class ValueWithDiagnostics {
+ private final T value;
+ private final List diagnostics;
+
+ public ValueWithDiagnostics(T value, List diagnostics) {
+ this.value = value;
+ this.diagnostics = List.copyOf(diagnostics);
+ }
+
+ public ValueWithDiagnostics(T value, Diagnostic... diagnostics) {
+ this.value = value;
+ this.diagnostics = List.of(diagnostics);
+ }
+
+ public T getValue() {
+ return value;
+ }
+
+ public List getDiagnostics() {
+ return diagnostics;
+ }
+
+ public void log() {
+ log(true, false);
+ }
+
+ public void log(boolean withException) {
+ log(withException, false);
+ }
+
+ public void log(boolean withException, boolean withStackTrace) {
+ for (Diagnostic diagnostic : diagnostics) {
+ diagnostic.log(withException, withStackTrace);
+ }
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/core/scripting/environment/EngineEnvironment.java b/core/src/main/java/art/arcane/iris/core/scripting/environment/EngineEnvironment.java
deleted file mode 100644
index 5682a89c6..000000000
--- a/core/src/main/java/art/arcane/iris/core/scripting/environment/EngineEnvironment.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package art.arcane.iris.core.scripting.environment;
-
-import art.arcane.iris.core.loader.IrisRegistrant;
-import art.arcane.iris.core.scripting.func.UpdateExecutor;
-import art.arcane.iris.core.scripting.kotlin.environment.IrisExecutionEnvironment;
-import art.arcane.iris.engine.framework.Engine;
-import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
-import lombok.NonNull;
-import org.bukkit.Chunk;
-import org.bukkit.Location;
-import org.bukkit.entity.Entity;
-import org.jetbrains.annotations.Nullable;
-
-public interface EngineEnvironment extends PackEnvironment {
- static EngineEnvironment create(@NonNull Engine engine) {
- return new IrisExecutionEnvironment(engine);
- }
-
- @NonNull
- Engine getEngine();
-
- @Nullable
- Object spawnMob(@NonNull String script, @NonNull Location location);
-
- void postSpawnMob(@NonNull String script, @NonNull Location location, @NonNull Entity mob);
-
- void preprocessObject(@NonNull String script, @NonNull IrisRegistrant object);
-
- void updateChunk(@NonNull String script, @NonNull MantleChunk> mantleChunk, @NonNull Chunk chunk, @NonNull UpdateExecutor executor);
-}
diff --git a/core/src/main/java/art/arcane/iris/core/scripting/environment/PackEnvironment.java b/core/src/main/java/art/arcane/iris/core/scripting/environment/PackEnvironment.java
deleted file mode 100644
index ff159a715..000000000
--- a/core/src/main/java/art/arcane/iris/core/scripting/environment/PackEnvironment.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package art.arcane.iris.core.scripting.environment;
-
-import art.arcane.iris.core.loader.IrisData;
-import art.arcane.iris.core.scripting.kotlin.environment.IrisPackExecutionEnvironment;
-import art.arcane.iris.engine.framework.Engine;
-import art.arcane.volmlib.util.math.RNG;
-import lombok.NonNull;
-import org.jetbrains.annotations.Nullable;
-
-public interface PackEnvironment extends SimpleEnvironment {
- static PackEnvironment create(@NonNull IrisData data) {
- return new IrisPackExecutionEnvironment(data);
- }
-
- @NonNull
- IrisData getData();
-
- @Nullable
- Object createNoise(@NonNull String script, @NonNull RNG rng);
-
- EngineEnvironment with(@NonNull Engine engine);
-}
\ No newline at end of file
diff --git a/core/src/main/java/art/arcane/iris/core/scripting/environment/SimpleEnvironment.java b/core/src/main/java/art/arcane/iris/core/scripting/environment/SimpleEnvironment.java
deleted file mode 100644
index 387b0ed2a..000000000
--- a/core/src/main/java/art/arcane/iris/core/scripting/environment/SimpleEnvironment.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package art.arcane.iris.core.scripting.environment;
-
-import art.arcane.iris.core.scripting.kotlin.environment.IrisSimpleExecutionEnvironment;
-import lombok.NonNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.util.Map;
-
-public interface SimpleEnvironment {
- static SimpleEnvironment create() {
- return new IrisSimpleExecutionEnvironment();
- }
-
- static SimpleEnvironment create(@NonNull File projectDir) {
- return new IrisSimpleExecutionEnvironment(projectDir);
- }
-
- void configureProject();
-
- void execute(@NonNull String script);
-
- void execute(@NonNull String script, @NonNull Class> type, @Nullable Map<@NonNull String, Object> vars);
-
- @Nullable
- Object evaluate(@NonNull String script);
-
- @Nullable
- Object evaluate(@NonNull String script, @NonNull Class> type, @Nullable Map<@NonNull String, Object> vars);
-}
\ No newline at end of file
diff --git a/core/src/main/java/art/arcane/iris/core/scripting/func/BiomeLookup.java b/core/src/main/java/art/arcane/iris/core/scripting/func/BiomeLookup.java
deleted file mode 100644
index f7d306c24..000000000
--- a/core/src/main/java/art/arcane/iris/core/scripting/func/BiomeLookup.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package art.arcane.iris.core.scripting.func;
-
-import art.arcane.iris.engine.object.IrisBiome;
-import art.arcane.volmlib.util.documentation.BlockCoordinates;
-
-@FunctionalInterface
-public interface BiomeLookup {
- @BlockCoordinates
- IrisBiome at(int x, int z);
-}
diff --git a/core/src/main/java/art/arcane/iris/core/scripting/func/UpdateExecutor.java b/core/src/main/java/art/arcane/iris/core/scripting/func/UpdateExecutor.java
deleted file mode 100644
index 3796ceb5d..000000000
--- a/core/src/main/java/art/arcane/iris/core/scripting/func/UpdateExecutor.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package art.arcane.iris.core.scripting.func;
-
-import org.jetbrains.annotations.NotNull;
-
-@FunctionalInterface
-public interface UpdateExecutor {
-
- @NotNull Runnable wrap(int delay, @NotNull Runnable runnable);
-
- @NotNull
- default Runnable wrap(@NotNull Runnable runnable) {
- return wrap(1, runnable);
- }
-
- default void execute(@NotNull Runnable runnable) {
- execute(1, runnable);
- }
-
- default void execute(int delay, @NotNull Runnable runnable) {
- wrap(delay, runnable).run();
- }
-}
diff --git a/core/src/main/java/art/arcane/iris/engine/IrisEngine.java b/core/src/main/java/art/arcane/iris/engine/IrisEngine.java
index ac485857e..6629df689 100644
--- a/core/src/main/java/art/arcane/iris/engine/IrisEngine.java
+++ b/core/src/main/java/art/arcane/iris/engine/IrisEngine.java
@@ -29,7 +29,6 @@ import art.arcane.iris.core.loader.ResourceLoader;
import art.arcane.iris.core.nms.container.BlockPos;
import art.arcane.iris.core.nms.container.Pair;
import art.arcane.iris.core.project.IrisProject;
-import art.arcane.iris.core.scripting.environment.EngineEnvironment;
import art.arcane.iris.core.service.PreservationSVC;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.data.cache.AtomicCache;
@@ -97,7 +96,6 @@ public class IrisEngine implements Engine {
private CompletableFuture hash32;
private EngineMode mode;
private EngineEffects effects;
- private EngineEnvironment execution;
private EngineWorldManager worldManager;
private volatile int parallelism;
private boolean failing;
@@ -131,7 +129,6 @@ public class IrisEngine implements Engine {
cleaning = new AtomicBoolean(false);
noisemapPrebakeRunning = new AtomicBoolean(false);
modeFallbackLogged = new AtomicBoolean(false);
- execution = getData().getEnvironment().with(this);
if (studio) {
getData().dump();
getData().clearLists();
@@ -190,7 +187,6 @@ public class IrisEngine implements Engine {
if (currentMode != null) {
currentMode.close();
}
- execution = getData().getEnvironment().with(this);
J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace());
}
@@ -207,7 +203,6 @@ public class IrisEngine implements Engine {
IrisWorldManager manager = new IrisWorldManager(this);
worldManager = manager;
manager.startManager();
- getDimension().getEngineScripts().forEach(execution::execute);
J.a(this::computeBiomeMaxes);
J.a(() -> {
File[] roots = getData().getLoaders()
diff --git a/core/src/main/java/art/arcane/iris/engine/IrisNoisemapPrebakePipeline.java b/core/src/main/java/art/arcane/iris/engine/IrisNoisemapPrebakePipeline.java
index 629d94746..b9bb90a19 100644
--- a/core/src/main/java/art/arcane/iris/engine/IrisNoisemapPrebakePipeline.java
+++ b/core/src/main/java/art/arcane/iris/engine/IrisNoisemapPrebakePipeline.java
@@ -82,7 +82,6 @@ public final class IrisNoisemapPrebakePipeline {
"ravines",
"mods",
"expressions",
- "scripts",
"images",
"snippet"
);
diff --git a/core/src/main/java/art/arcane/iris/engine/framework/Engine.java b/core/src/main/java/art/arcane/iris/engine/framework/Engine.java
index 778810b3b..e118be386 100644
--- a/core/src/main/java/art/arcane/iris/engine/framework/Engine.java
+++ b/core/src/main/java/art/arcane/iris/engine/framework/Engine.java
@@ -29,7 +29,6 @@ import art.arcane.iris.core.loader.IrisRegistrant;
import art.arcane.iris.core.nms.container.BlockPos;
import art.arcane.iris.core.nms.container.Pair;
import art.arcane.iris.core.pregenerator.ChunkUpdater;
-import art.arcane.iris.core.scripting.environment.EngineEnvironment;
import art.arcane.iris.core.service.ExternalDataSVC;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.IrisComplex;
@@ -114,8 +113,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
IrisContext getContext();
- EngineEnvironment getExecution();
-
double getMaxBiomeObjectDensity();
double getMaxBiomeDecoratorDensity();
@@ -390,16 +387,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
}, RNG.r.i(1, 20))); //Why is there a random delay here?
});
- chunk.raiseFlagUnchecked(MantleFlag.SCRIPT, () -> {
- var scripts = getDimension().getChunkUpdateScripts();
- if (scripts == null || scripts.isEmpty())
- return;
-
- for (var script : scripts) {
- getExecution().updateChunk(script, chunk, c, (delay, task) -> run(semaphore, c, task, delay));
- }
- });
-
try {
semaphore.acquire(1024);
} catch (InterruptedException ex) {
diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/MatterGenerator.java b/core/src/main/java/art/arcane/iris/engine/mantle/MatterGenerator.java
new file mode 100644
index 000000000..323dd159e
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/engine/mantle/MatterGenerator.java
@@ -0,0 +1,248 @@
+package art.arcane.iris.engine.mantle;
+
+import art.arcane.iris.Iris;
+import art.arcane.iris.core.IrisSettings;
+import art.arcane.iris.core.nms.container.Pair;
+import art.arcane.iris.engine.framework.Engine;
+import art.arcane.iris.util.common.misc.RegenRuntime;
+import art.arcane.iris.util.common.parallel.MultiBurst;
+import art.arcane.iris.util.project.context.ChunkContext;
+import art.arcane.iris.util.project.matter.TileWrapper;
+import art.arcane.volmlib.util.documentation.ChunkCoordinates;
+import art.arcane.volmlib.util.mantle.flag.MantleFlag;
+import art.arcane.volmlib.util.mantle.runtime.Mantle;
+import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
+import art.arcane.volmlib.util.matter.Matter;
+import org.bukkit.block.data.BlockData;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
+
+public interface MatterGenerator {
+ long REGEN_PASS_CACHE_TTL_MS = 600000L;
+ Executor DISPATCHER = MultiBurst.burst;
+ ConcurrentHashMap> REGEN_GENERATED_CHUNKS_BY_PASS = new ConcurrentHashMap<>();
+ ConcurrentHashMap> REGEN_CLEARED_CHUNKS_BY_PASS = new ConcurrentHashMap<>();
+ ConcurrentHashMap> REGEN_PLANNED_CHUNKS_BY_PASS = new ConcurrentHashMap<>();
+ ConcurrentHashMap REGEN_PASS_TOUCHED_MS = new ConcurrentHashMap<>();
+
+ Engine getEngine();
+
+ Mantle getMantle();
+
+ int getRadius();
+
+ int getRealRadius();
+
+ List, Integer>> getComponents();
+
+ @ChunkCoordinates
+ default void generateMatter(int x, int z, boolean multicore, ChunkContext context) {
+ if (!getEngine().getDimension().isUseMantle()) {
+ return;
+ }
+
+ boolean useMulticore = multicore || IrisSettings.get().getGenerator().isUseMulticoreMantle();
+ String threadName = Thread.currentThread().getName();
+ boolean regenThread = threadName.startsWith("Iris-Regen-");
+ boolean traceRegen = regenThread && IrisSettings.get().getGeneral().isDebug();
+ boolean forceRegen = regenThread;
+ String regenPassKey = forceRegen ? resolveRegenPassKey(threadName) : null;
+ boolean optimizedRegen = forceRegen && !IrisSettings.get().getGeneral().isDebug() && regenPassKey != null;
+ int writeRadius = optimizedRegen ? Math.min(getRadius(), getRealRadius()) : getRadius();
+ Set clearedChunks = optimizedRegen ? getRegenPassSet(REGEN_CLEARED_CHUNKS_BY_PASS, regenPassKey) : new HashSet<>();
+ Set plannedChunks = optimizedRegen ? getRegenPassSet(REGEN_PLANNED_CHUNKS_BY_PASS, regenPassKey) : null;
+
+ if (optimizedRegen) {
+ touchRegenPass(regenPassKey);
+ }
+
+ if (traceRegen) {
+ Iris.info("Regen matter start: center=" + x + "," + z
+ + " radius=" + getRadius()
+ + " realRadius=" + getRealRadius()
+ + " writeRadius=" + writeRadius
+ + " multicore=" + useMulticore
+ + " components=" + getComponents().size()
+ + " optimized=" + optimizedRegen
+ + " passKey=" + (regenPassKey == null ? "none" : regenPassKey)
+ + " thread=" + threadName);
+ }
+
+ try (MantleWriter writer = new MantleWriter(getEngine().getMantle(), getMantle(), x, z, writeRadius, useMulticore)) {
+ for (Pair, Integer> pair : getComponents()) {
+ int rawPassRadius = pair.getB();
+ int passRadius = optimizedRegen ? Math.min(rawPassRadius, writeRadius) : rawPassRadius;
+ String passFlags = pair.getA().stream().map(component -> component.getFlag().toString()).collect(Collectors.joining(","));
+ String passFlagKey = optimizedRegen ? regenPassKey + "|" + passFlags : null;
+ Set generatedChunks = passFlagKey == null ? null : getRegenPassSet(REGEN_GENERATED_CHUNKS_BY_PASS, passFlagKey);
+ int visitedChunks = 0;
+ int clearedCount = 0;
+ int plannedSkipped = 0;
+ int componentSkipped = 0;
+ int componentForcedReset = 0;
+ int launchedLayers = 0;
+ int dedupSkipped = 0;
+ List> launchedTasks = useMulticore ? new ArrayList<>() : null;
+
+ if (passFlagKey != null) {
+ touchRegenPass(passFlagKey);
+ }
+ if (traceRegen) {
+ Iris.info("Regen matter pass start: center=" + x + "," + z
+ + " passRadius=" + passRadius
+ + " rawPassRadius=" + rawPassRadius
+ + " flags=[" + passFlags + "]");
+ }
+
+ for (int i = -passRadius; i <= passRadius; i++) {
+ for (int j = -passRadius; j <= passRadius; j++) {
+ int passX = x + i;
+ int passZ = z + j;
+ visitedChunks++;
+ long passKey = chunkKey(passX, passZ);
+ if (generatedChunks != null && !generatedChunks.add(passKey)) {
+ dedupSkipped++;
+ continue;
+ }
+
+ MantleChunk chunk = writer.acquireChunk(passX, passZ);
+ if (forceRegen) {
+ if (clearedChunks.add(passKey)) {
+ chunk.deleteSlices(BlockData.class);
+ chunk.deleteSlices(String.class);
+ chunk.deleteSlices(TileWrapper.class);
+ chunk.flag(MantleFlag.PLANNED, false);
+ clearedCount++;
+ }
+ }
+
+ if (!forceRegen && chunk.isFlagged(MantleFlag.PLANNED)) {
+ plannedSkipped++;
+ continue;
+ }
+
+ for (MantleComponent component : pair.getA()) {
+ if (!forceRegen && chunk.isFlagged(component.getFlag())) {
+ componentSkipped++;
+ continue;
+ }
+ if (forceRegen && chunk.isFlagged(component.getFlag())) {
+ chunk.flag(component.getFlag(), false);
+ componentForcedReset++;
+ }
+
+ launchedLayers++;
+ int finalPassX = passX;
+ int finalPassZ = passZ;
+ MantleChunk finalChunk = chunk;
+ MantleComponent finalComponent = component;
+ Runnable task = () -> finalChunk.raiseFlagUnchecked(finalComponent.getFlag(),
+ () -> finalComponent.generateLayer(writer, finalPassX, finalPassZ, context));
+
+ if (useMulticore) {
+ launchedTasks.add(CompletableFuture.runAsync(task, DISPATCHER));
+ } else {
+ task.run();
+ }
+ }
+ }
+ }
+
+ if (useMulticore) {
+ for (CompletableFuture launchedTask : launchedTasks) {
+ launchedTask.join();
+ }
+ }
+
+ if (traceRegen) {
+ Iris.info("Regen matter pass done: center=" + x + "," + z
+ + " passRadius=" + passRadius
+ + " rawPassRadius=" + rawPassRadius
+ + " visited=" + visitedChunks
+ + " cleared=" + clearedCount
+ + " dedupSkipped=" + dedupSkipped
+ + " plannedSkipped=" + plannedSkipped
+ + " componentSkipped=" + componentSkipped
+ + " componentForcedReset=" + componentForcedReset
+ + " launchedLayers=" + launchedLayers
+ + " flags=[" + passFlags + "]");
+ }
+ }
+
+ for (int i = -getRealRadius(); i <= getRealRadius(); i++) {
+ for (int j = -getRealRadius(); j <= getRealRadius(); j++) {
+ int realX = x + i;
+ int realZ = z + j;
+ long realKey = chunkKey(realX, realZ);
+ if (plannedChunks != null && !plannedChunks.add(realKey)) {
+ continue;
+ }
+ writer.acquireChunk(realX, realZ).flag(MantleFlag.PLANNED, true);
+ }
+ }
+ }
+
+ if (traceRegen) {
+ Iris.info("Regen matter done: center=" + x + "," + z
+ + " markedRealRadius=" + getRealRadius()
+ + " forceRegen=" + forceRegen);
+ }
+ }
+
+ private static long chunkKey(int x, int z) {
+ return (((long) x) << 32) ^ (z & 0xffffffffL);
+ }
+
+ private static Set getRegenPassSet(ConcurrentHashMap> store, String passKey) {
+ return store.computeIfAbsent(passKey, key -> ConcurrentHashMap.newKeySet());
+ }
+
+ private static String resolveRegenPassKey(String threadName) {
+ String runtimeKey = RegenRuntime.getRunId();
+ if (runtimeKey != null && !runtimeKey.isBlank()) {
+ return runtimeKey;
+ }
+
+ if (!threadName.startsWith("Iris-Regen-")) {
+ return null;
+ }
+
+ String suffix = threadName.substring("Iris-Regen-".length());
+ int lastDash = suffix.lastIndexOf('-');
+ if (lastDash <= 0) {
+ return suffix;
+ }
+ return suffix.substring(0, lastDash);
+ }
+
+ private static void touchRegenPass(String passKey) {
+ long now = System.currentTimeMillis();
+ REGEN_PASS_TOUCHED_MS.put(passKey, now);
+ if (REGEN_PASS_TOUCHED_MS.size() <= 64) {
+ return;
+ }
+
+ Iterator> iterator = REGEN_PASS_TOUCHED_MS.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry entry = iterator.next();
+ if (now - entry.getValue() <= REGEN_PASS_CACHE_TTL_MS) {
+ continue;
+ }
+
+ String key = entry.getKey();
+ iterator.remove();
+ REGEN_GENERATED_CHUNKS_BY_PASS.remove(key);
+ REGEN_CLEARED_CHUNKS_BY_PASS.remove(key);
+ REGEN_PLANNED_CHUNKS_BY_PASS.remove(key);
+ }
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java
index 62cfefc0d..a368568bd 100644
--- a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java
+++ b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java
@@ -78,7 +78,6 @@ public class IrisDimension extends IrisRegistrant {
private final transient AtomicCache cosr = new AtomicCache<>();
private final transient AtomicCache rad = new AtomicCache<>();
private final transient AtomicCache featuresUsed = new AtomicCache<>();
- private final transient AtomicCache>> cachedPreProcessors = new AtomicCache<>();
private final transient AtomicCache