diff --git a/build.gradle b/build.gradle index 94767f4c1..fe1ca5d0b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,332 +1,332 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -plugins { - id 'java' - id 'java-library' - id "io.freefair.lombok" version "6.3.0" - id "com.github.johnrengelman.shadow" version "7.1.2" - id "de.undercouch.download" version "5.0.1" -} - -version '2.6.1-1.19.4' -def nmsVersion = "1.19.4" //[NMS] -def apiVersion = '1.19' -def specialSourceVersion = '1.11.0' //[NMS] -def spigotJarVersion = '1.19.4-R0.1-SNAPSHOT' //[NMS] -def name = getRootProject().getName() // Defined in settings.gradle -def main = 'com.volmit.iris.Iris' - -// 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') -// ========================== UNIX ============================== -registerCustomOutputTaskUnix('CyberpwnLT', '/Users/danielmills/development/server/plugins') -registerCustomOutputTaskUnix('PsychoLT', '/Users/brianfopiano/Desktop/REMOTES/RemoteMinecraft/plugins') -// ============================================================== - -/** - * Gradle is weird sometimes, we need to delete the plugin yml from the build folder to actually filter properly. - */ -file(jar.archiveFile.get().getAsFile().getParentFile().getParentFile().getParentFile().getAbsolutePath() + '/build/resources/main/plugin.yml').delete() - -/** - * Expand properties into plugin yml - */ -processResources { - filesMatching('**/plugin.yml') { - expand( - 'name': name.toString(), - 'version': version.toString(), - 'main': main.toString(), - 'apiversion': apiVersion.toString() - ) - } -} - -/** - * Unified repo - */ -repositories { - mavenLocal { - content { - includeGroup("org.bukkit") - includeGroup("org.spigotmc") - } - } - mavenCentral() - maven { url "https://arcanearts.jfrog.io/artifactory/archives" } - maven { url "https://mvn.lumine.io/repository/maven-public/" } - maven { url "https://jitpack.io"} - - maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots" } - maven { url "https://mvn.lumine.io/repository/maven/" } - maven { url "https://repo.triumphteam.dev/snapshots" } - maven { url "https://repo.mineinabyss.com/releases" } -} - -/** - * We need parameter meta for the decree command system - */ -compileJava { - options.compilerArgs << '-parameters' -} - -/** - * Configure Iris for shading - */ -shadowJar { - //minimize() - append("plugin.yml") - relocate 'com.dfsek.paralithic', 'com.volmit.iris.util.paralithic' - relocate 'io.papermc.lib', 'com.volmit.iris.util.paper' - relocate 'net.kyori', 'com.volmit.iris.util.kyori' - dependencies { - include(dependency('io.papermc:paperlib')) - include(dependency('com.dfsek:Paralithic')) - include(dependency('net.kyori:')) - } -} - -configurations.all { - resolutionStrategy.cacheChangingModulesFor 60, 'minutes' - resolutionStrategy.cacheDynamicVersionsFor 60, 'minutes' -} - -/** - * Dependencies. - * - * Provided or classpath dependencies are not shaded and are available on the runtime classpath - * - * Shaded dependencies are not available at runtime, nor are they available on mvn central so they - * need to be shaded into the jar (increasing binary size) - * - * Dynamically loaded dependencies are defined in the plugin.yml (updating these must be updated in the - * plugin.yml also, otherwise they wont be available). These do not increase binary size). Only declare - * these dependencies if they are available on mvn central. - */ -dependencies { - // Provided or Classpath - compileOnly 'org.projectlombok:lombok:1.18.24' - annotationProcessor 'org.projectlombok:lombok:1.18.24' - implementation 'org.spigotmc:spigot-api:1.19.4-R0.1-SNAPSHOT' - implementation 'org.bukkit:craftbukkit:1.19.4-R0.1-SNAPSHOT:remapped-mojang' //[NMS] - - // Third Party Integrations - implementation 'com.github.oraxen:oraxen:1.152.5' - implementation 'com.github.LoneDev6:api-itemsadder:3.2.5' - implementation 'me.clip:placeholderapi:2.11.3' - //implementation files('libs/CustomItems.jar') - - // Shaded - implementation 'com.dfsek:Paralithic:0.4.0' - implementation 'io.papermc:paperlib:1.0.5' - implementation "net.kyori:adventure-text-minimessage:4.13.1" - implementation 'net.kyori:adventure-platform-bukkit:4.3.0' - implementation 'net.kyori:adventure-api:4.13.1' - implementation 'io.lumine:Mythic-Dist:5.2.1' - - // Dynamically Loaded - implementation 'io.timeandspace:smoothie-map:2.0.2' - implementation 'it.unimi.dsi:fastutil:8.5.8' - implementation 'com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2' - implementation 'org.zeroturnaround:zt-zip:1.14' - implementation 'com.google.code.gson:gson:2.9.0' - implementation 'org.ow2.asm:asm:9.2' - implementation 'com.google.guava:guava:31.1-jre' - implementation 'bsf:bsf:2.4.0' - implementation 'rhino:js:1.7R2' - implementation 'com.github.ben-manes.caffeine:caffeine:3.0.6' - implementation 'org.apache.commons:commons-lang3:3.12.0' -} - -if (JavaVersion.current().toString() != "17") { - System.err.println() - System.err.println("=========================================================================================================") - System.err.println("You must run gradle on Java 17. You are using " + JavaVersion.current()) - System.err.println() - System.err.println("=== For IDEs ===") - System.err.println("1. Configure the project for Java 17") - System.err.println("2. Configure the bundled gradle to use Java 17 in settings") - System.err.println() - System.err.println("=== For Command Line (gradlew) ===") - System.err.println("1. Install JDK 17 from https://www.oracle.com/java/technologies/javase/jdk17-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-17.0.1") - 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); -} - -def buildToolsJar = new File(buildDir, "buildtools/BuildTools.jar"); -def specialSourceJar = new File(buildDir, "specialsource/SpecialSource.jar"); -def buildToolsFolder = new File(buildDir, "buildtools"); -def specialSourceFolder = new File(buildDir, "specialsource"); -def buildToolsHint = new File(buildDir, "buildtools/craftbukkit-" + nmsVersion + ".jar"); -def outputShadeJar = new File(buildDir, "libs/Iris-" + version + "-all.jar"); -def ssiJar = new File(buildDir, "specialsource/Iris-" + version + "-all.jar"); -def ssobfJar = new File(buildDir, "specialsource/Iris-" + version + "-rmo.jar"); -def ssJar = new File(buildDir, "specialsource/Iris-" + version + "-rma.jar"); -def homePath = System.properties['user.home'] -def m2 = new File(homePath + "/.m2/repository") -def m2s = m2.getAbsolutePath(); - -// ======================== Building Mapped Jars ============================= -task downloadBuildtools(type: Download) { - group "remapping" - src 'https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar' - dest buildToolsJar - onlyIf { - !buildToolsJar.exists() - } -} - -task downloadSpecialSource(type: Download) { - group "remapping" - src 'https://repo.maven.apache.org/maven2/net/md-5/SpecialSource/' + specialSourceVersion + '/SpecialSource-'+specialSourceVersion+'-shaded.jar' - dest specialSourceJar - onlyIf { - !specialSourceJar.exists() - } -} - -task executeBuildTools(dependsOn: downloadBuildtools, type: JavaExec) -{ - group "remapping" - classpath = files(buildToolsJar) - workingDir = buildToolsFolder - args = [ - "--rev", - nmsVersion, - "--compile", - "craftbukkit", - "--remap" - ] - onlyIf { - !buildToolsHint.exists() - } -} - -task copyBuildToSpecialSource(type: Copy) -{ - group "remapping" - from outputShadeJar - into specialSourceFolder - dependsOn(downloadSpecialSource, shadowJar) -} - -task specialSourceRemapObfuscate(type: JavaExec) -{ - group "remapping" - dependsOn(copyBuildToSpecialSource, downloadSpecialSource, shadowJar) - workingDir = specialSourceFolder - classpath = files(specialSourceJar, - new File(m2s + "/org/spigotmc/spigot/" + spigotJarVersion + "/spigot-" + spigotJarVersion + "-remapped-mojang.jar")) - mainClass = "net.md_5.specialsource.SpecialSource" - args = [ - "--live", - "-i", - ssiJar.getName(), - "-o", - ssobfJar.getName(), - "-m", - m2s + "/org/spigotmc/minecraft-server/" + spigotJarVersion + "/minecraft-server-" + spigotJarVersion + "-maps-mojang.txt", - "--reverse", - ] -} - -task specialSourceRemap(type: JavaExec) -{ - group "remapping" - dependsOn(specialSourceRemapObfuscate) - workingDir = specialSourceFolder - classpath = files(specialSourceJar, - new File(m2s + "/org/spigotmc/spigot/" + spigotJarVersion + "/spigot-" + spigotJarVersion + "-remapped-obf.jar")) - mainClass = "net.md_5.specialsource.SpecialSource" - args = [ - "--live", - "-i", - ssobfJar.getName(), - "-o", - ssJar.getName(), - "-m", - m2s + "/org/spigotmc/minecraft-server/" + spigotJarVersion + "/minecraft-server-" + spigotJarVersion + "-maps-spigot.csrg" - ] -} - -tasks.compileJava.dependsOn(executeBuildTools) - -compileJava { - options.encoding = "UTF-8" -} - -task setup() -{ - group("iris") - dependsOn(clean, executeBuildTools) -} - -task iris(type: Copy) -{ - group "iris" - from ssJar - into buildDir - rename { String fileName -> - fileName.replace('Iris-' + version + '-rma.jar', "Iris-" + version + ".jar") - } - dependsOn(specialSourceRemap) -} - -def registerCustomOutputTask(name, path) { - if (!System.properties['os.name'].toLowerCase().contains('windows')) { - return; - } - - tasks.register('build' + name, Copy) { - group('development') - outputs.upToDateWhen { false } - dependsOn(iris) - from(new File(buildDir, "Iris-" + version + ".jar")) - into(file(path)) - rename { String fileName -> - fileName.replace("Iris-" + version + ".jar", "Iris.jar") - } - } -} - -def registerCustomOutputTaskUnix(name, path) { - if (System.properties['os.name'].toLowerCase().contains('windows')) { - return; - } - - tasks.register('build' + name, Copy) { - group('development') - outputs.upToDateWhen { false } - dependsOn(iris) - from(new File(buildDir, "Iris-" + version + ".jar")) - into(file(path)) - rename { String fileName -> - fileName.replace("Iris-" + version + ".jar", "Iris.jar") - } - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +plugins { + id 'java' + id 'java-library' + id "io.freefair.lombok" version "6.3.0" + id "com.github.johnrengelman.shadow" version "7.1.2" + id "de.undercouch.download" version "5.0.1" +} + +version '2.6.2-1.19.4' +def nmsVersion = "1.19.4" //[NMS] +def apiVersion = '1.19' +def specialSourceVersion = '1.11.0' //[NMS] +def spigotJarVersion = '1.19.4-R0.1-SNAPSHOT' //[NMS] +def name = getRootProject().getName() // Defined in settings.gradle +def main = 'com.volmit.iris.Iris' + +// 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') +// ========================== UNIX ============================== +registerCustomOutputTaskUnix('CyberpwnLT', '/Users/danielmills/development/server/plugins') +registerCustomOutputTaskUnix('PsychoLT', '/Users/brianfopiano/Desktop/REMOTES/RemoteMinecraft/plugins') +// ============================================================== + +/** + * Gradle is weird sometimes, we need to delete the plugin yml from the build folder to actually filter properly. + */ +file(jar.archiveFile.get().getAsFile().getParentFile().getParentFile().getParentFile().getAbsolutePath() + '/build/resources/main/plugin.yml').delete() + +/** + * Expand properties into plugin yml + */ +processResources { + filesMatching('**/plugin.yml') { + expand( + 'name': name.toString(), + 'version': version.toString(), + 'main': main.toString(), + 'apiversion': apiVersion.toString() + ) + } +} + +/** + * Unified repo + */ +repositories { + mavenLocal { + content { + includeGroup("org.bukkit") + includeGroup("org.spigotmc") + } + } + mavenCentral() + maven { url "https://arcanearts.jfrog.io/artifactory/archives" } + maven { url "https://mvn.lumine.io/repository/maven-public/" } + maven { url "https://jitpack.io"} + + maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots" } + maven { url "https://mvn.lumine.io/repository/maven/" } + maven { url "https://repo.triumphteam.dev/snapshots" } + maven { url "https://repo.mineinabyss.com/releases" } +} + +/** + * We need parameter meta for the decree command system + */ +compileJava { + options.compilerArgs << '-parameters' +} + +/** + * Configure Iris for shading + */ +shadowJar { + //minimize() + append("plugin.yml") + relocate 'com.dfsek.paralithic', 'com.volmit.iris.util.paralithic' + relocate 'io.papermc.lib', 'com.volmit.iris.util.paper' + relocate 'net.kyori', 'com.volmit.iris.util.kyori' + dependencies { + include(dependency('io.papermc:paperlib')) + include(dependency('com.dfsek:Paralithic')) + include(dependency('net.kyori:')) + } +} + +configurations.all { + resolutionStrategy.cacheChangingModulesFor 60, 'minutes' + resolutionStrategy.cacheDynamicVersionsFor 60, 'minutes' +} + +/** + * Dependencies. + * + * Provided or classpath dependencies are not shaded and are available on the runtime classpath + * + * Shaded dependencies are not available at runtime, nor are they available on mvn central so they + * need to be shaded into the jar (increasing binary size) + * + * Dynamically loaded dependencies are defined in the plugin.yml (updating these must be updated in the + * plugin.yml also, otherwise they wont be available). These do not increase binary size). Only declare + * these dependencies if they are available on mvn central. + */ +dependencies { + // Provided or Classpath + compileOnly 'org.projectlombok:lombok:1.18.24' + annotationProcessor 'org.projectlombok:lombok:1.18.24' + implementation 'org.spigotmc:spigot-api:1.19.4-R0.1-SNAPSHOT' + implementation 'org.bukkit:craftbukkit:1.19.4-R0.1-SNAPSHOT:remapped-mojang' //[NMS] + + // Third Party Integrations + implementation 'com.github.oraxen:oraxen:1.152.5' + implementation 'com.github.LoneDev6:api-itemsadder:3.4.1-r4' + implementation 'me.clip:placeholderapi:2.11.3' + //implementation files('libs/CustomItems.jar') + + // Shaded + implementation 'com.dfsek:Paralithic:0.4.0' + implementation 'io.papermc:paperlib:1.0.5' + implementation "net.kyori:adventure-text-minimessage:4.13.1" + implementation 'net.kyori:adventure-platform-bukkit:4.3.0' + implementation 'net.kyori:adventure-api:4.13.1' + implementation 'io.lumine:Mythic-Dist:5.2.1' + + // Dynamically Loaded + implementation 'io.timeandspace:smoothie-map:2.0.2' + implementation 'it.unimi.dsi:fastutil:8.5.8' + implementation 'com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2' + implementation 'org.zeroturnaround:zt-zip:1.14' + implementation 'com.google.code.gson:gson:2.9.0' + implementation 'org.ow2.asm:asm:9.2' + implementation 'com.google.guava:guava:31.1-jre' + implementation 'bsf:bsf:2.4.0' + implementation 'rhino:js:1.7R2' + implementation 'com.github.ben-manes.caffeine:caffeine:3.0.6' + implementation 'org.apache.commons:commons-lang3:3.12.0' +} + +if (JavaVersion.current().toString() != "17") { + System.err.println() + System.err.println("=========================================================================================================") + System.err.println("You must run gradle on Java 17. You are using " + JavaVersion.current()) + System.err.println() + System.err.println("=== For IDEs ===") + System.err.println("1. Configure the project for Java 17") + System.err.println("2. Configure the bundled gradle to use Java 17 in settings") + System.err.println() + System.err.println("=== For Command Line (gradlew) ===") + System.err.println("1. Install JDK 17 from https://www.oracle.com/java/technologies/javase/jdk17-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-17.0.1") + 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); +} + +def buildToolsJar = new File(buildDir, "buildtools/BuildTools.jar"); +def specialSourceJar = new File(buildDir, "specialsource/SpecialSource.jar"); +def buildToolsFolder = new File(buildDir, "buildtools"); +def specialSourceFolder = new File(buildDir, "specialsource"); +def buildToolsHint = new File(buildDir, "buildtools/craftbukkit-" + nmsVersion + ".jar"); +def outputShadeJar = new File(buildDir, "libs/Iris-" + version + "-all.jar"); +def ssiJar = new File(buildDir, "specialsource/Iris-" + version + "-all.jar"); +def ssobfJar = new File(buildDir, "specialsource/Iris-" + version + "-rmo.jar"); +def ssJar = new File(buildDir, "specialsource/Iris-" + version + "-rma.jar"); +def homePath = System.properties['user.home'] +def m2 = new File(homePath + "/.m2/repository") +def m2s = m2.getAbsolutePath(); + +// ======================== Building Mapped Jars ============================= +task downloadBuildtools(type: Download) { + group "remapping" + src 'https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar' + dest buildToolsJar + onlyIf { + !buildToolsJar.exists() + } +} + +task downloadSpecialSource(type: Download) { + group "remapping" + src 'https://repo.maven.apache.org/maven2/net/md-5/SpecialSource/' + specialSourceVersion + '/SpecialSource-'+specialSourceVersion+'-shaded.jar' + dest specialSourceJar + onlyIf { + !specialSourceJar.exists() + } +} + +task executeBuildTools(dependsOn: downloadBuildtools, type: JavaExec) +{ + group "remapping" + classpath = files(buildToolsJar) + workingDir = buildToolsFolder + args = [ + "--rev", + nmsVersion, + "--compile", + "craftbukkit", + "--remap" + ] + onlyIf { + !buildToolsHint.exists() + } +} + +task copyBuildToSpecialSource(type: Copy) +{ + group "remapping" + from outputShadeJar + into specialSourceFolder + dependsOn(downloadSpecialSource, shadowJar) +} + +task specialSourceRemapObfuscate(type: JavaExec) +{ + group "remapping" + dependsOn(copyBuildToSpecialSource, downloadSpecialSource, shadowJar) + workingDir = specialSourceFolder + classpath = files(specialSourceJar, + new File(m2s + "/org/spigotmc/spigot/" + spigotJarVersion + "/spigot-" + spigotJarVersion + "-remapped-mojang.jar")) + mainClass = "net.md_5.specialsource.SpecialSource" + args = [ + "--live", + "-i", + ssiJar.getName(), + "-o", + ssobfJar.getName(), + "-m", + m2s + "/org/spigotmc/minecraft-server/" + spigotJarVersion + "/minecraft-server-" + spigotJarVersion + "-maps-mojang.txt", + "--reverse", + ] +} + +task specialSourceRemap(type: JavaExec) +{ + group "remapping" + dependsOn(specialSourceRemapObfuscate) + workingDir = specialSourceFolder + classpath = files(specialSourceJar, + new File(m2s + "/org/spigotmc/spigot/" + spigotJarVersion + "/spigot-" + spigotJarVersion + "-remapped-obf.jar")) + mainClass = "net.md_5.specialsource.SpecialSource" + args = [ + "--live", + "-i", + ssobfJar.getName(), + "-o", + ssJar.getName(), + "-m", + m2s + "/org/spigotmc/minecraft-server/" + spigotJarVersion + "/minecraft-server-" + spigotJarVersion + "-maps-spigot.csrg" + ] +} + +tasks.compileJava.dependsOn(executeBuildTools) + +compileJava { + options.encoding = "UTF-8" +} + +task setup() +{ + group("iris") + dependsOn(clean, executeBuildTools) +} + +task iris(type: Copy) +{ + group "iris" + from ssJar + into buildDir + rename { String fileName -> + fileName.replace('Iris-' + version + '-rma.jar', "Iris-" + version + ".jar") + } + dependsOn(specialSourceRemap) +} + +def registerCustomOutputTask(name, path) { + if (!System.properties['os.name'].toLowerCase().contains('windows')) { + return; + } + + tasks.register('build' + name, Copy) { + group('development') + outputs.upToDateWhen { false } + dependsOn(iris) + from(new File(buildDir, "Iris-" + version + ".jar")) + into(file(path)) + rename { String fileName -> + fileName.replace("Iris-" + version + ".jar", "Iris.jar") + } + } +} + +def registerCustomOutputTaskUnix(name, path) { + if (System.properties['os.name'].toLowerCase().contains('windows')) { + return; + } + + tasks.register('build' + name, Copy) { + group('development') + outputs.upToDateWhen { false } + dependsOn(iris) + from(new File(buildDir, "Iris-" + version + ".jar")) + into(file(path)) + rename { String fileName -> + fileName.replace("Iris-" + version + ".jar", "Iris.jar") + } + } +} diff --git a/src/main/java/com/volmit/iris/Iris.java b/src/main/java/com/volmit/iris/Iris.java index 33ba6e23a..f494c91ea 100644 --- a/src/main/java/com/volmit/iris/Iris.java +++ b/src/main/java/com/volmit/iris/Iris.java @@ -1,768 +1,771 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonParser; -import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.ServerConfigurator; -import com.volmit.iris.core.link.IrisPapiExpansion; -import com.volmit.iris.core.link.MultiverseCoreLink; -import com.volmit.iris.core.link.MythicMobsLink; -import com.volmit.iris.core.loader.IrisData; -import com.volmit.iris.core.nms.INMS; -import com.volmit.iris.core.nms.v19_4.NMSBinding19_4; -import com.volmit.iris.core.pregenerator.LazyPregenerator; -import com.volmit.iris.core.service.StudioSVC; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.EnginePanic; -import com.volmit.iris.engine.object.IrisCompat; -import com.volmit.iris.engine.object.IrisDimension; -import com.volmit.iris.engine.object.IrisWorld; -import com.volmit.iris.engine.platform.BukkitChunkGenerator; -import com.volmit.iris.engine.platform.DummyChunkGenerator; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.exceptions.IrisException; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.function.NastyRunnable; -import com.volmit.iris.util.io.FileWatcher; -import com.volmit.iris.util.io.IO; -import com.volmit.iris.util.io.InstanceState; -import com.volmit.iris.util.io.JarScanner; -import com.volmit.iris.util.math.M; -import com.volmit.iris.util.math.RNG; -import com.volmit.iris.util.parallel.MultiBurst; -import com.volmit.iris.util.plugin.IrisService; -import com.volmit.iris.util.plugin.Metrics; -import com.volmit.iris.util.plugin.VolmitPlugin; -import com.volmit.iris.util.plugin.VolmitSender; -import com.volmit.iris.util.reflect.ShadeFix; -import com.volmit.iris.util.scheduling.J; -import com.volmit.iris.util.scheduling.Queue; -import com.volmit.iris.util.scheduling.ShurikenQueue; -import io.papermc.lib.PaperLib; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; -import net.kyori.adventure.text.serializer.ComponentSerializer; -import org.bukkit.*; -import org.bukkit.block.data.BlockData; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.generator.BiomeProvider; -import org.bukkit.generator.ChunkGenerator; -import org.bukkit.plugin.IllegalPluginAccessException; -import org.bukkit.plugin.Plugin; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.*; -import java.lang.annotation.Annotation; -import java.net.URL; -import java.util.Date; -import java.util.Map; - -@SuppressWarnings("CanBeFinal") -public class Iris extends VolmitPlugin implements Listener { - public static final String OVERWORLD_TAG = "3002"; - - private static final Queue syncJobs = new ShurikenQueue<>(); - - public static Iris instance; - public static BukkitAudiences audiences; - public static MultiverseCoreLink linkMultiverseCore; - public static MythicMobsLink linkMythicMobs; - public static IrisCompat compat; - public static FileWatcher configWatcher; - private static VolmitSender sender; - - static { - try { - fixShading(); - InstanceState.updateInstanceId(); - } catch (Throwable ignored) { - - } - } - - private final KList postShutdown = new KList<>(); - private KMap, IrisService> services; - - public static VolmitSender getSender() { - return sender; - } - - @SuppressWarnings("unchecked") - public static T service(Class c) { - return (T) instance.services.get(c); - } - - public static void callEvent(Event e) { - if (!e.isAsynchronous()) { - J.s(() -> Bukkit.getPluginManager().callEvent(e)); - } else { - Bukkit.getPluginManager().callEvent(e); - } - } - - public static KList initialize(String s, Class slicedClass) { - JarScanner js = new JarScanner(instance.getJarFile(), s); - KList v = new KList<>(); - J.attempt(js::scan); - for (Class i : js.getClasses()) { - if (slicedClass == null || i.isAnnotationPresent(slicedClass)) { - try { - v.add(i.getDeclaredConstructor().newInstance()); - } catch (Throwable ignored) { - - } - } - } - - return v; - } - - public static KList> getClasses(String s, Class slicedClass) { - JarScanner js = new JarScanner(instance.getJarFile(), s); - KList> v = new KList<>(); - J.attempt(js::scan); - for (Class i : js.getClasses()) { - if (slicedClass == null || i.isAnnotationPresent(slicedClass)) { - try { - v.add(i); - } catch (Throwable ignored) { - - } - } - } - - return v; - } - - public static KList initialize(String s) { - return initialize(s, null); - } - - public static void sq(Runnable r) { - synchronized (syncJobs) { - syncJobs.queue(r); - } - } - - public static File getTemp() { - return instance.getDataFolder("cache", "temp"); - } - - public static void msg(String string) { - try { - sender.sendMessage(string); - } catch (Throwable e) { - try { - System.out.println(instance.getTag() + string.replaceAll("(<([^>]+)>)", "")); - } catch (Throwable ignored1) { - - } - } - } - - public static File getCached(String name, String url) { - String h = IO.hash(name + "@" + url); - File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); - - if (!f.exists()) { - try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { - byte[] dataBuffer = new byte[1024]; - int bytesRead; - while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { - fileOutputStream.write(dataBuffer, 0, bytesRead); - Iris.verbose("Aquiring " + name); - } - } catch (IOException e) { - Iris.reportError(e); - } - } - - return f.exists() ? f : null; - } - - public static String getNonCached(String name, String url) { - String h = IO.hash(name + "*" + url); - File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); - - try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { - byte[] dataBuffer = new byte[1024]; - int bytesRead; - while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { - fileOutputStream.write(dataBuffer, 0, bytesRead); - } - } catch (IOException e) { - Iris.reportError(e); - } - - try { - return IO.readAll(f); - } catch (IOException e) { - Iris.reportError(e); - } - - return ""; - } - - public static File getNonCachedFile(String name, String url) { - String h = IO.hash(name + "*" + url); - File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); - Iris.verbose("Download " + name + " -> " + url); - try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { - byte[] dataBuffer = new byte[1024]; - int bytesRead; - while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { - fileOutputStream.write(dataBuffer, 0, bytesRead); - } - - fileOutputStream.flush(); - } catch (IOException e) { - e.printStackTrace(); - Iris.reportError(e); - } - - return f; - } - - public static void warn(String format, Object... objs) { - msg(C.YELLOW + String.format(format, objs)); - } - - public static void error(String format, Object... objs) { - msg(C.RED + String.format(format, objs)); - } - - public static void debug(String string) { - if (!IrisSettings.get().getGeneral().isDebug()) { - return; - } - - try { - throw new RuntimeException(); - } catch (Throwable e) { - try { - String[] cc = e.getStackTrace()[1].getClassName().split("\\Q.\\E"); - - if (cc.length > 5) { - debug(cc[3] + "/" + cc[4] + "/" + cc[cc.length - 1], e.getStackTrace()[1].getLineNumber(), string); - } else { - debug(cc[3] + "/" + cc[4], e.getStackTrace()[1].getLineNumber(), string); - } - } catch (Throwable ex) { - debug("Origin", -1, string); - } - } - } - - public static void debug(String category, int line, String string) { - if (!IrisSettings.get().getGeneral().isDebug()) { - return; - } - if (IrisSettings.get().getGeneral().isUseConsoleCustomColors()) { - msg("" + category + " <#bf3b76>" + line + " " + C.LIGHT_PURPLE + string.replaceAll("\\Q<\\E", "[").replaceAll("\\Q>\\E", "]")); - } else { - msg(C.BLUE + category + ":" + C.AQUA + line + C.RESET + C.LIGHT_PURPLE + " " + string.replaceAll("\\Q<\\E", "[").replaceAll("\\Q>\\E", "]")); - - } - } - - public static void verbose(String string) { - debug(string); - } - - public static void success(String string) { - msg(C.IRIS + string); - } - - public static void info(String format, Object... args) { - msg(C.WHITE + String.format(format, args)); - } - - @SuppressWarnings("deprecation") - public static void later(NastyRunnable object) { - try { - Bukkit.getScheduler().scheduleAsyncDelayedTask(instance, () -> - { - try { - object.run(); - } catch (Throwable e) { - e.printStackTrace(); - Iris.reportError(e); - } - }, RNG.r.i(100, 1200)); - } catch (IllegalPluginAccessException ignored) { - - } - } - - public static int jobCount() { - return syncJobs.size(); - } - - public static void clearQueues() { - synchronized (syncJobs) { - syncJobs.clear(); - } - } - - private static int getJavaVersion() { - String version = System.getProperty("java.version"); - if (version.startsWith("1.")) { - version = version.substring(2, 3); - } else { - int dot = version.indexOf("."); - if (dot != -1) { - version = version.substring(0, dot); - } - } - return Integer.parseInt(version); - } - - public static void reportErrorChunk(int x, int z, Throwable e, String extra) { - if (IrisSettings.get().getGeneral().isDebug()) { - File f = instance.getDataFile("debug", "chunk-errors", "chunk." + x + "." + z + ".txt"); - - if (!f.exists()) { - J.attempt(() -> { - PrintWriter pw = new PrintWriter(f); - pw.println("Thread: " + Thread.currentThread().getName()); - pw.println("First: " + new Date(M.ms())); - e.printStackTrace(pw); - pw.close(); - }); - } - - Iris.debug("Chunk " + x + "," + z + " Exception Logged: " + e.getClass().getSimpleName() + ": " + C.RESET + "" + C.LIGHT_PURPLE + e.getMessage()); - } - } - - public static void reportError(Throwable e) { - if (IrisSettings.get().getGeneral().isDebug()) { - String n = e.getClass().getCanonicalName() + "-" + e.getStackTrace()[0].getClassName() + "-" + e.getStackTrace()[0].getLineNumber(); - - if (e.getCause() != null) { - n += "-" + e.getCause().getStackTrace()[0].getClassName() + "-" + e.getCause().getStackTrace()[0].getLineNumber(); - } - - File f = instance.getDataFile("debug", "caught-exceptions", n + ".txt"); - - if (!f.exists()) { - J.attempt(() -> { - PrintWriter pw = new PrintWriter(f); - pw.println("Thread: " + Thread.currentThread().getName()); - pw.println("First: " + new Date(M.ms())); - e.printStackTrace(pw); - pw.close(); - }); - } - - Iris.debug("Exception Logged: " + e.getClass().getSimpleName() + ": " + C.RESET + "" + C.LIGHT_PURPLE + e.getMessage()); - } - } - - public static void dump() { - try { - File fi = Iris.instance.getDataFile("dump", "td-" + new java.sql.Date(M.ms()) + ".txt"); - FileOutputStream fos = new FileOutputStream(fi); - Map f = Thread.getAllStackTraces(); - PrintWriter pw = new PrintWriter(fos); - for (Thread i : f.keySet()) { - pw.println("========================================"); - pw.println("Thread: '" + i.getName() + "' ID: " + i.getId() + " STATUS: " + i.getState().name()); - - for (StackTraceElement j : f.get(i)) { - pw.println(" @ " + j.toString()); - } - - pw.println("========================================"); - pw.println(); - pw.println(); - } - - pw.close(); - System.out.println("DUMPED! See " + fi.getAbsolutePath()); - } catch (Throwable e) { - e.printStackTrace(); - } - } - - public static void panic() { - EnginePanic.panic(); - } - - public static void addPanic(String s, String v) { - EnginePanic.add(s, v); - } - - private static void fixShading() { - ShadeFix.fix(ComponentSerializer.class); - } - - private void enable() { - instance = this; - services = new KMap<>(); - initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class) i.getClass(), (IrisService) i)); - INMS.get(); - IO.delete(new File("iris")); - setupAudience(); - sender = new VolmitSender(Bukkit.getConsoleSender()); - sender.setTag(getTag()); - instance = this; - compat = IrisCompat.configured(getDataFile("compat.json")); - linkMultiverseCore = new MultiverseCoreLink(); - linkMythicMobs = new MythicMobsLink(); - configWatcher = new FileWatcher(getDataFile("settings.json")); - services.values().forEach(IrisService::onEnable); - services.values().forEach(this::registerListener); - J.s(() -> { - J.a(() -> PaperLib.suggestPaper(this)); - J.a(() -> IO.delete(getTemp())); - J.a(LazyPregenerator::loadLazyGenerators, 100); - J.a(this::bstats); - J.ar(this::checkConfigHotload, 60); - J.sr(this::tickQueue, 0); - J.s(this::setupPapi); - J.a(ServerConfigurator::configure, 20); - splash(); - autoStartStudio(); - checkForBukkitWorlds(); - IrisToolbelt.retainMantleDataForSlice(String.class.getCanonicalName()); - IrisToolbelt.retainMantleDataForSlice(BlockData.class.getCanonicalName()); - }); - } - - private void checkForBukkitWorlds() { - FileConfiguration fc = new YamlConfiguration(); - try { - fc.load(new File("bukkit.yml")); - ConfigurationSection section = fc.getConfigurationSection("worlds"); - if(section == null) { - return; - } - - for(String s : section.getKeys(false)){ - ConfigurationSection entry = section.getConfigurationSection(s); - if(!entry.contains("generator", true)) { - continue; - } - - String generator = entry.getString("generator"); - if(generator.startsWith("Iris:")) { - generator = generator.split("\\Q:\\E")[1]; - } else if(generator.equalsIgnoreCase("Iris")) { - generator = IrisSettings.get().getGenerator().getDefaultWorldType(); - } else { - continue; - } - - Iris.info("2 World: %s | Generator: %s", s, generator); - - if(Bukkit.getWorlds().stream().anyMatch(w -> w.getName().equals(s))) { - continue; - } - - Iris.info(C.LIGHT_PURPLE + "Preparing Spawn for " + s + "' using Iris:" + generator + "..."); - new WorldCreator(s) - .generator(getDefaultWorldGenerator(s, generator)) - .environment(IrisData.loadAnyDimension(generator).getEnvironment()) - .createWorld(); - Iris.info(C.LIGHT_PURPLE + "Loaded " + s + "!"); - } - } catch (Throwable e) { - e.printStackTrace(); - } - } - - private void autoStartStudio() { - if (IrisSettings.get().getStudio().isAutoStartDefaultStudio()) { - Iris.info("Starting up auto Studio!"); - try { - Player r = new KList<>(getServer().getOnlinePlayers()).getRandom(); - Iris.service(StudioSVC.class).open(r != null ? new VolmitSender(r) : sender, 1337, IrisSettings.get().getGenerator().getDefaultWorldType(), (w) -> { - J.s(() -> { - for (Player i : getServer().getOnlinePlayers()) { - i.setGameMode(GameMode.SPECTATOR); - i.teleport(new Location(w, 0, 200, 0)); - } - }); - }); - } catch (IrisException e) { - e.printStackTrace(); - } - } - } - - private void setupAudience() { - try { - audiences = BukkitAudiences.create(this); - } catch (Throwable e) { - e.printStackTrace(); - IrisSettings.get().getGeneral().setUseConsoleCustomColors(false); - IrisSettings.get().getGeneral().setUseCustomColorsIngame(false); - Iris.error("Failed to setup Adventure API... No custom colors :("); - } - } - - public void postShutdown(Runnable r) { - postShutdown.add(r); - } - - public void onEnable() { - enable(); - super.onEnable(); - Bukkit.getPluginManager().registerEvents(this, this); - setupChecks(); - } - - public void onDisable() { - services.values().forEach(IrisService::onDisable); - Bukkit.getScheduler().cancelTasks(this); - HandlerList.unregisterAll((Plugin) this); - postShutdown.forEach(Runnable::run); - services.clear(); - MultiBurst.burst.close(); - super.onDisable(); - } - - private void setupPapi() { - if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { - new IrisPapiExpansion().register(); - } - } - - @Override - public void start() { - - } - - @Override - public void stop() { - - } - - @Override - public String getTag(String subTag) { - return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.IRIS + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; - } - - private boolean setupChecks() { - boolean passed = true; - Iris.info("Version Information: " + instance.getServer().getVersion() + " | " + instance.getServer().getBukkitVersion()); - if (!instance.getServer().getBukkitVersion().contains(NMSBinding19_4.NMS_VERSION)) { - passed = false; - Iris.warn("============================================"); - Iris.warn("="); - Iris.warn("="); - Iris.warn("="); - Iris.warn("Iris is not compatible with this version of Minecraft.\nPlease use " + NMSBinding19_4.NMS_VERSION + " or use an older version of Iris."); - Iris.warn("="); - Iris.warn("="); - Iris.warn("="); - Iris.warn("============================================"); - } - if (!instance.getServer().getVersion().contains("Purpur")) { - passed = false; - Iris.info("We recommend using Purpur for the best experience with Iris."); - Iris.info("Purpur is a fork of Paper that is optimized for performance and stability."); - Iris.info("Plugins that work on Spigot / Paper work on Purpur."); - Iris.info("You can download it here: https://purpurmc.org"); - } - return passed; - } - - private void checkConfigHotload() { - if (configWatcher.checkModified()) { - IrisSettings.invalidate(); - IrisSettings.get(); - configWatcher.checkModified(); - Iris.info("Hotloaded settings.json "); - } - } - - private void tickQueue() { - synchronized (Iris.syncJobs) { - if (!Iris.syncJobs.hasNext()) { - return; - } - - long ms = M.ms(); - - while (Iris.syncJobs.hasNext() && M.ms() - ms < 25) { - try { - Iris.syncJobs.next().run(); - } catch (Throwable e) { - e.printStackTrace(); - Iris.reportError(e); - } - } - } - } - - private void bstats() { - if (IrisSettings.get().getGeneral().isPluginMetrics()) { - J.s(() -> new Metrics(Iris.instance, 8757)); - } - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - return super.onCommand(sender, command, label, args); - } - - public void imsg(CommandSender s, String msg) { - s.sendMessage(C.IRIS + "[" + C.DARK_GRAY + "Iris" + C.IRIS + "]" + C.GRAY + ": " + msg); - } - - @Nullable - @Override - public BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) { - Iris.debug("Biome Provider Called for " + worldName + " using ID: " + id); - return super.getDefaultBiomeProvider(worldName, id); - } - - @Override - public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { - Iris.debug("Default World Generator Called for " + worldName + " using ID: " + id); - if (worldName.equals("test")) { - try { - throw new RuntimeException(); - } catch (Throwable e) { - Iris.info(e.getStackTrace()[1].getClassName()); - if (e.getStackTrace()[1].getClassName().contains("com.onarandombox.MultiverseCore")) { - Iris.debug("MVC Test detected, Quick! Send them the dummy!"); - return new DummyChunkGenerator(); - } - } - } - - IrisDimension dim; - if (id == null || id.isEmpty()) { - dim = IrisData.loadAnyDimension(IrisSettings.get().getGenerator().getDefaultWorldType()); - } else { - dim = IrisData.loadAnyDimension(id); - } - Iris.debug("Generator ID: " + id + " requested by bukkit/plugin"); - - if (dim == null) { - Iris.warn("Unable to find dimension type " + id + " Looking for online packs..."); - - service(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, true); - dim = IrisData.loadAnyDimension(id); - - if (dim == null) { - throw new RuntimeException("Can't find dimension " + id + "!"); - } else { - Iris.info("Resolved missing dimension, proceeding with generation."); - } - } - - Iris.debug("Assuming IrisDimension: " + dim.getName()); - - IrisWorld w = IrisWorld.builder() - .name(worldName) - .seed(1337) - .environment(dim.getEnvironment()) - .worldFolder(new File(Bukkit.getWorldContainer(), worldName)) - .minHeight(dim.getMinHeight()) - .maxHeight(dim.getMaxHeight()) - .build(); - - Iris.debug("Generator Config: " + w.toString()); - - File ff = new File(w.worldFolder(), "iris/pack"); - if (!ff.exists() || ff.listFiles().length == 0) { - ff.mkdirs(); - service(StudioSVC.class).installIntoWorld(sender, dim.getLoadKey(), ff.getParentFile()); - } - - return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey()); - } - - public void splash() { - if (!IrisSettings.get().getGeneral().isSplashLogoStartup()) { - return; - } - - // @NoArgsConstructor - String padd = Form.repeat(" ", 8); - String padd2 = Form.repeat(" ", 4); - String[] info = {"", "", "", "", "", padd2 + C.IRIS + " Iris", padd2 + C.GRAY + " by " + "Volmit Software", padd2 + C.GRAY + " v" + C.IRIS + getDescription().getVersion(), - }; - String[] splash = { - padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@", - padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + C.IRIS + " .(((()))). ", - padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + C.IRIS + " .((((((())))))). ", - padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + C.IRIS + " ((((((((())))))))) " + C.GRAY + " @", - padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + C.IRIS + " ((((((((-))))))))) " + C.GRAY + " @@", - padd + C.GRAY + "@@@&&" + C.IRIS + " ((((((({ })))))))) " + C.GRAY + " &&@@@", - padd + C.GRAY + "@@" + C.IRIS + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@", - padd + C.GRAY + "@" + C.IRIS + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@", - padd + C.GRAY + "" + C.IRIS + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@", - padd + C.GRAY + "" + C.IRIS + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@", - padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@" - }; - //@done - Iris.info("Server type & version: " + Bukkit.getVersion()); - Iris.info("Bukkit version: " + Bukkit.getBukkitVersion()); - Iris.info("Java version: " + getJavaVersion()); - Iris.info("Custom Biomes: " + INMS.get().countCustomBiomes()); - setupChecks(); - printPacks(); - - for (int i = 0; i < info.length; i++) { - splash[i] += info[i]; - } - - Iris.info("\n\n " + new KList<>(splash).toString("\n") + "\n"); - } - - private void printPacks() { - File packFolder = Iris.service(StudioSVC.class).getWorkspaceFolder(); - File[] packs = packFolder.listFiles(File::isDirectory); - if (packs == null || packs.length == 0) - return; - Iris.info("Custom Dimensions: " + packs.length); - for (File f : packs) - printPack(f); - } - - private void printPack(File pack) { - String dimName = pack.getName(); - String version = "???"; - try (FileReader r = new FileReader(new File(pack, "dimensions/" + dimName + ".json"))) { - JsonObject json = JsonParser.parseReader(r).getAsJsonObject(); - if (json.has("version")) - version = json.get("version").getAsString(); - } catch (IOException | JsonParseException ignored) { - } - Iris.info(" " + dimName + " v" + version); - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.ServerConfigurator; +import com.volmit.iris.core.link.IrisPapiExpansion; +import com.volmit.iris.core.link.MultiverseCoreLink; +import com.volmit.iris.core.link.MythicMobsLink; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.nms.v19_4.NMSBinding19_4; +import com.volmit.iris.core.pregenerator.LazyPregenerator; +import com.volmit.iris.core.service.StudioSVC; +import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.engine.EnginePanic; +import com.volmit.iris.engine.object.IrisCompat; +import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.engine.object.IrisWorld; +import com.volmit.iris.engine.platform.BukkitChunkGenerator; +import com.volmit.iris.engine.platform.DummyChunkGenerator; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.exceptions.IrisException; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.function.NastyRunnable; +import com.volmit.iris.util.io.FileWatcher; +import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.io.InstanceState; +import com.volmit.iris.util.io.JarScanner; +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.plugin.IrisService; +import com.volmit.iris.util.plugin.Metrics; +import com.volmit.iris.util.plugin.VolmitPlugin; +import com.volmit.iris.util.plugin.VolmitSender; +import com.volmit.iris.util.reflect.ShadeFix; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.Queue; +import com.volmit.iris.util.scheduling.ShurikenQueue; +import io.papermc.lib.PaperLib; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import net.kyori.adventure.text.serializer.ComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.WorldCreator; +import org.bukkit.block.data.BlockData; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.generator.BiomeProvider; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.plugin.IllegalPluginAccessException; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.lang.annotation.Annotation; +import java.net.URL; +import java.util.Date; +import java.util.Map; + +@SuppressWarnings("CanBeFinal") +public class Iris extends VolmitPlugin implements Listener { + public static final String OVERWORLD_TAG = "3002"; + + private static final Queue syncJobs = new ShurikenQueue<>(); + + public static Iris instance; + public static BukkitAudiences audiences; + public static MultiverseCoreLink linkMultiverseCore; + public static MythicMobsLink linkMythicMobs; + public static IrisCompat compat; + public static FileWatcher configWatcher; + private static VolmitSender sender; + + static { + try { + fixShading(); + InstanceState.updateInstanceId(); + } catch (Throwable ignored) { + + } + } + + private final KList postShutdown = new KList<>(); + private KMap, IrisService> services; + + public static VolmitSender getSender() { + return sender; + } + + @SuppressWarnings("unchecked") + public static T service(Class c) { + return (T) instance.services.get(c); + } + + public static void callEvent(Event e) { + if (!e.isAsynchronous()) { + J.s(() -> Bukkit.getPluginManager().callEvent(e)); + } else { + Bukkit.getPluginManager().callEvent(e); + } + } + + public static KList initialize(String s, Class slicedClass) { + JarScanner js = new JarScanner(instance.getJarFile(), s); + KList v = new KList<>(); + J.attempt(js::scan); + for (Class i : js.getClasses()) { + if (slicedClass == null || i.isAnnotationPresent(slicedClass)) { + try { + v.add(i.getDeclaredConstructor().newInstance()); + } catch (Throwable ignored) { + + } + } + } + + return v; + } + + public static KList> getClasses(String s, Class slicedClass) { + JarScanner js = new JarScanner(instance.getJarFile(), s); + KList> v = new KList<>(); + J.attempt(js::scan); + for (Class i : js.getClasses()) { + if (slicedClass == null || i.isAnnotationPresent(slicedClass)) { + try { + v.add(i); + } catch (Throwable ignored) { + + } + } + } + + return v; + } + + public static KList initialize(String s) { + return initialize(s, null); + } + + public static void sq(Runnable r) { + synchronized (syncJobs) { + syncJobs.queue(r); + } + } + + public static File getTemp() { + return instance.getDataFolder("cache", "temp"); + } + + public static void msg(String string) { + try { + sender.sendMessage(string); + } catch (Throwable e) { + try { + System.out.println(instance.getTag() + string.replaceAll("(<([^>]+)>)", "")); + } catch (Throwable ignored1) { + + } + } + } + + public static File getCached(String name, String url) { + String h = IO.hash(name + "@" + url); + File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); + + if (!f.exists()) { + try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { + byte[] dataBuffer = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead); + Iris.verbose("Aquiring " + name); + } + } catch (IOException e) { + Iris.reportError(e); + } + } + + return f.exists() ? f : null; + } + + public static String getNonCached(String name, String url) { + String h = IO.hash(name + "*" + url); + File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); + + try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { + byte[] dataBuffer = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead); + } + } catch (IOException e) { + Iris.reportError(e); + } + + try { + return IO.readAll(f); + } catch (IOException e) { + Iris.reportError(e); + } + + return ""; + } + + public static File getNonCachedFile(String name, String url) { + String h = IO.hash(name + "*" + url); + File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); + Iris.verbose("Download " + name + " -> " + url); + try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { + byte[] dataBuffer = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead); + } + + fileOutputStream.flush(); + } catch (IOException e) { + e.printStackTrace(); + Iris.reportError(e); + } + + return f; + } + + public static void warn(String format, Object... objs) { + msg(C.YELLOW + String.format(format, objs)); + } + + public static void error(String format, Object... objs) { + msg(C.RED + String.format(format, objs)); + } + + public static void debug(String string) { + if (!IrisSettings.get().getGeneral().isDebug()) { + return; + } + + try { + throw new RuntimeException(); + } catch (Throwable e) { + try { + String[] cc = e.getStackTrace()[1].getClassName().split("\\Q.\\E"); + + if (cc.length > 5) { + debug(cc[3] + "/" + cc[4] + "/" + cc[cc.length - 1], e.getStackTrace()[1].getLineNumber(), string); + } else { + debug(cc[3] + "/" + cc[4], e.getStackTrace()[1].getLineNumber(), string); + } + } catch (Throwable ex) { + debug("Origin", -1, string); + } + } + } + + public static void debug(String category, int line, String string) { + if (!IrisSettings.get().getGeneral().isDebug()) { + return; + } + if (IrisSettings.get().getGeneral().isUseConsoleCustomColors()) { + msg("" + category + " <#bf3b76>" + line + " " + C.LIGHT_PURPLE + string.replaceAll("\\Q<\\E", "[").replaceAll("\\Q>\\E", "]")); + } else { + msg(C.BLUE + category + ":" + C.AQUA + line + C.RESET + C.LIGHT_PURPLE + " " + string.replaceAll("\\Q<\\E", "[").replaceAll("\\Q>\\E", "]")); + + } + } + + public static void verbose(String string) { + debug(string); + } + + public static void success(String string) { + msg(C.IRIS + string); + } + + public static void info(String format, Object... args) { + msg(C.WHITE + String.format(format, args)); + } + + @SuppressWarnings("deprecation") + public static void later(NastyRunnable object) { + try { + Bukkit.getScheduler().scheduleAsyncDelayedTask(instance, () -> + { + try { + object.run(); + } catch (Throwable e) { + e.printStackTrace(); + Iris.reportError(e); + } + }, RNG.r.i(100, 1200)); + } catch (IllegalPluginAccessException ignored) { + + } + } + + public static int jobCount() { + return syncJobs.size(); + } + + public static void clearQueues() { + synchronized (syncJobs) { + syncJobs.clear(); + } + } + + private static int getJavaVersion() { + String version = System.getProperty("java.version"); + if (version.startsWith("1.")) { + version = version.substring(2, 3); + } else { + int dot = version.indexOf("."); + if (dot != -1) { + version = version.substring(0, dot); + } + } + return Integer.parseInt(version); + } + + public static void reportErrorChunk(int x, int z, Throwable e, String extra) { + if (IrisSettings.get().getGeneral().isDebug()) { + File f = instance.getDataFile("debug", "chunk-errors", "chunk." + x + "." + z + ".txt"); + + if (!f.exists()) { + J.attempt(() -> { + PrintWriter pw = new PrintWriter(f); + pw.println("Thread: " + Thread.currentThread().getName()); + pw.println("First: " + new Date(M.ms())); + e.printStackTrace(pw); + pw.close(); + }); + } + + Iris.debug("Chunk " + x + "," + z + " Exception Logged: " + e.getClass().getSimpleName() + ": " + C.RESET + "" + C.LIGHT_PURPLE + e.getMessage()); + } + } + + public static void reportError(Throwable e) { + if (IrisSettings.get().getGeneral().isDebug()) { + String n = e.getClass().getCanonicalName() + "-" + e.getStackTrace()[0].getClassName() + "-" + e.getStackTrace()[0].getLineNumber(); + + if (e.getCause() != null) { + n += "-" + e.getCause().getStackTrace()[0].getClassName() + "-" + e.getCause().getStackTrace()[0].getLineNumber(); + } + + File f = instance.getDataFile("debug", "caught-exceptions", n + ".txt"); + + if (!f.exists()) { + J.attempt(() -> { + PrintWriter pw = new PrintWriter(f); + pw.println("Thread: " + Thread.currentThread().getName()); + pw.println("First: " + new Date(M.ms())); + e.printStackTrace(pw); + pw.close(); + }); + } + + Iris.debug("Exception Logged: " + e.getClass().getSimpleName() + ": " + C.RESET + "" + C.LIGHT_PURPLE + e.getMessage()); + } + } + + public static void dump() { + try { + File fi = Iris.instance.getDataFile("dump", "td-" + new java.sql.Date(M.ms()) + ".txt"); + FileOutputStream fos = new FileOutputStream(fi); + Map f = Thread.getAllStackTraces(); + PrintWriter pw = new PrintWriter(fos); + for (Thread i : f.keySet()) { + pw.println("========================================"); + pw.println("Thread: '" + i.getName() + "' ID: " + i.getId() + " STATUS: " + i.getState().name()); + + for (StackTraceElement j : f.get(i)) { + pw.println(" @ " + j.toString()); + } + + pw.println("========================================"); + pw.println(); + pw.println(); + } + + pw.close(); + System.out.println("DUMPED! See " + fi.getAbsolutePath()); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + public static void panic() { + EnginePanic.panic(); + } + + public static void addPanic(String s, String v) { + EnginePanic.add(s, v); + } + + private static void fixShading() { + ShadeFix.fix(ComponentSerializer.class); + } + + private void enable() { + instance = this; + services = new KMap<>(); + initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class) i.getClass(), (IrisService) i)); + INMS.get(); + IO.delete(new File("iris")); + setupAudience(); + sender = new VolmitSender(Bukkit.getConsoleSender()); + sender.setTag(getTag()); + instance = this; + compat = IrisCompat.configured(getDataFile("compat.json")); + linkMultiverseCore = new MultiverseCoreLink(); + linkMythicMobs = new MythicMobsLink(); + configWatcher = new FileWatcher(getDataFile("settings.json")); + services.values().forEach(IrisService::onEnable); + services.values().forEach(this::registerListener); + J.s(() -> { + J.a(() -> PaperLib.suggestPaper(this)); + J.a(() -> IO.delete(getTemp())); + J.a(LazyPregenerator::loadLazyGenerators, 100); + J.a(this::bstats); + J.ar(this::checkConfigHotload, 60); + J.sr(this::tickQueue, 0); + J.s(this::setupPapi); + J.a(ServerConfigurator::configure, 20); + splash(); + autoStartStudio(); + checkForBukkitWorlds(); + IrisToolbelt.retainMantleDataForSlice(String.class.getCanonicalName()); + IrisToolbelt.retainMantleDataForSlice(BlockData.class.getCanonicalName()); + }); + } + + private void checkForBukkitWorlds() { + FileConfiguration fc = new YamlConfiguration(); + try { + fc.load(new File("bukkit.yml")); + ConfigurationSection section = fc.getConfigurationSection("worlds"); + if (section == null) { + return; + } + + for (String s : section.getKeys(false)) { + ConfigurationSection entry = section.getConfigurationSection(s); + if (!entry.contains("generator", true)) { + continue; + } + + String generator = entry.getString("generator"); + if (generator.startsWith("Iris:")) { + generator = generator.split("\\Q:\\E")[1]; + } else if (generator.equalsIgnoreCase("Iris")) { + generator = IrisSettings.get().getGenerator().getDefaultWorldType(); + } else { + continue; + } + + Iris.info("2 World: %s | Generator: %s", s, generator); + + if (Bukkit.getWorlds().stream().anyMatch(w -> w.getName().equals(s))) { + continue; + } + + Iris.info(C.LIGHT_PURPLE + "Preparing Spawn for " + s + "' using Iris:" + generator + "..."); + new WorldCreator(s) + .generator(getDefaultWorldGenerator(s, generator)) + .environment(IrisData.loadAnyDimension(generator).getEnvironment()) + .createWorld(); + Iris.info(C.LIGHT_PURPLE + "Loaded " + s + "!"); + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + + private void autoStartStudio() { + if (IrisSettings.get().getStudio().isAutoStartDefaultStudio()) { + Iris.info("Starting up auto Studio!"); + try { + Player r = new KList<>(getServer().getOnlinePlayers()).getRandom(); + Iris.service(StudioSVC.class).open(r != null ? new VolmitSender(r) : sender, 1337, IrisSettings.get().getGenerator().getDefaultWorldType(), (w) -> { + J.s(() -> { + for (Player i : getServer().getOnlinePlayers()) { + i.setGameMode(GameMode.SPECTATOR); + i.teleport(new Location(w, 0, 200, 0)); + } + }); + }); + } catch (IrisException e) { + e.printStackTrace(); + } + } + } + + private void setupAudience() { + try { + audiences = BukkitAudiences.create(this); + } catch (Throwable e) { + e.printStackTrace(); + IrisSettings.get().getGeneral().setUseConsoleCustomColors(false); + IrisSettings.get().getGeneral().setUseCustomColorsIngame(false); + Iris.error("Failed to setup Adventure API... No custom colors :("); + } + } + + public void postShutdown(Runnable r) { + postShutdown.add(r); + } + + public void onEnable() { + enable(); + super.onEnable(); + Bukkit.getPluginManager().registerEvents(this, this); + setupChecks(); + } + + public void onDisable() { + services.values().forEach(IrisService::onDisable); + Bukkit.getScheduler().cancelTasks(this); + HandlerList.unregisterAll((Plugin) this); + postShutdown.forEach(Runnable::run); + services.clear(); + MultiBurst.burst.close(); + super.onDisable(); + } + + private void setupPapi() { + if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { + new IrisPapiExpansion().register(); + } + } + + @Override + public void start() { + + } + + @Override + public void stop() { + + } + + @Override + public String getTag(String subTag) { + return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.IRIS + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; + } + + private boolean setupChecks() { + boolean passed = true; + Iris.info("Version Information: " + instance.getServer().getVersion() + " | " + instance.getServer().getBukkitVersion()); + if (!instance.getServer().getBukkitVersion().contains(NMSBinding19_4.NMS_VERSION)) { + passed = false; + Iris.warn("============================================"); + Iris.warn("="); + Iris.warn("="); + Iris.warn("="); + Iris.warn("Iris is not compatible with this version of Minecraft.\nPlease use " + NMSBinding19_4.NMS_VERSION + " or use an older version of Iris."); + Iris.warn("="); + Iris.warn("="); + Iris.warn("="); + Iris.warn("============================================"); + } + if (!instance.getServer().getVersion().contains("Purpur")) { + passed = false; + Iris.info("We recommend using Purpur for the best experience with Iris."); + Iris.info("Purpur is a fork of Paper that is optimized for performance and stability."); + Iris.info("Plugins that work on Spigot / Paper work on Purpur."); + Iris.info("You can download it here: https://purpurmc.org"); + } + return passed; + } + + private void checkConfigHotload() { + if (configWatcher.checkModified()) { + IrisSettings.invalidate(); + IrisSettings.get(); + configWatcher.checkModified(); + Iris.info("Hotloaded settings.json "); + } + } + + private void tickQueue() { + synchronized (Iris.syncJobs) { + if (!Iris.syncJobs.hasNext()) { + return; + } + + long ms = M.ms(); + + while (Iris.syncJobs.hasNext() && M.ms() - ms < 25) { + try { + Iris.syncJobs.next().run(); + } catch (Throwable e) { + e.printStackTrace(); + Iris.reportError(e); + } + } + } + } + + private void bstats() { + if (IrisSettings.get().getGeneral().isPluginMetrics()) { + J.s(() -> new Metrics(Iris.instance, 8757)); + } + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + return super.onCommand(sender, command, label, args); + } + + public void imsg(CommandSender s, String msg) { + s.sendMessage(C.IRIS + "[" + C.DARK_GRAY + "Iris" + C.IRIS + "]" + C.GRAY + ": " + msg); + } + + @Nullable + @Override + public BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) { + Iris.debug("Biome Provider Called for " + worldName + " using ID: " + id); + return super.getDefaultBiomeProvider(worldName, id); + } + + @Override + public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { + Iris.debug("Default World Generator Called for " + worldName + " using ID: " + id); + if (worldName.equals("test")) { + try { + throw new RuntimeException(); + } catch (Throwable e) { + Iris.info(e.getStackTrace()[1].getClassName()); + if (e.getStackTrace()[1].getClassName().contains("com.onarandombox.MultiverseCore")) { + Iris.debug("MVC Test detected, Quick! Send them the dummy!"); + return new DummyChunkGenerator(); + } + } + } + + IrisDimension dim; + if (id == null || id.isEmpty()) { + dim = IrisData.loadAnyDimension(IrisSettings.get().getGenerator().getDefaultWorldType()); + } else { + dim = IrisData.loadAnyDimension(id); + } + Iris.debug("Generator ID: " + id + " requested by bukkit/plugin"); + + if (dim == null) { + Iris.warn("Unable to find dimension type " + id + " Looking for online packs..."); + + service(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, true); + dim = IrisData.loadAnyDimension(id); + + if (dim == null) { + throw new RuntimeException("Can't find dimension " + id + "!"); + } else { + Iris.info("Resolved missing dimension, proceeding with generation."); + } + } + + Iris.debug("Assuming IrisDimension: " + dim.getName()); + + IrisWorld w = IrisWorld.builder() + .name(worldName) + .seed(1337) + .environment(dim.getEnvironment()) + .worldFolder(new File(Bukkit.getWorldContainer(), worldName)) + .minHeight(dim.getMinHeight()) + .maxHeight(dim.getMaxHeight()) + .build(); + + Iris.debug("Generator Config: " + w.toString()); + + File ff = new File(w.worldFolder(), "iris/pack"); + if (!ff.exists() || ff.listFiles().length == 0) { + ff.mkdirs(); + service(StudioSVC.class).installIntoWorld(sender, dim.getLoadKey(), ff.getParentFile()); + } + + return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey()); + } + + public void splash() { + if (!IrisSettings.get().getGeneral().isSplashLogoStartup()) { + return; + } + + // @NoArgsConstructor + String padd = Form.repeat(" ", 8); + String padd2 = Form.repeat(" ", 4); + String[] info = {"", "", "", "", "", padd2 + C.IRIS + " Iris", padd2 + C.GRAY + " by " + "Volmit Software", padd2 + C.GRAY + " v" + C.IRIS + getDescription().getVersion(), + }; + String[] splash = { + padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@", + padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + C.IRIS + " .(((()))). ", + padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + C.IRIS + " .((((((())))))). ", + padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + C.IRIS + " ((((((((())))))))) " + C.GRAY + " @", + padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + C.IRIS + " ((((((((-))))))))) " + C.GRAY + " @@", + padd + C.GRAY + "@@@&&" + C.IRIS + " ((((((({ })))))))) " + C.GRAY + " &&@@@", + padd + C.GRAY + "@@" + C.IRIS + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@", + padd + C.GRAY + "@" + C.IRIS + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@", + padd + C.GRAY + "" + C.IRIS + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@", + padd + C.GRAY + "" + C.IRIS + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@", + padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@" + }; + //@done + Iris.info("Server type & version: " + Bukkit.getVersion()); + Iris.info("Bukkit version: " + Bukkit.getBukkitVersion()); + Iris.info("Java version: " + getJavaVersion()); + Iris.info("Custom Biomes: " + INMS.get().countCustomBiomes()); + setupChecks(); + printPacks(); + + for (int i = 0; i < info.length; i++) { + splash[i] += info[i]; + } + + Iris.info("\n\n " + new KList<>(splash).toString("\n") + "\n"); + } + + private void printPacks() { + File packFolder = Iris.service(StudioSVC.class).getWorkspaceFolder(); + File[] packs = packFolder.listFiles(File::isDirectory); + if (packs == null || packs.length == 0) + return; + Iris.info("Custom Dimensions: " + packs.length); + for (File f : packs) + printPack(f); + } + + private void printPack(File pack) { + String dimName = pack.getName(); + String version = "???"; + try (FileReader r = new FileReader(new File(pack, "dimensions/" + dimName + ".json"))) { + JsonObject json = JsonParser.parseReader(r).getAsJsonObject(); + if (json.has("version")) + version = json.get("version").getAsString(); + } catch (IOException | JsonParseException ignored) { + } + Iris.info(" " + dimName + " v" + version); + } +} diff --git a/src/main/java/com/volmit/iris/core/commands/CommandIris.java b/src/main/java/com/volmit/iris/core/commands/CommandIris.java index 6b3d50cc1..b41a31ab0 100644 --- a/src/main/java/com/volmit/iris/core/commands/CommandIris.java +++ b/src/main/java/com/volmit/iris/core/commands/CommandIris.java @@ -1,345 +1,342 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.core.commands; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.service.StudioSVC; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.engine.object.IrisDimension; -import com.volmit.iris.engine.object.IrisEntity; -import com.volmit.iris.engine.platform.PlatformChunkGenerator; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.decree.DecreeContext; -import com.volmit.iris.util.decree.DecreeExecutor; -import com.volmit.iris.util.decree.DecreeOrigin; -import com.volmit.iris.util.decree.annotations.Decree; -import com.volmit.iris.util.decree.annotations.Param; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.parallel.BurstExecutor; -import com.volmit.iris.util.parallel.MultiBurst; -import com.volmit.iris.util.plugin.VolmitSender; -import com.volmit.iris.util.scheduling.J; -import com.volmit.iris.util.scheduling.jobs.QueueJob; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.util.Vector; - -import java.io.File; -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -@Decree(name = "iris", aliases = {"ir", "irs"}, description = "Basic Command") -public class CommandIris implements DecreeExecutor { - private CommandStudio studio; - private CommandPregen pregen; - private CommandSettings settings; - private CommandObject object; - private CommandJigsaw jigsaw; - private CommandWhat what; - private CommandEdit edit; - private CommandFind find; - - @Decree(description = "Create a new world", aliases = {"+", "c"}) - public void create( - @Param(aliases = "world-name", description = "The name of the world to create") - String name, - @Param(aliases = "dimension", description = "The dimension type to create the world with", defaultValue = "default") - IrisDimension type, - @Param(description = "The seed to generate the world with", defaultValue = "1337") - long seed - ) { - if (name.equals("iris")) { - sender().sendMessage(C.RED + "You cannot use the world name \"iris\" for creating worlds as Iris uses this directory for studio worlds."); - sender().sendMessage(C.RED + "May we suggest the name \"IrisWorld\" instead?"); - return; - } - - if (new File(Bukkit.getWorldContainer(), name).exists()) { - sender().sendMessage(C.RED + "That folder already exists!"); - return; - } - - try { - IrisToolbelt.createWorld() - .dimension(type.getLoadKey()) - .name(name) - .seed(seed) - .sender(sender()) - .studio(false) - .create(); - } catch (Throwable e) { - sender().sendMessage(C.RED + "Exception raised during creation. See the console for more details."); - Iris.error("Exception raised during world creation: " + e.getMessage()); - Iris.reportError(e); - return; - } - - sender().sendMessage(C.GREEN + "Successfully created your world!"); - } - - @Decree(description = "Remove an Iris world", aliases = {"del", "rm"}, sync = true) - public void remove( - @Param(description = "The world to remove") - World world, - @Param(description = "Whether to also remove the folder (if set to false, just does not load the world)", defaultValue = "true") - boolean delete - ) { - if (!IrisToolbelt.isIrisWorld(world)) { - sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", Bukkit.getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList())); - return; - } - sender().sendMessage(C.GREEN + "Removing world: " + world.getName()); - try { - if (IrisToolbelt.removeWorld(world)) { - sender().sendMessage(C.GREEN + "Successfully removed " + world.getName() + " from bukkit.yml"); - } else { - sender().sendMessage(C.YELLOW + "Looks like the world was already removed from bukkit.yml"); - } - } catch (IOException e) { - sender().sendMessage(C.RED + "Failed to save bukkit.yml because of " + e.getMessage()); - e.printStackTrace(); - } - IrisToolbelt.evacuate(world, "Deleting world"); - Bukkit.unloadWorld(world, false); - if (delete && world.getWorldFolder().delete()) { - sender().sendMessage(C.GREEN + "Successfully removed world folder"); - } else { - sender().sendMessage(C.RED + "Failed to remove world folder"); - } - } - - @Decree(description = "Print version information") - public void version() { - sender().sendMessage(C.GREEN + "Iris v" + Iris.instance.getDescription().getVersion() + " by Volmit Software"); - } - - @Decree(description = "Print world height information", origin = DecreeOrigin.PLAYER) - public void height() { - sender().sendMessage(C.GREEN + "" + sender().player().getWorld().getMinHeight() + " to " + sender().player().getWorld().getMaxHeight()); - sender().sendMessage(C.GREEN + "Total Height: " + (sender().player().getWorld().getMaxHeight() - sender().player().getWorld().getMinHeight())); - } - - @Decree(description = "QOL command to open a overworld studio world.", sync = true) - public void so() { - sender().sendMessage(C.GREEN + "Opening studio for the \"Overworld\" pack (seed: 1337)"); - Iris.service(StudioSVC.class).open(sender(), 1337, "overworld"); - } - - @Decree(description = "Set aura spins") - public void aura( - @Param(description = "The h color value", defaultValue = "-20") - int h, - @Param(description = "The s color value", defaultValue = "7") - int s, - @Param(description = "The b color value", defaultValue = "8") - int b - ) { - IrisSettings.get().getGeneral().setSpinh(h); - IrisSettings.get().getGeneral().setSpins(s); - IrisSettings.get().getGeneral().setSpinb(b); - IrisSettings.get().forceSave(); - sender().sendMessage("Aura Spins updated to " + h + " " + s + " " + b); - } - - @Decree(description = "Bitwise calculations") - public void bitwise( - @Param(description = "The first value to run calculations on") - int value1, - @Param(description = "The operator: | & ^ ≺≺ ≻≻ %") - String operator, - @Param(description = "The second value to run calculations on") - int value2 - ) { - Integer v = null; - switch (operator) { - case "|" -> v = value1 | value2; - case "&" -> v = value1 & value2; - case "^" -> v = value1 ^ value2; - case "%" -> v = value1 % value2; - case ">>" -> v = value1 >> value2; - case "<<" -> v = value1 << value2; - } - if (v == null) { - sender().sendMessage(C.RED + "The operator you entered: (" + operator + ") is invalid!"); - return; - } - sender().sendMessage(C.GREEN + "" + value1 + " " + C.GREEN + operator.replaceAll("<", "≺").replaceAll(">", "≻").replaceAll("%", "%") + " " + C.GREEN + value2 + C.GREEN + " returns " + C.GREEN + v); - } - - @Decree(description = "Toggle debug") - public void debug( - @Param(name = "on", description = "Whether or not debug should be on", defaultValue = "other") - Boolean on - ) { - boolean to = on == null ? !IrisSettings.get().getGeneral().isDebug() : on; - IrisSettings.get().getGeneral().setDebug(to); - IrisSettings.get().forceSave(); - sender().sendMessage(C.GREEN + "Set debug to: " + to); - } - - @Decree(description = "Download a project.", aliases = "dl") - public void download( - @Param(name = "pack", description = "The pack to download", defaultValue = "overworld", aliases = "project") - String pack, - @Param(name = "branch", description = "The branch to download from", defaultValue = "main") - String branch, - @Param(name = "trim", description = "Whether or not to download a trimmed version (do not enable when editing)", defaultValue = "false") - boolean trim, - @Param(name = "overwrite", description = "Whether or not to overwrite the pack with the downloaded one", aliases = "force", defaultValue = "false") - boolean overwrite - ) { - sender().sendMessage(C.GREEN + "Downloading pack: " + pack + "/" + branch + (trim ? " trimmed" : "") + (overwrite ? " overwriting" : "")); - if (pack.equals("overworld")) { - String url = "https://github.com/IrisDimensions/overworld/releases/download/" + Iris.OVERWORLD_TAG + "/overworld.zip"; - Iris.service(StudioSVC.class).downloadRelease(sender(), url, trim, overwrite); - } else { - Iris.service(StudioSVC.class).downloadSearch(sender(), "IrisDimensions/" + pack + "/" + branch, trim, overwrite); - } - } - - @Decree(description = "Get metrics for your world", aliases = "measure", origin = DecreeOrigin.PLAYER) - public void metrics() { - if (!IrisToolbelt.isIrisWorld(world())) { - sender().sendMessage(C.RED + "You must be in an Iris world"); - return; - } - sender().sendMessage(C.GREEN + "Sending metrics..."); - engine().printMetrics(sender()); - } - - @Decree(description = "Reload configuration file (this is also done automatically)") - public void reload() { - IrisSettings.invalidate(); - IrisSettings.get(); - sender().sendMessage(C.GREEN + "Hotloaded settings"); - } - - @Decree(name = "regen", description = "Regenerate nearby chunks.", aliases = "rg", sync = true, origin = DecreeOrigin.PLAYER) - public void regen( - @Param(name = "radius", description = "The radius of nearby cunks", defaultValue = "5") - int radius - ) { - if (IrisToolbelt.isIrisWorld(player().getWorld())) { - VolmitSender sender = sender(); - J.a(() -> { - DecreeContext.touch(sender); - PlatformChunkGenerator plat = IrisToolbelt.access(player().getWorld()); - Engine engine = plat.getEngine(); - try { - Chunk cx = player().getLocation().getChunk(); - KList js = new KList<>(); - BurstExecutor b = MultiBurst.burst.burst(); - b.setMulticore(false); - int rad = engine.getMantle().getRealRadius(); - for (int i = -(radius + rad); i <= radius + rad; i++) { - for (int j = -(radius + rad); j <= radius + rad; j++) { - engine.getMantle().getMantle().deleteChunk(i + cx.getX(), j + cx.getZ()); - } - } - - for (int i = -radius; i <= radius; i++) { - for (int j = -radius; j <= radius; j++) { - int finalJ = j; - int finalI = i; - b.queue(() -> plat.injectChunkReplacement(player().getWorld(), finalI + cx.getX(), finalJ + cx.getZ(), (f) -> { - synchronized (js) { - js.add(f); - } - })); - } - } - - b.complete(); - sender().sendMessage(C.GREEN + "Regenerating " + Form.f(js.size()) + " Sections"); - QueueJob r = new QueueJob<>() { - final KList> futures = new KList<>(); - - @Override - public void execute(Runnable runnable) { - futures.add(J.sfut(runnable)); - - if (futures.size() > 64) { - while (futures.isNotEmpty()) { - try { - futures.remove(0).get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - } - } - - @Override - public String getName() { - return "Regenerating"; - } - }; - r.queue(js); - r.execute(sender()); - } catch (Throwable e) { - sender().sendMessage("Unable to parse view-distance"); - } - }); - } else { - sender().sendMessage(C.RED + "You must be in an Iris World to use regen!"); - } - } - - @Decree(description = "Update the pack of a world (UNSAFE!)", name = "^world", aliases = "update-world") - public void updateWorld( - @Param(description = "The world to update", contextual = true) - World world, - @Param(description = "The pack to install into the world", contextual = true, aliases = "dimension") - IrisDimension pack, - @Param(description = "Make sure to make a backup & read the warnings first!", defaultValue = "false", aliases = "c") - boolean confirm, - @Param(description = "Should Iris download the pack again for you", defaultValue = "false", name = "fresh-download", aliases = {"fresh", "new"}) - boolean freshDownload - ) { - if (!confirm) { - sender().sendMessage(new String[]{ - C.RED + "You should always make a backup before using this", - C.YELLOW + "Issues caused by this can be, but are not limited to:", - C.YELLOW + " - Broken chunks (cut-offs) between old and new chunks (before & after the update)", - C.YELLOW + " - Regenerated chunks that do not fit in with the old chunks", - C.YELLOW + " - Structures not spawning again when regenerating", - C.YELLOW + " - Caves not lining up", - C.YELLOW + " - Terrain layers not lining up", - C.RED + "Now that you are aware of the risks, and have made a back-up:", - C.RED + "/iris ^world " + world.getName() + " " + pack.getLoadKey() + " confirm=true" - }); - return; - } - - File folder = world.getWorldFolder(); - folder.mkdirs(); - - if (freshDownload) { - Iris.service(StudioSVC.class).downloadSearch(sender(), pack.getLoadKey(), false, true); - } - - Iris.service(StudioSVC.class).installIntoWorld(sender(), pack.getLoadKey(), folder); - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.core.commands; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.service.StudioSVC; +import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.engine.platform.PlatformChunkGenerator; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.decree.DecreeContext; +import com.volmit.iris.util.decree.DecreeExecutor; +import com.volmit.iris.util.decree.DecreeOrigin; +import com.volmit.iris.util.decree.annotations.Decree; +import com.volmit.iris.util.decree.annotations.Param; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.parallel.BurstExecutor; +import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.plugin.VolmitSender; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.jobs.QueueJob; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.World; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +@Decree(name = "iris", aliases = {"ir", "irs"}, description = "Basic Command") +public class CommandIris implements DecreeExecutor { + private CommandStudio studio; + private CommandPregen pregen; + private CommandSettings settings; + private CommandObject object; + private CommandJigsaw jigsaw; + private CommandWhat what; + private CommandEdit edit; + private CommandFind find; + + @Decree(description = "Create a new world", aliases = {"+", "c"}) + public void create( + @Param(aliases = "world-name", description = "The name of the world to create") + String name, + @Param(aliases = "dimension", description = "The dimension type to create the world with", defaultValue = "default") + IrisDimension type, + @Param(description = "The seed to generate the world with", defaultValue = "1337") + long seed + ) { + if (name.equals("iris")) { + sender().sendMessage(C.RED + "You cannot use the world name \"iris\" for creating worlds as Iris uses this directory for studio worlds."); + sender().sendMessage(C.RED + "May we suggest the name \"IrisWorld\" instead?"); + return; + } + + if (new File(Bukkit.getWorldContainer(), name).exists()) { + sender().sendMessage(C.RED + "That folder already exists!"); + return; + } + + try { + IrisToolbelt.createWorld() + .dimension(type.getLoadKey()) + .name(name) + .seed(seed) + .sender(sender()) + .studio(false) + .create(); + } catch (Throwable e) { + sender().sendMessage(C.RED + "Exception raised during creation. See the console for more details."); + Iris.error("Exception raised during world creation: " + e.getMessage()); + Iris.reportError(e); + return; + } + + sender().sendMessage(C.GREEN + "Successfully created your world!"); + } + + @Decree(description = "Remove an Iris world", aliases = {"del", "rm"}, sync = true) + public void remove( + @Param(description = "The world to remove") + World world, + @Param(description = "Whether to also remove the folder (if set to false, just does not load the world)", defaultValue = "true") + boolean delete + ) { + if (!IrisToolbelt.isIrisWorld(world)) { + sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", Bukkit.getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList())); + return; + } + sender().sendMessage(C.GREEN + "Removing world: " + world.getName()); + try { + if (IrisToolbelt.removeWorld(world)) { + sender().sendMessage(C.GREEN + "Successfully removed " + world.getName() + " from bukkit.yml"); + } else { + sender().sendMessage(C.YELLOW + "Looks like the world was already removed from bukkit.yml"); + } + } catch (IOException e) { + sender().sendMessage(C.RED + "Failed to save bukkit.yml because of " + e.getMessage()); + e.printStackTrace(); + } + IrisToolbelt.evacuate(world, "Deleting world"); + Bukkit.unloadWorld(world, false); + if (delete && world.getWorldFolder().delete()) { + sender().sendMessage(C.GREEN + "Successfully removed world folder"); + } else { + sender().sendMessage(C.RED + "Failed to remove world folder"); + } + } + + @Decree(description = "Print version information") + public void version() { + sender().sendMessage(C.GREEN + "Iris v" + Iris.instance.getDescription().getVersion() + " by Volmit Software"); + } + + @Decree(description = "Print world height information", origin = DecreeOrigin.PLAYER) + public void height() { + sender().sendMessage(C.GREEN + "" + sender().player().getWorld().getMinHeight() + " to " + sender().player().getWorld().getMaxHeight()); + sender().sendMessage(C.GREEN + "Total Height: " + (sender().player().getWorld().getMaxHeight() - sender().player().getWorld().getMinHeight())); + } + + @Decree(description = "QOL command to open a overworld studio world.", sync = true) + public void so() { + sender().sendMessage(C.GREEN + "Opening studio for the \"Overworld\" pack (seed: 1337)"); + Iris.service(StudioSVC.class).open(sender(), 1337, "overworld"); + } + + @Decree(description = "Set aura spins") + public void aura( + @Param(description = "The h color value", defaultValue = "-20") + int h, + @Param(description = "The s color value", defaultValue = "7") + int s, + @Param(description = "The b color value", defaultValue = "8") + int b + ) { + IrisSettings.get().getGeneral().setSpinh(h); + IrisSettings.get().getGeneral().setSpins(s); + IrisSettings.get().getGeneral().setSpinb(b); + IrisSettings.get().forceSave(); + sender().sendMessage("Aura Spins updated to " + h + " " + s + " " + b); + } + + @Decree(description = "Bitwise calculations") + public void bitwise( + @Param(description = "The first value to run calculations on") + int value1, + @Param(description = "The operator: | & ^ ≺≺ ≻≻ %") + String operator, + @Param(description = "The second value to run calculations on") + int value2 + ) { + Integer v = null; + switch (operator) { + case "|" -> v = value1 | value2; + case "&" -> v = value1 & value2; + case "^" -> v = value1 ^ value2; + case "%" -> v = value1 % value2; + case ">>" -> v = value1 >> value2; + case "<<" -> v = value1 << value2; + } + if (v == null) { + sender().sendMessage(C.RED + "The operator you entered: (" + operator + ") is invalid!"); + return; + } + sender().sendMessage(C.GREEN + "" + value1 + " " + C.GREEN + operator.replaceAll("<", "≺").replaceAll(">", "≻").replaceAll("%", "%") + " " + C.GREEN + value2 + C.GREEN + " returns " + C.GREEN + v); + } + + @Decree(description = "Toggle debug") + public void debug( + @Param(name = "on", description = "Whether or not debug should be on", defaultValue = "other") + Boolean on + ) { + boolean to = on == null ? !IrisSettings.get().getGeneral().isDebug() : on; + IrisSettings.get().getGeneral().setDebug(to); + IrisSettings.get().forceSave(); + sender().sendMessage(C.GREEN + "Set debug to: " + to); + } + + @Decree(description = "Download a project.", aliases = "dl") + public void download( + @Param(name = "pack", description = "The pack to download", defaultValue = "overworld", aliases = "project") + String pack, + @Param(name = "branch", description = "The branch to download from", defaultValue = "main") + String branch, + @Param(name = "trim", description = "Whether or not to download a trimmed version (do not enable when editing)", defaultValue = "false") + boolean trim, + @Param(name = "overwrite", description = "Whether or not to overwrite the pack with the downloaded one", aliases = "force", defaultValue = "false") + boolean overwrite + ) { + sender().sendMessage(C.GREEN + "Downloading pack: " + pack + "/" + branch + (trim ? " trimmed" : "") + (overwrite ? " overwriting" : "")); + if (pack.equals("overworld")) { + String url = "https://github.com/IrisDimensions/overworld/releases/download/" + Iris.OVERWORLD_TAG + "/overworld.zip"; + Iris.service(StudioSVC.class).downloadRelease(sender(), url, trim, overwrite); + } else { + Iris.service(StudioSVC.class).downloadSearch(sender(), "IrisDimensions/" + pack + "/" + branch, trim, overwrite); + } + } + + @Decree(description = "Get metrics for your world", aliases = "measure", origin = DecreeOrigin.PLAYER) + public void metrics() { + if (!IrisToolbelt.isIrisWorld(world())) { + sender().sendMessage(C.RED + "You must be in an Iris world"); + return; + } + sender().sendMessage(C.GREEN + "Sending metrics..."); + engine().printMetrics(sender()); + } + + @Decree(description = "Reload configuration file (this is also done automatically)") + public void reload() { + IrisSettings.invalidate(); + IrisSettings.get(); + sender().sendMessage(C.GREEN + "Hotloaded settings"); + } + + @Decree(name = "regen", description = "Regenerate nearby chunks.", aliases = "rg", sync = true, origin = DecreeOrigin.PLAYER) + public void regen( + @Param(name = "radius", description = "The radius of nearby cunks", defaultValue = "5") + int radius + ) { + if (IrisToolbelt.isIrisWorld(player().getWorld())) { + VolmitSender sender = sender(); + J.a(() -> { + DecreeContext.touch(sender); + PlatformChunkGenerator plat = IrisToolbelt.access(player().getWorld()); + Engine engine = plat.getEngine(); + try { + Chunk cx = player().getLocation().getChunk(); + KList js = new KList<>(); + BurstExecutor b = MultiBurst.burst.burst(); + b.setMulticore(false); + int rad = engine.getMantle().getRealRadius(); + for (int i = -(radius + rad); i <= radius + rad; i++) { + for (int j = -(radius + rad); j <= radius + rad; j++) { + engine.getMantle().getMantle().deleteChunk(i + cx.getX(), j + cx.getZ()); + } + } + + for (int i = -radius; i <= radius; i++) { + for (int j = -radius; j <= radius; j++) { + int finalJ = j; + int finalI = i; + b.queue(() -> plat.injectChunkReplacement(player().getWorld(), finalI + cx.getX(), finalJ + cx.getZ(), (f) -> { + synchronized (js) { + js.add(f); + } + })); + } + } + + b.complete(); + sender().sendMessage(C.GREEN + "Regenerating " + Form.f(js.size()) + " Sections"); + QueueJob r = new QueueJob<>() { + final KList> futures = new KList<>(); + + @Override + public void execute(Runnable runnable) { + futures.add(J.sfut(runnable)); + + if (futures.size() > 64) { + while (futures.isNotEmpty()) { + try { + futures.remove(0).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + } + } + + @Override + public String getName() { + return "Regenerating"; + } + }; + r.queue(js); + r.execute(sender()); + } catch (Throwable e) { + sender().sendMessage("Unable to parse view-distance"); + } + }); + } else { + sender().sendMessage(C.RED + "You must be in an Iris World to use regen!"); + } + } + + @Decree(description = "Update the pack of a world (UNSAFE!)", name = "^world", aliases = "update-world") + public void updateWorld( + @Param(description = "The world to update", contextual = true) + World world, + @Param(description = "The pack to install into the world", contextual = true, aliases = "dimension") + IrisDimension pack, + @Param(description = "Make sure to make a backup & read the warnings first!", defaultValue = "false", aliases = "c") + boolean confirm, + @Param(description = "Should Iris download the pack again for you", defaultValue = "false", name = "fresh-download", aliases = {"fresh", "new"}) + boolean freshDownload + ) { + if (!confirm) { + sender().sendMessage(new String[]{ + C.RED + "You should always make a backup before using this", + C.YELLOW + "Issues caused by this can be, but are not limited to:", + C.YELLOW + " - Broken chunks (cut-offs) between old and new chunks (before & after the update)", + C.YELLOW + " - Regenerated chunks that do not fit in with the old chunks", + C.YELLOW + " - Structures not spawning again when regenerating", + C.YELLOW + " - Caves not lining up", + C.YELLOW + " - Terrain layers not lining up", + C.RED + "Now that you are aware of the risks, and have made a back-up:", + C.RED + "/iris ^world " + world.getName() + " " + pack.getLoadKey() + " confirm=true" + }); + return; + } + + File folder = world.getWorldFolder(); + folder.mkdirs(); + + if (freshDownload) { + Iris.service(StudioSVC.class).downloadSearch(sender(), pack.getLoadKey(), false, true); + } + + Iris.service(StudioSVC.class).installIntoWorld(sender(), pack.getLoadKey(), folder); + } +} diff --git a/src/main/java/com/volmit/iris/core/gui/components/IrisRenderer.java b/src/main/java/com/volmit/iris/core/gui/components/IrisRenderer.java index d1f2ca751..2820b8999 100644 --- a/src/main/java/com/volmit/iris/core/gui/components/IrisRenderer.java +++ b/src/main/java/com/volmit/iris/core/gui/components/IrisRenderer.java @@ -1,86 +1,85 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.core.gui.components; - -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.engine.object.IrisBiome; -import com.volmit.iris.engine.object.IrisBiomeGeneratorLink; -import com.volmit.iris.util.interpolation.IrisInterpolation; - -import java.awt.*; -import java.awt.image.BufferedImage; -import java.util.function.BiFunction; - -public class IrisRenderer { - private final Engine renderer; - - public IrisRenderer(Engine renderer) { - this.renderer = renderer; - } - - public BufferedImage render(double sx, double sz, double size, int resolution, RenderType currentType) { - BufferedImage image = new BufferedImage(resolution, resolution, BufferedImage.TYPE_INT_RGB); - BiFunction colorFunction = (d, dx) -> Color.black.getRGB(); - - switch (currentType) { - case BIOME, DECORATOR_LOAD, OBJECT_LOAD, LAYER_LOAD -> - colorFunction = (x, z) -> renderer.getComplex().getTrueBiomeStream().get(x, z).getColor(renderer, currentType).getRGB(); - case BIOME_LAND -> - colorFunction = (x, z) -> renderer.getComplex().getLandBiomeStream().get(x, z).getColor(renderer, currentType).getRGB(); - case BIOME_SEA -> - colorFunction = (x, z) -> renderer.getComplex().getSeaBiomeStream().get(x, z).getColor(renderer, currentType).getRGB(); - case REGION -> - colorFunction = (x, z) -> renderer.getComplex().getRegionStream().get(x, z).getColor(renderer.getComplex(), currentType).getRGB(); - case CAVE_LAND -> - colorFunction = (x, z) -> renderer.getComplex().getCaveBiomeStream().get(x, z).getColor(renderer, currentType).getRGB(); - case HEIGHT -> - colorFunction = (x, z) -> Color.getHSBColor(renderer.getComplex().getHeightStream().get(x, z).floatValue(), 100, 100).getRGB(); - case CONTINENT -> - colorFunction = (x, z) -> { - IrisBiome b = renderer.getBiome((int) Math.round(x), renderer.getMaxHeight() - 1, (int) Math.round(z)); - IrisBiomeGeneratorLink g = b.getGenerators().get(0); - Color c; - if (g.getMax() <= 0) { - // Max is below water level, so it is most likely an ocean biome - c = Color.BLUE; - } else if (g.getMin() < 0) { - // Min is below water level, but max is not, so it is most likely a shore biome - c = Color.YELLOW; - } else { - // Both min and max are above water level, so it is most likely a land biome - c = Color.GREEN; - } - return c.getRGB(); - }; - } - - double x, z; - int i, j; - for (i = 0; i < resolution; i++) { - x = IrisInterpolation.lerp(sx, sx + size, (double) i / (double) (resolution)); - - for (j = 0; j < resolution; j++) { - z = IrisInterpolation.lerp(sz, sz + size, (double) j / (double) (resolution)); - image.setRGB(i, j, colorFunction.apply(x, z)); - } - } - - return image; - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.core.gui.components; + +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.IrisBiome; +import com.volmit.iris.engine.object.IrisBiomeGeneratorLink; +import com.volmit.iris.util.interpolation.IrisInterpolation; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.function.BiFunction; + +public class IrisRenderer { + private final Engine renderer; + + public IrisRenderer(Engine renderer) { + this.renderer = renderer; + } + + public BufferedImage render(double sx, double sz, double size, int resolution, RenderType currentType) { + BufferedImage image = new BufferedImage(resolution, resolution, BufferedImage.TYPE_INT_RGB); + BiFunction colorFunction = (d, dx) -> Color.black.getRGB(); + + switch (currentType) { + case BIOME, DECORATOR_LOAD, OBJECT_LOAD, LAYER_LOAD -> + colorFunction = (x, z) -> renderer.getComplex().getTrueBiomeStream().get(x, z).getColor(renderer, currentType).getRGB(); + case BIOME_LAND -> + colorFunction = (x, z) -> renderer.getComplex().getLandBiomeStream().get(x, z).getColor(renderer, currentType).getRGB(); + case BIOME_SEA -> + colorFunction = (x, z) -> renderer.getComplex().getSeaBiomeStream().get(x, z).getColor(renderer, currentType).getRGB(); + case REGION -> + colorFunction = (x, z) -> renderer.getComplex().getRegionStream().get(x, z).getColor(renderer.getComplex(), currentType).getRGB(); + case CAVE_LAND -> + colorFunction = (x, z) -> renderer.getComplex().getCaveBiomeStream().get(x, z).getColor(renderer, currentType).getRGB(); + case HEIGHT -> + colorFunction = (x, z) -> Color.getHSBColor(renderer.getComplex().getHeightStream().get(x, z).floatValue(), 100, 100).getRGB(); + case CONTINENT -> colorFunction = (x, z) -> { + IrisBiome b = renderer.getBiome((int) Math.round(x), renderer.getMaxHeight() - 1, (int) Math.round(z)); + IrisBiomeGeneratorLink g = b.getGenerators().get(0); + Color c; + if (g.getMax() <= 0) { + // Max is below water level, so it is most likely an ocean biome + c = Color.BLUE; + } else if (g.getMin() < 0) { + // Min is below water level, but max is not, so it is most likely a shore biome + c = Color.YELLOW; + } else { + // Both min and max are above water level, so it is most likely a land biome + c = Color.GREEN; + } + return c.getRGB(); + }; + } + + double x, z; + int i, j; + for (i = 0; i < resolution; i++) { + x = IrisInterpolation.lerp(sx, sx + size, (double) i / (double) (resolution)); + + for (j = 0; j < resolution; j++) { + z = IrisInterpolation.lerp(sz, sz + size, (double) j / (double) (resolution)); + image.setRGB(i, j, colorFunction.apply(x, z)); + } + } + + return image; + } +} diff --git a/src/main/java/com/volmit/iris/core/link/Identifier.java b/src/main/java/com/volmit/iris/core/link/Identifier.java index 049f1b25b..4059016d0 100644 --- a/src/main/java/com/volmit/iris/core/link/Identifier.java +++ b/src/main/java/com/volmit/iris/core/link/Identifier.java @@ -1,33 +1,33 @@ -package com.volmit.iris.core.link; - -import org.bukkit.NamespacedKey; - -public record Identifier(String namespace, String key) { - - private static final String DEFAULT_NAMESPACE = "minecraft"; - - public static Identifier fromString(String id) { - String[] strings = id.split(":", 2); - if(strings.length == 1) { - return new Identifier(DEFAULT_NAMESPACE, strings[0]); - } else { - return new Identifier(strings[0], strings[1]); - } - } - - @Override - public String toString() { - return namespace + ":" + key; - } - - @Override - public boolean equals(Object obj) { - if(obj instanceof Identifier i) { - return i.namespace().equals(this.namespace) && i.key().equals(this.key); - } else if(obj instanceof NamespacedKey i) { - return i.getNamespace().equals(this.namespace) && i.getKey().equals(this.key); - } else { - return false; - } - } -} +package com.volmit.iris.core.link; + +import org.bukkit.NamespacedKey; + +public record Identifier(String namespace, String key) { + + private static final String DEFAULT_NAMESPACE = "minecraft"; + + public static Identifier fromString(String id) { + String[] strings = id.split(":", 2); + if (strings.length == 1) { + return new Identifier(DEFAULT_NAMESPACE, strings[0]); + } else { + return new Identifier(strings[0], strings[1]); + } + } + + @Override + public String toString() { + return namespace + ":" + key; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Identifier i) { + return i.namespace().equals(this.namespace) && i.key().equals(this.key); + } else if (obj instanceof NamespacedKey i) { + return i.getNamespace().equals(this.namespace) && i.getKey().equals(this.key); + } else { + return false; + } + } +} diff --git a/src/main/java/com/volmit/iris/core/link/ItemAdderDataProvider.java b/src/main/java/com/volmit/iris/core/link/ItemAdderDataProvider.java index ec563cdd8..67f594323 100644 --- a/src/main/java/com/volmit/iris/core/link/ItemAdderDataProvider.java +++ b/src/main/java/com/volmit/iris/core/link/ItemAdderDataProvider.java @@ -1,71 +1,70 @@ -package com.volmit.iris.core.link; - -import com.volmit.iris.Iris; -import com.volmit.iris.util.collection.KList; -import dev.lone.itemsadder.api.CustomBlock; -import dev.lone.itemsadder.api.CustomStack; -import org.bukkit.block.data.BlockData; -import org.bukkit.inventory.ItemStack; - -import java.util.Arrays; -import java.util.MissingResourceException; - -public class ItemAdderDataProvider extends ExternalDataProvider { - - private KList itemNamespaces, blockNamespaces; - - public ItemAdderDataProvider() { - super("ItemsAdder"); - } - - @Override - public void init() { - this.itemNamespaces = new KList<>(); - this.blockNamespaces = new KList<>(); - - for (Identifier i : getItemTypes()) { - itemNamespaces.addIfMissing(i.namespace()); - } - for (Identifier i : getBlockTypes()) { - blockNamespaces.addIfMissing(i.namespace()); - Iris.info("Found ItemAdder Block: " + i); - } - } - - @Override - public BlockData getBlockData(Identifier blockId) throws MissingResourceException { - return CustomBlock.getBaseBlockData(blockId.toString()); - } - - @Override - public ItemStack getItemStack(Identifier itemId) throws MissingResourceException { - CustomStack stack = CustomStack.getInstance(itemId.toString()); - if (stack == null) { - throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key()); - } - return stack.getItemStack(); - } - - @Override - public Identifier[] getBlockTypes() { - KList keys = new KList<>(); - for (String s : CustomBlock.getNamespacedIdsInRegistry()) { - keys.add(Identifier.fromString(s)); - } - return keys.toArray(new Identifier[0]); - } - - @Override - public Identifier[] getItemTypes() { - KList keys = new KList<>(); - for (String s : CustomStack.getNamespacedIdsInRegistry()) { - keys.add(Identifier.fromString(s)); - } - return keys.toArray(new Identifier[0]); - } - - @Override - public boolean isValidProvider(Identifier id, boolean isItem) { - return isItem ? this.itemNamespaces.contains(id.namespace()) : this.blockNamespaces.contains(id.namespace()); - } -} +package com.volmit.iris.core.link; + +import com.volmit.iris.Iris; +import com.volmit.iris.util.collection.KList; +import dev.lone.itemsadder.api.CustomBlock; +import dev.lone.itemsadder.api.CustomStack; +import org.bukkit.block.data.BlockData; +import org.bukkit.inventory.ItemStack; + +import java.util.MissingResourceException; + +public class ItemAdderDataProvider extends ExternalDataProvider { + + private KList itemNamespaces, blockNamespaces; + + public ItemAdderDataProvider() { + super("ItemsAdder"); + } + + @Override + public void init() { + this.itemNamespaces = new KList<>(); + this.blockNamespaces = new KList<>(); + + for (Identifier i : getItemTypes()) { + itemNamespaces.addIfMissing(i.namespace()); + } + for (Identifier i : getBlockTypes()) { + blockNamespaces.addIfMissing(i.namespace()); + Iris.info("Found ItemAdder Block: " + i); + } + } + + @Override + public BlockData getBlockData(Identifier blockId) throws MissingResourceException { + return CustomBlock.getBaseBlockData(blockId.toString()); + } + + @Override + public ItemStack getItemStack(Identifier itemId) throws MissingResourceException { + CustomStack stack = CustomStack.getInstance(itemId.toString()); + if (stack == null) { + throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key()); + } + return stack.getItemStack(); + } + + @Override + public Identifier[] getBlockTypes() { + KList keys = new KList<>(); + for (String s : CustomBlock.getNamespacedIdsInRegistry()) { + keys.add(Identifier.fromString(s)); + } + return keys.toArray(new Identifier[0]); + } + + @Override + public Identifier[] getItemTypes() { + KList keys = new KList<>(); + for (String s : CustomStack.getNamespacedIdsInRegistry()) { + keys.add(Identifier.fromString(s)); + } + return keys.toArray(new Identifier[0]); + } + + @Override + public boolean isValidProvider(Identifier id, boolean isItem) { + return isItem ? this.itemNamespaces.contains(id.namespace()) : this.blockNamespaces.contains(id.namespace()); + } +} diff --git a/src/main/java/com/volmit/iris/core/link/OraxenDataProvider.java b/src/main/java/com/volmit/iris/core/link/OraxenDataProvider.java index b1a46100a..068c8c18e 100644 --- a/src/main/java/com/volmit/iris/core/link/OraxenDataProvider.java +++ b/src/main/java/com/volmit/iris/core/link/OraxenDataProvider.java @@ -1,132 +1,134 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.core.link; - -import com.volmit.iris.Iris; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.reflect.WrappedField; -import io.th0rgal.oraxen.api.OraxenItems; -import io.th0rgal.oraxen.items.ItemBuilder; -import io.th0rgal.oraxen.mechanics.MechanicFactory; -import io.th0rgal.oraxen.mechanics.MechanicsManager; -import io.th0rgal.oraxen.mechanics.provided.gameplay.block.BlockMechanic; -import io.th0rgal.oraxen.mechanics.provided.gameplay.block.BlockMechanicFactory; -import io.th0rgal.oraxen.mechanics.provided.gameplay.noteblock.NoteBlockMechanicFactory; -import io.th0rgal.oraxen.mechanics.provided.gameplay.stringblock.StringBlockMechanicFactory; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.MultipleFacing; -import org.bukkit.inventory.ItemStack; - -import java.util.Map; -import java.util.MissingResourceException; -import java.util.Optional; - -public class OraxenDataProvider extends ExternalDataProvider { - - private static final String FIELD_FACTORIES_MAP = "FACTORIES_BY_MECHANIC_ID"; - - private WrappedField> factories; - - public OraxenDataProvider() { - super("Oraxen"); - } - - @Override - public void init() { - Iris.info("Setting up Oraxen Link..."); - this.factories = new WrappedField<>(MechanicsManager.class, FIELD_FACTORIES_MAP); - if(this.factories.hasFailed()) { - Iris.error("Failed to set up Oraxen Link: Unable to fetch MechanicFactoriesMap!"); - } - } - - @Override - public BlockData getBlockData(Identifier blockId) throws MissingResourceException { - MechanicFactory factory = getFactory(blockId); - if (factory instanceof NoteBlockMechanicFactory f) - return f.createNoteBlockData(blockId.key()); - else if (factory instanceof BlockMechanicFactory f) { - MultipleFacing newBlockData = (MultipleFacing) Bukkit.createBlockData(Material.MUSHROOM_STEM); - BlockMechanic.setBlockFacing(newBlockData, ((BlockMechanic) f.getMechanic(blockId.key())).getCustomVariation()); - return newBlockData; - } else if (factory instanceof StringBlockMechanicFactory f) { - return f.createTripwireData(blockId.key()); - } else - throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); - } - - @Override - public ItemStack getItemStack(Identifier itemId) throws MissingResourceException { - Optional opt = OraxenItems.getOptionalItemById(itemId.key()); - return opt.orElseThrow(() -> new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key())).build(); - } - - @Override - public Identifier[] getBlockTypes() { - KList names = new KList<>(); - for (String name : OraxenItems.getItemNames()) { - try { - Identifier key = new Identifier("oraxen", name); - if (getBlockData(key) != null) - names.add(key); - } catch (MissingResourceException ignored) { } - } - - return names.toArray(new Identifier[0]); - } - - @Override - public Identifier[] getItemTypes() { - KList names = new KList<>(); - for (String name : OraxenItems.getItemNames()) { - try { - Identifier key = new Identifier("oraxen", name); - if (getItemStack(key) != null) - names.add(key); - } catch (MissingResourceException ignored) { } - } - - return names.toArray(new Identifier[0]); - } - - @Override - public boolean isReady() { - if(super.isReady()) { - if(factories == null) { - this.factories = new WrappedField<>(MechanicsManager.class, FIELD_FACTORIES_MAP); - } - return super.isReady() && !factories.hasFailed(); - } - return false; - } - - @Override - public boolean isValidProvider(Identifier key, boolean isItem) { - return key.namespace().equalsIgnoreCase("oraxen"); - } - - private MechanicFactory getFactory(Identifier key) throws MissingResourceException { - return factories.get().values().stream() - .filter(i -> i.getItems().contains(key.key())) - .findFirst() - .orElseThrow(() -> new MissingResourceException("Failed to find BlockData!", key.namespace(), key.key())); - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.core.link; + +import com.volmit.iris.Iris; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.reflect.WrappedField; +import io.th0rgal.oraxen.api.OraxenItems; +import io.th0rgal.oraxen.items.ItemBuilder; +import io.th0rgal.oraxen.mechanics.MechanicFactory; +import io.th0rgal.oraxen.mechanics.MechanicsManager; +import io.th0rgal.oraxen.mechanics.provided.gameplay.block.BlockMechanic; +import io.th0rgal.oraxen.mechanics.provided.gameplay.block.BlockMechanicFactory; +import io.th0rgal.oraxen.mechanics.provided.gameplay.noteblock.NoteBlockMechanicFactory; +import io.th0rgal.oraxen.mechanics.provided.gameplay.stringblock.StringBlockMechanicFactory; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.MultipleFacing; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; +import java.util.MissingResourceException; +import java.util.Optional; + +public class OraxenDataProvider extends ExternalDataProvider { + + private static final String FIELD_FACTORIES_MAP = "FACTORIES_BY_MECHANIC_ID"; + + private WrappedField> factories; + + public OraxenDataProvider() { + super("Oraxen"); + } + + @Override + public void init() { + Iris.info("Setting up Oraxen Link..."); + this.factories = new WrappedField<>(MechanicsManager.class, FIELD_FACTORIES_MAP); + if (this.factories.hasFailed()) { + Iris.error("Failed to set up Oraxen Link: Unable to fetch MechanicFactoriesMap!"); + } + } + + @Override + public BlockData getBlockData(Identifier blockId) throws MissingResourceException { + MechanicFactory factory = getFactory(blockId); + if (factory instanceof NoteBlockMechanicFactory f) + return f.createNoteBlockData(blockId.key()); + else if (factory instanceof BlockMechanicFactory f) { + MultipleFacing newBlockData = (MultipleFacing) Bukkit.createBlockData(Material.MUSHROOM_STEM); + BlockMechanic.setBlockFacing(newBlockData, ((BlockMechanic) f.getMechanic(blockId.key())).getCustomVariation()); + return newBlockData; + } else if (factory instanceof StringBlockMechanicFactory f) { + return f.createTripwireData(blockId.key()); + } else + throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); + } + + @Override + public ItemStack getItemStack(Identifier itemId) throws MissingResourceException { + Optional opt = OraxenItems.getOptionalItemById(itemId.key()); + return opt.orElseThrow(() -> new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key())).build(); + } + + @Override + public Identifier[] getBlockTypes() { + KList names = new KList<>(); + for (String name : OraxenItems.getItemNames()) { + try { + Identifier key = new Identifier("oraxen", name); + if (getBlockData(key) != null) + names.add(key); + } catch (MissingResourceException ignored) { + } + } + + return names.toArray(new Identifier[0]); + } + + @Override + public Identifier[] getItemTypes() { + KList names = new KList<>(); + for (String name : OraxenItems.getItemNames()) { + try { + Identifier key = new Identifier("oraxen", name); + if (getItemStack(key) != null) + names.add(key); + } catch (MissingResourceException ignored) { + } + } + + return names.toArray(new Identifier[0]); + } + + @Override + public boolean isReady() { + if (super.isReady()) { + if (factories == null) { + this.factories = new WrappedField<>(MechanicsManager.class, FIELD_FACTORIES_MAP); + } + return super.isReady() && !factories.hasFailed(); + } + return false; + } + + @Override + public boolean isValidProvider(Identifier key, boolean isItem) { + return key.namespace().equalsIgnoreCase("oraxen"); + } + + private MechanicFactory getFactory(Identifier key) throws MissingResourceException { + return factories.get().values().stream() + .filter(i -> i.getItems().contains(key.key())) + .findFirst() + .orElseThrow(() -> new MissingResourceException("Failed to find BlockData!", key.namespace(), key.key())); + } +} diff --git a/src/main/java/com/volmit/iris/core/nms/v19_4/CustomBiomeSource.java b/src/main/java/com/volmit/iris/core/nms/v19_4/CustomBiomeSource.java index 50c7baf9f..53753e936 100644 --- a/src/main/java/com/volmit/iris/core/nms/v19_4/CustomBiomeSource.java +++ b/src/main/java/com/volmit/iris/core/nms/v19_4/CustomBiomeSource.java @@ -1,157 +1,157 @@ -package com.volmit.iris.core.nms.v19_4; - -import com.mojang.serialization.Codec; -import com.volmit.iris.Iris; -import com.volmit.iris.engine.data.cache.AtomicCache; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.engine.object.IrisBiome; -import com.volmit.iris.engine.object.IrisBiomeCustom; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.math.RNG; -import net.minecraft.core.Holder; -import net.minecraft.core.Registry; -import net.minecraft.core.RegistryAccess; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.biome.Biome; -import net.minecraft.world.level.biome.BiomeSource; -import net.minecraft.world.level.biome.Climate; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.craftbukkit.v1_19_R3.CraftServer; -import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; -import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlock; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -public class CustomBiomeSource extends BiomeSource { - - private final long seed; - private final Engine engine; - private final Registry biomeCustomRegistry; - private final Registry biomeRegistry; - private final AtomicCache registryAccess = new AtomicCache<>(); - private final RNG rng; - private final KMap> customBiomes; - - public CustomBiomeSource(long seed, Engine engine, World world) { - this.engine = engine; - this.seed = seed; - this.biomeCustomRegistry = registry().registry(Registries.BIOME).orElse(null); - this.biomeRegistry = ((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())).registry(Registries.BIOME).orElse(null); - this.rng = new RNG(engine.getSeedManager().getBiome()); - this.customBiomes = fillCustomBiomes(biomeCustomRegistry, engine); - } - - @Override - protected Stream> collectPossibleBiomes() { - return getAllBiomes( - ((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())) - .registry(Registries.BIOME).orElse(null), - ((CraftWorld) engine.getWorld().realWorld()).getHandle().registryAccess().registry(Registries.BIOME).orElse(null), - engine).stream(); - } - - private static List> getAllBiomes(Registry customRegistry, Registry registry, Engine engine) { - List> b = new ArrayList<>(); - - for (IrisBiome i : engine.getAllBiomes()) { - if (i.isCustom()) { - for (IrisBiomeCustom j : i.getCustomDerivitives()) { - b.add(customRegistry.getHolder(customRegistry.getResourceKey(customRegistry - .get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get()); - } - } else { - b.add(CraftBlock.biomeToBiomeBase(registry, i.getVanillaDerivative())); - } - } - - return b; - } - - private static Object getFor(Class type, Object source) { - Object o = fieldFor(type, source); - - if (o != null) { - return o; - } - - return invokeFor(type, source); - } - - private static Object fieldFor(Class returns, Object in) { - return fieldForClass(returns, in.getClass(), in); - } - - private static Object invokeFor(Class returns, Object in) { - for (Method i : in.getClass().getMethods()) { - if (i.getReturnType().equals(returns)) { - i.setAccessible(true); - try { - Iris.debug("[NMS] Found " + returns.getSimpleName() + " in " + in.getClass().getSimpleName() + "." + i.getName() + "()"); - return i.invoke(in); - } catch (Throwable e) { - e.printStackTrace(); - } - } - } - - return null; - } - - @SuppressWarnings("unchecked") - private static T fieldForClass(Class returnType, Class sourceType, Object in) { - for (Field i : sourceType.getDeclaredFields()) { - if (i.getType().equals(returnType)) { - i.setAccessible(true); - try { - Iris.debug("[NMS] Found " + returnType.getSimpleName() + " in " + sourceType.getSimpleName() + "." + i.getName()); - return (T) i.get(in); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - } - return null; - } - - private KMap> fillCustomBiomes(Registry customRegistry, Engine engine) { - KMap> m = new KMap<>(); - - for (IrisBiome i : engine.getAllBiomes()) { - if (i.isCustom()) { - for (IrisBiomeCustom j : i.getCustomDerivitives()) { - m.put(j.getId(), customRegistry.getHolder(customRegistry.getResourceKey(customRegistry - .get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get()); - } - } - } - - return m; - } - - private RegistryAccess registry() { - return registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())); - } - - @Override - protected Codec codec() { - throw new UnsupportedOperationException("Not supported"); - } - - @Override - public Holder getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) { - int m = (y - engine.getMinHeight()) << 2; - IrisBiome ib = engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2); - if (ib.isCustom()) { - return customBiomes.get(ib.getCustomBiome(rng, x << 2, m, z << 2).getId()); - } else { - org.bukkit.block.Biome v = ib.getSkyBiome(rng, x << 2, m, z << 2); - return CraftBlock.biomeToBiomeBase(biomeRegistry, v); - } - } +package com.volmit.iris.core.nms.v19_4; + +import com.mojang.serialization.Codec; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.IrisBiome; +import com.volmit.iris.engine.object.IrisBiomeCustom; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.math.RNG; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.biome.Climate; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_19_R3.CraftServer; +import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlock; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +public class CustomBiomeSource extends BiomeSource { + + private final long seed; + private final Engine engine; + private final Registry biomeCustomRegistry; + private final Registry biomeRegistry; + private final AtomicCache registryAccess = new AtomicCache<>(); + private final RNG rng; + private final KMap> customBiomes; + + public CustomBiomeSource(long seed, Engine engine, World world) { + this.engine = engine; + this.seed = seed; + this.biomeCustomRegistry = registry().registry(Registries.BIOME).orElse(null); + this.biomeRegistry = ((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())).registry(Registries.BIOME).orElse(null); + this.rng = new RNG(engine.getSeedManager().getBiome()); + this.customBiomes = fillCustomBiomes(biomeCustomRegistry, engine); + } + + private static List> getAllBiomes(Registry customRegistry, Registry registry, Engine engine) { + List> b = new ArrayList<>(); + + for (IrisBiome i : engine.getAllBiomes()) { + if (i.isCustom()) { + for (IrisBiomeCustom j : i.getCustomDerivitives()) { + b.add(customRegistry.getHolder(customRegistry.getResourceKey(customRegistry + .get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get()); + } + } else { + b.add(CraftBlock.biomeToBiomeBase(registry, i.getVanillaDerivative())); + } + } + + return b; + } + + private static Object getFor(Class type, Object source) { + Object o = fieldFor(type, source); + + if (o != null) { + return o; + } + + return invokeFor(type, source); + } + + private static Object fieldFor(Class returns, Object in) { + return fieldForClass(returns, in.getClass(), in); + } + + private static Object invokeFor(Class returns, Object in) { + for (Method i : in.getClass().getMethods()) { + if (i.getReturnType().equals(returns)) { + i.setAccessible(true); + try { + Iris.debug("[NMS] Found " + returns.getSimpleName() + " in " + in.getClass().getSimpleName() + "." + i.getName() + "()"); + return i.invoke(in); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + return null; + } + + @SuppressWarnings("unchecked") + private static T fieldForClass(Class returnType, Class sourceType, Object in) { + for (Field i : sourceType.getDeclaredFields()) { + if (i.getType().equals(returnType)) { + i.setAccessible(true); + try { + Iris.debug("[NMS] Found " + returnType.getSimpleName() + " in " + sourceType.getSimpleName() + "." + i.getName()); + return (T) i.get(in); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + return null; + } + + @Override + protected Stream> collectPossibleBiomes() { + return getAllBiomes( + ((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())) + .registry(Registries.BIOME).orElse(null), + ((CraftWorld) engine.getWorld().realWorld()).getHandle().registryAccess().registry(Registries.BIOME).orElse(null), + engine).stream(); + } + + private KMap> fillCustomBiomes(Registry customRegistry, Engine engine) { + KMap> m = new KMap<>(); + + for (IrisBiome i : engine.getAllBiomes()) { + if (i.isCustom()) { + for (IrisBiomeCustom j : i.getCustomDerivitives()) { + m.put(j.getId(), customRegistry.getHolder(customRegistry.getResourceKey(customRegistry + .get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get()); + } + } + } + + return m; + } + + private RegistryAccess registry() { + return registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())); + } + + @Override + protected Codec codec() { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public Holder getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) { + int m = (y - engine.getMinHeight()) << 2; + IrisBiome ib = engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2); + if (ib.isCustom()) { + return customBiomes.get(ib.getCustomBiome(rng, x << 2, m, z << 2).getId()); + } else { + org.bukkit.block.Biome v = ib.getSkyBiome(rng, x << 2, m, z << 2); + return CraftBlock.biomeToBiomeBase(biomeRegistry, v); + } + } } \ No newline at end of file diff --git a/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java b/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java index 2bc7f157f..04606f179 100644 --- a/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java +++ b/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java @@ -1,115 +1,118 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.core.service; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.link.*; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.plugin.IrisService; -import io.th0rgal.oraxen.api.OraxenBlocks; -import lombok.Data; -import org.bukkit.Bukkit; -import org.bukkit.block.data.BlockData; -import org.bukkit.event.EventHandler; -import org.bukkit.event.server.PluginEnableEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.MissingResourceException; -import java.util.Optional; - -@Data -public class ExternalDataSVC implements IrisService { - - private KList providers = new KList<>(), activeProviders = new KList<>(); - - @Override - public void onEnable() { - Iris.info("Loading ExternalDataProvider..."); - Bukkit.getPluginManager().registerEvents(this, Iris.instance); - - providers.add(new OraxenDataProvider()); - if (Bukkit.getPluginManager().getPlugin("Oraxen") != null){ - Iris.info("Oraxen found, loading OraxenDataProvider..."); - } - providers.add(new ItemAdderDataProvider()); - if (Bukkit.getPluginManager().getPlugin("ItemAdder") != null){ - Iris.info("ItemAdder found, loading ItemAdderDataProvider..."); - } - - for (ExternalDataProvider p : providers) { - if (p.isReady()) { - activeProviders.add(p); - p.init(); - Iris.info("Enabled ExternalDataProvider for %s.", p.getPluginId()); - } - } - } - - @Override - public void onDisable() { } - - @EventHandler - public void onPluginEnable(PluginEnableEvent e) { - if(activeProviders.stream().noneMatch(p -> p.getPlugin().equals(e.getPlugin()))) { - providers.stream().filter(p -> p.isReady() && p.getPlugin().equals(e.getPlugin())).findFirst().ifPresent(edp -> { - activeProviders.add(edp); - edp.init(); - Iris.info("Enabled ExternalDataProvider for %s.", edp.getPluginId()); - }); - } - } - - public Optional getBlockData(Identifier key) { - Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(key, false)).findFirst(); - if (provider.isEmpty()) - return Optional.empty(); - try { - return Optional.of(provider.get().getBlockData(key)); - } catch (MissingResourceException e) { - Iris.error(e.getMessage() + " - [" + e.getClassName() + ":" + e.getKey() + "]"); - return Optional.empty(); - } - } - - public Optional getItemStack(Identifier key) { - Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(key, true)).findFirst(); - if (provider.isEmpty()) { - Iris.warn("No matching Provider found for modded material \"%s\"!", key); - return Optional.empty(); - } - try { - return Optional.of(provider.get().getItemStack(key)); - } catch (MissingResourceException e) { - Iris.error(e.getMessage() + " - [" + e.getClassName() + ":" + e.getKey() + "]"); - return Optional.empty(); - } - } - - public Identifier[] getAllBlockIdentifiers() { - KList names = new KList<>(); - activeProviders.forEach(p -> names.add(p.getBlockTypes())); - return names.toArray(new Identifier[0]); - } - - public Identifier[] getAllItemIdentifiers() { - KList names = new KList<>(); - activeProviders.forEach(p -> names.add(p.getItemTypes())); - return names.toArray(new Identifier[0]); - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.core.service; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.link.ExternalDataProvider; +import com.volmit.iris.core.link.Identifier; +import com.volmit.iris.core.link.ItemAdderDataProvider; +import com.volmit.iris.core.link.OraxenDataProvider; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.plugin.IrisService; +import lombok.Data; +import org.bukkit.Bukkit; +import org.bukkit.block.data.BlockData; +import org.bukkit.event.EventHandler; +import org.bukkit.event.server.PluginEnableEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.MissingResourceException; +import java.util.Optional; + +@Data +public class ExternalDataSVC implements IrisService { + + private KList providers = new KList<>(), activeProviders = new KList<>(); + + @Override + public void onEnable() { + Iris.info("Loading ExternalDataProvider..."); + Bukkit.getPluginManager().registerEvents(this, Iris.instance); + + providers.add(new OraxenDataProvider()); + if (Bukkit.getPluginManager().getPlugin("Oraxen") != null) { + Iris.info("Oraxen found, loading OraxenDataProvider..."); + } + providers.add(new ItemAdderDataProvider()); + if (Bukkit.getPluginManager().getPlugin("ItemAdder") != null) { + Iris.info("ItemAdder found, loading ItemAdderDataProvider..."); + } + + for (ExternalDataProvider p : providers) { + if (p.isReady()) { + activeProviders.add(p); + p.init(); + Iris.info("Enabled ExternalDataProvider for %s.", p.getPluginId()); + } + } + } + + @Override + public void onDisable() { + } + + @EventHandler + public void onPluginEnable(PluginEnableEvent e) { + if (activeProviders.stream().noneMatch(p -> p.getPlugin().equals(e.getPlugin()))) { + providers.stream().filter(p -> p.isReady() && p.getPlugin().equals(e.getPlugin())).findFirst().ifPresent(edp -> { + activeProviders.add(edp); + edp.init(); + Iris.info("Enabled ExternalDataProvider for %s.", edp.getPluginId()); + }); + } + } + + public Optional getBlockData(Identifier key) { + Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(key, false)).findFirst(); + if (provider.isEmpty()) + return Optional.empty(); + try { + return Optional.of(provider.get().getBlockData(key)); + } catch (MissingResourceException e) { + Iris.error(e.getMessage() + " - [" + e.getClassName() + ":" + e.getKey() + "]"); + return Optional.empty(); + } + } + + public Optional getItemStack(Identifier key) { + Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(key, true)).findFirst(); + if (provider.isEmpty()) { + Iris.warn("No matching Provider found for modded material \"%s\"!", key); + return Optional.empty(); + } + try { + return Optional.of(provider.get().getItemStack(key)); + } catch (MissingResourceException e) { + Iris.error(e.getMessage() + " - [" + e.getClassName() + ":" + e.getKey() + "]"); + return Optional.empty(); + } + } + + public Identifier[] getAllBlockIdentifiers() { + KList names = new KList<>(); + activeProviders.forEach(p -> names.add(p.getBlockTypes())); + return names.toArray(new Identifier[0]); + } + + public Identifier[] getAllItemIdentifiers() { + KList names = new KList<>(); + activeProviders.forEach(p -> names.add(p.getItemTypes())); + return names.toArray(new Identifier[0]); + } +} diff --git a/src/main/java/com/volmit/iris/engine/IrisEngine.java b/src/main/java/com/volmit/iris/engine/IrisEngine.java index 2f6ce7fe6..7b6c1850b 100644 --- a/src/main/java/com/volmit/iris/engine/IrisEngine.java +++ b/src/main/java/com/volmit/iris/engine/IrisEngine.java @@ -1,522 +1,521 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine; - -import com.google.common.util.concurrent.AtomicDouble; -import com.google.gson.Gson; -import com.volmit.iris.Iris; -import com.volmit.iris.core.ServerConfigurator; -import com.volmit.iris.core.events.IrisEngineHotloadEvent; -import com.volmit.iris.core.gui.PregeneratorJob; -import com.volmit.iris.core.project.IrisProject; -import com.volmit.iris.core.service.PreservationSVC; -import com.volmit.iris.engine.data.cache.AtomicCache; -import com.volmit.iris.engine.framework.*; -import com.volmit.iris.engine.mantle.EngineMantle; -import com.volmit.iris.engine.object.*; -import com.volmit.iris.engine.scripting.EngineExecutionEnvironment; -import com.volmit.iris.util.atomics.AtomicRollingSequence; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.context.ChunkContext; -import com.volmit.iris.util.context.IrisContext; -import com.volmit.iris.util.documentation.BlockCoordinates; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.hunk.Hunk; -import com.volmit.iris.util.io.IO; -import com.volmit.iris.util.mantle.MantleFlag; -import com.volmit.iris.util.math.M; -import com.volmit.iris.util.math.RNG; -import com.volmit.iris.util.matter.MatterStructurePOI; -import com.volmit.iris.util.scheduling.ChronoLatch; -import com.volmit.iris.util.scheduling.J; -import com.volmit.iris.util.scheduling.PrecisionStopwatch; -import lombok.Data; -import net.minecraft.core.BlockPos; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.block.Biome; -import org.bukkit.block.data.BlockData; -import org.bukkit.command.CommandSender; -import oshi.util.tuples.Pair; - -import java.io.File; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -@Data -public class IrisEngine implements Engine { - private final AtomicInteger bud; - private final AtomicInteger buds; - private final AtomicInteger generated; - private final AtomicInteger generatedLast; - private final AtomicDouble perSecond; - private final AtomicLong lastGPS; - private final EngineTarget target; - private final IrisContext context; - private final EngineMantle mantle; - private final ChronoLatch perSecondLatch; - private final ChronoLatch perSecondBudLatch; - private final EngineMetrics metrics; - private final boolean studio; - private final AtomicRollingSequence wallClock; - private final int art; - private final AtomicCache engineData = new AtomicCache<>(); - private final AtomicBoolean cleaning; - private final ChronoLatch cleanLatch; - private final SeedManager seedManager; - private EngineMode mode; - private EngineEffects effects; - private EngineExecutionEnvironment execution; - private EngineWorldManager worldManager; - private volatile int parallelism; - private volatile int minHeight; - private boolean failing; - private boolean closed; - private int cacheId; - private double maxBiomeObjectDensity; - private double maxBiomeLayerDensity; - private double maxBiomeDecoratorDensity; - private IrisComplex complex; - - public IrisEngine(EngineTarget target, boolean studio) { - this.studio = studio; - this.target = target; - getEngineData(); - verifySeed(); - this.seedManager = new SeedManager(target.getWorld().getRawWorldSeed()); - bud = new AtomicInteger(0); - buds = new AtomicInteger(0); - metrics = new EngineMetrics(32); - cleanLatch = new ChronoLatch(10000); - generatedLast = new AtomicInteger(0); - perSecond = new AtomicDouble(0); - perSecondLatch = new ChronoLatch(1000, false); - perSecondBudLatch = new ChronoLatch(1000, false); - wallClock = new AtomicRollingSequence(32); - lastGPS = new AtomicLong(M.ms()); - generated = new AtomicInteger(0); - mantle = new IrisEngineMantle(this); - context = new IrisContext(this); - cleaning = new AtomicBoolean(false); - context.touch(); - getData().setEngine(this); - getData().loadPrefetch(this); - Iris.info("Initializing Engine: " + target.getWorld().name() + "/" + target.getDimension().getLoadKey() + " (" + target.getDimension().getDimensionHeight() + " height) Seed: " + getSeedManager().getSeed()); - minHeight = 0; - failing = false; - closed = false; - art = J.ar(this::tickRandomPlayer, 0); - setupEngine(); - Iris.debug("Engine Initialized " + getCacheID()); - } - - private void verifySeed() { - if (getEngineData().getSeed() != null && getEngineData().getSeed() != target.getWorld().getRawWorldSeed()) { - target.getWorld().setRawWorldSeed(getEngineData().getSeed()); - } - } - - private void tickRandomPlayer() { - recycle(); - if (perSecondBudLatch.flip()) { - buds.set(bud.get()); - bud.set(0); - } - - if (effects != null) { - effects.tickRandomPlayer(); - } - } - - private void prehotload() { - worldManager.close(); - complex.close(); - execution.close(); - effects.close(); - mode.close(); - - J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace()); - } - - private void setupEngine() { - try { - Iris.debug("Setup Engine " + getCacheID()); - cacheId = RNG.r.nextInt(); - worldManager = new IrisWorldManager(this); - complex = new IrisComplex(this); - execution = new IrisExecutionEnvironment(this); - effects = new IrisEngineEffects(this); - setupMode(); - J.a(this::computeBiomeMaxes); - } catch (Throwable e) { - Iris.error("FAILED TO SETUP ENGINE!"); - e.printStackTrace(); - } - - Iris.debug("Engine Setup Complete " + getCacheID()); - } - - private void setupMode() { - if (mode != null) { - mode.close(); - } - - mode = getDimension().getMode().getType().create(this); - } - - @Override - public void generateMatter(int x, int z, boolean multicore, ChunkContext context) { - getMantle().generateMatter(x, z, multicore, context); - } - - @Override - public Set getObjectsAt(int x, int z) { - return getMantle().getObjectComponent().guess(x, z); - } - - @Override - public Set> getPOIsAt(int chunkX, int chunkY) { - Set> pois = new HashSet<>(); - getMantle().getMantle().iterateChunk(chunkX, chunkY, MatterStructurePOI.class, (x, y, z, d) -> pois.add(new Pair<>(d.getType(), new BlockPos(x, y, z)))); - return pois; - } - - @Override - public IrisJigsawStructure getStructureAt(int x, int z) { - return getMantle().getJigsawComponent().guess(x, z); - } - - private void warmupChunk(int x, int z) { - for (int i = 0; i < 16; i++) { - for (int j = 0; j < 16; j++) { - int xx = x + (i << 4); - int zz = z + (z << 4); - getComplex().getTrueBiomeStream().get(xx, zz); - getComplex().getHeightStream().get(xx, zz); - } - } - } - - @Override - public void hotload() { - hotloadSilently(); - Iris.callEvent(new IrisEngineHotloadEvent(this)); - } - - public void hotloadComplex() { - complex.close(); - complex = new IrisComplex(this); - } - - public void hotloadSilently() { - getData().dump(); - getData().clearLists(); - getTarget().setDimension(getData().getDimensionLoader().load(getDimension().getLoadKey())); - prehotload(); - setupEngine(); - J.a(() -> { - synchronized (ServerConfigurator.class) { - ServerConfigurator.installDataPacks(false); - } - }); - } - - @Override - public IrisEngineData getEngineData() { - return engineData.aquire(() -> { - //TODO: Method this file - File f = new File(getWorld().worldFolder(), "iris/engine-data/" + getDimension().getLoadKey() + ".json"); - - if (!f.exists()) { - try { - f.getParentFile().mkdirs(); - IO.writeAll(f, new Gson().toJson(new IrisEngineData())); - } catch (IOException e) { - e.printStackTrace(); - } - } - - try { - return new Gson().fromJson(IO.readAll(f), IrisEngineData.class); - } catch (Throwable e) { - e.printStackTrace(); - } - - return new IrisEngineData(); - }); - } - - @Override - public int getGenerated() { - return generated.get(); - } - - @Override - public double getGeneratedPerSecond() { - if (perSecondLatch.flip()) { - double g = generated.get() - generatedLast.get(); - generatedLast.set(generated.get()); - - if (g == 0) { - return 0; - } - - long dur = M.ms() - lastGPS.get(); - lastGPS.set(M.ms()); - perSecond.set(g / ((double) (dur) / 1000D)); - } - - return perSecond.get(); - } - - @Override - public boolean isStudio() { - return studio; - } - - private void computeBiomeMaxes() { - for (IrisBiome i : getDimension().getAllBiomes(this)) { - double density = 0; - - for (IrisObjectPlacement j : i.getObjects()) { - density += j.getDensity() * j.getChance(); - } - - maxBiomeObjectDensity = Math.max(maxBiomeObjectDensity, density); - density = 0; - - for (IrisDecorator j : i.getDecorators()) { - density += Math.max(j.getStackMax(), 1) * j.getChance(); - } - - maxBiomeDecoratorDensity = Math.max(maxBiomeDecoratorDensity, density); - density = 0; - - for (IrisBiomePaletteLayer j : i.getLayers()) { - density++; - } - - maxBiomeLayerDensity = Math.max(maxBiomeLayerDensity, density); - } - } - - @Override - public int getBlockUpdatesPerSecond() { - return buds.get(); - } - - public void printMetrics(CommandSender sender) { - KMap totals = new KMap<>(); - KMap weights = new KMap<>(); - double masterWallClock = wallClock.getAverage(); - KMap timings = getMetrics().pull(); - double totalWeight = 0; - double wallClock = getMetrics().getTotal().getAverage(); - - for (double j : timings.values()) { - totalWeight += j; - } - - for (String j : timings.k()) { - weights.put(getName() + "." + j, (wallClock / totalWeight) * timings.get(j)); - } - - totals.put(getName(), wallClock); - - double mtotals = 0; - - for (double i : totals.values()) { - mtotals += i; - } - - for (String i : totals.k()) { - totals.put(i, (masterWallClock / mtotals) * totals.get(i)); - } - - double v = 0; - - for (double i : weights.values()) { - v += i; - } - - for (String i : weights.k()) { - weights.put(i, weights.get(i) / v); - } - - sender.sendMessage("Total: " + C.BOLD + C.WHITE + Form.duration(masterWallClock, 0)); - - for (String i : totals.k()) { - sender.sendMessage(" Engine " + C.UNDERLINE + C.GREEN + i + C.RESET + ": " + C.BOLD + C.WHITE + Form.duration(totals.get(i), 0)); - } - - sender.sendMessage("Details: "); - - for (String i : weights.sortKNumber().reverse()) { - String befb = C.UNDERLINE + "" + C.GREEN + "" + i.split("\\Q[\\E")[0] + C.RESET + C.GRAY + "["; - String num = C.GOLD + i.split("\\Q[\\E")[1].split("]")[0] + C.RESET + C.GRAY + "]."; - String afb = C.ITALIC + "" + C.AQUA + i.split("\\Q]\\E")[1].substring(1) + C.RESET + C.GRAY; - - sender.sendMessage(" " + befb + num + afb + ": " + C.BOLD + C.WHITE + Form.pc(weights.get(i), 0)); - } - } - - @Override - public void close() { - PregeneratorJob.shutdownInstance(); - closed = true; - J.car(art); - getWorldManager().close(); - getTarget().close(); - saveEngineData(); - getMantle().close(); - getComplex().close(); - mode.close(); - getData().dump(); - getData().clearLists(); - Iris.service(PreservationSVC.class).dereference(); - Iris.debug("Engine Fully Shutdown!"); - complex = null; - } - - @Override - public boolean isClosed() { - return closed; - } - - @Override - public void recycle() { - if (!cleanLatch.flip()) { - return; - } - - if (cleaning.get()) { - cleanLatch.flipDown(); - return; - } - - cleaning.set(true); - - J.a(() -> { - try { - getMantle().trim(); - getData().getObjectLoader().clean(); - } catch (Throwable e) { - Iris.reportError(e); - Iris.error("Cleanup failed! Enable debug to see stacktrace."); - } - - cleaning.lazySet(false); - }); - } - - @BlockCoordinates - @Override - public void generate(int x, int z, Hunk vblocks, Hunk vbiomes, boolean multicore) throws WrongEngineBroException { - if (closed) { - throw new WrongEngineBroException(); - } - - context.touch(); - getEngineData().getStatistics().generatedChunk(); - try { - PrecisionStopwatch p = PrecisionStopwatch.start(); - Hunk blocks = vblocks.listen((xx, y, zz, t) -> catchBlockUpdates(x + xx, y + getMinHeight(), z + zz, t)); - - if (getDimension().isDebugChunkCrossSections() && ((x >> 4) % getDimension().getDebugCrossSectionsMod() == 0 || (z >> 4) % getDimension().getDebugCrossSectionsMod() == 0)) { - for (int i = 0; i < 16; i++) { - for (int j = 0; j < 16; j++) { - blocks.set(i, 0, j, Material.CRYING_OBSIDIAN.createBlockData()); - } - } - } else { - mode.generate(x, z, blocks, vbiomes, multicore); - } - - getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true); - getMetrics().getTotal().put(p.getMilliseconds()); - generated.incrementAndGet(); - - if (generated.get() == 661) { - J.a(() -> getData().savePrefetch(this)); - } - } catch (Throwable e) { - Iris.reportError(e); - fail("Failed to generate " + x + ", " + z, e); - } - } - - @Override - public void saveEngineData() { - //TODO: Method this file - File f = new File(getWorld().worldFolder(), "iris/engine-data/" + getDimension().getLoadKey() + ".json"); - f.getParentFile().mkdirs(); - try { - IO.writeAll(f, new Gson().toJson(getEngineData())); - Iris.debug("Saved Engine Data"); - } catch (IOException e) { - Iris.error("Failed to save Engine Data"); - e.printStackTrace(); - } - } - - @Override - public void blockUpdatedMetric() { - bud.incrementAndGet(); - } - - @Override - public IrisBiome getFocus() { - if (getDimension().getFocus() == null || getDimension().getFocus().trim().isEmpty()) { - return null; - } - - return getData().getBiomeLoader().load(getDimension().getFocus()); - } - - @Override - public IrisRegion getFocusRegion() { - if (getDimension().getFocusRegion() == null || getDimension().getFocusRegion().trim().isEmpty()) { - return null; - } - - return getData().getRegionLoader().load(getDimension().getFocusRegion()); - } - - @Override - public void fail(String error, Throwable e) { - failing = true; - Iris.error(error); - e.printStackTrace(); - } - - @Override - public boolean hasFailed() { - return failing; - } - - @Override - public int getCacheID() { - return cacheId; - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.engine; + +import com.google.common.util.concurrent.AtomicDouble; +import com.google.gson.Gson; +import com.volmit.iris.Iris; +import com.volmit.iris.core.ServerConfigurator; +import com.volmit.iris.core.events.IrisEngineHotloadEvent; +import com.volmit.iris.core.gui.PregeneratorJob; +import com.volmit.iris.core.project.IrisProject; +import com.volmit.iris.core.service.PreservationSVC; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.*; +import com.volmit.iris.engine.mantle.EngineMantle; +import com.volmit.iris.engine.object.*; +import com.volmit.iris.engine.scripting.EngineExecutionEnvironment; +import com.volmit.iris.util.atomics.AtomicRollingSequence; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.context.ChunkContext; +import com.volmit.iris.util.context.IrisContext; +import com.volmit.iris.util.documentation.BlockCoordinates; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.hunk.Hunk; +import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.mantle.MantleFlag; +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.matter.MatterStructurePOI; +import com.volmit.iris.util.scheduling.ChronoLatch; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import lombok.Data; +import net.minecraft.core.BlockPos; +import org.bukkit.Material; +import org.bukkit.block.Biome; +import org.bukkit.block.data.BlockData; +import org.bukkit.command.CommandSender; +import oshi.util.tuples.Pair; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +@Data +public class IrisEngine implements Engine { + private final AtomicInteger bud; + private final AtomicInteger buds; + private final AtomicInteger generated; + private final AtomicInteger generatedLast; + private final AtomicDouble perSecond; + private final AtomicLong lastGPS; + private final EngineTarget target; + private final IrisContext context; + private final EngineMantle mantle; + private final ChronoLatch perSecondLatch; + private final ChronoLatch perSecondBudLatch; + private final EngineMetrics metrics; + private final boolean studio; + private final AtomicRollingSequence wallClock; + private final int art; + private final AtomicCache engineData = new AtomicCache<>(); + private final AtomicBoolean cleaning; + private final ChronoLatch cleanLatch; + private final SeedManager seedManager; + private EngineMode mode; + private EngineEffects effects; + private EngineExecutionEnvironment execution; + private EngineWorldManager worldManager; + private volatile int parallelism; + private volatile int minHeight; + private boolean failing; + private boolean closed; + private int cacheId; + private double maxBiomeObjectDensity; + private double maxBiomeLayerDensity; + private double maxBiomeDecoratorDensity; + private IrisComplex complex; + + public IrisEngine(EngineTarget target, boolean studio) { + this.studio = studio; + this.target = target; + getEngineData(); + verifySeed(); + this.seedManager = new SeedManager(target.getWorld().getRawWorldSeed()); + bud = new AtomicInteger(0); + buds = new AtomicInteger(0); + metrics = new EngineMetrics(32); + cleanLatch = new ChronoLatch(10000); + generatedLast = new AtomicInteger(0); + perSecond = new AtomicDouble(0); + perSecondLatch = new ChronoLatch(1000, false); + perSecondBudLatch = new ChronoLatch(1000, false); + wallClock = new AtomicRollingSequence(32); + lastGPS = new AtomicLong(M.ms()); + generated = new AtomicInteger(0); + mantle = new IrisEngineMantle(this); + context = new IrisContext(this); + cleaning = new AtomicBoolean(false); + context.touch(); + getData().setEngine(this); + getData().loadPrefetch(this); + Iris.info("Initializing Engine: " + target.getWorld().name() + "/" + target.getDimension().getLoadKey() + " (" + target.getDimension().getDimensionHeight() + " height) Seed: " + getSeedManager().getSeed()); + minHeight = 0; + failing = false; + closed = false; + art = J.ar(this::tickRandomPlayer, 0); + setupEngine(); + Iris.debug("Engine Initialized " + getCacheID()); + } + + private void verifySeed() { + if (getEngineData().getSeed() != null && getEngineData().getSeed() != target.getWorld().getRawWorldSeed()) { + target.getWorld().setRawWorldSeed(getEngineData().getSeed()); + } + } + + private void tickRandomPlayer() { + recycle(); + if (perSecondBudLatch.flip()) { + buds.set(bud.get()); + bud.set(0); + } + + if (effects != null) { + effects.tickRandomPlayer(); + } + } + + private void prehotload() { + worldManager.close(); + complex.close(); + execution.close(); + effects.close(); + mode.close(); + + J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace()); + } + + private void setupEngine() { + try { + Iris.debug("Setup Engine " + getCacheID()); + cacheId = RNG.r.nextInt(); + worldManager = new IrisWorldManager(this); + complex = new IrisComplex(this); + execution = new IrisExecutionEnvironment(this); + effects = new IrisEngineEffects(this); + setupMode(); + J.a(this::computeBiomeMaxes); + } catch (Throwable e) { + Iris.error("FAILED TO SETUP ENGINE!"); + e.printStackTrace(); + } + + Iris.debug("Engine Setup Complete " + getCacheID()); + } + + private void setupMode() { + if (mode != null) { + mode.close(); + } + + mode = getDimension().getMode().getType().create(this); + } + + @Override + public void generateMatter(int x, int z, boolean multicore, ChunkContext context) { + getMantle().generateMatter(x, z, multicore, context); + } + + @Override + public Set getObjectsAt(int x, int z) { + return getMantle().getObjectComponent().guess(x, z); + } + + @Override + public Set> getPOIsAt(int chunkX, int chunkY) { + Set> pois = new HashSet<>(); + getMantle().getMantle().iterateChunk(chunkX, chunkY, MatterStructurePOI.class, (x, y, z, d) -> pois.add(new Pair<>(d.getType(), new BlockPos(x, y, z)))); + return pois; + } + + @Override + public IrisJigsawStructure getStructureAt(int x, int z) { + return getMantle().getJigsawComponent().guess(x, z); + } + + private void warmupChunk(int x, int z) { + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + int xx = x + (i << 4); + int zz = z + (z << 4); + getComplex().getTrueBiomeStream().get(xx, zz); + getComplex().getHeightStream().get(xx, zz); + } + } + } + + @Override + public void hotload() { + hotloadSilently(); + Iris.callEvent(new IrisEngineHotloadEvent(this)); + } + + public void hotloadComplex() { + complex.close(); + complex = new IrisComplex(this); + } + + public void hotloadSilently() { + getData().dump(); + getData().clearLists(); + getTarget().setDimension(getData().getDimensionLoader().load(getDimension().getLoadKey())); + prehotload(); + setupEngine(); + J.a(() -> { + synchronized (ServerConfigurator.class) { + ServerConfigurator.installDataPacks(false); + } + }); + } + + @Override + public IrisEngineData getEngineData() { + return engineData.aquire(() -> { + //TODO: Method this file + File f = new File(getWorld().worldFolder(), "iris/engine-data/" + getDimension().getLoadKey() + ".json"); + + if (!f.exists()) { + try { + f.getParentFile().mkdirs(); + IO.writeAll(f, new Gson().toJson(new IrisEngineData())); + } catch (IOException e) { + e.printStackTrace(); + } + } + + try { + return new Gson().fromJson(IO.readAll(f), IrisEngineData.class); + } catch (Throwable e) { + e.printStackTrace(); + } + + return new IrisEngineData(); + }); + } + + @Override + public int getGenerated() { + return generated.get(); + } + + @Override + public double getGeneratedPerSecond() { + if (perSecondLatch.flip()) { + double g = generated.get() - generatedLast.get(); + generatedLast.set(generated.get()); + + if (g == 0) { + return 0; + } + + long dur = M.ms() - lastGPS.get(); + lastGPS.set(M.ms()); + perSecond.set(g / ((double) (dur) / 1000D)); + } + + return perSecond.get(); + } + + @Override + public boolean isStudio() { + return studio; + } + + private void computeBiomeMaxes() { + for (IrisBiome i : getDimension().getAllBiomes(this)) { + double density = 0; + + for (IrisObjectPlacement j : i.getObjects()) { + density += j.getDensity() * j.getChance(); + } + + maxBiomeObjectDensity = Math.max(maxBiomeObjectDensity, density); + density = 0; + + for (IrisDecorator j : i.getDecorators()) { + density += Math.max(j.getStackMax(), 1) * j.getChance(); + } + + maxBiomeDecoratorDensity = Math.max(maxBiomeDecoratorDensity, density); + density = 0; + + for (IrisBiomePaletteLayer j : i.getLayers()) { + density++; + } + + maxBiomeLayerDensity = Math.max(maxBiomeLayerDensity, density); + } + } + + @Override + public int getBlockUpdatesPerSecond() { + return buds.get(); + } + + public void printMetrics(CommandSender sender) { + KMap totals = new KMap<>(); + KMap weights = new KMap<>(); + double masterWallClock = wallClock.getAverage(); + KMap timings = getMetrics().pull(); + double totalWeight = 0; + double wallClock = getMetrics().getTotal().getAverage(); + + for (double j : timings.values()) { + totalWeight += j; + } + + for (String j : timings.k()) { + weights.put(getName() + "." + j, (wallClock / totalWeight) * timings.get(j)); + } + + totals.put(getName(), wallClock); + + double mtotals = 0; + + for (double i : totals.values()) { + mtotals += i; + } + + for (String i : totals.k()) { + totals.put(i, (masterWallClock / mtotals) * totals.get(i)); + } + + double v = 0; + + for (double i : weights.values()) { + v += i; + } + + for (String i : weights.k()) { + weights.put(i, weights.get(i) / v); + } + + sender.sendMessage("Total: " + C.BOLD + C.WHITE + Form.duration(masterWallClock, 0)); + + for (String i : totals.k()) { + sender.sendMessage(" Engine " + C.UNDERLINE + C.GREEN + i + C.RESET + ": " + C.BOLD + C.WHITE + Form.duration(totals.get(i), 0)); + } + + sender.sendMessage("Details: "); + + for (String i : weights.sortKNumber().reverse()) { + String befb = C.UNDERLINE + "" + C.GREEN + "" + i.split("\\Q[\\E")[0] + C.RESET + C.GRAY + "["; + String num = C.GOLD + i.split("\\Q[\\E")[1].split("]")[0] + C.RESET + C.GRAY + "]."; + String afb = C.ITALIC + "" + C.AQUA + i.split("\\Q]\\E")[1].substring(1) + C.RESET + C.GRAY; + + sender.sendMessage(" " + befb + num + afb + ": " + C.BOLD + C.WHITE + Form.pc(weights.get(i), 0)); + } + } + + @Override + public void close() { + PregeneratorJob.shutdownInstance(); + closed = true; + J.car(art); + getWorldManager().close(); + getTarget().close(); + saveEngineData(); + getMantle().close(); + getComplex().close(); + mode.close(); + getData().dump(); + getData().clearLists(); + Iris.service(PreservationSVC.class).dereference(); + Iris.debug("Engine Fully Shutdown!"); + complex = null; + } + + @Override + public boolean isClosed() { + return closed; + } + + @Override + public void recycle() { + if (!cleanLatch.flip()) { + return; + } + + if (cleaning.get()) { + cleanLatch.flipDown(); + return; + } + + cleaning.set(true); + + J.a(() -> { + try { + getMantle().trim(); + getData().getObjectLoader().clean(); + } catch (Throwable e) { + Iris.reportError(e); + Iris.error("Cleanup failed! Enable debug to see stacktrace."); + } + + cleaning.lazySet(false); + }); + } + + @BlockCoordinates + @Override + public void generate(int x, int z, Hunk vblocks, Hunk vbiomes, boolean multicore) throws WrongEngineBroException { + if (closed) { + throw new WrongEngineBroException(); + } + + context.touch(); + getEngineData().getStatistics().generatedChunk(); + try { + PrecisionStopwatch p = PrecisionStopwatch.start(); + Hunk blocks = vblocks.listen((xx, y, zz, t) -> catchBlockUpdates(x + xx, y + getMinHeight(), z + zz, t)); + + if (getDimension().isDebugChunkCrossSections() && ((x >> 4) % getDimension().getDebugCrossSectionsMod() == 0 || (z >> 4) % getDimension().getDebugCrossSectionsMod() == 0)) { + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + blocks.set(i, 0, j, Material.CRYING_OBSIDIAN.createBlockData()); + } + } + } else { + mode.generate(x, z, blocks, vbiomes, multicore); + } + + getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true); + getMetrics().getTotal().put(p.getMilliseconds()); + generated.incrementAndGet(); + + if (generated.get() == 661) { + J.a(() -> getData().savePrefetch(this)); + } + } catch (Throwable e) { + Iris.reportError(e); + fail("Failed to generate " + x + ", " + z, e); + } + } + + @Override + public void saveEngineData() { + //TODO: Method this file + File f = new File(getWorld().worldFolder(), "iris/engine-data/" + getDimension().getLoadKey() + ".json"); + f.getParentFile().mkdirs(); + try { + IO.writeAll(f, new Gson().toJson(getEngineData())); + Iris.debug("Saved Engine Data"); + } catch (IOException e) { + Iris.error("Failed to save Engine Data"); + e.printStackTrace(); + } + } + + @Override + public void blockUpdatedMetric() { + bud.incrementAndGet(); + } + + @Override + public IrisBiome getFocus() { + if (getDimension().getFocus() == null || getDimension().getFocus().trim().isEmpty()) { + return null; + } + + return getData().getBiomeLoader().load(getDimension().getFocus()); + } + + @Override + public IrisRegion getFocusRegion() { + if (getDimension().getFocusRegion() == null || getDimension().getFocusRegion().trim().isEmpty()) { + return null; + } + + return getData().getRegionLoader().load(getDimension().getFocusRegion()); + } + + @Override + public void fail(String error, Throwable e) { + failing = true; + Iris.error(error); + e.printStackTrace(); + } + + @Override + public boolean hasFailed() { + return failing; + } + + @Override + public int getCacheID() { + return cacheId; + } +} diff --git a/src/main/java/com/volmit/iris/engine/framework/EngineAssignedWorldManager.java b/src/main/java/com/volmit/iris/engine/framework/EngineAssignedWorldManager.java index 6332d289e..5219c5e6f 100644 --- a/src/main/java/com/volmit/iris/engine/framework/EngineAssignedWorldManager.java +++ b/src/main/java/com/volmit/iris/engine/framework/EngineAssignedWorldManager.java @@ -1,179 +1,179 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine.framework; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.events.IrisEngineHotloadEvent; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.math.Position2; -import com.volmit.iris.util.plugin.VolmitSender; -import org.bukkit.*; -import org.bukkit.entity.EnderSignal; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.world.ChunkLoadEvent; -import org.bukkit.event.world.WorldSaveEvent; -import org.bukkit.event.world.WorldUnloadEvent; -import org.bukkit.inventory.EquipmentSlot; - -import java.util.concurrent.atomic.AtomicBoolean; - -public abstract class EngineAssignedWorldManager extends EngineAssignedComponent implements EngineWorldManager, Listener { - private final int taskId; - protected AtomicBoolean ignoreTP = new AtomicBoolean(false); - - public EngineAssignedWorldManager() { - super(null, null); - taskId = -1; - } - - public EngineAssignedWorldManager(Engine engine) { - super(engine, "World"); - Iris.instance.registerListener(this); - taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, this::onTick, 0, 0); - } - - @EventHandler - public void on(IrisEngineHotloadEvent e) { - for (Player i : e.getEngine().getWorld().getPlayers()) { - i.playSound(i.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_BREAK, 1f, 1.8f); - VolmitSender s = new VolmitSender(i); - s.sendTitle(C.IRIS + "Engine " + C.AQUA + "Hotloaded", 70, 60, 410); - } - } - -// @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) -// public void on(PlayerTeleportEvent e) { -// if(ignoreTP.get()) { -// System.out.println("IgTP1"); -// return; -// } -// -// if(!PaperLib.isPaper() || e.getTo() == null) { -// System.out.println("IgTP2"); -// -//// return; -// } -// -//// try { -//// System.out.println("IgTP3"); -//// -//// if(e.getTo().getWorld().equals(getTarget().getWorld().realWorld())) { -//// System.out.println("IgTP4"); -//// -//// getEngine().getWorldManager().teleportAsync(e); -//// } -//// } catch(Throwable ex) { -//// -//// } -// } - - @EventHandler - public void on(WorldSaveEvent e) { - if (e.getWorld().equals(getTarget().getWorld().realWorld())) { - getEngine().save(); - } - } - - @EventHandler - public void onItemUse(PlayerInteractEvent e) { - if (e.getItem() == null || e.getHand() != EquipmentSlot.HAND) { - return; - } - if (e.getAction() == Action.LEFT_CLICK_BLOCK || e.getAction() == Action.LEFT_CLICK_AIR) { - return; - } - if (e.getPlayer().getWorld().equals(getTarget().getWorld().realWorld()) && e.getItem().getType() == Material.ENDER_EYE) { - if (e.getClickedBlock() != null && e.getClickedBlock().getType() == Material.END_PORTAL_FRAME) { - return; - } - - KList positions = getEngine().getDimension().getStrongholds(getEngine().getSeedManager().getSpawn()); - if (positions.isEmpty()) { - return; - } - - Position2 playerPos = new Position2(e.getPlayer().getLocation().getBlockX(), e.getPlayer().getLocation().getBlockZ()); - Position2 pr = positions.get(0); - double d = pr.distance(playerPos); - - for (Position2 pos : positions) { - double distance = pos.distance(playerPos); - if (distance < d) { - d = distance; - pr = pos; - } - } - - if (e.getPlayer().getGameMode() != GameMode.CREATIVE) { - if (e.getItem().getAmount() > 1) { - e.getPlayer().getInventory().getItemInMainHand().setAmount(e.getItem().getAmount() - 1); - } else { - e.getPlayer().getInventory().setItemInMainHand(null); - } - } - - EnderSignal eye = e.getPlayer().getWorld().spawn(e.getPlayer().getLocation().clone().add(0, 0.5F, 0), EnderSignal.class); - eye.setTargetLocation(new Location(e.getPlayer().getWorld(), pr.getX(), 40, pr.getZ())); - eye.getWorld().playSound(eye, Sound.ENTITY_ENDER_EYE_LAUNCH, 1, 1); - Iris.debug("ESignal: " + eye.getTargetLocation().getBlockX() + " " + eye.getTargetLocation().getBlockX()); - } - } - - @EventHandler - public void on(WorldUnloadEvent e) { - if (e.getWorld().equals(getTarget().getWorld().realWorld())) { - getEngine().close(); - } - } - - @EventHandler - public void on(BlockBreakEvent e) { - if (e.getPlayer().getWorld().equals(getTarget().getWorld().realWorld())) { - onBlockBreak(e); - } - } - - @EventHandler - public void on(BlockPlaceEvent e) { - if (e.getPlayer().getWorld().equals(getTarget().getWorld().realWorld())) { - onBlockPlace(e); - } - } - - @EventHandler - public void on(ChunkLoadEvent e) { - if (e.getChunk().getWorld().equals(getTarget().getWorld().realWorld())) { - onChunkLoad(e.getChunk(), e.isNewChunk()); - } - } - - @Override - public void close() { - super.close(); - Iris.instance.unregisterListener(this); - Bukkit.getScheduler().cancelTask(taskId); - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.engine.framework; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.events.IrisEngineHotloadEvent; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.plugin.VolmitSender; +import org.bukkit.*; +import org.bukkit.entity.EnderSignal; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.event.world.WorldSaveEvent; +import org.bukkit.event.world.WorldUnloadEvent; +import org.bukkit.inventory.EquipmentSlot; + +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class EngineAssignedWorldManager extends EngineAssignedComponent implements EngineWorldManager, Listener { + private final int taskId; + protected AtomicBoolean ignoreTP = new AtomicBoolean(false); + + public EngineAssignedWorldManager() { + super(null, null); + taskId = -1; + } + + public EngineAssignedWorldManager(Engine engine) { + super(engine, "World"); + Iris.instance.registerListener(this); + taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, this::onTick, 0, 0); + } + + @EventHandler + public void on(IrisEngineHotloadEvent e) { + for (Player i : e.getEngine().getWorld().getPlayers()) { + i.playSound(i.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_BREAK, 1f, 1.8f); + VolmitSender s = new VolmitSender(i); + s.sendTitle(C.IRIS + "Engine " + C.AQUA + "Hotloaded", 70, 60, 410); + } + } + +// @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) +// public void on(PlayerTeleportEvent e) { +// if(ignoreTP.get()) { +// System.out.println("IgTP1"); +// return; +// } +// +// if(!PaperLib.isPaper() || e.getTo() == null) { +// System.out.println("IgTP2"); +// +//// return; +// } +// +//// try { +//// System.out.println("IgTP3"); +//// +//// if(e.getTo().getWorld().equals(getTarget().getWorld().realWorld())) { +//// System.out.println("IgTP4"); +//// +//// getEngine().getWorldManager().teleportAsync(e); +//// } +//// } catch(Throwable ex) { +//// +//// } +// } + + @EventHandler + public void on(WorldSaveEvent e) { + if (e.getWorld().equals(getTarget().getWorld().realWorld())) { + getEngine().save(); + } + } + + @EventHandler + public void onItemUse(PlayerInteractEvent e) { + if (e.getItem() == null || e.getHand() != EquipmentSlot.HAND) { + return; + } + if (e.getAction() == Action.LEFT_CLICK_BLOCK || e.getAction() == Action.LEFT_CLICK_AIR) { + return; + } + if (e.getPlayer().getWorld().equals(getTarget().getWorld().realWorld()) && e.getItem().getType() == Material.ENDER_EYE) { + if (e.getClickedBlock() != null && e.getClickedBlock().getType() == Material.END_PORTAL_FRAME) { + return; + } + + KList positions = getEngine().getDimension().getStrongholds(getEngine().getSeedManager().getSpawn()); + if (positions.isEmpty()) { + return; + } + + Position2 playerPos = new Position2(e.getPlayer().getLocation().getBlockX(), e.getPlayer().getLocation().getBlockZ()); + Position2 pr = positions.get(0); + double d = pr.distance(playerPos); + + for (Position2 pos : positions) { + double distance = pos.distance(playerPos); + if (distance < d) { + d = distance; + pr = pos; + } + } + + if (e.getPlayer().getGameMode() != GameMode.CREATIVE) { + if (e.getItem().getAmount() > 1) { + e.getPlayer().getInventory().getItemInMainHand().setAmount(e.getItem().getAmount() - 1); + } else { + e.getPlayer().getInventory().setItemInMainHand(null); + } + } + + EnderSignal eye = e.getPlayer().getWorld().spawn(e.getPlayer().getLocation().clone().add(0, 0.5F, 0), EnderSignal.class); + eye.setTargetLocation(new Location(e.getPlayer().getWorld(), pr.getX(), 40, pr.getZ())); + eye.getWorld().playSound(eye, Sound.ENTITY_ENDER_EYE_LAUNCH, 1, 1); + Iris.debug("ESignal: " + eye.getTargetLocation().getBlockX() + " " + eye.getTargetLocation().getBlockX()); + } + } + + @EventHandler + public void on(WorldUnloadEvent e) { + if (e.getWorld().equals(getTarget().getWorld().realWorld())) { + getEngine().close(); + } + } + + @EventHandler + public void on(BlockBreakEvent e) { + if (e.getPlayer().getWorld().equals(getTarget().getWorld().realWorld())) { + onBlockBreak(e); + } + } + + @EventHandler + public void on(BlockPlaceEvent e) { + if (e.getPlayer().getWorld().equals(getTarget().getWorld().realWorld())) { + onBlockPlace(e); + } + } + + @EventHandler + public void on(ChunkLoadEvent e) { + if (e.getChunk().getWorld().equals(getTarget().getWorld().realWorld())) { + onChunkLoad(e.getChunk(), e.isNewChunk()); + } + } + + @Override + public void close() { + super.close(); + Iris.instance.unregisterListener(this); + Bukkit.getScheduler().cancelTask(taskId); + } +} diff --git a/src/main/java/com/volmit/iris/engine/object/IrisDimension.java b/src/main/java/com/volmit/iris/engine/object/IrisDimension.java index 99a429d86..bb1cf5cf1 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisDimension.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisDimension.java @@ -1,572 +1,574 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine.object; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; -import com.volmit.iris.core.loader.IrisRegistrant; -import com.volmit.iris.engine.data.cache.AtomicCache; -import com.volmit.iris.engine.object.annotations.*; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.data.DataProvider; -import com.volmit.iris.util.io.IO; -import com.volmit.iris.util.json.JSONObject; -import com.volmit.iris.util.math.Position2; -import com.volmit.iris.util.math.RNG; -import com.volmit.iris.util.noise.CNG; -import com.volmit.iris.util.plugin.VolmitSender; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; -import org.bukkit.Material; -import org.bukkit.World.Environment; -import org.bukkit.block.data.BlockData; - -import java.io.File; -import java.io.IOException; - -@Accessors(chain = true) -@AllArgsConstructor -@NoArgsConstructor -@Desc("Represents a dimension") -@Data -@EqualsAndHashCode(callSuper = false) -public class IrisDimension extends IrisRegistrant { - public static final BlockData STONE = Material.STONE.createBlockData(); - public static final BlockData WATER = Material.WATER.createBlockData(); - private static final String DP_OVERWORLD_DEFAULT = """ - { - "ambient_light": 0.0, - "bed_works": true, - "coordinate_scale": 1.0, - "effects": "minecraft:overworld", - "has_ceiling": false, - "has_raids": true, - "has_skylight": true, - "infiniburn": "#minecraft:infiniburn_overworld", - "monster_spawn_block_light_limit": 0, - "monster_spawn_light_level": { - "type": "minecraft:uniform", - "value": { - "max_inclusive": 7, - "min_inclusive": 0 - } - }, - "natural": true, - "piglin_safe": false, - "respawn_anchor_works": false, - "ultrawarm": false - }"""; - - private static final String DP_NETHER_DEFAULT = """ - { - "ambient_light": 0.1, - "bed_works": false, - "coordinate_scale": 8.0, - "effects": "minecraft:the_nether", - "fixed_time": 18000, - "has_ceiling": true, - "has_raids": false, - "has_skylight": false, - "infiniburn": "#minecraft:infiniburn_nether", - "monster_spawn_block_light_limit": 15, - "monster_spawn_light_level": 7, - "natural": false, - "piglin_safe": true, - "respawn_anchor_works": true, - "ultrawarm": true - }"""; - - private static final String DP_END_DEFAULT = """ - { - "ambient_light": 0.0, - "bed_works": false, - "coordinate_scale": 1.0, - "effects": "minecraft:the_end", - "fixed_time": 6000, - "has_ceiling": false, - "has_raids": true, - "has_skylight": false, - "infiniburn": "#minecraft:infiniburn_end", - "monster_spawn_block_light_limit": 0, - "monster_spawn_light_level": { - "type": "minecraft:uniform", - "value": { - "max_inclusive": 7, - "min_inclusive": 0 - } - }, - "natural": false, - "piglin_safe": false, - "respawn_anchor_works": false, - "ultrawarm": false - }"""; - private final transient AtomicCache parallaxSize = new AtomicCache<>(); - private final transient AtomicCache rockLayerGenerator = new AtomicCache<>(); - private final transient AtomicCache fluidLayerGenerator = new AtomicCache<>(); - private final transient AtomicCache coordFracture = new AtomicCache<>(); - private final transient AtomicCache sinr = new AtomicCache<>(); - 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> strongholdsCache = new AtomicCache<>(); - @MinNumber(2) - @Required - @Desc("The human readable name of this dimension") - private String name = "A Dimension"; - @MinNumber(1) - @MaxNumber(2032) - @Desc("Maximum height at which players can be teleported to through gameplay.") - private int logicalHeight = 256; - @Desc("Maximum height at which players can be teleported to through gameplay.") - private int logicalHeightEnd = 256; - @Desc("Maximum height at which players can be teleported to through gameplay.") - private int logicalHeightNether = 256; - @RegistryListResource(IrisJigsawStructure.class) - @Desc("If defined, Iris will place the given jigsaw structure where minecraft should place the overworld stronghold.") - private String stronghold; - @Desc("If set to true, Iris will remove chunks to allow visualizing cross sections of chunks easily") - private boolean debugChunkCrossSections = false; - @Desc("Vertically split up the biome palettes with 3 air blocks in between to visualize them") - private boolean explodeBiomePalettes = false; - @Desc("Studio Mode for testing different parts of the world") - private StudioMode studioMode = StudioMode.NORMAL; - @MinNumber(1) - @MaxNumber(16) - @Desc("Customize the palette height explosion") - private int explodeBiomePaletteSize = 3; - @MinNumber(2) - @MaxNumber(16) - @Desc("Every X/Z % debugCrossSectionsMod == 0 cuts the chunk") - private int debugCrossSectionsMod = 3; - @Desc("The average distance between strongholds") - private int strongholdJumpDistance = 1280; - @Desc("Define the maximum strongholds to place") - private int maxStrongholds = 14; - @Desc("Tree growth override settings") - private IrisTreeSettings treeSettings = new IrisTreeSettings(); - @Desc("Spawn Entities in this dimension over time. Iris will continually replenish these mobs just like vanilla does.") - @ArrayType(min = 1, type = String.class) - @RegistryListResource(IrisSpawner.class) - private KList entitySpawners = new KList<>(); - @Desc("Reference loot tables in this area") - private IrisLootReference loot = new IrisLootReference(); - @MinNumber(0) - @Desc("The version of this dimension. Changing this will stop users from accidentally upgrading (and breaking their worlds).") - private int version = 1; - @ArrayType(min = 1, type = IrisBlockDrops.class) - @Desc("Define custom block drops for this dimension") - private KList blockDrops = new KList<>(); - @Desc("Should bedrock be generated or not.") - private boolean bedrock = true; - @MinNumber(0) - @MaxNumber(1) - @Desc("The land chance. Up to 1.0 for total land or 0.0 for total sea") - private double landChance = 0.625; - @Desc("The placement style of regions") - private IrisGeneratorStyle regionStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); - @Desc("The placement style of land/sea") - private IrisGeneratorStyle continentalStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); - @Desc("The placement style of biomes") - private IrisGeneratorStyle landBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); - @Desc("The placement style of biomes") - private IrisGeneratorStyle shoreBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); - @Desc("The placement style of biomes") - private IrisGeneratorStyle seaBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); - @Desc("The placement style of biomes") - private IrisGeneratorStyle caveBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); - @Desc("Instead of filling objects with air, fills them with cobweb so you can see them") - private boolean debugSmartBore = false; - @Desc("Generate decorations or not") - private boolean decorate = true; - @Desc("Use post processing or not") - private boolean postProcessing = true; - @Desc("Add slabs in post processing") - private boolean postProcessingSlabs = true; - @Desc("Add painted walls in post processing") - private boolean postProcessingWalls = true; - @Desc("Carving configuration for the dimension") - private IrisCarving carving = new IrisCarving(); - @Desc("Configuration of fluid bodies such as rivers & lakes") - private IrisFluidBodies fluidBodies = new IrisFluidBodies(); - @Desc("The world environment") - private Environment environment = Environment.NORMAL; - @RegistryListResource(IrisRegion.class) - @Required - @ArrayType(min = 1, type = String.class) - @Desc("Define all of the regions to include in this dimension. Dimensions -> Regions -> Biomes -> Objects etc") - private KList regions = new KList<>(); - @ArrayType(min = 1, type = IrisJigsawStructurePlacement.class) - @Desc("Jigsaw structures") - private KList jigsawStructures = new KList<>(); - @Required - @MinNumber(0) - @MaxNumber(1024) - @Desc("The fluid height for this dimension") - private int fluidHeight = 63; - @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") - private IrisRange dimensionHeight = new IrisRange(-64, 320); - @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") - private IrisRange dimensionHeightEnd = new IrisRange(-64, 320); - @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") - private IrisRange dimensionHeightNether = new IrisRange(-64, 320); - @RegistryListResource(IrisBiome.class) - @Desc("Keep this either undefined or empty. Setting any biome name into this will force iris to only generate the specified biome. Great for testing.") - private String focus = ""; - @RegistryListResource(IrisRegion.class) - @Desc("Keep this either undefined or empty. Setting any region name into this will force iris to only generate the specified region. Great for testing.") - private String focusRegion = ""; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("Zoom in or out the biome size. Higher = bigger biomes") - private double biomeZoom = 5D; - @MinNumber(0) - @MaxNumber(360) - @Desc("You can rotate the input coordinates by an angle. This can make terrain appear more natural (less sharp corners and lines). This literally rotates the entire dimension by an angle. Hint: Try 12 degrees or something not on a 90 or 45 degree angle.") - private double dimensionAngleDeg = 0; - @Required - @Desc("Define the mode of this dimension (required!)") - private IrisDimensionMode mode = new IrisDimensionMode(); - @MinNumber(0) - @MaxNumber(8192) - @Desc("Coordinate fracturing applies noise to the input coordinates. This creates the 'iris swirls' and wavy features. The distance pushes these waves further into places they shouldnt be. This is a block value multiplier.") - private double coordFractureDistance = 20; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("Coordinate fracturing zoom. Higher = less frequent warping, Lower = more frequent and rapid warping / swirls.") - private double coordFractureZoom = 8; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("This zooms in the land space") - private double landZoom = 1; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("This zooms oceanic biomes") - private double seaZoom = 1; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("Zoom in continents") - private double continentZoom = 1; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("Change the size of regions") - private double regionZoom = 1; - @Desc("Disable this to stop placing objects, entities, features & updates") - private boolean useMantle = true; - @Desc("Prevent Leaf decay as if placed in creative mode") - private boolean preventLeafDecay = false; - @ArrayType(min = 1, type = IrisDepositGenerator.class) - @Desc("Define global deposit generators") - private KList deposits = new KList<>(); - @ArrayType(min = 1, type = IrisShapedGeneratorStyle.class) - @Desc("Overlay additional noise on top of the interoplated terrain.") - private KList overlayNoise = new KList<>(); - @Desc("If true, the spawner system has infinite energy. This is NOT recommended because it would allow for mobs to keep spawning over and over without a rate limit") - private boolean infiniteEnergy = false; - @MinNumber(0) - @MaxNumber(10000) - @Desc("This is the maximum energy you can have in a dimension") - private double maximumEnergy = 1000; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("The rock zoom mostly for zooming in on a wispy palette") - private double rockZoom = 5; - @Desc("The palette of blocks for 'stone'") - private IrisMaterialPalette rockPalette = new IrisMaterialPalette().qclear().qadd("stone"); - @Desc("The palette of blocks for 'water'") - private IrisMaterialPalette fluidPalette = new IrisMaterialPalette().qclear().qadd("water"); - @Desc("Remove cartographers so they do not crash the server (Iris worlds only)") - private boolean removeCartographersDueToCrash = true; - @Desc("Notify players of cancelled cartographer villager in this radius in blocks (set to -1 to disable, -2 for everyone)") - private int notifyPlayersOfCartographerCancelledRadius = 30; - @Desc("Collection of ores to be generated") - @ArrayType(type = IrisOreGenerator.class, min = 1) - private KList ores = new KList<>(); - @MinNumber(0) - @MaxNumber(318) - @Desc("The Subterrain Fluid Layer Height") - private int caveLavaHeight = 8; - - public int getMaxHeight() { - return (int) getDimensionHeight().getMax(); - } - - public int getMinHeight() { - return (int) getDimensionHeight().getMin(); - } - - public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data) { - if (ores.isEmpty()) { - return null; - } - BlockData b = null; - for (IrisOreGenerator i : ores) { - - b = i.generate(x, y, z, rng, data); - if (b != null) { - return b; - } - } - return null; - } - - public KList getStrongholds(long seed) { - return strongholdsCache.aquire(() -> { - KList pos = new KList<>(); - int jump = strongholdJumpDistance; - RNG rng = new RNG((seed * 223) + 12945); - for (int i = 0; i < maxStrongholds + 1; i++) { - int m = i + 1; - pos.add(new Position2( - (int) ((rng.i(jump * i) + (jump * i)) * (rng.b() ? -1D : 1D)), - (int) ((rng.i(jump * i) + (jump * i)) * (rng.b() ? -1D : 1D)) - )); - } - - pos.remove(0); - - return pos; - }); - } - - public int getFluidHeight() { - return fluidHeight - (int) dimensionHeight.getMin(); - } - - public CNG getCoordFracture(RNG rng, int signature) { - return coordFracture.aquire(() -> - { - CNG coordFracture = CNG.signature(rng.nextParallelRNG(signature)); - coordFracture.scale(0.012 / coordFractureZoom); - return coordFracture; - }); - } - - public double getDimensionAngle() { - return rad.aquire(() -> Math.toRadians(dimensionAngleDeg)); - } - - public Environment getEnvironment() { - return environment; - } - - public boolean hasFocusRegion() { - return !focusRegion.equals(""); - } - - public String getFocusRegion() { - return focusRegion; - } - - public double sinRotate() { - return sinr.aquire(() -> Math.sin(getDimensionAngle())); - } - - public double cosRotate() { - return cosr.aquire(() -> Math.cos(getDimensionAngle())); - } - - public KList getAllRegions(DataProvider g) { - KList r = new KList<>(); - - for (String i : getRegions()) { - r.add(g.getData().getRegionLoader().load(i)); - } - - return r; - } - - public KList getAllAnyRegions() { - KList r = new KList<>(); - - for (String i : getRegions()) { - r.add(IrisData.loadAnyRegion(i)); - } - - return r; - } - - public KList getAllBiomes(DataProvider g) { - return g.getData().getBiomeLoader().loadAll(g.getData().getBiomeLoader().getPossibleKeys()); - } - - public KList getAllAnyBiomes() { - KList r = new KList<>(); - - for (IrisRegion i : getAllAnyRegions()) { - if (i == null) { - continue; - } - - r.addAll(i.getAllAnyBiomes()); - } - - return r; - } - - public IrisGeneratorStyle getBiomeStyle(InferredType type) { - switch (type) { - case CAVE: - return caveBiomeStyle; - case LAND: - return landBiomeStyle; - case SEA: - return seaBiomeStyle; - case SHORE: - return shoreBiomeStyle; - default: - break; - } - - return landBiomeStyle; - } - - public boolean installDataPack(DataProvider data, File datapacks) { - boolean write = false; - boolean changed = false; - - IO.delete(new File(datapacks, "iris/data/" + getLoadKey().toLowerCase())); - - for (IrisBiome i : getAllBiomes(data)) { - if (i.isCustom()) { - write = true; - - for (IrisBiomeCustom j : i.getCustomDerivitives()) { - File output = new File(datapacks, "iris/data/" + getLoadKey().toLowerCase() + "/worldgen/biome/" + j.getId() + ".json"); - - if (!output.exists()) { - changed = true; - } - - Iris.verbose(" Installing Data Pack Biome: " + output.getPath()); - output.getParentFile().mkdirs(); - try { - IO.writeAll(output, j.generateJson()); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - } - } - } - - if (!dimensionHeight.equals(new IrisRange(-64, 320)) && this.name.equalsIgnoreCase("overworld")) { - Iris.verbose(" Installing Data Pack Dimension Types: \"minecraft:overworld\", \"minecraft:the_nether\", \"minecraft:the_end\""); - changed = writeDimensionType(changed, datapacks); - } - - if (write) { - File mcm = new File(datapacks, "iris/pack.mcmeta"); - try { - IO.writeAll(mcm, """ - { - "pack": { - "description": "Iris Data Pack. This pack contains all installed Iris Packs' resources.", - "pack_format": 10 - } - } - """); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - Iris.verbose(" Installing Data Pack MCMeta: " + mcm.getPath()); - } - - return changed; - } - - @Override - public String getFolderName() { - return "dimensions"; - } - - @Override - public String getTypeName() { - return "Dimension"; - } - - @Override - public void scanForErrors(JSONObject p, VolmitSender sender) { - - } - - public boolean writeDimensionType(boolean changed, File datapacks) { - File dimTypeOverworld = new File(datapacks, "iris/data/minecraft/dimension_type/overworld.json"); - if (!dimTypeOverworld.exists()) - changed = true; - dimTypeOverworld.getParentFile().mkdirs(); - try { - IO.writeAll(dimTypeOverworld, generateDatapackJsonOverworld()); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - - - File dimTypeNether = new File(datapacks, "iris/data/minecraft/dimension_type/the_nether.json"); - if (!dimTypeNether.exists()) - changed = true; - dimTypeNether.getParentFile().mkdirs(); - try { - IO.writeAll(dimTypeNether, generateDatapackJsonNether()); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - - - File dimTypeEnd = new File(datapacks, "iris/data/minecraft/dimension_type/the_end.json"); - if (!dimTypeEnd.exists()) - changed = true; - dimTypeEnd.getParentFile().mkdirs(); - try { - IO.writeAll(dimTypeEnd, generateDatapackJsonEnd()); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - - return changed; - } - - private String generateDatapackJsonOverworld() { - JSONObject obj = new JSONObject(DP_OVERWORLD_DEFAULT); - obj.put("min_y", dimensionHeight.getMin()); - obj.put("height", dimensionHeight.getMax() - dimensionHeight.getMin()); - obj.put("logical_height", logicalHeight); - return obj.toString(4); - } - private String generateDatapackJsonNether() { - JSONObject obj = new JSONObject(DP_NETHER_DEFAULT); - obj.put("min_y", dimensionHeightNether.getMin()); - obj.put("height", dimensionHeightNether.getMax() - dimensionHeightNether.getMin()); - obj.put("logical_height", logicalHeightNether); - return obj.toString(4); - } - private String generateDatapackJsonEnd() { - JSONObject obj = new JSONObject(DP_END_DEFAULT); - obj.put("min_y", dimensionHeightEnd.getMin()); - obj.put("height", dimensionHeightEnd.getMax() - dimensionHeightEnd.getMin()); - obj.put("logical_height", logicalHeightEnd); - return obj.toString(4); - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.engine.object; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.loader.IrisRegistrant; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.object.annotations.*; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.data.DataProvider; +import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.noise.CNG; +import com.volmit.iris.util.plugin.VolmitSender; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.bukkit.Material; +import org.bukkit.World.Environment; +import org.bukkit.block.data.BlockData; + +import java.io.File; +import java.io.IOException; + +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +@Desc("Represents a dimension") +@Data +@EqualsAndHashCode(callSuper = false) +public class IrisDimension extends IrisRegistrant { + public static final BlockData STONE = Material.STONE.createBlockData(); + public static final BlockData WATER = Material.WATER.createBlockData(); + private static final String DP_OVERWORLD_DEFAULT = """ + { + "ambient_light": 0.0, + "bed_works": true, + "coordinate_scale": 1.0, + "effects": "minecraft:overworld", + "has_ceiling": false, + "has_raids": true, + "has_skylight": true, + "infiniburn": "#minecraft:infiniburn_overworld", + "monster_spawn_block_light_limit": 0, + "monster_spawn_light_level": { + "type": "minecraft:uniform", + "value": { + "max_inclusive": 7, + "min_inclusive": 0 + } + }, + "natural": true, + "piglin_safe": false, + "respawn_anchor_works": false, + "ultrawarm": false + }"""; + + private static final String DP_NETHER_DEFAULT = """ + { + "ambient_light": 0.1, + "bed_works": false, + "coordinate_scale": 8.0, + "effects": "minecraft:the_nether", + "fixed_time": 18000, + "has_ceiling": true, + "has_raids": false, + "has_skylight": false, + "infiniburn": "#minecraft:infiniburn_nether", + "monster_spawn_block_light_limit": 15, + "monster_spawn_light_level": 7, + "natural": false, + "piglin_safe": true, + "respawn_anchor_works": true, + "ultrawarm": true + }"""; + + private static final String DP_END_DEFAULT = """ + { + "ambient_light": 0.0, + "bed_works": false, + "coordinate_scale": 1.0, + "effects": "minecraft:the_end", + "fixed_time": 6000, + "has_ceiling": false, + "has_raids": true, + "has_skylight": false, + "infiniburn": "#minecraft:infiniburn_end", + "monster_spawn_block_light_limit": 0, + "monster_spawn_light_level": { + "type": "minecraft:uniform", + "value": { + "max_inclusive": 7, + "min_inclusive": 0 + } + }, + "natural": false, + "piglin_safe": false, + "respawn_anchor_works": false, + "ultrawarm": false + }"""; + private final transient AtomicCache parallaxSize = new AtomicCache<>(); + private final transient AtomicCache rockLayerGenerator = new AtomicCache<>(); + private final transient AtomicCache fluidLayerGenerator = new AtomicCache<>(); + private final transient AtomicCache coordFracture = new AtomicCache<>(); + private final transient AtomicCache sinr = new AtomicCache<>(); + 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> strongholdsCache = new AtomicCache<>(); + @MinNumber(2) + @Required + @Desc("The human readable name of this dimension") + private String name = "A Dimension"; + @MinNumber(1) + @MaxNumber(2032) + @Desc("Maximum height at which players can be teleported to through gameplay.") + private int logicalHeight = 256; + @Desc("Maximum height at which players can be teleported to through gameplay.") + private int logicalHeightEnd = 256; + @Desc("Maximum height at which players can be teleported to through gameplay.") + private int logicalHeightNether = 256; + @RegistryListResource(IrisJigsawStructure.class) + @Desc("If defined, Iris will place the given jigsaw structure where minecraft should place the overworld stronghold.") + private String stronghold; + @Desc("If set to true, Iris will remove chunks to allow visualizing cross sections of chunks easily") + private boolean debugChunkCrossSections = false; + @Desc("Vertically split up the biome palettes with 3 air blocks in between to visualize them") + private boolean explodeBiomePalettes = false; + @Desc("Studio Mode for testing different parts of the world") + private StudioMode studioMode = StudioMode.NORMAL; + @MinNumber(1) + @MaxNumber(16) + @Desc("Customize the palette height explosion") + private int explodeBiomePaletteSize = 3; + @MinNumber(2) + @MaxNumber(16) + @Desc("Every X/Z % debugCrossSectionsMod == 0 cuts the chunk") + private int debugCrossSectionsMod = 3; + @Desc("The average distance between strongholds") + private int strongholdJumpDistance = 1280; + @Desc("Define the maximum strongholds to place") + private int maxStrongholds = 14; + @Desc("Tree growth override settings") + private IrisTreeSettings treeSettings = new IrisTreeSettings(); + @Desc("Spawn Entities in this dimension over time. Iris will continually replenish these mobs just like vanilla does.") + @ArrayType(min = 1, type = String.class) + @RegistryListResource(IrisSpawner.class) + private KList entitySpawners = new KList<>(); + @Desc("Reference loot tables in this area") + private IrisLootReference loot = new IrisLootReference(); + @MinNumber(0) + @Desc("The version of this dimension. Changing this will stop users from accidentally upgrading (and breaking their worlds).") + private int version = 1; + @ArrayType(min = 1, type = IrisBlockDrops.class) + @Desc("Define custom block drops for this dimension") + private KList blockDrops = new KList<>(); + @Desc("Should bedrock be generated or not.") + private boolean bedrock = true; + @MinNumber(0) + @MaxNumber(1) + @Desc("The land chance. Up to 1.0 for total land or 0.0 for total sea") + private double landChance = 0.625; + @Desc("The placement style of regions") + private IrisGeneratorStyle regionStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); + @Desc("The placement style of land/sea") + private IrisGeneratorStyle continentalStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); + @Desc("The placement style of biomes") + private IrisGeneratorStyle landBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); + @Desc("The placement style of biomes") + private IrisGeneratorStyle shoreBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); + @Desc("The placement style of biomes") + private IrisGeneratorStyle seaBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); + @Desc("The placement style of biomes") + private IrisGeneratorStyle caveBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); + @Desc("Instead of filling objects with air, fills them with cobweb so you can see them") + private boolean debugSmartBore = false; + @Desc("Generate decorations or not") + private boolean decorate = true; + @Desc("Use post processing or not") + private boolean postProcessing = true; + @Desc("Add slabs in post processing") + private boolean postProcessingSlabs = true; + @Desc("Add painted walls in post processing") + private boolean postProcessingWalls = true; + @Desc("Carving configuration for the dimension") + private IrisCarving carving = new IrisCarving(); + @Desc("Configuration of fluid bodies such as rivers & lakes") + private IrisFluidBodies fluidBodies = new IrisFluidBodies(); + @Desc("The world environment") + private Environment environment = Environment.NORMAL; + @RegistryListResource(IrisRegion.class) + @Required + @ArrayType(min = 1, type = String.class) + @Desc("Define all of the regions to include in this dimension. Dimensions -> Regions -> Biomes -> Objects etc") + private KList regions = new KList<>(); + @ArrayType(min = 1, type = IrisJigsawStructurePlacement.class) + @Desc("Jigsaw structures") + private KList jigsawStructures = new KList<>(); + @Required + @MinNumber(0) + @MaxNumber(1024) + @Desc("The fluid height for this dimension") + private int fluidHeight = 63; + @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") + private IrisRange dimensionHeight = new IrisRange(-64, 320); + @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") + private IrisRange dimensionHeightEnd = new IrisRange(-64, 320); + @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") + private IrisRange dimensionHeightNether = new IrisRange(-64, 320); + @RegistryListResource(IrisBiome.class) + @Desc("Keep this either undefined or empty. Setting any biome name into this will force iris to only generate the specified biome. Great for testing.") + private String focus = ""; + @RegistryListResource(IrisRegion.class) + @Desc("Keep this either undefined or empty. Setting any region name into this will force iris to only generate the specified region. Great for testing.") + private String focusRegion = ""; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("Zoom in or out the biome size. Higher = bigger biomes") + private double biomeZoom = 5D; + @MinNumber(0) + @MaxNumber(360) + @Desc("You can rotate the input coordinates by an angle. This can make terrain appear more natural (less sharp corners and lines). This literally rotates the entire dimension by an angle. Hint: Try 12 degrees or something not on a 90 or 45 degree angle.") + private double dimensionAngleDeg = 0; + @Required + @Desc("Define the mode of this dimension (required!)") + private IrisDimensionMode mode = new IrisDimensionMode(); + @MinNumber(0) + @MaxNumber(8192) + @Desc("Coordinate fracturing applies noise to the input coordinates. This creates the 'iris swirls' and wavy features. The distance pushes these waves further into places they shouldnt be. This is a block value multiplier.") + private double coordFractureDistance = 20; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("Coordinate fracturing zoom. Higher = less frequent warping, Lower = more frequent and rapid warping / swirls.") + private double coordFractureZoom = 8; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("This zooms in the land space") + private double landZoom = 1; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("This zooms oceanic biomes") + private double seaZoom = 1; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("Zoom in continents") + private double continentZoom = 1; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("Change the size of regions") + private double regionZoom = 1; + @Desc("Disable this to stop placing objects, entities, features & updates") + private boolean useMantle = true; + @Desc("Prevent Leaf decay as if placed in creative mode") + private boolean preventLeafDecay = false; + @ArrayType(min = 1, type = IrisDepositGenerator.class) + @Desc("Define global deposit generators") + private KList deposits = new KList<>(); + @ArrayType(min = 1, type = IrisShapedGeneratorStyle.class) + @Desc("Overlay additional noise on top of the interoplated terrain.") + private KList overlayNoise = new KList<>(); + @Desc("If true, the spawner system has infinite energy. This is NOT recommended because it would allow for mobs to keep spawning over and over without a rate limit") + private boolean infiniteEnergy = false; + @MinNumber(0) + @MaxNumber(10000) + @Desc("This is the maximum energy you can have in a dimension") + private double maximumEnergy = 1000; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("The rock zoom mostly for zooming in on a wispy palette") + private double rockZoom = 5; + @Desc("The palette of blocks for 'stone'") + private IrisMaterialPalette rockPalette = new IrisMaterialPalette().qclear().qadd("stone"); + @Desc("The palette of blocks for 'water'") + private IrisMaterialPalette fluidPalette = new IrisMaterialPalette().qclear().qadd("water"); + @Desc("Remove cartographers so they do not crash the server (Iris worlds only)") + private boolean removeCartographersDueToCrash = true; + @Desc("Notify players of cancelled cartographer villager in this radius in blocks (set to -1 to disable, -2 for everyone)") + private int notifyPlayersOfCartographerCancelledRadius = 30; + @Desc("Collection of ores to be generated") + @ArrayType(type = IrisOreGenerator.class, min = 1) + private KList ores = new KList<>(); + @MinNumber(0) + @MaxNumber(318) + @Desc("The Subterrain Fluid Layer Height") + private int caveLavaHeight = 8; + + public int getMaxHeight() { + return (int) getDimensionHeight().getMax(); + } + + public int getMinHeight() { + return (int) getDimensionHeight().getMin(); + } + + public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data) { + if (ores.isEmpty()) { + return null; + } + BlockData b = null; + for (IrisOreGenerator i : ores) { + + b = i.generate(x, y, z, rng, data); + if (b != null) { + return b; + } + } + return null; + } + + public KList getStrongholds(long seed) { + return strongholdsCache.aquire(() -> { + KList pos = new KList<>(); + int jump = strongholdJumpDistance; + RNG rng = new RNG((seed * 223) + 12945); + for (int i = 0; i < maxStrongholds + 1; i++) { + int m = i + 1; + pos.add(new Position2( + (int) ((rng.i(jump * i) + (jump * i)) * (rng.b() ? -1D : 1D)), + (int) ((rng.i(jump * i) + (jump * i)) * (rng.b() ? -1D : 1D)) + )); + } + + pos.remove(0); + + return pos; + }); + } + + public int getFluidHeight() { + return fluidHeight - (int) dimensionHeight.getMin(); + } + + public CNG getCoordFracture(RNG rng, int signature) { + return coordFracture.aquire(() -> + { + CNG coordFracture = CNG.signature(rng.nextParallelRNG(signature)); + coordFracture.scale(0.012 / coordFractureZoom); + return coordFracture; + }); + } + + public double getDimensionAngle() { + return rad.aquire(() -> Math.toRadians(dimensionAngleDeg)); + } + + public Environment getEnvironment() { + return environment; + } + + public boolean hasFocusRegion() { + return !focusRegion.equals(""); + } + + public String getFocusRegion() { + return focusRegion; + } + + public double sinRotate() { + return sinr.aquire(() -> Math.sin(getDimensionAngle())); + } + + public double cosRotate() { + return cosr.aquire(() -> Math.cos(getDimensionAngle())); + } + + public KList getAllRegions(DataProvider g) { + KList r = new KList<>(); + + for (String i : getRegions()) { + r.add(g.getData().getRegionLoader().load(i)); + } + + return r; + } + + public KList getAllAnyRegions() { + KList r = new KList<>(); + + for (String i : getRegions()) { + r.add(IrisData.loadAnyRegion(i)); + } + + return r; + } + + public KList getAllBiomes(DataProvider g) { + return g.getData().getBiomeLoader().loadAll(g.getData().getBiomeLoader().getPossibleKeys()); + } + + public KList getAllAnyBiomes() { + KList r = new KList<>(); + + for (IrisRegion i : getAllAnyRegions()) { + if (i == null) { + continue; + } + + r.addAll(i.getAllAnyBiomes()); + } + + return r; + } + + public IrisGeneratorStyle getBiomeStyle(InferredType type) { + switch (type) { + case CAVE: + return caveBiomeStyle; + case LAND: + return landBiomeStyle; + case SEA: + return seaBiomeStyle; + case SHORE: + return shoreBiomeStyle; + default: + break; + } + + return landBiomeStyle; + } + + public boolean installDataPack(DataProvider data, File datapacks) { + boolean write = false; + boolean changed = false; + + IO.delete(new File(datapacks, "iris/data/" + getLoadKey().toLowerCase())); + + for (IrisBiome i : getAllBiomes(data)) { + if (i.isCustom()) { + write = true; + + for (IrisBiomeCustom j : i.getCustomDerivitives()) { + File output = new File(datapacks, "iris/data/" + getLoadKey().toLowerCase() + "/worldgen/biome/" + j.getId() + ".json"); + + if (!output.exists()) { + changed = true; + } + + Iris.verbose(" Installing Data Pack Biome: " + output.getPath()); + output.getParentFile().mkdirs(); + try { + IO.writeAll(output, j.generateJson()); + } catch (IOException e) { + Iris.reportError(e); + e.printStackTrace(); + } + } + } + } + + if (!dimensionHeight.equals(new IrisRange(-64, 320)) && this.name.equalsIgnoreCase("overworld")) { + Iris.verbose(" Installing Data Pack Dimension Types: \"minecraft:overworld\", \"minecraft:the_nether\", \"minecraft:the_end\""); + changed = writeDimensionType(changed, datapacks); + } + + if (write) { + File mcm = new File(datapacks, "iris/pack.mcmeta"); + try { + IO.writeAll(mcm, """ + { + "pack": { + "description": "Iris Data Pack. This pack contains all installed Iris Packs' resources.", + "pack_format": 10 + } + } + """); + } catch (IOException e) { + Iris.reportError(e); + e.printStackTrace(); + } + Iris.verbose(" Installing Data Pack MCMeta: " + mcm.getPath()); + } + + return changed; + } + + @Override + public String getFolderName() { + return "dimensions"; + } + + @Override + public String getTypeName() { + return "Dimension"; + } + + @Override + public void scanForErrors(JSONObject p, VolmitSender sender) { + + } + + public boolean writeDimensionType(boolean changed, File datapacks) { + File dimTypeOverworld = new File(datapacks, "iris/data/minecraft/dimension_type/overworld.json"); + if (!dimTypeOverworld.exists()) + changed = true; + dimTypeOverworld.getParentFile().mkdirs(); + try { + IO.writeAll(dimTypeOverworld, generateDatapackJsonOverworld()); + } catch (IOException e) { + Iris.reportError(e); + e.printStackTrace(); + } + + + File dimTypeNether = new File(datapacks, "iris/data/minecraft/dimension_type/the_nether.json"); + if (!dimTypeNether.exists()) + changed = true; + dimTypeNether.getParentFile().mkdirs(); + try { + IO.writeAll(dimTypeNether, generateDatapackJsonNether()); + } catch (IOException e) { + Iris.reportError(e); + e.printStackTrace(); + } + + + File dimTypeEnd = new File(datapacks, "iris/data/minecraft/dimension_type/the_end.json"); + if (!dimTypeEnd.exists()) + changed = true; + dimTypeEnd.getParentFile().mkdirs(); + try { + IO.writeAll(dimTypeEnd, generateDatapackJsonEnd()); + } catch (IOException e) { + Iris.reportError(e); + e.printStackTrace(); + } + + return changed; + } + + private String generateDatapackJsonOverworld() { + JSONObject obj = new JSONObject(DP_OVERWORLD_DEFAULT); + obj.put("min_y", dimensionHeight.getMin()); + obj.put("height", dimensionHeight.getMax() - dimensionHeight.getMin()); + obj.put("logical_height", logicalHeight); + return obj.toString(4); + } + + private String generateDatapackJsonNether() { + JSONObject obj = new JSONObject(DP_NETHER_DEFAULT); + obj.put("min_y", dimensionHeightNether.getMin()); + obj.put("height", dimensionHeightNether.getMax() - dimensionHeightNether.getMin()); + obj.put("logical_height", logicalHeightNether); + return obj.toString(4); + } + + private String generateDatapackJsonEnd() { + JSONObject obj = new JSONObject(DP_END_DEFAULT); + obj.put("min_y", dimensionHeightEnd.getMin()); + obj.put("height", dimensionHeightEnd.getMax() - dimensionHeightEnd.getMin()); + obj.put("logical_height", logicalHeightEnd); + return obj.toString(4); + } +} diff --git a/src/main/java/com/volmit/iris/engine/object/IrisLoot.java b/src/main/java/com/volmit/iris/engine/object/IrisLoot.java index 3b535216b..4e4d48645 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisLoot.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisLoot.java @@ -1,252 +1,252 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine.object; - -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.volmit.iris.Iris; -import com.volmit.iris.core.link.Identifier; -import com.volmit.iris.core.service.ExternalDataSVC; -import com.volmit.iris.engine.data.cache.AtomicCache; -import com.volmit.iris.engine.object.annotations.*; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.data.B; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.json.JSONObject; -import com.volmit.iris.util.math.RNG; -import com.volmit.iris.util.noise.CNG; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.TagParser; -import org.bukkit.DyeColor; -import org.bukkit.Material; -import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.LeatherArmorMeta; -import org.bukkit.material.Colorable; - -import java.awt.*; -import java.util.Optional; - -@Snippet("loot") -@Accessors(chain = true) -@NoArgsConstructor -@AllArgsConstructor -@Desc("Represents a loot entry") -@Data -public class IrisLoot { - private final transient AtomicCache chance = new AtomicCache<>(); - @Desc("The target inventory slot types to fill this loot with") - private InventorySlotType slotTypes = InventorySlotType.STORAGE; - @MinNumber(1) - @Desc("The sub rarity of this loot. Calculated after this loot table has been picked.") - private int rarity = 1; - @MinNumber(1) - @Desc("Minimum amount of this loot") - private int minAmount = 1; - @MinNumber(1) - @Desc("Maximum amount of this loot") - private int maxAmount = 1; - @MinNumber(1) - @Desc("The display name of this item") - private String displayName = null; - @MinNumber(0) - @MaxNumber(1) - @Desc("Minimum durability percent") - private double minDurability = 0; - @MinNumber(0) - @MaxNumber(1) - @Desc("Maximum durability percent") - private double maxDurability = 1; - @Desc("Define a custom model identifier 1.14+ only") - private Integer customModel = null; - @Desc("Set this to true to prevent it from being broken") - private boolean unbreakable = false; - @ArrayType(min = 1, type = ItemFlag.class) - @Desc("The item flags to add") - private KList itemFlags = new KList<>(); - @Desc("Apply enchantments to this item") - @ArrayType(min = 1, type = IrisEnchantment.class) - private KList enchantments = new KList<>(); - @Desc("Apply attribute modifiers to this item") - @ArrayType(min = 1, type = IrisAttributeModifier.class) - private KList attributes = new KList<>(); - @ArrayType(min = 1, type = String.class) - @Desc("Add lore to this item") - private KList lore = new KList<>(); - @RegistryListItemType - @Required - @Desc("This is the item or block type. Does not accept minecraft:*, only materials such as DIAMOND_SWORD or DIRT. The exception are modded materials, as they require a namespace.") - private String type = ""; - @Desc("The dye color") - private DyeColor dyeColor = null; - @Desc("The leather armor color") - private String leatherColor = null; - @Desc("Defines a custom NBT Tag for the item.") - private KMap customNbt; - - public Material getType() { - return B.getMaterial(type); - } - - public ItemStack get(boolean debug, RNG rng) { - try { - ItemStack is = getItemStack(rng); - if (is == null) - return new ItemStack(Material.AIR); - is.setItemMeta(applyProperties(is, rng, debug, null)); - return applyCustomNbt(is); - } catch (Throwable e) { - Iris.reportError(e); - return new ItemStack(Material.AIR); - } - } - - public ItemStack get(boolean debug, boolean giveSomething, IrisLootTable table, RNG rng, int x, int y, int z) { - if (debug) { - chance.reset(); - } - - if (giveSomething || chance.aquire(() -> NoiseStyle.STATIC.create(rng)).fit(1, rarity * table.getRarity(), x, y, z) == 1) { - try { - ItemStack is = getItemStack(rng); - if (is == null) - return null; - is.setItemMeta(applyProperties(is, rng, debug, table)); - return applyCustomNbt(is); - } catch (Throwable e) { - //Iris.reportError(e); - e.printStackTrace(); - } - } - - return null; - } - - // TODO Better Third Party Item Acquisition - private ItemStack getItemStack(RNG rng) { - if (!type.startsWith("minecraft:") && type.contains(":")) { - Optional opt = Iris.service(ExternalDataSVC.class).getItemStack(Identifier.fromString(type)); - if (opt.isEmpty()) { - Iris.warn("Unknown Material: " + type); - return new ItemStack(Material.AIR); - } - ItemStack is = opt.get(); - is.setAmount(Math.max(1, rng.i(getMinAmount(), getMaxAmount()))); - return is; - } - return new ItemStack(getType(), Math.max(1, rng.i(getMinAmount(), getMaxAmount()))); - } - - private ItemMeta applyProperties(ItemStack is, RNG rng, boolean debug, IrisLootTable table) { - ItemMeta m = is.getItemMeta(); - if (m == null) { - return null; - } - - for (IrisEnchantment i : getEnchantments()) { - i.apply(rng, m); - } - - for (IrisAttributeModifier i : getAttributes()) { - i.apply(rng, m); - } - - m.setUnbreakable(isUnbreakable()); - for (ItemFlag i : getItemFlags()) { - m.addItemFlags(i); - } - - if (getCustomModel() != null) { - m.setCustomModelData(getCustomModel()); - } - - if (is.getType().getMaxDurability() > 0 && m instanceof Damageable d) { - int max = is.getType().getMaxDurability(); - d.setDamage((int) Math.round(Math.max(0, Math.min(max, (1D - rng.d(getMinDurability(), getMaxDurability())) * max)))); - } - - if (getLeatherColor() != null && m instanceof LeatherArmorMeta leather) { - Color c = Color.decode(getLeatherColor()); - leather.setColor(org.bukkit.Color.fromRGB(c.getRed(), c.getGreen(), c.getBlue())); - } - - if (getDyeColor() != null && m instanceof Colorable colorable) { - colorable.setColor(getDyeColor()); - } - - if(displayName != null) { - m.setLocalizedName(C.translateAlternateColorCodes('&', displayName)); - m.setDisplayName(C.translateAlternateColorCodes('&', displayName)); - } - - KList lore = new KList<>(); - - getLore().forEach((i) -> - { - String mf = C.translateAlternateColorCodes('&', i); - - if (mf.length() > 24) { - for (String g : Form.wrapWords(mf, 24).split("\\Q\n\\E")) { - lore.add(g.trim()); - } - } else { - lore.add(mf); - } - }); - - if (debug) { - if (table == null) { - if (lore.isNotEmpty()) { - lore.add(C.GRAY + "--------------------"); - } - lore.add(C.GRAY + "1 in " + (getRarity()) + " Chance (" + Form.pc(1D / (getRarity()), 5) + ")"); - } else { - if (lore.isNotEmpty()) { - lore.add(C.GRAY + "--------------------"); - } - - lore.add(C.GRAY + "From: " + table.getName() + " (" + Form.pc(1D / table.getRarity(), 5) + ")"); - lore.add(C.GRAY + "1 in " + (table.getRarity() * getRarity()) + " Chance (" + Form.pc(1D / (table.getRarity() * getRarity()), 5) + ")"); - } - } - - m.setLore(lore); - - return m; - } - - - private ItemStack applyCustomNbt(ItemStack stack) throws CommandSyntaxException { - if (customNbt == null || customNbt.isEmpty()) - return stack; - net.minecraft.world.item.ItemStack s = CraftItemStack.asNMSCopy(stack); - CompoundTag tag = TagParser.parseTag(new JSONObject(customNbt).toString()); - tag.merge(s.getOrCreateTag()); - s.setTag(tag); - return CraftItemStack.asBukkitCopy(s); - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.engine.object; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.volmit.iris.Iris; +import com.volmit.iris.core.link.Identifier; +import com.volmit.iris.core.service.ExternalDataSVC; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.object.annotations.*; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.data.B; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.noise.CNG; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.TagParser; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.LeatherArmorMeta; +import org.bukkit.material.Colorable; + +import java.awt.*; +import java.util.Optional; + +@Snippet("loot") +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Desc("Represents a loot entry") +@Data +public class IrisLoot { + private final transient AtomicCache chance = new AtomicCache<>(); + @Desc("The target inventory slot types to fill this loot with") + private InventorySlotType slotTypes = InventorySlotType.STORAGE; + @MinNumber(1) + @Desc("The sub rarity of this loot. Calculated after this loot table has been picked.") + private int rarity = 1; + @MinNumber(1) + @Desc("Minimum amount of this loot") + private int minAmount = 1; + @MinNumber(1) + @Desc("Maximum amount of this loot") + private int maxAmount = 1; + @MinNumber(1) + @Desc("The display name of this item") + private String displayName = null; + @MinNumber(0) + @MaxNumber(1) + @Desc("Minimum durability percent") + private double minDurability = 0; + @MinNumber(0) + @MaxNumber(1) + @Desc("Maximum durability percent") + private double maxDurability = 1; + @Desc("Define a custom model identifier 1.14+ only") + private Integer customModel = null; + @Desc("Set this to true to prevent it from being broken") + private boolean unbreakable = false; + @ArrayType(min = 1, type = ItemFlag.class) + @Desc("The item flags to add") + private KList itemFlags = new KList<>(); + @Desc("Apply enchantments to this item") + @ArrayType(min = 1, type = IrisEnchantment.class) + private KList enchantments = new KList<>(); + @Desc("Apply attribute modifiers to this item") + @ArrayType(min = 1, type = IrisAttributeModifier.class) + private KList attributes = new KList<>(); + @ArrayType(min = 1, type = String.class) + @Desc("Add lore to this item") + private KList lore = new KList<>(); + @RegistryListItemType + @Required + @Desc("This is the item or block type. Does not accept minecraft:*, only materials such as DIAMOND_SWORD or DIRT. The exception are modded materials, as they require a namespace.") + private String type = ""; + @Desc("The dye color") + private DyeColor dyeColor = null; + @Desc("The leather armor color") + private String leatherColor = null; + @Desc("Defines a custom NBT Tag for the item.") + private KMap customNbt; + + public Material getType() { + return B.getMaterial(type); + } + + public ItemStack get(boolean debug, RNG rng) { + try { + ItemStack is = getItemStack(rng); + if (is == null) + return new ItemStack(Material.AIR); + is.setItemMeta(applyProperties(is, rng, debug, null)); + return applyCustomNbt(is); + } catch (Throwable e) { + Iris.reportError(e); + return new ItemStack(Material.AIR); + } + } + + public ItemStack get(boolean debug, boolean giveSomething, IrisLootTable table, RNG rng, int x, int y, int z) { + if (debug) { + chance.reset(); + } + + if (giveSomething || chance.aquire(() -> NoiseStyle.STATIC.create(rng)).fit(1, rarity * table.getRarity(), x, y, z) == 1) { + try { + ItemStack is = getItemStack(rng); + if (is == null) + return null; + is.setItemMeta(applyProperties(is, rng, debug, table)); + return applyCustomNbt(is); + } catch (Throwable e) { + //Iris.reportError(e); + e.printStackTrace(); + } + } + + return null; + } + + // TODO Better Third Party Item Acquisition + private ItemStack getItemStack(RNG rng) { + if (!type.startsWith("minecraft:") && type.contains(":")) { + Optional opt = Iris.service(ExternalDataSVC.class).getItemStack(Identifier.fromString(type)); + if (opt.isEmpty()) { + Iris.warn("Unknown Material: " + type); + return new ItemStack(Material.AIR); + } + ItemStack is = opt.get(); + is.setAmount(Math.max(1, rng.i(getMinAmount(), getMaxAmount()))); + return is; + } + return new ItemStack(getType(), Math.max(1, rng.i(getMinAmount(), getMaxAmount()))); + } + + private ItemMeta applyProperties(ItemStack is, RNG rng, boolean debug, IrisLootTable table) { + ItemMeta m = is.getItemMeta(); + if (m == null) { + return null; + } + + for (IrisEnchantment i : getEnchantments()) { + i.apply(rng, m); + } + + for (IrisAttributeModifier i : getAttributes()) { + i.apply(rng, m); + } + + m.setUnbreakable(isUnbreakable()); + for (ItemFlag i : getItemFlags()) { + m.addItemFlags(i); + } + + if (getCustomModel() != null) { + m.setCustomModelData(getCustomModel()); + } + + if (is.getType().getMaxDurability() > 0 && m instanceof Damageable d) { + int max = is.getType().getMaxDurability(); + d.setDamage((int) Math.round(Math.max(0, Math.min(max, (1D - rng.d(getMinDurability(), getMaxDurability())) * max)))); + } + + if (getLeatherColor() != null && m instanceof LeatherArmorMeta leather) { + Color c = Color.decode(getLeatherColor()); + leather.setColor(org.bukkit.Color.fromRGB(c.getRed(), c.getGreen(), c.getBlue())); + } + + if (getDyeColor() != null && m instanceof Colorable colorable) { + colorable.setColor(getDyeColor()); + } + + if (displayName != null) { + m.setLocalizedName(C.translateAlternateColorCodes('&', displayName)); + m.setDisplayName(C.translateAlternateColorCodes('&', displayName)); + } + + KList lore = new KList<>(); + + getLore().forEach((i) -> + { + String mf = C.translateAlternateColorCodes('&', i); + + if (mf.length() > 24) { + for (String g : Form.wrapWords(mf, 24).split("\\Q\n\\E")) { + lore.add(g.trim()); + } + } else { + lore.add(mf); + } + }); + + if (debug) { + if (table == null) { + if (lore.isNotEmpty()) { + lore.add(C.GRAY + "--------------------"); + } + lore.add(C.GRAY + "1 in " + (getRarity()) + " Chance (" + Form.pc(1D / (getRarity()), 5) + ")"); + } else { + if (lore.isNotEmpty()) { + lore.add(C.GRAY + "--------------------"); + } + + lore.add(C.GRAY + "From: " + table.getName() + " (" + Form.pc(1D / table.getRarity(), 5) + ")"); + lore.add(C.GRAY + "1 in " + (table.getRarity() * getRarity()) + " Chance (" + Form.pc(1D / (table.getRarity() * getRarity()), 5) + ")"); + } + } + + m.setLore(lore); + + return m; + } + + + private ItemStack applyCustomNbt(ItemStack stack) throws CommandSyntaxException { + if (customNbt == null || customNbt.isEmpty()) + return stack; + net.minecraft.world.item.ItemStack s = CraftItemStack.asNMSCopy(stack); + CompoundTag tag = TagParser.parseTag(new JSONObject(customNbt).toString()); + tag.merge(s.getOrCreateTag()); + s.setTag(tag); + return CraftItemStack.asBukkitCopy(s); + } +} diff --git a/src/main/java/com/volmit/iris/engine/object/IrisObject.java b/src/main/java/com/volmit/iris/engine/object/IrisObject.java index 1dedf72b8..651a033ce 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisObject.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisObject.java @@ -1,1223 +1,1226 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine.object; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; -import com.volmit.iris.core.loader.IrisRegistrant; -import com.volmit.iris.engine.data.cache.AtomicCache; -import com.volmit.iris.engine.framework.placer.HeightmapObjectPlacer; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.context.IrisContext; -import com.volmit.iris.util.data.B; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.interpolation.IrisInterpolation; -import com.volmit.iris.util.json.JSONObject; -import com.volmit.iris.util.math.*; -import com.volmit.iris.util.matter.MatterMarker; -import com.volmit.iris.util.parallel.BurstExecutor; -import com.volmit.iris.util.parallel.MultiBurst; -import com.volmit.iris.util.plugin.VolmitSender; -import com.volmit.iris.util.scheduling.IrisLock; -import com.volmit.iris.util.scheduling.PrecisionStopwatch; -import com.volmit.iris.util.stream.ProceduralStream; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.BlockState; -import org.bukkit.block.TileState; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.MultipleFacing; -import org.bukkit.block.data.Waterlogged; -import org.bukkit.block.data.type.Leaves; -import org.bukkit.util.BlockVector; -import org.bukkit.util.Vector; - -import java.io.*; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiConsumer; - -@Accessors(chain = true) -@EqualsAndHashCode(callSuper = false) -public class IrisObject extends IrisRegistrant { - protected static final Vector HALF = new Vector(0.5, 0.5, 0.5); - protected static final BlockData AIR = B.get("CAVE_AIR"); - protected static final BlockData VAIR = B.get("VOID_AIR"); - protected static final BlockData VAIR_DEBUG = B.get("COBWEB"); - protected static final BlockData[] SNOW_LAYERS = new BlockData[]{B.get("minecraft:snow[layers=1]"), B.get("minecraft:snow[layers=2]"), B.get("minecraft:snow[layers=3]"), B.get("minecraft:snow[layers=4]"), B.get("minecraft:snow[layers=5]"), B.get("minecraft:snow[layers=6]"), B.get("minecraft:snow[layers=7]"), B.get("minecraft:snow[layers=8]")}; - protected transient final IrisLock readLock = new IrisLock("read-conclock"); - @Getter - @Setter - protected transient volatile boolean smartBored = false; - @Getter - @Setter - protected transient IrisLock lock = new IrisLock("Preloadcache"); - @Setter - protected transient AtomicCache aabb = new AtomicCache<>(); - private KMap blocks; - private KMap> states; - @Getter - @Setter - private int w; - @Getter - @Setter - private int d; - @Getter - @Setter - private int h; - @Getter - @Setter - private transient BlockVector center; - - public IrisObject(int w, int h, int d) { - blocks = new KMap<>(); - states = new KMap<>(); - this.w = w; - this.h = h; - this.d = d; - center = new BlockVector(w / 2, h / 2, d / 2); - } - - public IrisObject() { - this(0, 0, 0); - } - - public static BlockVector getCenterForSize(BlockVector size) { - return new BlockVector(size.getX() / 2, size.getY() / 2, size.getZ() / 2); - } - - public static AxisAlignedBB getAABBFor(BlockVector size) { - BlockVector center = new BlockVector(size.getX() / 2, size.getY() / 2, size.getZ() / 2); - return new AxisAlignedBB(new IrisPosition(new BlockVector(0, 0, 0).subtract(center).toBlockVector()), - new IrisPosition(new BlockVector(size.getX() - 1, size.getY() - 1, size.getZ() - 1).subtract(center).toBlockVector())); - } - - @SuppressWarnings({"resource", "RedundantSuppression"}) - public static BlockVector sampleSize(File file) throws IOException { - FileInputStream in = new FileInputStream(file); - DataInputStream din = new DataInputStream(in); - BlockVector bv = new BlockVector(din.readInt(), din.readInt(), din.readInt()); - Iris.later(din::close); - return bv; - } - - private static List blocksBetweenTwoPoints(Vector loc1, Vector loc2) { - List locations = new ArrayList<>(); - int topBlockX = Math.max(loc1.getBlockX(), loc2.getBlockX()); - int bottomBlockX = Math.min(loc1.getBlockX(), loc2.getBlockX()); - int topBlockY = Math.max(loc1.getBlockY(), loc2.getBlockY()); - int bottomBlockY = Math.min(loc1.getBlockY(), loc2.getBlockY()); - int topBlockZ = Math.max(loc1.getBlockZ(), loc2.getBlockZ()); - int bottomBlockZ = Math.min(loc1.getBlockZ(), loc2.getBlockZ()); - - for (int x = bottomBlockX; x <= topBlockX; x++) { - for (int z = bottomBlockZ; z <= topBlockZ; z++) { - for (int y = bottomBlockY; y <= topBlockY; y++) { - locations.add(new BlockVector(x, y, z)); - } - } - } - return locations; - } - - public AxisAlignedBB getAABB() { - return aabb.aquire(() -> getAABBFor(new BlockVector(w, h, d))); - } - - public void ensureSmartBored(boolean debug) { - if (smartBored) { - return; - } - - PrecisionStopwatch p = PrecisionStopwatch.start(); - BlockData vair = debug ? VAIR_DEBUG : VAIR; - lock.lock(); - AtomicInteger applied = new AtomicInteger(); - if (getBlocks().isEmpty()) { - lock.unlock(); - Iris.warn("Cannot Smart Bore " + getLoadKey() + " because it has 0 blocks in it."); - smartBored = true; - return; - } - - BlockVector max = new BlockVector(Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE); - BlockVector min = new BlockVector(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE); - - for (BlockVector i : getBlocks().keySet()) { - max.setX(Math.max(i.getX(), max.getX())); - min.setX(Math.min(i.getX(), min.getX())); - max.setY(Math.max(i.getY(), max.getY())); - min.setY(Math.min(i.getY(), min.getY())); - max.setZ(Math.max(i.getZ(), max.getZ())); - min.setZ(Math.min(i.getZ(), min.getZ())); - } - - BurstExecutor burst = MultiBurst.burst.burst(); - - // Smash X - for (int rayY = min.getBlockY(); rayY <= max.getBlockY(); rayY++) { - int finalRayY = rayY; - burst.queue(() -> { - for (int rayZ = min.getBlockZ(); rayZ <= max.getBlockZ(); rayZ++) { - int start = Integer.MAX_VALUE; - int end = Integer.MIN_VALUE; - - for (int ray = min.getBlockX(); ray <= max.getBlockX(); ray++) { - if (getBlocks().containsKey(new BlockVector(ray, finalRayY, rayZ))) { - start = Math.min(ray, start); - end = Math.max(ray, end); - } - } - - if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) { - for (int i = start; i <= end; i++) { - BlockVector v = new BlockVector(i, finalRayY, rayZ); - - if (!B.isAir(getBlocks().get(v))) { - getBlocks().computeIfAbsent(v, (vv) -> vair); - applied.getAndIncrement(); - } - } - } - } - }); - } - - // Smash Y - for (int rayX = min.getBlockX(); rayX <= max.getBlockX(); rayX++) { - int finalRayX = rayX; - burst.queue(() -> { - for (int rayZ = min.getBlockZ(); rayZ <= max.getBlockZ(); rayZ++) { - int start = Integer.MAX_VALUE; - int end = Integer.MIN_VALUE; - - for (int ray = min.getBlockY(); ray <= max.getBlockY(); ray++) { - if (getBlocks().containsKey(new BlockVector(finalRayX, ray, rayZ))) { - start = Math.min(ray, start); - end = Math.max(ray, end); - } - } - - if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) { - for (int i = start; i <= end; i++) { - BlockVector v = new BlockVector(finalRayX, i, rayZ); - - if (!B.isAir(getBlocks().get(v))) { - getBlocks().computeIfAbsent(v, (vv) -> vair); - applied.getAndIncrement(); - } - } - } - } - }); - } - - // Smash Z - for (int rayX = min.getBlockX(); rayX <= max.getBlockX(); rayX++) { - int finalRayX = rayX; - burst.queue(() -> { - for (int rayY = min.getBlockY(); rayY <= max.getBlockY(); rayY++) { - int start = Integer.MAX_VALUE; - int end = Integer.MIN_VALUE; - - for (int ray = min.getBlockZ(); ray <= max.getBlockZ(); ray++) { - if (getBlocks().containsKey(new BlockVector(finalRayX, rayY, ray))) { - start = Math.min(ray, start); - end = Math.max(ray, end); - } - } - - if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) { - for (int i = start; i <= end; i++) { - BlockVector v = new BlockVector(finalRayX, rayY, i); - - if (!B.isAir(getBlocks().get(v))) { - getBlocks().computeIfAbsent(v, (vv) -> vair); - applied.getAndIncrement(); - } - } - } - } - }); - } - - burst.complete(); - smartBored = true; - lock.unlock(); - Iris.debug("Smart Bore: " + getLoadKey() + " in " + Form.duration(p.getMilliseconds(), 2) + " (" + Form.f(applied.get()) + ")"); - } - - public synchronized IrisObject copy() { - IrisObject o = new IrisObject(w, h, d); - o.setLoadKey(o.getLoadKey()); - o.setCenter(getCenter().clone()); - - for (BlockVector i : getBlocks().keySet()) { - o.getBlocks().put(i.clone(), Objects.requireNonNull(getBlocks().get(i)).clone()); - } - - for (BlockVector i : getStates().keySet()) { - o.getStates().put(i.clone(), Objects.requireNonNull(getStates().get(i)).clone()); - } - - return o; - } - - public void readLegacy(InputStream in) throws IOException { - DataInputStream din = new DataInputStream(in); - this.w = din.readInt(); - this.h = din.readInt(); - this.d = din.readInt(); - center = new BlockVector(w / 2, h / 2, d / 2); - int s = din.readInt(); - - for (int i = 0; i < s; i++) { - getBlocks().put(new BlockVector(din.readShort(), din.readShort(), din.readShort()), B.get(din.readUTF())); - } - - try { - int size = din.readInt(); - - for (int i = 0; i < size; i++) { - getStates().put(new BlockVector(din.readShort(), din.readShort(), din.readShort()), TileData.read(din)); - } - } catch (Throwable e) { - Iris.reportError(e); - - } - } - - public void read(InputStream in) throws Throwable { - DataInputStream din = new DataInputStream(in); - this.w = din.readInt(); - this.h = din.readInt(); - this.d = din.readInt(); - if (!din.readUTF().equals("Iris V2 IOB;")) { - return; - } - center = new BlockVector(w / 2, h / 2, d / 2); - int s = din.readShort(); - int i; - KList palette = new KList<>(); - - for (i = 0; i < s; i++) { - palette.add(din.readUTF()); - } - - s = din.readInt(); - - for (i = 0; i < s; i++) { - getBlocks().put(new BlockVector(din.readShort(), din.readShort(), din.readShort()), B.get(palette.get(din.readShort()))); - } - - s = din.readInt(); - - for (i = 0; i < s; i++) { - getStates().put(new BlockVector(din.readShort(), din.readShort(), din.readShort()), TileData.read(din)); - } - } - - public void write(OutputStream o) throws IOException { - DataOutputStream dos = new DataOutputStream(o); - dos.writeInt(w); - dos.writeInt(h); - dos.writeInt(d); - dos.writeUTF("Iris V2 IOB;"); - KList palette = new KList<>(); - - for (BlockData i : getBlocks().values()) { - palette.addIfMissing(i.getAsString()); - } - - dos.writeShort(palette.size()); - - for (String i : palette) { - dos.writeUTF(i); - } - - dos.writeInt(getBlocks().size()); - - for (BlockVector i : getBlocks().keySet()) { - dos.writeShort(i.getBlockX()); - dos.writeShort(i.getBlockY()); - dos.writeShort(i.getBlockZ()); - dos.writeShort(palette.indexOf(getBlocks().get(i).getAsString())); - } - - dos.writeInt(getStates().size()); - for (BlockVector i : getStates().keySet()) { - dos.writeShort(i.getBlockX()); - dos.writeShort(i.getBlockY()); - dos.writeShort(i.getBlockZ()); - getStates().get(i).toBinary(dos); - } - } - - public void read(File file) throws IOException { - FileInputStream fin = new FileInputStream(file); - try { - read(fin); - fin.close(); - } catch (Throwable e) { - Iris.reportError(e); - fin.close(); - fin = new FileInputStream(file); - readLegacy(fin); - fin.close(); - } - } - - public void write(File file) throws IOException { - if (file == null) { - return; - } - - FileOutputStream out = new FileOutputStream(file); - write(out); - out.close(); - } - - public void shrinkwrap() { - BlockVector min = new BlockVector(); - BlockVector max = new BlockVector(); - - for (BlockVector i : getBlocks().keySet()) { - min.setX(Math.min(min.getX(), i.getX())); - min.setY(Math.min(min.getY(), i.getY())); - min.setZ(Math.min(min.getZ(), i.getZ())); - max.setX(Math.max(max.getX(), i.getX())); - max.setY(Math.max(max.getY(), i.getY())); - max.setZ(Math.max(max.getZ(), i.getZ())); - } - - w = max.getBlockX() - min.getBlockX(); - h = max.getBlockY() - min.getBlockY(); - d = max.getBlockZ() - min.getBlockZ(); - } - - public void clean() { - KMap d = new KMap<>(); - - for (BlockVector i : getBlocks().keySet()) { - d.put(new BlockVector(i.getBlockX(), i.getBlockY(), i.getBlockZ()), Objects.requireNonNull(getBlocks().get(i))); - } - - KMap> dx = new KMap<>(); - - for (BlockVector i : getBlocks().keySet()) { - d.put(new BlockVector(i.getBlockX(), i.getBlockY(), i.getBlockZ()), Objects.requireNonNull(getBlocks().get(i))); - } - - for (BlockVector i : getStates().keySet()) { - dx.put(new BlockVector(i.getBlockX(), i.getBlockY(), i.getBlockZ()), Objects.requireNonNull(getStates().get(i))); - } - - blocks = d; - states = dx; - } - - public BlockVector getSigned(int x, int y, int z) { - if (x >= w || y >= h || z >= d) { - throw new RuntimeException(x + " " + y + " " + z + " exceeds limit of " + w + " " + h + " " + d); - } - - return new BlockVector(x, y, z).subtract(center).toBlockVector(); - } - - public void setUnsigned(int x, int y, int z, BlockData block) { - BlockVector v = getSigned(x, y, z); - - if (block == null) { - getBlocks().remove(v); - getStates().remove(v); - } else { - getBlocks().put(v, block); - } - } - - public void setUnsigned(int x, int y, int z, Block block) { - BlockVector v = getSigned(x, y, z); - - if (block == null) { - getBlocks().remove(v); - getStates().remove(v); - } else { - BlockData data = block.getBlockData(); - getBlocks().put(v, data); - TileData state = TileData.getTileState(block); - if (state != null) { - Iris.info("Saved State " + v); - getStates().put(v, state); - } - } - } - - public int place(int x, int z, IObjectPlacer placer, IrisObjectPlacement config, RNG rng, IrisData rdata) { - return place(x, -1, z, placer, config, rng, rdata); - } - - public int place(int x, int z, IObjectPlacer placer, IrisObjectPlacement config, RNG rng, CarveResult c, IrisData rdata) { - return place(x, -1, z, placer, config, rng, null, c, rdata); - } - - public int place(int x, int yv, int z, IObjectPlacer placer, IrisObjectPlacement config, RNG rng, IrisData rdata) { - return place(x, yv, z, placer, config, rng, null, null, rdata); - } - - public int place(Location loc, IObjectPlacer placer, IrisObjectPlacement config, RNG rng, IrisData rdata) { - return place(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), placer, config, rng, rdata); - } - - public int place(int x, int yv, int z, IObjectPlacer oplacer, IrisObjectPlacement config, RNG rng, BiConsumer listener, CarveResult c, IrisData rdata) { - IObjectPlacer placer = (config.getHeightmap() != null) ? new HeightmapObjectPlacer(oplacer.getEngine() == null ? IrisContext.get().getEngine() : oplacer.getEngine(), rng, x, yv, z, config, oplacer) : oplacer; - - if (rdata != null) { - // Slope condition - if (!config.getSlopeCondition().isDefault() && - !config.getSlopeCondition().isValid(rdata.getEngine().getComplex().getSlopeStream().get(x, z))) { - return -1; - } - - // Rotation calculation - int slopeRotationY = 0; - ProceduralStream heightStream = rdata.getEngine().getComplex().getHeightStream(); - if (config.isRotateTowardsSlope()) { - // Whichever side of the rectangle that bounds the object is lowest is the 'direction' of the slope (simply said). - double hNorth = heightStream.get(x, z + ((float) d) / 2); - double hEast = heightStream.get(x + ((float) w) / 2, z); - double hSouth = heightStream.get(x, z - ((float) d) / 2); - double hWest = heightStream.get(x - ((float) w) / 2, z); - double min = Math.min(Math.min(hNorth, hEast), Math.min(hSouth, hWest)); - if (min == hNorth) { - slopeRotationY = 0; - } else if (min == hEast) { - slopeRotationY = 90; - } else if (min == hSouth) { - slopeRotationY = 180; - } else if (min == hWest) { - slopeRotationY = 270; - } - } - double newRotation = config.getRotation().getYAxis().getMin() + slopeRotationY; - config.getRotation().setYAxis(new IrisAxisRotationClamp(true, false, newRotation, newRotation, 360)); - config.getRotation().setEnabled(true); - } - - if (config.isSmartBore()) { - ensureSmartBored(placer.isDebugSmartBore()); - } - - boolean warped = !config.getWarp().isFlat(); - boolean stilting = (config.getMode().equals(ObjectPlaceMode.STILT) || config.getMode().equals(ObjectPlaceMode.FAST_STILT) || - config.getMode() == ObjectPlaceMode.MIN_STILT || config.getMode() == ObjectPlaceMode.FAST_MIN_STILT || - config.getMode() == ObjectPlaceMode.CENTER_STILT); - KMap heightmap = config.getSnow() > 0 ? new KMap<>() : null; - int spinx = rng.imax() / 1000; - int spiny = rng.imax() / 1000; - int spinz = rng.imax() / 1000; - int rty = config.getRotation().rotate(new BlockVector(0, getCenter().getBlockY(), 0), spinx, spiny, spinz).getBlockY(); - int ty = config.getTranslate().translate(new BlockVector(0, getCenter().getBlockY(), 0), config.getRotation(), spinx, spiny, spinz).getBlockY(); - int y = -1; - int xx, zz; - int yrand = config.getTranslate().getYRandom(); - yrand = yrand > 0 ? rng.i(0, yrand) : yrand < 0 ? rng.i(yrand, 0) : yrand; - boolean bail = false; - - if (yv < 0) { - if (config.getMode().equals(ObjectPlaceMode.CENTER_HEIGHT) || config.getMode() == ObjectPlaceMode.CENTER_STILT) { - y = (c != null ? c.getSurface() : placer.getHighest(x, z, getLoader(), config.isUnderwater())) + rty; - if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) { - bail = true; - } - } else if (config.getMode().equals(ObjectPlaceMode.MAX_HEIGHT) || config.getMode().equals(ObjectPlaceMode.STILT)) { - BlockVector offset = new BlockVector(config.getTranslate().getX(), config.getTranslate().getY(), config.getTranslate().getZ()); - BlockVector rotatedDimensions = config.getRotation().rotate(new BlockVector(getW(), getH(), getD()), spinx, spiny, spinz).clone(); - int xLength = (rotatedDimensions.getBlockX() / 2) + offset.getBlockX(); - int minX = Math.min(x - xLength, x + xLength); - int maxX = Math.max(x - xLength, x + xLength); - int zLength = (rotatedDimensions.getBlockZ() / 2) + offset.getBlockZ(); - int minZ = Math.min(z - zLength, z + zLength); - int maxZ = Math.max(z - zLength, z + zLength); - for (int i = minX; i <= maxX; i++) { - for (int ii = minZ; ii <= maxZ; ii++) { - int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty; - if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { - bail = true; - break; - } - if (h > y) - y = h; - } - } - } else if (config.getMode().equals(ObjectPlaceMode.FAST_MAX_HEIGHT) || config.getMode().equals(ObjectPlaceMode.FAST_STILT)) { - BlockVector offset = new BlockVector(config.getTranslate().getX(), config.getTranslate().getY(), config.getTranslate().getZ()); - BlockVector rotatedDimensions = config.getRotation().rotate(new BlockVector(getW(), getH(), getD()), spinx, spiny, spinz).clone(); - - int xRadius = (rotatedDimensions.getBlockX() / 2); - int xLength = xRadius + offset.getBlockX(); - int minX = Math.min(x - xLength, x + xLength); - int maxX = Math.max(x - xLength, x + xLength); - int zRadius = (rotatedDimensions.getBlockZ() / 2); - int zLength = zRadius + offset.getBlockZ(); - int minZ = Math.min(z - zLength, z + zLength); - int maxZ = Math.max(z - zLength, z + zLength); - - for (int i = minX; i <= maxX; i += Math.abs(xRadius) + 1) { - for (int ii = minZ; ii <= maxZ; ii += Math.abs(zRadius) + 1) { - int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty; - if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { - bail = true; - break; - } - if (h > y) - y = h; - } - } - } else if (config.getMode().equals(ObjectPlaceMode.MIN_HEIGHT) || config.getMode() == ObjectPlaceMode.MIN_STILT) { - y = rdata.getEngine().getHeight() + 1; - BlockVector offset = new BlockVector(config.getTranslate().getX(), config.getTranslate().getY(), config.getTranslate().getZ()); - BlockVector rotatedDimensions = config.getRotation().rotate(new BlockVector(getW(), getH(), getD()), spinx, spiny, spinz).clone(); - - int xLength = (rotatedDimensions.getBlockX() / 2) + offset.getBlockX(); - int minX = Math.min(x - xLength, x + xLength); - int maxX = Math.max(x - xLength, x + xLength); - int zLength = (rotatedDimensions.getBlockZ() / 2) + offset.getBlockZ(); - int minZ = Math.min(z - zLength, z + zLength); - int maxZ = Math.max(z - zLength, z + zLength); - for (int i = minX; i <= maxX; i++) { - for (int ii = minZ; ii <= maxZ; ii++) { - int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty; - if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { - bail = true; - break; - } - if (h < y) { - y = h; - } - } - } - } else if (config.getMode().equals(ObjectPlaceMode.FAST_MIN_HEIGHT) || config.getMode() == ObjectPlaceMode.FAST_MIN_STILT) { - y = rdata.getEngine().getHeight() + 1; - BlockVector offset = new BlockVector(config.getTranslate().getX(), config.getTranslate().getY(), config.getTranslate().getZ()); - BlockVector rotatedDimensions = config.getRotation().rotate(new BlockVector(getW(), getH(), getD()), spinx, spiny, spinz).clone(); - - int xRadius = (rotatedDimensions.getBlockX() / 2); - int xLength = xRadius + offset.getBlockX(); - int minX = Math.min(x - xLength, x + xLength); - int maxX = Math.max(x - xLength, x + xLength); - int zRadius = (rotatedDimensions.getBlockZ() / 2); - int zLength = zRadius + offset.getBlockZ(); - int minZ = Math.min(z - zLength, z + zLength); - int maxZ = Math.max(z - zLength, z + zLength); - - for (int i = minX; i <= maxX; i += Math.abs(xRadius) + 1) { - for (int ii = minZ; ii <= maxZ; ii += Math.abs(zRadius) + 1) { - int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty; - if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { - bail = true; - break; - } - if (h < y) { - y = h; - } - } - } - } else if (config.getMode().equals(ObjectPlaceMode.PAINT)) { - y = placer.getHighest(x, z, getLoader(), config.isUnderwater()) + rty; - if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) { - bail = true; - } - } - } else { - y = yv; - if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) { - bail = true; - } - } - - if (yv >= 0 && config.isBottom()) { - y += Math.floorDiv(h, 2); - bail = placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z); - } - - if (bail) { - return -1; - } - - if (yv < 0) { - if (!config.isUnderwater() && !config.isOnwater() && placer.isUnderwater(x, z)) { - return -1; - } - } - - if (c != null && Math.max(0, h + yrand + ty) + 1 >= c.getHeight()) { - return -1; - } - - if (config.isUnderwater() && y + rty + ty >= placer.getFluidHeight()) { - return -1; - } - - if (!config.getClamp().canPlace(y + rty + ty, y - rty + ty)) { - return -1; - } - - if (config.isBore()) { - BlockVector offset = new BlockVector(config.getTranslate().getX(), config.getTranslate().getY(), config.getTranslate().getZ()); - for (int i = x - Math.floorDiv(w, 2) + (int) offset.getX(); i <= x + Math.floorDiv(w, 2) - (w % 2 == 0 ? 1 : 0) + (int) offset.getX(); i++) { - for (int j = y - Math.floorDiv(h, 2) - config.getBoreExtendMinY() + (int) offset.getY(); j <= y + Math.floorDiv(h, 2) + config.getBoreExtendMaxY() - (h % 2 == 0 ? 1 : 0) + (int) offset.getY(); j++) { - for (int k = z - Math.floorDiv(d, 2) + (int) offset.getZ(); k <= z + Math.floorDiv(d, 2) - (d % 2 == 0 ? 1 : 0) + (int) offset.getX(); k++) { - placer.set(i, j, k, AIR); - } - } - } - } - - int lowest = Integer.MAX_VALUE; - y += yrand; - readLock.lock(); - - KMap markers = null; - - try { - if (config.getMarkers().isNotEmpty() && placer.getEngine() != null) { - markers = new KMap<>(); - for (IrisObjectMarker j : config.getMarkers()) { - IrisMarker marker = getLoader().getMarkerLoader().load(j.getMarker()); - - if (marker == null) { - continue; - } - - int max = j.getMaximumMarkers(); - - for (BlockVector i : getBlocks().k().shuffle()) { - if (max <= 0) { - break; - } - - BlockData data = getBlocks().get(i); - - for (BlockData k : j.getMark(rdata)) { - if (max <= 0) { - break; - } - - if (j.isExact() ? k.matches(data) : k.getMaterial().equals(data.getMaterial())) { - boolean a = !blocks.containsKey(new BlockVector(i.clone().add(new BlockVector(0, 1, 0)))); - boolean fff = !blocks.containsKey(new BlockVector(i.clone().add(new BlockVector(0, 2, 0)))); - - if (!marker.isEmptyAbove() || (a && fff)) { - markers.put(i, j.getMarker()); - max--; - } - } - } - } - } - } - - for (BlockVector g : getBlocks().keySet()) { - BlockData d; - TileData tile = null; - - try { - d = getBlocks().get(g); - tile = getStates().get(g); - } catch (Throwable e) { - Iris.reportError(e); - Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (cme)"); - d = AIR; - } - - if (d == null) { - Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (null)"); - d = AIR; - } - - BlockVector i = g.clone(); - BlockData data = d.clone(); - i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).clone(); - i = config.getTranslate().translate(i.clone(), config.getRotation(), spinx, spiny, spinz).clone(); - - if (stilting && i.getBlockY() < lowest && !B.isAir(data)) { - lowest = i.getBlockY(); - } - - if (placer.isPreventingDecay() && (data) instanceof Leaves && !((Leaves) (data)).isPersistent()) { - ((Leaves) data).setPersistent(true); - } - - for (IrisObjectReplace j : config.getEdit()) { - if (rng.chance(j.getChance())) { - for (BlockData k : j.getFind(rdata)) { - if (j.isExact() ? k.matches(data) : k.getMaterial().equals(data.getMaterial())) { - BlockData newData = j.getReplace(rng, i.getX() + x, i.getY() + y, i.getZ() + z, rdata).clone(); - - if (newData.getMaterial() == data.getMaterial()) - data = data.merge(newData); - else - data = newData; - - if (newData.getMaterial() == Material.SPAWNER) { - Optional> t = j.getReplace().getTile(rng, x, y, z, rdata); - if (t.isPresent()) { - tile = t.get(); - } - } - } - } - } - } - - data = config.getRotation().rotate(data, spinx, spiny, spinz); - xx = x + (int) Math.round(i.getX()); - - int yy = y + (int) Math.round(i.getY()); - zz = z + (int) Math.round(i.getZ()); - - if (warped) { - xx += config.warp(rng, i.getX() + x, i.getY() + y, i.getZ() + z, getLoader()); - zz += config.warp(rng, i.getZ() + z, i.getY() + y, i.getX() + x, getLoader()); - } - - if (yv < 0 && (config.getMode().equals(ObjectPlaceMode.PAINT)) && !B.isVineBlock(data)) { - yy = (int) Math.round(i.getY()) + Math.floorDiv(h, 2) + placer.getHighest(xx, zz, getLoader(), config.isUnderwater()); - } - - if (heightmap != null) { - Position2 pos = new Position2(xx, zz); - - if (!heightmap.containsKey(pos)) { - heightmap.put(pos, yy); - } - - if (heightmap.get(pos) < yy) { - heightmap.put(pos, yy); - } - } - - if (config.isMeld() && !placer.isSolid(xx, yy, zz)) { - continue; - } - - if ((config.isWaterloggable() || config.isUnderwater()) && yy <= placer.getFluidHeight() && data instanceof Waterlogged) { - // TODO Here - ((Waterlogged) data).setWaterlogged(true); - } - - if (B.isVineBlock(data)) { - MultipleFacing f = (MultipleFacing) data; - for (BlockFace face : f.getAllowedFaces()) { - BlockData facingBlock = placer.get(xx + face.getModX(), yy + face.getModY(), zz + face.getModZ()); - if (B.isSolid(facingBlock) && !B.isVineBlock(facingBlock)) { - f.setFace(face, true); - } - } - } - - if (listener != null) { - listener.accept(new BlockPosition(xx, yy, zz), data); - } - - if (markers != null && markers.containsKey(g)) { - placer.getEngine().getMantle().getMantle().set(xx, yy, zz, new MatterMarker(markers.get(g))); - } - - boolean wouldReplace = B.isSolid(placer.get(xx, yy, zz)) && B.isVineBlock(data); - - if (!data.getMaterial().equals(Material.AIR) && !data.getMaterial().equals(Material.CAVE_AIR) && !wouldReplace) { - placer.set(xx, yy, zz, data); - if (tile != null) { - placer.setTile(xx, yy, zz, tile); - } - } - } - } catch (Throwable e) { - Iris.reportError(e); - } - readLock.unlock(); - - if (stilting) { - readLock.lock(); - IrisStiltSettings settings = config.getStiltSettings(); - for (BlockVector g : getBlocks().keySet()) { - BlockData d; - - if (settings == null || settings.getPalette() == null) { - try { - d = getBlocks().get(g); - } catch (Throwable e) { - Iris.reportError(e); - Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (stilt cme)"); - d = AIR; - } - - if (d == null) { - Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (stilt null)"); - d = AIR; - } - } else - d = config.getStiltSettings().getPalette().get(rng, x, y, z, rdata); - - - BlockVector i = g.clone(); - i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).clone(); - i = config.getTranslate().translate(i.clone(), config.getRotation(), spinx, spiny, spinz).clone(); - d = config.getRotation().rotate(d, spinx, spiny, spinz); - - if (i.getBlockY() != lowest) - continue; - - for (IrisObjectReplace j : config.getEdit()) { - if (rng.chance(j.getChance())) { - for (BlockData k : j.getFind(rdata)) { - if (j.isExact() ? k.matches(d) : k.getMaterial().equals(d.getMaterial())) { - BlockData newData = j.getReplace(rng, i.getX() + x, i.getY() + y, i.getZ() + z, rdata).clone(); - - if (newData.getMaterial() == d.getMaterial()) { - d = d.merge(newData); - } else { - d = newData; - } - } - } - } - } - - if (d == null || B.isAir(d)) - continue; - - xx = x + (int) Math.round(i.getX()); - zz = z + (int) Math.round(i.getZ()); - - if (warped) { - xx += config.warp(rng, i.getX() + x, i.getY() + y, i.getZ() + z, getLoader()); - zz += config.warp(rng, i.getZ() + z, i.getY() + y, i.getX() + x, getLoader()); - } - - int highest = placer.getHighest(xx, zz, getLoader(), true); - - if ((config.isWaterloggable() || config.isUnderwater()) && highest <= placer.getFluidHeight() && d instanceof Waterlogged) - ((Waterlogged) d).setWaterlogged(true); - - if (yv >= 0 && config.isBottom()) - y += Math.floorDiv(h, 2); - - int lowerBound = highest - 1; - if (settings != null) { - lowerBound -= config.getStiltSettings().getOverStilt() - rng.i(0, config.getStiltSettings().getYRand()); - if (settings.getYMax() != 0) - lowerBound -= Math.min(config.getStiltSettings().getYMax() - (lowest + y - highest), 0); - } - for (int j = lowest + y; j > lowerBound; j--) { - if (B.isVineBlock(d)) { - MultipleFacing f = (MultipleFacing) d; - for (BlockFace face : f.getAllowedFaces()) { - BlockData facingBlock = placer.get(xx + face.getModX(), j + face.getModY(), zz + face.getModZ()); - if (B.isSolid(facingBlock) && !B.isVineBlock(facingBlock)) { - f.setFace(face, true); - } - } - } - placer.set(xx, j, zz, d); - } - - } - - readLock.unlock(); - } - - if (heightmap != null) { - RNG rngx = rng.nextParallelRNG(3468854); - - for (Position2 i : heightmap.k()) { - int vx = i.getX(); - int vy = heightmap.get(i); - int vz = i.getZ(); - - if (config.getSnow() > 0) { - int height = rngx.i(0, (int) (config.getSnow() * 7)); - placer.set(vx, vy + 1, vz, SNOW_LAYERS[Math.max(Math.min(height, 7), 0)]); - } - } - } - - return y; - } - - public IrisObject rotateCopy(IrisObjectRotation rt) { - IrisObject copy = copy(); - copy.rotate(rt, 0, 0, 0); - return copy; - } - - public void rotate(IrisObjectRotation r, int spinx, int spiny, int spinz) { - KMap d = new KMap<>(); - - for (BlockVector i : getBlocks().keySet()) { - d.put(r.rotate(i.clone(), spinx, spiny, spinz), r.rotate(getBlocks().get(i).clone(), - spinx, spiny, spinz)); - } - - KMap> dx = new KMap<>(); - - for (BlockVector i : getStates().keySet()) { - dx.put(r.rotate(i.clone(), spinx, spiny, spinz), getStates().get(i)); - } - - blocks = d; - states = dx; - } - - public void place(Location at) { - for (BlockVector i : getBlocks().keySet()) { - Block b = at.clone().add(0, getCenter().getY(), 0).add(i).getBlock(); - b.setBlockData(Objects.requireNonNull(getBlocks().get(i)), false); - - if (getStates().containsKey(i)) { - Iris.info(Objects.requireNonNull(states.get(i)).toString()); - BlockState st = b.getState(); - Objects.requireNonNull(getStates().get(i)).toBukkitTry(st); - st.update(); - } - } - } - - public void placeCenterY(Location at) { - for (BlockVector i : getBlocks().keySet()) { - Block b = at.clone().add(getCenter().getX(), getCenter().getY(), getCenter().getZ()).add(i).getBlock(); - b.setBlockData(Objects.requireNonNull(getBlocks().get(i)), false); - - if (getStates().containsKey(i)) { - Objects.requireNonNull(getStates().get(i)).toBukkitTry(b.getState()); - } - } - } - - public synchronized KMap getBlocks() { - return blocks; - } - - public synchronized KMap> getStates() { - return states; - } - - public void unplaceCenterY(Location at) { - for (BlockVector i : getBlocks().keySet()) { - at.clone().add(getCenter().getX(), getCenter().getY(), getCenter().getZ()).add(i).getBlock().setBlockData(AIR, false); - } - } - - public IrisObject scaled(double scale, IrisObjectPlacementScaleInterpolator interpolation) { - Vector sm1 = new Vector(scale - 1, scale - 1, scale - 1); - scale = Math.max(0.001, Math.min(50, scale)); - if (scale < 1) { - scale = scale - 0.0001; - } - - IrisPosition l1 = getAABB().max(); - IrisPosition l2 = getAABB().min(); - @SuppressWarnings({"unchecked", "rawtypes"}) HashMap placeBlock = new HashMap(); - - Vector center = getCenter(); - if (getH() == 2) { - center = center.setY(center.getBlockY() + 0.5); - } - if (getW() == 2) { - center = center.setX(center.getBlockX() + 0.5); - } - if (getD() == 2) { - center = center.setZ(center.getBlockZ() + 0.5); - } - - IrisObject oo = new IrisObject((int) Math.ceil((w * scale) + (scale * 2)), (int) Math.ceil((h * scale) + (scale * 2)), (int) Math.ceil((d * scale) + (scale * 2))); - - for (Map.Entry entry : blocks.entrySet()) { - BlockData bd = entry.getValue(); - placeBlock.put(entry.getKey().clone().add(HALF).subtract(center) - .multiply(scale).add(sm1).toBlockVector(), bd); - } - - for (Map.Entry entry : placeBlock.entrySet()) { - BlockVector v = entry.getKey(); - if (scale > 1) { - for (BlockVector vec : blocksBetweenTwoPoints(v.clone().add(center), v.clone().add(center).add(sm1))) { - oo.getBlocks().put(vec, entry.getValue()); - } - } else { - oo.setUnsigned(v.getBlockX(), v.getBlockY(), v.getBlockZ(), entry.getValue()); - } - } - - if (scale > 1) { - switch (interpolation) { - case TRILINEAR -> oo.trilinear((int) Math.round(scale)); - case TRICUBIC -> oo.tricubic((int) Math.round(scale)); - case TRIHERMITE -> oo.trihermite((int) Math.round(scale)); - } - } - - return oo; - } - - public void trilinear(int rad) { - KMap v = getBlocks().copy(); - KMap b = new KMap<>(); - BlockVector min = getAABB().minbv(); - BlockVector max = getAABB().maxbv(); - - for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { - for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { - for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { - if (IrisInterpolation.getTrilinear(x, y, z, rad, (xx, yy, zz) -> { - BlockData data = v.get(new BlockVector((int) xx, (int) yy, (int) zz)); - - if (data == null || data.getMaterial().isAir()) { - return 0; - } - - return 1; - }) >= 0.5) { - b.put(new BlockVector(x, y, z), nearestBlockData(x, y, z)); - } else { - b.put(new BlockVector(x, y, z), AIR); - } - } - } - } - - blocks = b; - } - - public void tricubic(int rad) { - KMap v = getBlocks().copy(); - KMap b = new KMap<>(); - BlockVector min = getAABB().minbv(); - BlockVector max = getAABB().maxbv(); - - for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { - for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { - for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { - if (IrisInterpolation.getTricubic(x, y, z, rad, (xx, yy, zz) -> { - BlockData data = v.get(new BlockVector((int) xx, (int) yy, (int) zz)); - - if (data == null || data.getMaterial().isAir()) { - return 0; - } - - return 1; - }) >= 0.5) { - b.put(new BlockVector(x, y, z), nearestBlockData(x, y, z)); - } else { - b.put(new BlockVector(x, y, z), AIR); - } - } - } - } - - blocks = b; - } - - public void trihermite(int rad) { - trihermite(rad, 0D, 0D); - } - - public void trihermite(int rad, double tension, double bias) { - KMap v = getBlocks().copy(); - KMap b = new KMap<>(); - BlockVector min = getAABB().minbv(); - BlockVector max = getAABB().maxbv(); - - for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { - for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { - for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { - if (IrisInterpolation.getTrihermite(x, y, z, rad, (xx, yy, zz) -> { - BlockData data = v.get(new BlockVector((int) xx, (int) yy, (int) zz)); - - if (data == null || data.getMaterial().isAir()) { - return 0; - } - - return 1; - }, tension, bias) >= 0.5) { - b.put(new BlockVector(x, y, z), nearestBlockData(x, y, z)); - } else { - b.put(new BlockVector(x, y, z), AIR); - } - } - } - } - - blocks = b; - } - - private BlockData nearestBlockData(int x, int y, int z) { - BlockVector vv = new BlockVector(x, y, z); - BlockData r = getBlocks().get(vv); - - if (r != null && !r.getMaterial().isAir()) { - return r; - } - - double d = Double.MAX_VALUE; - - for (Map.Entry entry : blocks.entrySet()) { - BlockData dat = entry.getValue(); - - if (dat.getMaterial().isAir()) { - continue; - } - - double dx = entry.getKey().distanceSquared(vv); - - if (dx < d) { - d = dx; - r = dat; - } - } - - return r; - } - - public int volume() { - return blocks.size(); - } - - @Override - public String getFolderName() { - return "objects"; - } - - @Override - public String getTypeName() { - return "Object"; - } - - @Override - public void scanForErrors(JSONObject p, VolmitSender sender) { - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.engine.object; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.loader.IrisRegistrant; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.placer.HeightmapObjectPlacer; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.context.IrisContext; +import com.volmit.iris.util.data.B; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.interpolation.IrisInterpolation; +import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.math.AxisAlignedBB; +import com.volmit.iris.util.math.BlockPosition; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.matter.MatterMarker; +import com.volmit.iris.util.parallel.BurstExecutor; +import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.plugin.VolmitSender; +import com.volmit.iris.util.scheduling.IrisLock; +import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import com.volmit.iris.util.stream.ProceduralStream; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.TileState; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.MultipleFacing; +import org.bukkit.block.data.Waterlogged; +import org.bukkit.block.data.type.Leaves; +import org.bukkit.util.BlockVector; +import org.bukkit.util.Vector; + +import java.io.*; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; + +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = false) +public class IrisObject extends IrisRegistrant { + protected static final Vector HALF = new Vector(0.5, 0.5, 0.5); + protected static final BlockData AIR = B.get("CAVE_AIR"); + protected static final BlockData VAIR = B.get("VOID_AIR"); + protected static final BlockData VAIR_DEBUG = B.get("COBWEB"); + protected static final BlockData[] SNOW_LAYERS = new BlockData[]{B.get("minecraft:snow[layers=1]"), B.get("minecraft:snow[layers=2]"), B.get("minecraft:snow[layers=3]"), B.get("minecraft:snow[layers=4]"), B.get("minecraft:snow[layers=5]"), B.get("minecraft:snow[layers=6]"), B.get("minecraft:snow[layers=7]"), B.get("minecraft:snow[layers=8]")}; + protected transient final IrisLock readLock = new IrisLock("read-conclock"); + @Getter + @Setter + protected transient volatile boolean smartBored = false; + @Getter + @Setter + protected transient IrisLock lock = new IrisLock("Preloadcache"); + @Setter + protected transient AtomicCache aabb = new AtomicCache<>(); + private KMap blocks; + private KMap> states; + @Getter + @Setter + private int w; + @Getter + @Setter + private int d; + @Getter + @Setter + private int h; + @Getter + @Setter + private transient BlockVector center; + + public IrisObject(int w, int h, int d) { + blocks = new KMap<>(); + states = new KMap<>(); + this.w = w; + this.h = h; + this.d = d; + center = new BlockVector(w / 2, h / 2, d / 2); + } + + public IrisObject() { + this(0, 0, 0); + } + + public static BlockVector getCenterForSize(BlockVector size) { + return new BlockVector(size.getX() / 2, size.getY() / 2, size.getZ() / 2); + } + + public static AxisAlignedBB getAABBFor(BlockVector size) { + BlockVector center = new BlockVector(size.getX() / 2, size.getY() / 2, size.getZ() / 2); + return new AxisAlignedBB(new IrisPosition(new BlockVector(0, 0, 0).subtract(center).toBlockVector()), + new IrisPosition(new BlockVector(size.getX() - 1, size.getY() - 1, size.getZ() - 1).subtract(center).toBlockVector())); + } + + @SuppressWarnings({"resource", "RedundantSuppression"}) + public static BlockVector sampleSize(File file) throws IOException { + FileInputStream in = new FileInputStream(file); + DataInputStream din = new DataInputStream(in); + BlockVector bv = new BlockVector(din.readInt(), din.readInt(), din.readInt()); + Iris.later(din::close); + return bv; + } + + private static List blocksBetweenTwoPoints(Vector loc1, Vector loc2) { + List locations = new ArrayList<>(); + int topBlockX = Math.max(loc1.getBlockX(), loc2.getBlockX()); + int bottomBlockX = Math.min(loc1.getBlockX(), loc2.getBlockX()); + int topBlockY = Math.max(loc1.getBlockY(), loc2.getBlockY()); + int bottomBlockY = Math.min(loc1.getBlockY(), loc2.getBlockY()); + int topBlockZ = Math.max(loc1.getBlockZ(), loc2.getBlockZ()); + int bottomBlockZ = Math.min(loc1.getBlockZ(), loc2.getBlockZ()); + + for (int x = bottomBlockX; x <= topBlockX; x++) { + for (int z = bottomBlockZ; z <= topBlockZ; z++) { + for (int y = bottomBlockY; y <= topBlockY; y++) { + locations.add(new BlockVector(x, y, z)); + } + } + } + return locations; + } + + public AxisAlignedBB getAABB() { + return aabb.aquire(() -> getAABBFor(new BlockVector(w, h, d))); + } + + public void ensureSmartBored(boolean debug) { + if (smartBored) { + return; + } + + PrecisionStopwatch p = PrecisionStopwatch.start(); + BlockData vair = debug ? VAIR_DEBUG : VAIR; + lock.lock(); + AtomicInteger applied = new AtomicInteger(); + if (getBlocks().isEmpty()) { + lock.unlock(); + Iris.warn("Cannot Smart Bore " + getLoadKey() + " because it has 0 blocks in it."); + smartBored = true; + return; + } + + BlockVector max = new BlockVector(Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE); + BlockVector min = new BlockVector(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE); + + for (BlockVector i : getBlocks().keySet()) { + max.setX(Math.max(i.getX(), max.getX())); + min.setX(Math.min(i.getX(), min.getX())); + max.setY(Math.max(i.getY(), max.getY())); + min.setY(Math.min(i.getY(), min.getY())); + max.setZ(Math.max(i.getZ(), max.getZ())); + min.setZ(Math.min(i.getZ(), min.getZ())); + } + + BurstExecutor burst = MultiBurst.burst.burst(); + + // Smash X + for (int rayY = min.getBlockY(); rayY <= max.getBlockY(); rayY++) { + int finalRayY = rayY; + burst.queue(() -> { + for (int rayZ = min.getBlockZ(); rayZ <= max.getBlockZ(); rayZ++) { + int start = Integer.MAX_VALUE; + int end = Integer.MIN_VALUE; + + for (int ray = min.getBlockX(); ray <= max.getBlockX(); ray++) { + if (getBlocks().containsKey(new BlockVector(ray, finalRayY, rayZ))) { + start = Math.min(ray, start); + end = Math.max(ray, end); + } + } + + if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) { + for (int i = start; i <= end; i++) { + BlockVector v = new BlockVector(i, finalRayY, rayZ); + + if (!B.isAir(getBlocks().get(v))) { + getBlocks().computeIfAbsent(v, (vv) -> vair); + applied.getAndIncrement(); + } + } + } + } + }); + } + + // Smash Y + for (int rayX = min.getBlockX(); rayX <= max.getBlockX(); rayX++) { + int finalRayX = rayX; + burst.queue(() -> { + for (int rayZ = min.getBlockZ(); rayZ <= max.getBlockZ(); rayZ++) { + int start = Integer.MAX_VALUE; + int end = Integer.MIN_VALUE; + + for (int ray = min.getBlockY(); ray <= max.getBlockY(); ray++) { + if (getBlocks().containsKey(new BlockVector(finalRayX, ray, rayZ))) { + start = Math.min(ray, start); + end = Math.max(ray, end); + } + } + + if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) { + for (int i = start; i <= end; i++) { + BlockVector v = new BlockVector(finalRayX, i, rayZ); + + if (!B.isAir(getBlocks().get(v))) { + getBlocks().computeIfAbsent(v, (vv) -> vair); + applied.getAndIncrement(); + } + } + } + } + }); + } + + // Smash Z + for (int rayX = min.getBlockX(); rayX <= max.getBlockX(); rayX++) { + int finalRayX = rayX; + burst.queue(() -> { + for (int rayY = min.getBlockY(); rayY <= max.getBlockY(); rayY++) { + int start = Integer.MAX_VALUE; + int end = Integer.MIN_VALUE; + + for (int ray = min.getBlockZ(); ray <= max.getBlockZ(); ray++) { + if (getBlocks().containsKey(new BlockVector(finalRayX, rayY, ray))) { + start = Math.min(ray, start); + end = Math.max(ray, end); + } + } + + if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) { + for (int i = start; i <= end; i++) { + BlockVector v = new BlockVector(finalRayX, rayY, i); + + if (!B.isAir(getBlocks().get(v))) { + getBlocks().computeIfAbsent(v, (vv) -> vair); + applied.getAndIncrement(); + } + } + } + } + }); + } + + burst.complete(); + smartBored = true; + lock.unlock(); + Iris.debug("Smart Bore: " + getLoadKey() + " in " + Form.duration(p.getMilliseconds(), 2) + " (" + Form.f(applied.get()) + ")"); + } + + public synchronized IrisObject copy() { + IrisObject o = new IrisObject(w, h, d); + o.setLoadKey(o.getLoadKey()); + o.setCenter(getCenter().clone()); + + for (BlockVector i : getBlocks().keySet()) { + o.getBlocks().put(i.clone(), Objects.requireNonNull(getBlocks().get(i)).clone()); + } + + for (BlockVector i : getStates().keySet()) { + o.getStates().put(i.clone(), Objects.requireNonNull(getStates().get(i)).clone()); + } + + return o; + } + + public void readLegacy(InputStream in) throws IOException { + DataInputStream din = new DataInputStream(in); + this.w = din.readInt(); + this.h = din.readInt(); + this.d = din.readInt(); + center = new BlockVector(w / 2, h / 2, d / 2); + int s = din.readInt(); + + for (int i = 0; i < s; i++) { + getBlocks().put(new BlockVector(din.readShort(), din.readShort(), din.readShort()), B.get(din.readUTF())); + } + + try { + int size = din.readInt(); + + for (int i = 0; i < size; i++) { + getStates().put(new BlockVector(din.readShort(), din.readShort(), din.readShort()), TileData.read(din)); + } + } catch (Throwable e) { + Iris.reportError(e); + + } + } + + public void read(InputStream in) throws Throwable { + DataInputStream din = new DataInputStream(in); + this.w = din.readInt(); + this.h = din.readInt(); + this.d = din.readInt(); + if (!din.readUTF().equals("Iris V2 IOB;")) { + return; + } + center = new BlockVector(w / 2, h / 2, d / 2); + int s = din.readShort(); + int i; + KList palette = new KList<>(); + + for (i = 0; i < s; i++) { + palette.add(din.readUTF()); + } + + s = din.readInt(); + + for (i = 0; i < s; i++) { + getBlocks().put(new BlockVector(din.readShort(), din.readShort(), din.readShort()), B.get(palette.get(din.readShort()))); + } + + s = din.readInt(); + + for (i = 0; i < s; i++) { + getStates().put(new BlockVector(din.readShort(), din.readShort(), din.readShort()), TileData.read(din)); + } + } + + public void write(OutputStream o) throws IOException { + DataOutputStream dos = new DataOutputStream(o); + dos.writeInt(w); + dos.writeInt(h); + dos.writeInt(d); + dos.writeUTF("Iris V2 IOB;"); + KList palette = new KList<>(); + + for (BlockData i : getBlocks().values()) { + palette.addIfMissing(i.getAsString()); + } + + dos.writeShort(palette.size()); + + for (String i : palette) { + dos.writeUTF(i); + } + + dos.writeInt(getBlocks().size()); + + for (BlockVector i : getBlocks().keySet()) { + dos.writeShort(i.getBlockX()); + dos.writeShort(i.getBlockY()); + dos.writeShort(i.getBlockZ()); + dos.writeShort(palette.indexOf(getBlocks().get(i).getAsString())); + } + + dos.writeInt(getStates().size()); + for (BlockVector i : getStates().keySet()) { + dos.writeShort(i.getBlockX()); + dos.writeShort(i.getBlockY()); + dos.writeShort(i.getBlockZ()); + getStates().get(i).toBinary(dos); + } + } + + public void read(File file) throws IOException { + FileInputStream fin = new FileInputStream(file); + try { + read(fin); + fin.close(); + } catch (Throwable e) { + Iris.reportError(e); + fin.close(); + fin = new FileInputStream(file); + readLegacy(fin); + fin.close(); + } + } + + public void write(File file) throws IOException { + if (file == null) { + return; + } + + FileOutputStream out = new FileOutputStream(file); + write(out); + out.close(); + } + + public void shrinkwrap() { + BlockVector min = new BlockVector(); + BlockVector max = new BlockVector(); + + for (BlockVector i : getBlocks().keySet()) { + min.setX(Math.min(min.getX(), i.getX())); + min.setY(Math.min(min.getY(), i.getY())); + min.setZ(Math.min(min.getZ(), i.getZ())); + max.setX(Math.max(max.getX(), i.getX())); + max.setY(Math.max(max.getY(), i.getY())); + max.setZ(Math.max(max.getZ(), i.getZ())); + } + + w = max.getBlockX() - min.getBlockX(); + h = max.getBlockY() - min.getBlockY(); + d = max.getBlockZ() - min.getBlockZ(); + } + + public void clean() { + KMap d = new KMap<>(); + + for (BlockVector i : getBlocks().keySet()) { + d.put(new BlockVector(i.getBlockX(), i.getBlockY(), i.getBlockZ()), Objects.requireNonNull(getBlocks().get(i))); + } + + KMap> dx = new KMap<>(); + + for (BlockVector i : getBlocks().keySet()) { + d.put(new BlockVector(i.getBlockX(), i.getBlockY(), i.getBlockZ()), Objects.requireNonNull(getBlocks().get(i))); + } + + for (BlockVector i : getStates().keySet()) { + dx.put(new BlockVector(i.getBlockX(), i.getBlockY(), i.getBlockZ()), Objects.requireNonNull(getStates().get(i))); + } + + blocks = d; + states = dx; + } + + public BlockVector getSigned(int x, int y, int z) { + if (x >= w || y >= h || z >= d) { + throw new RuntimeException(x + " " + y + " " + z + " exceeds limit of " + w + " " + h + " " + d); + } + + return new BlockVector(x, y, z).subtract(center).toBlockVector(); + } + + public void setUnsigned(int x, int y, int z, BlockData block) { + BlockVector v = getSigned(x, y, z); + + if (block == null) { + getBlocks().remove(v); + getStates().remove(v); + } else { + getBlocks().put(v, block); + } + } + + public void setUnsigned(int x, int y, int z, Block block) { + BlockVector v = getSigned(x, y, z); + + if (block == null) { + getBlocks().remove(v); + getStates().remove(v); + } else { + BlockData data = block.getBlockData(); + getBlocks().put(v, data); + TileData state = TileData.getTileState(block); + if (state != null) { + Iris.info("Saved State " + v); + getStates().put(v, state); + } + } + } + + public int place(int x, int z, IObjectPlacer placer, IrisObjectPlacement config, RNG rng, IrisData rdata) { + return place(x, -1, z, placer, config, rng, rdata); + } + + public int place(int x, int z, IObjectPlacer placer, IrisObjectPlacement config, RNG rng, CarveResult c, IrisData rdata) { + return place(x, -1, z, placer, config, rng, null, c, rdata); + } + + public int place(int x, int yv, int z, IObjectPlacer placer, IrisObjectPlacement config, RNG rng, IrisData rdata) { + return place(x, yv, z, placer, config, rng, null, null, rdata); + } + + public int place(Location loc, IObjectPlacer placer, IrisObjectPlacement config, RNG rng, IrisData rdata) { + return place(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), placer, config, rng, rdata); + } + + public int place(int x, int yv, int z, IObjectPlacer oplacer, IrisObjectPlacement config, RNG rng, BiConsumer listener, CarveResult c, IrisData rdata) { + IObjectPlacer placer = (config.getHeightmap() != null) ? new HeightmapObjectPlacer(oplacer.getEngine() == null ? IrisContext.get().getEngine() : oplacer.getEngine(), rng, x, yv, z, config, oplacer) : oplacer; + + if (rdata != null) { + // Slope condition + if (!config.getSlopeCondition().isDefault() && + !config.getSlopeCondition().isValid(rdata.getEngine().getComplex().getSlopeStream().get(x, z))) { + return -1; + } + + // Rotation calculation + int slopeRotationY = 0; + ProceduralStream heightStream = rdata.getEngine().getComplex().getHeightStream(); + if (config.isRotateTowardsSlope()) { + // Whichever side of the rectangle that bounds the object is lowest is the 'direction' of the slope (simply said). + double hNorth = heightStream.get(x, z + ((float) d) / 2); + double hEast = heightStream.get(x + ((float) w) / 2, z); + double hSouth = heightStream.get(x, z - ((float) d) / 2); + double hWest = heightStream.get(x - ((float) w) / 2, z); + double min = Math.min(Math.min(hNorth, hEast), Math.min(hSouth, hWest)); + if (min == hNorth) { + slopeRotationY = 0; + } else if (min == hEast) { + slopeRotationY = 90; + } else if (min == hSouth) { + slopeRotationY = 180; + } else if (min == hWest) { + slopeRotationY = 270; + } + } + double newRotation = config.getRotation().getYAxis().getMin() + slopeRotationY; + config.getRotation().setYAxis(new IrisAxisRotationClamp(true, false, newRotation, newRotation, 360)); + config.getRotation().setEnabled(true); + } + + if (config.isSmartBore()) { + ensureSmartBored(placer.isDebugSmartBore()); + } + + boolean warped = !config.getWarp().isFlat(); + boolean stilting = (config.getMode().equals(ObjectPlaceMode.STILT) || config.getMode().equals(ObjectPlaceMode.FAST_STILT) || + config.getMode() == ObjectPlaceMode.MIN_STILT || config.getMode() == ObjectPlaceMode.FAST_MIN_STILT || + config.getMode() == ObjectPlaceMode.CENTER_STILT); + KMap heightmap = config.getSnow() > 0 ? new KMap<>() : null; + int spinx = rng.imax() / 1000; + int spiny = rng.imax() / 1000; + int spinz = rng.imax() / 1000; + int rty = config.getRotation().rotate(new BlockVector(0, getCenter().getBlockY(), 0), spinx, spiny, spinz).getBlockY(); + int ty = config.getTranslate().translate(new BlockVector(0, getCenter().getBlockY(), 0), config.getRotation(), spinx, spiny, spinz).getBlockY(); + int y = -1; + int xx, zz; + int yrand = config.getTranslate().getYRandom(); + yrand = yrand > 0 ? rng.i(0, yrand) : yrand < 0 ? rng.i(yrand, 0) : yrand; + boolean bail = false; + + if (yv < 0) { + if (config.getMode().equals(ObjectPlaceMode.CENTER_HEIGHT) || config.getMode() == ObjectPlaceMode.CENTER_STILT) { + y = (c != null ? c.getSurface() : placer.getHighest(x, z, getLoader(), config.isUnderwater())) + rty; + if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) { + bail = true; + } + } else if (config.getMode().equals(ObjectPlaceMode.MAX_HEIGHT) || config.getMode().equals(ObjectPlaceMode.STILT)) { + BlockVector offset = new BlockVector(config.getTranslate().getX(), config.getTranslate().getY(), config.getTranslate().getZ()); + BlockVector rotatedDimensions = config.getRotation().rotate(new BlockVector(getW(), getH(), getD()), spinx, spiny, spinz).clone(); + int xLength = (rotatedDimensions.getBlockX() / 2) + offset.getBlockX(); + int minX = Math.min(x - xLength, x + xLength); + int maxX = Math.max(x - xLength, x + xLength); + int zLength = (rotatedDimensions.getBlockZ() / 2) + offset.getBlockZ(); + int minZ = Math.min(z - zLength, z + zLength); + int maxZ = Math.max(z - zLength, z + zLength); + for (int i = minX; i <= maxX; i++) { + for (int ii = minZ; ii <= maxZ; ii++) { + int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty; + if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { + bail = true; + break; + } + if (h > y) + y = h; + } + } + } else if (config.getMode().equals(ObjectPlaceMode.FAST_MAX_HEIGHT) || config.getMode().equals(ObjectPlaceMode.FAST_STILT)) { + BlockVector offset = new BlockVector(config.getTranslate().getX(), config.getTranslate().getY(), config.getTranslate().getZ()); + BlockVector rotatedDimensions = config.getRotation().rotate(new BlockVector(getW(), getH(), getD()), spinx, spiny, spinz).clone(); + + int xRadius = (rotatedDimensions.getBlockX() / 2); + int xLength = xRadius + offset.getBlockX(); + int minX = Math.min(x - xLength, x + xLength); + int maxX = Math.max(x - xLength, x + xLength); + int zRadius = (rotatedDimensions.getBlockZ() / 2); + int zLength = zRadius + offset.getBlockZ(); + int minZ = Math.min(z - zLength, z + zLength); + int maxZ = Math.max(z - zLength, z + zLength); + + for (int i = minX; i <= maxX; i += Math.abs(xRadius) + 1) { + for (int ii = minZ; ii <= maxZ; ii += Math.abs(zRadius) + 1) { + int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty; + if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { + bail = true; + break; + } + if (h > y) + y = h; + } + } + } else if (config.getMode().equals(ObjectPlaceMode.MIN_HEIGHT) || config.getMode() == ObjectPlaceMode.MIN_STILT) { + y = rdata.getEngine().getHeight() + 1; + BlockVector offset = new BlockVector(config.getTranslate().getX(), config.getTranslate().getY(), config.getTranslate().getZ()); + BlockVector rotatedDimensions = config.getRotation().rotate(new BlockVector(getW(), getH(), getD()), spinx, spiny, spinz).clone(); + + int xLength = (rotatedDimensions.getBlockX() / 2) + offset.getBlockX(); + int minX = Math.min(x - xLength, x + xLength); + int maxX = Math.max(x - xLength, x + xLength); + int zLength = (rotatedDimensions.getBlockZ() / 2) + offset.getBlockZ(); + int minZ = Math.min(z - zLength, z + zLength); + int maxZ = Math.max(z - zLength, z + zLength); + for (int i = minX; i <= maxX; i++) { + for (int ii = minZ; ii <= maxZ; ii++) { + int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty; + if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { + bail = true; + break; + } + if (h < y) { + y = h; + } + } + } + } else if (config.getMode().equals(ObjectPlaceMode.FAST_MIN_HEIGHT) || config.getMode() == ObjectPlaceMode.FAST_MIN_STILT) { + y = rdata.getEngine().getHeight() + 1; + BlockVector offset = new BlockVector(config.getTranslate().getX(), config.getTranslate().getY(), config.getTranslate().getZ()); + BlockVector rotatedDimensions = config.getRotation().rotate(new BlockVector(getW(), getH(), getD()), spinx, spiny, spinz).clone(); + + int xRadius = (rotatedDimensions.getBlockX() / 2); + int xLength = xRadius + offset.getBlockX(); + int minX = Math.min(x - xLength, x + xLength); + int maxX = Math.max(x - xLength, x + xLength); + int zRadius = (rotatedDimensions.getBlockZ() / 2); + int zLength = zRadius + offset.getBlockZ(); + int minZ = Math.min(z - zLength, z + zLength); + int maxZ = Math.max(z - zLength, z + zLength); + + for (int i = minX; i <= maxX; i += Math.abs(xRadius) + 1) { + for (int ii = minZ; ii <= maxZ; ii += Math.abs(zRadius) + 1) { + int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty; + if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { + bail = true; + break; + } + if (h < y) { + y = h; + } + } + } + } else if (config.getMode().equals(ObjectPlaceMode.PAINT)) { + y = placer.getHighest(x, z, getLoader(), config.isUnderwater()) + rty; + if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) { + bail = true; + } + } + } else { + y = yv; + if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) { + bail = true; + } + } + + if (yv >= 0 && config.isBottom()) { + y += Math.floorDiv(h, 2); + bail = placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z); + } + + if (bail) { + return -1; + } + + if (yv < 0) { + if (!config.isUnderwater() && !config.isOnwater() && placer.isUnderwater(x, z)) { + return -1; + } + } + + if (c != null && Math.max(0, h + yrand + ty) + 1 >= c.getHeight()) { + return -1; + } + + if (config.isUnderwater() && y + rty + ty >= placer.getFluidHeight()) { + return -1; + } + + if (!config.getClamp().canPlace(y + rty + ty, y - rty + ty)) { + return -1; + } + + if (config.isBore()) { + BlockVector offset = new BlockVector(config.getTranslate().getX(), config.getTranslate().getY(), config.getTranslate().getZ()); + for (int i = x - Math.floorDiv(w, 2) + (int) offset.getX(); i <= x + Math.floorDiv(w, 2) - (w % 2 == 0 ? 1 : 0) + (int) offset.getX(); i++) { + for (int j = y - Math.floorDiv(h, 2) - config.getBoreExtendMinY() + (int) offset.getY(); j <= y + Math.floorDiv(h, 2) + config.getBoreExtendMaxY() - (h % 2 == 0 ? 1 : 0) + (int) offset.getY(); j++) { + for (int k = z - Math.floorDiv(d, 2) + (int) offset.getZ(); k <= z + Math.floorDiv(d, 2) - (d % 2 == 0 ? 1 : 0) + (int) offset.getX(); k++) { + placer.set(i, j, k, AIR); + } + } + } + } + + int lowest = Integer.MAX_VALUE; + y += yrand; + readLock.lock(); + + KMap markers = null; + + try { + if (config.getMarkers().isNotEmpty() && placer.getEngine() != null) { + markers = new KMap<>(); + for (IrisObjectMarker j : config.getMarkers()) { + IrisMarker marker = getLoader().getMarkerLoader().load(j.getMarker()); + + if (marker == null) { + continue; + } + + int max = j.getMaximumMarkers(); + + for (BlockVector i : getBlocks().k().shuffle()) { + if (max <= 0) { + break; + } + + BlockData data = getBlocks().get(i); + + for (BlockData k : j.getMark(rdata)) { + if (max <= 0) { + break; + } + + if (j.isExact() ? k.matches(data) : k.getMaterial().equals(data.getMaterial())) { + boolean a = !blocks.containsKey(new BlockVector(i.clone().add(new BlockVector(0, 1, 0)))); + boolean fff = !blocks.containsKey(new BlockVector(i.clone().add(new BlockVector(0, 2, 0)))); + + if (!marker.isEmptyAbove() || (a && fff)) { + markers.put(i, j.getMarker()); + max--; + } + } + } + } + } + } + + for (BlockVector g : getBlocks().keySet()) { + BlockData d; + TileData tile = null; + + try { + d = getBlocks().get(g); + tile = getStates().get(g); + } catch (Throwable e) { + Iris.reportError(e); + Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (cme)"); + d = AIR; + } + + if (d == null) { + Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (null)"); + d = AIR; + } + + BlockVector i = g.clone(); + BlockData data = d.clone(); + i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).clone(); + i = config.getTranslate().translate(i.clone(), config.getRotation(), spinx, spiny, spinz).clone(); + + if (stilting && i.getBlockY() < lowest && !B.isAir(data)) { + lowest = i.getBlockY(); + } + + if (placer.isPreventingDecay() && (data) instanceof Leaves && !((Leaves) (data)).isPersistent()) { + ((Leaves) data).setPersistent(true); + } + + for (IrisObjectReplace j : config.getEdit()) { + if (rng.chance(j.getChance())) { + for (BlockData k : j.getFind(rdata)) { + if (j.isExact() ? k.matches(data) : k.getMaterial().equals(data.getMaterial())) { + BlockData newData = j.getReplace(rng, i.getX() + x, i.getY() + y, i.getZ() + z, rdata).clone(); + + if (newData.getMaterial() == data.getMaterial()) + data = data.merge(newData); + else + data = newData; + + if (newData.getMaterial() == Material.SPAWNER) { + Optional> t = j.getReplace().getTile(rng, x, y, z, rdata); + if (t.isPresent()) { + tile = t.get(); + } + } + } + } + } + } + + data = config.getRotation().rotate(data, spinx, spiny, spinz); + xx = x + (int) Math.round(i.getX()); + + int yy = y + (int) Math.round(i.getY()); + zz = z + (int) Math.round(i.getZ()); + + if (warped) { + xx += config.warp(rng, i.getX() + x, i.getY() + y, i.getZ() + z, getLoader()); + zz += config.warp(rng, i.getZ() + z, i.getY() + y, i.getX() + x, getLoader()); + } + + if (yv < 0 && (config.getMode().equals(ObjectPlaceMode.PAINT)) && !B.isVineBlock(data)) { + yy = (int) Math.round(i.getY()) + Math.floorDiv(h, 2) + placer.getHighest(xx, zz, getLoader(), config.isUnderwater()); + } + + if (heightmap != null) { + Position2 pos = new Position2(xx, zz); + + if (!heightmap.containsKey(pos)) { + heightmap.put(pos, yy); + } + + if (heightmap.get(pos) < yy) { + heightmap.put(pos, yy); + } + } + + if (config.isMeld() && !placer.isSolid(xx, yy, zz)) { + continue; + } + + if ((config.isWaterloggable() || config.isUnderwater()) && yy <= placer.getFluidHeight() && data instanceof Waterlogged) { + // TODO Here + ((Waterlogged) data).setWaterlogged(true); + } + + if (B.isVineBlock(data)) { + MultipleFacing f = (MultipleFacing) data; + for (BlockFace face : f.getAllowedFaces()) { + BlockData facingBlock = placer.get(xx + face.getModX(), yy + face.getModY(), zz + face.getModZ()); + if (B.isSolid(facingBlock) && !B.isVineBlock(facingBlock)) { + f.setFace(face, true); + } + } + } + + if (listener != null) { + listener.accept(new BlockPosition(xx, yy, zz), data); + } + + if (markers != null && markers.containsKey(g)) { + placer.getEngine().getMantle().getMantle().set(xx, yy, zz, new MatterMarker(markers.get(g))); + } + + boolean wouldReplace = B.isSolid(placer.get(xx, yy, zz)) && B.isVineBlock(data); + + if (!data.getMaterial().equals(Material.AIR) && !data.getMaterial().equals(Material.CAVE_AIR) && !wouldReplace) { + placer.set(xx, yy, zz, data); + if (tile != null) { + placer.setTile(xx, yy, zz, tile); + } + } + } + } catch (Throwable e) { + Iris.reportError(e); + } + readLock.unlock(); + + if (stilting) { + readLock.lock(); + IrisStiltSettings settings = config.getStiltSettings(); + for (BlockVector g : getBlocks().keySet()) { + BlockData d; + + if (settings == null || settings.getPalette() == null) { + try { + d = getBlocks().get(g); + } catch (Throwable e) { + Iris.reportError(e); + Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (stilt cme)"); + d = AIR; + } + + if (d == null) { + Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (stilt null)"); + d = AIR; + } + } else + d = config.getStiltSettings().getPalette().get(rng, x, y, z, rdata); + + + BlockVector i = g.clone(); + i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).clone(); + i = config.getTranslate().translate(i.clone(), config.getRotation(), spinx, spiny, spinz).clone(); + d = config.getRotation().rotate(d, spinx, spiny, spinz); + + if (i.getBlockY() != lowest) + continue; + + for (IrisObjectReplace j : config.getEdit()) { + if (rng.chance(j.getChance())) { + for (BlockData k : j.getFind(rdata)) { + if (j.isExact() ? k.matches(d) : k.getMaterial().equals(d.getMaterial())) { + BlockData newData = j.getReplace(rng, i.getX() + x, i.getY() + y, i.getZ() + z, rdata).clone(); + + if (newData.getMaterial() == d.getMaterial()) { + d = d.merge(newData); + } else { + d = newData; + } + } + } + } + } + + if (d == null || B.isAir(d)) + continue; + + xx = x + (int) Math.round(i.getX()); + zz = z + (int) Math.round(i.getZ()); + + if (warped) { + xx += config.warp(rng, i.getX() + x, i.getY() + y, i.getZ() + z, getLoader()); + zz += config.warp(rng, i.getZ() + z, i.getY() + y, i.getX() + x, getLoader()); + } + + int highest = placer.getHighest(xx, zz, getLoader(), true); + + if ((config.isWaterloggable() || config.isUnderwater()) && highest <= placer.getFluidHeight() && d instanceof Waterlogged) + ((Waterlogged) d).setWaterlogged(true); + + if (yv >= 0 && config.isBottom()) + y += Math.floorDiv(h, 2); + + int lowerBound = highest - 1; + if (settings != null) { + lowerBound -= config.getStiltSettings().getOverStilt() - rng.i(0, config.getStiltSettings().getYRand()); + if (settings.getYMax() != 0) + lowerBound -= Math.min(config.getStiltSettings().getYMax() - (lowest + y - highest), 0); + } + for (int j = lowest + y; j > lowerBound; j--) { + if (B.isVineBlock(d)) { + MultipleFacing f = (MultipleFacing) d; + for (BlockFace face : f.getAllowedFaces()) { + BlockData facingBlock = placer.get(xx + face.getModX(), j + face.getModY(), zz + face.getModZ()); + if (B.isSolid(facingBlock) && !B.isVineBlock(facingBlock)) { + f.setFace(face, true); + } + } + } + placer.set(xx, j, zz, d); + } + + } + + readLock.unlock(); + } + + if (heightmap != null) { + RNG rngx = rng.nextParallelRNG(3468854); + + for (Position2 i : heightmap.k()) { + int vx = i.getX(); + int vy = heightmap.get(i); + int vz = i.getZ(); + + if (config.getSnow() > 0) { + int height = rngx.i(0, (int) (config.getSnow() * 7)); + placer.set(vx, vy + 1, vz, SNOW_LAYERS[Math.max(Math.min(height, 7), 0)]); + } + } + } + + return y; + } + + public IrisObject rotateCopy(IrisObjectRotation rt) { + IrisObject copy = copy(); + copy.rotate(rt, 0, 0, 0); + return copy; + } + + public void rotate(IrisObjectRotation r, int spinx, int spiny, int spinz) { + KMap d = new KMap<>(); + + for (BlockVector i : getBlocks().keySet()) { + d.put(r.rotate(i.clone(), spinx, spiny, spinz), r.rotate(getBlocks().get(i).clone(), + spinx, spiny, spinz)); + } + + KMap> dx = new KMap<>(); + + for (BlockVector i : getStates().keySet()) { + dx.put(r.rotate(i.clone(), spinx, spiny, spinz), getStates().get(i)); + } + + blocks = d; + states = dx; + } + + public void place(Location at) { + for (BlockVector i : getBlocks().keySet()) { + Block b = at.clone().add(0, getCenter().getY(), 0).add(i).getBlock(); + b.setBlockData(Objects.requireNonNull(getBlocks().get(i)), false); + + if (getStates().containsKey(i)) { + Iris.info(Objects.requireNonNull(states.get(i)).toString()); + BlockState st = b.getState(); + Objects.requireNonNull(getStates().get(i)).toBukkitTry(st); + st.update(); + } + } + } + + public void placeCenterY(Location at) { + for (BlockVector i : getBlocks().keySet()) { + Block b = at.clone().add(getCenter().getX(), getCenter().getY(), getCenter().getZ()).add(i).getBlock(); + b.setBlockData(Objects.requireNonNull(getBlocks().get(i)), false); + + if (getStates().containsKey(i)) { + Objects.requireNonNull(getStates().get(i)).toBukkitTry(b.getState()); + } + } + } + + public synchronized KMap getBlocks() { + return blocks; + } + + public synchronized KMap> getStates() { + return states; + } + + public void unplaceCenterY(Location at) { + for (BlockVector i : getBlocks().keySet()) { + at.clone().add(getCenter().getX(), getCenter().getY(), getCenter().getZ()).add(i).getBlock().setBlockData(AIR, false); + } + } + + public IrisObject scaled(double scale, IrisObjectPlacementScaleInterpolator interpolation) { + Vector sm1 = new Vector(scale - 1, scale - 1, scale - 1); + scale = Math.max(0.001, Math.min(50, scale)); + if (scale < 1) { + scale = scale - 0.0001; + } + + IrisPosition l1 = getAABB().max(); + IrisPosition l2 = getAABB().min(); + @SuppressWarnings({"unchecked", "rawtypes"}) HashMap placeBlock = new HashMap(); + + Vector center = getCenter(); + if (getH() == 2) { + center = center.setY(center.getBlockY() + 0.5); + } + if (getW() == 2) { + center = center.setX(center.getBlockX() + 0.5); + } + if (getD() == 2) { + center = center.setZ(center.getBlockZ() + 0.5); + } + + IrisObject oo = new IrisObject((int) Math.ceil((w * scale) + (scale * 2)), (int) Math.ceil((h * scale) + (scale * 2)), (int) Math.ceil((d * scale) + (scale * 2))); + + for (Map.Entry entry : blocks.entrySet()) { + BlockData bd = entry.getValue(); + placeBlock.put(entry.getKey().clone().add(HALF).subtract(center) + .multiply(scale).add(sm1).toBlockVector(), bd); + } + + for (Map.Entry entry : placeBlock.entrySet()) { + BlockVector v = entry.getKey(); + if (scale > 1) { + for (BlockVector vec : blocksBetweenTwoPoints(v.clone().add(center), v.clone().add(center).add(sm1))) { + oo.getBlocks().put(vec, entry.getValue()); + } + } else { + oo.setUnsigned(v.getBlockX(), v.getBlockY(), v.getBlockZ(), entry.getValue()); + } + } + + if (scale > 1) { + switch (interpolation) { + case TRILINEAR -> oo.trilinear((int) Math.round(scale)); + case TRICUBIC -> oo.tricubic((int) Math.round(scale)); + case TRIHERMITE -> oo.trihermite((int) Math.round(scale)); + } + } + + return oo; + } + + public void trilinear(int rad) { + KMap v = getBlocks().copy(); + KMap b = new KMap<>(); + BlockVector min = getAABB().minbv(); + BlockVector max = getAABB().maxbv(); + + for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { + for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { + for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { + if (IrisInterpolation.getTrilinear(x, y, z, rad, (xx, yy, zz) -> { + BlockData data = v.get(new BlockVector((int) xx, (int) yy, (int) zz)); + + if (data == null || data.getMaterial().isAir()) { + return 0; + } + + return 1; + }) >= 0.5) { + b.put(new BlockVector(x, y, z), nearestBlockData(x, y, z)); + } else { + b.put(new BlockVector(x, y, z), AIR); + } + } + } + } + + blocks = b; + } + + public void tricubic(int rad) { + KMap v = getBlocks().copy(); + KMap b = new KMap<>(); + BlockVector min = getAABB().minbv(); + BlockVector max = getAABB().maxbv(); + + for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { + for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { + for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { + if (IrisInterpolation.getTricubic(x, y, z, rad, (xx, yy, zz) -> { + BlockData data = v.get(new BlockVector((int) xx, (int) yy, (int) zz)); + + if (data == null || data.getMaterial().isAir()) { + return 0; + } + + return 1; + }) >= 0.5) { + b.put(new BlockVector(x, y, z), nearestBlockData(x, y, z)); + } else { + b.put(new BlockVector(x, y, z), AIR); + } + } + } + } + + blocks = b; + } + + public void trihermite(int rad) { + trihermite(rad, 0D, 0D); + } + + public void trihermite(int rad, double tension, double bias) { + KMap v = getBlocks().copy(); + KMap b = new KMap<>(); + BlockVector min = getAABB().minbv(); + BlockVector max = getAABB().maxbv(); + + for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { + for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { + for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { + if (IrisInterpolation.getTrihermite(x, y, z, rad, (xx, yy, zz) -> { + BlockData data = v.get(new BlockVector((int) xx, (int) yy, (int) zz)); + + if (data == null || data.getMaterial().isAir()) { + return 0; + } + + return 1; + }, tension, bias) >= 0.5) { + b.put(new BlockVector(x, y, z), nearestBlockData(x, y, z)); + } else { + b.put(new BlockVector(x, y, z), AIR); + } + } + } + } + + blocks = b; + } + + private BlockData nearestBlockData(int x, int y, int z) { + BlockVector vv = new BlockVector(x, y, z); + BlockData r = getBlocks().get(vv); + + if (r != null && !r.getMaterial().isAir()) { + return r; + } + + double d = Double.MAX_VALUE; + + for (Map.Entry entry : blocks.entrySet()) { + BlockData dat = entry.getValue(); + + if (dat.getMaterial().isAir()) { + continue; + } + + double dx = entry.getKey().distanceSquared(vv); + + if (dx < d) { + d = dx; + r = dat; + } + } + + return r; + } + + public int volume() { + return blocks.size(); + } + + @Override + public String getFolderName() { + return "objects"; + } + + @Override + public String getTypeName() { + return "Object"; + } + + @Override + public void scanForErrors(JSONObject p, VolmitSender sender) { + } +} diff --git a/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java b/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java index 77e46d586..71f3e6298 100644 --- a/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java +++ b/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java @@ -1,426 +1,426 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine.platform; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; -import com.volmit.iris.core.nms.v19_4.CustomBiomeSource; -import com.volmit.iris.core.service.StudioSVC; -import com.volmit.iris.engine.IrisEngine; -import com.volmit.iris.engine.data.chunk.TerrainChunk; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.engine.framework.EngineTarget; -import com.volmit.iris.engine.object.IrisDimension; -import com.volmit.iris.engine.object.IrisWorld; -import com.volmit.iris.engine.object.StudioMode; -import com.volmit.iris.engine.platform.studio.StudioGenerator; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.data.IrisBiomeStorage; -import com.volmit.iris.util.hunk.Hunk; -import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder; -import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder; -import com.volmit.iris.util.io.ReactiveFolder; -import com.volmit.iris.util.scheduling.ChronoLatch; -import com.volmit.iris.util.scheduling.J; -import com.volmit.iris.util.scheduling.Looper; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.Setter; -import net.minecraft.server.level.ServerLevel; -import org.bukkit.*; -import org.bukkit.block.Biome; -import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.world.WorldInitEvent; -import org.bukkit.generator.BiomeProvider; -import org.bukkit.generator.BlockPopulator; -import org.bukkit.generator.ChunkGenerator; -import org.bukkit.generator.WorldInfo; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import sun.misc.Unsafe; - -import java.io.File; -import java.lang.reflect.Field; -import java.util.List; -import java.util.Random; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Consumer; - -@EqualsAndHashCode(callSuper = true) -@Data -public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChunkGenerator, Listener { - private static final int LOAD_LOCKS = Runtime.getRuntime().availableProcessors() * 4; - private final Semaphore loadLock; - private final IrisWorld world; - private final File dataLocation; - private final String dimensionKey; - private final ReactiveFolder folder; - private final ReentrantLock lock = new ReentrantLock(); - private final KList populators; - private final ChronoLatch hotloadChecker; - private final AtomicBoolean setup; - private final boolean studio; - private final AtomicInteger a = new AtomicInteger(0); - private Engine engine; - private Looper hotloader; - private StudioMode lastMode; - private DummyBiomeProvider dummyBiomeProvider; - @Setter - private StudioGenerator studioGenerator; - - private boolean initialized = false; - - public BukkitChunkGenerator(IrisWorld world, boolean studio, File dataLocation, String dimensionKey) { - setup = new AtomicBoolean(false); - studioGenerator = null; - dummyBiomeProvider = new DummyBiomeProvider(); - populators = new KList<>(); - loadLock = new Semaphore(LOAD_LOCKS); - this.world = world; - this.hotloadChecker = new ChronoLatch(1000, false); - this.studio = studio; - this.dataLocation = dataLocation; - this.dimensionKey = dimensionKey; - this.folder = new ReactiveFolder(dataLocation, (_a, _b, _c) -> hotload()); - Bukkit.getServer().getPluginManager().registerEvents(this, Iris.instance); - } - - private static Field getField(Class clazz, String fieldName) - throws NoSuchFieldException { - try { - return clazz.getDeclaredField(fieldName); - } catch (NoSuchFieldException e) { - Class superClass = clazz.getSuperclass(); - if (superClass == null) { - throw e; - } else { - return getField(superClass, fieldName); - } - } - } - - @EventHandler - public void onWorldInit(WorldInitEvent event) { - try { - if(!initialized) { - world.setRawWorldSeed(event.getWorld().getSeed()); - if (world.name().equals(event.getWorld().getName())) { - ServerLevel serverLevel = ((CraftWorld) event.getWorld()).getHandle(); - Engine engine = getEngine(event.getWorld()); - Class clazz = serverLevel.getChunkSource().chunkMap.generator.getClass(); - Field biomeSource = getField(clazz, "b"); - biomeSource.setAccessible(true); - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - Unsafe unsafe = (Unsafe) unsafeField.get(null); - CustomBiomeSource customBiomeSource = new CustomBiomeSource(event.getWorld().getSeed(), engine, event.getWorld()); - unsafe.putObject(biomeSource.get(serverLevel.getChunkSource().chunkMap.generator), unsafe.objectFieldOffset(biomeSource), customBiomeSource); - biomeSource.set(serverLevel.getChunkSource().chunkMap.generator, customBiomeSource); - Iris.info("Injected Iris Biome Source into " + event.getWorld().getName()); - initialized = true; - } - } - } catch (Throwable e) { - e.printStackTrace(); - } - } - - private void setupEngine() { - IrisData data = IrisData.get(dataLocation); - IrisDimension dimension = data.getDimensionLoader().load(dimensionKey); - - if (dimension == null) { - Iris.error("Oh No! There's no pack in " + data.getDataFolder().getPath() + " or... there's no dimension for the key " + dimensionKey); - IrisDimension test = IrisData.loadAnyDimension(dimensionKey); - - if (test != null) { - Iris.warn("Looks like " + dimensionKey + " exists in " + test.getLoadFile().getPath() + " "); - Iris.service(StudioSVC.class).installIntoWorld(Iris.getSender(), dimensionKey, dataLocation.getParentFile().getParentFile()); - Iris.warn("Attempted to install into " + data.getDataFolder().getPath()); - data.dump(); - data.clearLists(); - test = data.getDimensionLoader().load(dimensionKey); - - if (test != null) { - Iris.success("Woo! Patched the Engine!"); - dimension = test; - } else { - Iris.error("Failed to patch dimension!"); - throw new RuntimeException("Missing Dimension: " + dimensionKey); - } - } else { - Iris.error("Nope, you don't have an installation containing " + dimensionKey + " try downloading it?"); - throw new RuntimeException("Missing Dimension: " + dimensionKey); - } - } - - lastMode = StudioMode.NORMAL; - engine = new IrisEngine(new EngineTarget(world, dimension, data), studio); - populators.clear(); - } - - @Override - public void injectChunkReplacement(World world, int x, int z, Consumer jobs) { - try { - loadLock.acquire(); - IrisBiomeStorage st = new IrisBiomeStorage(); - TerrainChunk tc = TerrainChunk.createUnsafe(world, st); - Hunk blocks = Hunk.view(tc); - Hunk biomes = Hunk.view(tc, tc.getMinHeight(), tc.getMaxHeight()); - this.world.bind(world); - getEngine().generate(x << 4, z << 4, blocks, biomes, true); - Iris.debug("Regenerated " + x + " " + z); - int t = 0; - for (int i = getEngine().getHeight() >> 4; i >= 0; i--) { - if (!world.isChunkLoaded(x, z)) { - continue; - } - - Chunk c = world.getChunkAt(x, z); - for (Entity ee : c.getEntities()) { - if (ee instanceof Player) { - continue; - } - - J.s(ee::remove); - } - - J.s(() -> engine.getWorldManager().onChunkLoad(c, false)); - - int finalI = i; - jobs.accept(() -> { - - for (int xx = 0; xx < 16; xx++) { - for (int yy = 0; yy < 16; yy++) { - for (int zz = 0; zz < 16; zz++) { - if (yy + (finalI << 4) >= engine.getHeight() || yy + (finalI << 4) < 0) { - continue; - } - c.getBlock(xx, yy + (finalI << 4) + world.getMinHeight(), zz) - .setBlockData(tc.getBlockData(xx, yy + (finalI << 4) + world.getMinHeight(), zz), false); - } - } - } - }); - } - - loadLock.release(); - } catch (Throwable e) { - loadLock.release(); - Iris.error("======================================"); - e.printStackTrace(); - Iris.reportErrorChunk(x, z, e, "CHUNK"); - Iris.error("======================================"); - - ChunkData d = Bukkit.createChunkData(world); - - for (int i = 0; i < 16; i++) { - for (int j = 0; j < 16; j++) { - d.setBlock(i, 0, j, Material.RED_GLAZED_TERRACOTTA.createBlockData()); - } - } - } - } - - private Engine getEngine(WorldInfo world) { - if (setup.get()) { - return getEngine(); - } - - lock.lock(); - - if (setup.get()) { - return getEngine(); - } - - - setup.set(true); - getWorld().setRawWorldSeed(world.getSeed()); - setupEngine(); - this.hotloader = studio ? new Looper() { - @Override - protected long loop() { - if (hotloadChecker.flip()) { - folder.check(); - } - - return 250; - } - } : null; - - if (studio) { - hotloader.setPriority(Thread.MIN_PRIORITY); - hotloader.start(); - hotloader.setName(getTarget().getWorld().name() + " Hotloader"); - } - - lock.unlock(); - - return engine; - } - - @Override - public void close() { - withExclusiveControl(() -> { - if (isStudio()) { - hotloader.interrupt(); - } - - getEngine().close(); - folder.clear(); - populators.clear(); - - }); - } - - @Override - public boolean isStudio() { - return studio; - } - - @Override - public void hotload() { - if (!isStudio()) { - return; - } - - withExclusiveControl(() -> getEngine().hotload()); - } - - public void withExclusiveControl(Runnable r) { - J.a(() -> { - try { - loadLock.acquire(LOAD_LOCKS); - r.run(); - loadLock.release(LOAD_LOCKS); - } catch (Throwable e) { - Iris.reportError(e); - } - }); - } - - @Override - public void touch(World world) { - getEngine(world); - } - - @Override - public void generateNoise(@NotNull WorldInfo world, @NotNull Random random, int x, int z, @NotNull ChunkGenerator.ChunkData d) { - try { - getEngine(world); - computeStudioGenerator(); - TerrainChunk tc = TerrainChunk.create(d, new IrisBiomeStorage()); - this.world.bind(world); - if (studioGenerator != null) { - studioGenerator.generateChunk(getEngine(), tc, x, z); - } else { - ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc); - BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight()); - getEngine().generate(x << 4, z << 4, blocks, biomes, false); - blocks.apply(); - biomes.apply(); - } - - Iris.debug("Generated " + x + " " + z); - } catch (Throwable e) { - Iris.error("======================================"); - e.printStackTrace(); - Iris.reportErrorChunk(x, z, e, "CHUNK"); - Iris.error("======================================"); - - for (int i = 0; i < 16; i++) { - for (int j = 0; j < 16; j++) { - d.setBlock(i, 0, j, Material.RED_GLAZED_TERRACOTTA.createBlockData()); - } - } - } - } - - @Override - public int getBaseHeight(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull HeightMap heightMap) { - return 4; - } - - private void computeStudioGenerator() { - if (!getEngine().getDimension().getStudioMode().equals(lastMode)) { - lastMode = getEngine().getDimension().getStudioMode(); - getEngine().getDimension().getStudioMode().inject(this); - } - } - - @NotNull - @Override - public List getDefaultPopulators(@NotNull World world) { - return populators; - } - - @Override - public boolean isParallelCapable() { - return true; - } - - @Override - public boolean shouldGenerateCaves() { - return false; - } - - @Override - public boolean shouldGenerateDecorations() { - return false; - } - - @Override - public boolean shouldGenerateMobs() { - return false; - } - - @Override - public boolean shouldGenerateStructures() { - return false; - } - - @Override - public boolean shouldGenerateNoise() { - return false; - } - - @Override - public boolean shouldGenerateSurface() { - return false; - } - - @Override - public boolean shouldGenerateBedrock() { - return false; - } - - @Nullable - @Override - public BiomeProvider getDefaultBiomeProvider(@NotNull WorldInfo worldInfo) { - return dummyBiomeProvider; - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.engine.platform; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.nms.v19_4.CustomBiomeSource; +import com.volmit.iris.core.service.StudioSVC; +import com.volmit.iris.engine.IrisEngine; +import com.volmit.iris.engine.data.chunk.TerrainChunk; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.framework.EngineTarget; +import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.engine.object.IrisWorld; +import com.volmit.iris.engine.object.StudioMode; +import com.volmit.iris.engine.platform.studio.StudioGenerator; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.data.IrisBiomeStorage; +import com.volmit.iris.util.hunk.Hunk; +import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder; +import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder; +import com.volmit.iris.util.io.ReactiveFolder; +import com.volmit.iris.util.scheduling.ChronoLatch; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.Looper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Setter; +import net.minecraft.server.level.ServerLevel; +import org.bukkit.*; +import org.bukkit.block.Biome; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldInitEvent; +import org.bukkit.generator.BiomeProvider; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.generator.WorldInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import sun.misc.Unsafe; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; + +@EqualsAndHashCode(callSuper = true) +@Data +public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChunkGenerator, Listener { + private static final int LOAD_LOCKS = Runtime.getRuntime().availableProcessors() * 4; + private final Semaphore loadLock; + private final IrisWorld world; + private final File dataLocation; + private final String dimensionKey; + private final ReactiveFolder folder; + private final ReentrantLock lock = new ReentrantLock(); + private final KList populators; + private final ChronoLatch hotloadChecker; + private final AtomicBoolean setup; + private final boolean studio; + private final AtomicInteger a = new AtomicInteger(0); + private Engine engine; + private Looper hotloader; + private StudioMode lastMode; + private DummyBiomeProvider dummyBiomeProvider; + @Setter + private StudioGenerator studioGenerator; + + private boolean initialized = false; + + public BukkitChunkGenerator(IrisWorld world, boolean studio, File dataLocation, String dimensionKey) { + setup = new AtomicBoolean(false); + studioGenerator = null; + dummyBiomeProvider = new DummyBiomeProvider(); + populators = new KList<>(); + loadLock = new Semaphore(LOAD_LOCKS); + this.world = world; + this.hotloadChecker = new ChronoLatch(1000, false); + this.studio = studio; + this.dataLocation = dataLocation; + this.dimensionKey = dimensionKey; + this.folder = new ReactiveFolder(dataLocation, (_a, _b, _c) -> hotload()); + Bukkit.getServer().getPluginManager().registerEvents(this, Iris.instance); + } + + private static Field getField(Class clazz, String fieldName) + throws NoSuchFieldException { + try { + return clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException e) { + Class superClass = clazz.getSuperclass(); + if (superClass == null) { + throw e; + } else { + return getField(superClass, fieldName); + } + } + } + + @EventHandler + public void onWorldInit(WorldInitEvent event) { + try { + if (!initialized) { + world.setRawWorldSeed(event.getWorld().getSeed()); + if (world.name().equals(event.getWorld().getName())) { + ServerLevel serverLevel = ((CraftWorld) event.getWorld()).getHandle(); + Engine engine = getEngine(event.getWorld()); + Class clazz = serverLevel.getChunkSource().chunkMap.generator.getClass(); + Field biomeSource = getField(clazz, "b"); + biomeSource.setAccessible(true); + Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + Unsafe unsafe = (Unsafe) unsafeField.get(null); + CustomBiomeSource customBiomeSource = new CustomBiomeSource(event.getWorld().getSeed(), engine, event.getWorld()); + unsafe.putObject(biomeSource.get(serverLevel.getChunkSource().chunkMap.generator), unsafe.objectFieldOffset(biomeSource), customBiomeSource); + biomeSource.set(serverLevel.getChunkSource().chunkMap.generator, customBiomeSource); + Iris.info("Injected Iris Biome Source into " + event.getWorld().getName()); + initialized = true; + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + + private void setupEngine() { + IrisData data = IrisData.get(dataLocation); + IrisDimension dimension = data.getDimensionLoader().load(dimensionKey); + + if (dimension == null) { + Iris.error("Oh No! There's no pack in " + data.getDataFolder().getPath() + " or... there's no dimension for the key " + dimensionKey); + IrisDimension test = IrisData.loadAnyDimension(dimensionKey); + + if (test != null) { + Iris.warn("Looks like " + dimensionKey + " exists in " + test.getLoadFile().getPath() + " "); + Iris.service(StudioSVC.class).installIntoWorld(Iris.getSender(), dimensionKey, dataLocation.getParentFile().getParentFile()); + Iris.warn("Attempted to install into " + data.getDataFolder().getPath()); + data.dump(); + data.clearLists(); + test = data.getDimensionLoader().load(dimensionKey); + + if (test != null) { + Iris.success("Woo! Patched the Engine!"); + dimension = test; + } else { + Iris.error("Failed to patch dimension!"); + throw new RuntimeException("Missing Dimension: " + dimensionKey); + } + } else { + Iris.error("Nope, you don't have an installation containing " + dimensionKey + " try downloading it?"); + throw new RuntimeException("Missing Dimension: " + dimensionKey); + } + } + + lastMode = StudioMode.NORMAL; + engine = new IrisEngine(new EngineTarget(world, dimension, data), studio); + populators.clear(); + } + + @Override + public void injectChunkReplacement(World world, int x, int z, Consumer jobs) { + try { + loadLock.acquire(); + IrisBiomeStorage st = new IrisBiomeStorage(); + TerrainChunk tc = TerrainChunk.createUnsafe(world, st); + Hunk blocks = Hunk.view(tc); + Hunk biomes = Hunk.view(tc, tc.getMinHeight(), tc.getMaxHeight()); + this.world.bind(world); + getEngine().generate(x << 4, z << 4, blocks, biomes, true); + Iris.debug("Regenerated " + x + " " + z); + int t = 0; + for (int i = getEngine().getHeight() >> 4; i >= 0; i--) { + if (!world.isChunkLoaded(x, z)) { + continue; + } + + Chunk c = world.getChunkAt(x, z); + for (Entity ee : c.getEntities()) { + if (ee instanceof Player) { + continue; + } + + J.s(ee::remove); + } + + J.s(() -> engine.getWorldManager().onChunkLoad(c, false)); + + int finalI = i; + jobs.accept(() -> { + + for (int xx = 0; xx < 16; xx++) { + for (int yy = 0; yy < 16; yy++) { + for (int zz = 0; zz < 16; zz++) { + if (yy + (finalI << 4) >= engine.getHeight() || yy + (finalI << 4) < 0) { + continue; + } + c.getBlock(xx, yy + (finalI << 4) + world.getMinHeight(), zz) + .setBlockData(tc.getBlockData(xx, yy + (finalI << 4) + world.getMinHeight(), zz), false); + } + } + } + }); + } + + loadLock.release(); + } catch (Throwable e) { + loadLock.release(); + Iris.error("======================================"); + e.printStackTrace(); + Iris.reportErrorChunk(x, z, e, "CHUNK"); + Iris.error("======================================"); + + ChunkData d = Bukkit.createChunkData(world); + + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + d.setBlock(i, 0, j, Material.RED_GLAZED_TERRACOTTA.createBlockData()); + } + } + } + } + + private Engine getEngine(WorldInfo world) { + if (setup.get()) { + return getEngine(); + } + + lock.lock(); + + if (setup.get()) { + return getEngine(); + } + + + setup.set(true); + getWorld().setRawWorldSeed(world.getSeed()); + setupEngine(); + this.hotloader = studio ? new Looper() { + @Override + protected long loop() { + if (hotloadChecker.flip()) { + folder.check(); + } + + return 250; + } + } : null; + + if (studio) { + hotloader.setPriority(Thread.MIN_PRIORITY); + hotloader.start(); + hotloader.setName(getTarget().getWorld().name() + " Hotloader"); + } + + lock.unlock(); + + return engine; + } + + @Override + public void close() { + withExclusiveControl(() -> { + if (isStudio()) { + hotloader.interrupt(); + } + + getEngine().close(); + folder.clear(); + populators.clear(); + + }); + } + + @Override + public boolean isStudio() { + return studio; + } + + @Override + public void hotload() { + if (!isStudio()) { + return; + } + + withExclusiveControl(() -> getEngine().hotload()); + } + + public void withExclusiveControl(Runnable r) { + J.a(() -> { + try { + loadLock.acquire(LOAD_LOCKS); + r.run(); + loadLock.release(LOAD_LOCKS); + } catch (Throwable e) { + Iris.reportError(e); + } + }); + } + + @Override + public void touch(World world) { + getEngine(world); + } + + @Override + public void generateNoise(@NotNull WorldInfo world, @NotNull Random random, int x, int z, @NotNull ChunkGenerator.ChunkData d) { + try { + getEngine(world); + computeStudioGenerator(); + TerrainChunk tc = TerrainChunk.create(d, new IrisBiomeStorage()); + this.world.bind(world); + if (studioGenerator != null) { + studioGenerator.generateChunk(getEngine(), tc, x, z); + } else { + ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc); + BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight()); + getEngine().generate(x << 4, z << 4, blocks, biomes, false); + blocks.apply(); + biomes.apply(); + } + + Iris.debug("Generated " + x + " " + z); + } catch (Throwable e) { + Iris.error("======================================"); + e.printStackTrace(); + Iris.reportErrorChunk(x, z, e, "CHUNK"); + Iris.error("======================================"); + + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + d.setBlock(i, 0, j, Material.RED_GLAZED_TERRACOTTA.createBlockData()); + } + } + } + } + + @Override + public int getBaseHeight(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull HeightMap heightMap) { + return 4; + } + + private void computeStudioGenerator() { + if (!getEngine().getDimension().getStudioMode().equals(lastMode)) { + lastMode = getEngine().getDimension().getStudioMode(); + getEngine().getDimension().getStudioMode().inject(this); + } + } + + @NotNull + @Override + public List getDefaultPopulators(@NotNull World world) { + return populators; + } + + @Override + public boolean isParallelCapable() { + return true; + } + + @Override + public boolean shouldGenerateCaves() { + return false; + } + + @Override + public boolean shouldGenerateDecorations() { + return false; + } + + @Override + public boolean shouldGenerateMobs() { + return false; + } + + @Override + public boolean shouldGenerateStructures() { + return false; + } + + @Override + public boolean shouldGenerateNoise() { + return false; + } + + @Override + public boolean shouldGenerateSurface() { + return false; + } + + @Override + public boolean shouldGenerateBedrock() { + return false; + } + + @Nullable + @Override + public BiomeProvider getDefaultBiomeProvider(@NotNull WorldInfo worldInfo) { + return dummyBiomeProvider; + } +} diff --git a/src/main/java/com/volmit/iris/util/decree/context/VectorContextHandler.java b/src/main/java/com/volmit/iris/util/decree/context/VectorContextHandler.java index e96d6f0fb..84247f326 100644 --- a/src/main/java/com/volmit/iris/util/decree/context/VectorContextHandler.java +++ b/src/main/java/com/volmit/iris/util/decree/context/VectorContextHandler.java @@ -1,39 +1,37 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.util.decree.context; - -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.object.IrisBiome; -import com.volmit.iris.util.decree.DecreeContextHandler; -import com.volmit.iris.util.plugin.VolmitSender; -import org.bukkit.util.Vector; - -public class VectorContextHandler implements DecreeContextHandler { - public Class getType() { - return Vector.class; - } - - public Vector handle(VolmitSender sender) { - if (sender.isPlayer()) { - return sender.player().getLocation().toVector(); - } - - return null; - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.util.decree.context; + +import com.volmit.iris.util.decree.DecreeContextHandler; +import com.volmit.iris.util.plugin.VolmitSender; +import org.bukkit.util.Vector; + +public class VectorContextHandler implements DecreeContextHandler { + public Class getType() { + return Vector.class; + } + + public Vector handle(VolmitSender sender) { + if (sender.isPlayer()) { + return sender.player().getLocation().toVector(); + } + + return null; + } +} diff --git a/src/main/java/com/volmit/iris/util/reflect/WrappedField.java b/src/main/java/com/volmit/iris/util/reflect/WrappedField.java index 4428c608b..3249fa07f 100644 --- a/src/main/java/com/volmit/iris/util/reflect/WrappedField.java +++ b/src/main/java/com/volmit/iris/util/reflect/WrappedField.java @@ -1,42 +1,42 @@ -package com.volmit.iris.util.reflect; - -import com.volmit.iris.Iris; - -import java.lang.reflect.Field; - -public class WrappedField { - - private final Field field; - - public WrappedField(Class origin, String methodName) { - Field f = null; - try { - f = origin.getDeclaredField(methodName); - f.setAccessible(true); - } catch(NoSuchFieldException e) { - Iris.error("Failed to created WrappedField %s#%s: %s%s", origin.getSimpleName(), methodName, e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage()); - } - this.field = f; - } - - public T get() { - return get(null); - } - - public T get(C instance) { - if(field == null) { - return null; - } - - try { - return (T)field.get(instance); - } catch(IllegalAccessException e) { - Iris.error("Failed to get WrappedField %s#%s: %s%s", field.getDeclaringClass().getSimpleName(), field.getName(), e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage()); - return null; - } - } - - public boolean hasFailed() { - return field == null; - } -} +package com.volmit.iris.util.reflect; + +import com.volmit.iris.Iris; + +import java.lang.reflect.Field; + +public class WrappedField { + + private final Field field; + + public WrappedField(Class origin, String methodName) { + Field f = null; + try { + f = origin.getDeclaredField(methodName); + f.setAccessible(true); + } catch (NoSuchFieldException e) { + Iris.error("Failed to created WrappedField %s#%s: %s%s", origin.getSimpleName(), methodName, e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage()); + } + this.field = f; + } + + public T get() { + return get(null); + } + + public T get(C instance) { + if (field == null) { + return null; + } + + try { + return (T) field.get(instance); + } catch (IllegalAccessException e) { + Iris.error("Failed to get WrappedField %s#%s: %s%s", field.getDeclaringClass().getSimpleName(), field.getName(), e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage()); + return null; + } + } + + public boolean hasFailed() { + return field == null; + } +} diff --git a/src/main/java/com/volmit/iris/util/reflect/WrappedReturningMethod.java b/src/main/java/com/volmit/iris/util/reflect/WrappedReturningMethod.java index 711e32bb4..338044781 100644 --- a/src/main/java/com/volmit/iris/util/reflect/WrappedReturningMethod.java +++ b/src/main/java/com/volmit/iris/util/reflect/WrappedReturningMethod.java @@ -1,39 +1,39 @@ -package com.volmit.iris.util.reflect; - -import com.volmit.iris.Iris; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -public final class WrappedReturningMethod { - - private final Method method; - - public WrappedReturningMethod(Class origin, String methodName, Class... paramTypes) { - Method m = null; - try { - m = origin.getDeclaredMethod(methodName, paramTypes); - m.setAccessible(true); - } catch(NoSuchMethodException e) { - Iris.error("Failed to created WrappedMethod %s#%s: %s%s", origin.getSimpleName(), methodName, e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage()); - } - this.method = m; - } - - public R invoke(Object... args) { - return invoke(null, args); - } - - public R invoke(C instance, Object... args) { - if(method == null) { - return null; - } - - try { - return (R)method.invoke(instance, args); - } catch(InvocationTargetException | IllegalAccessException e) { - Iris.error("Failed to invoke WrappedMethod %s#%s: %s%s", method.getDeclaringClass().getSimpleName(), method.getName(), e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage()); - return null; - } - } -} +package com.volmit.iris.util.reflect; + +import com.volmit.iris.Iris; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public final class WrappedReturningMethod { + + private final Method method; + + public WrappedReturningMethod(Class origin, String methodName, Class... paramTypes) { + Method m = null; + try { + m = origin.getDeclaredMethod(methodName, paramTypes); + m.setAccessible(true); + } catch (NoSuchMethodException e) { + Iris.error("Failed to created WrappedMethod %s#%s: %s%s", origin.getSimpleName(), methodName, e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage()); + } + this.method = m; + } + + public R invoke(Object... args) { + return invoke(null, args); + } + + public R invoke(C instance, Object... args) { + if (method == null) { + return null; + } + + try { + return (R) method.invoke(instance, args); + } catch (InvocationTargetException | IllegalAccessException e) { + Iris.error("Failed to invoke WrappedMethod %s#%s: %s%s", method.getDeclaringClass().getSimpleName(), method.getName(), e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage()); + return null; + } + } +} diff --git a/src/main/java/com/volmit/iris/util/uniques/UniqueRenderer.java b/src/main/java/com/volmit/iris/util/uniques/UniqueRenderer.java index 6bcc6f70d..633b52e1c 100644 --- a/src/main/java/com/volmit/iris/util/uniques/UniqueRenderer.java +++ b/src/main/java/com/volmit/iris/util/uniques/UniqueRenderer.java @@ -1,289 +1,289 @@ -package com.volmit.iris.util.uniques; - -import com.volmit.iris.engine.object.NoiseStyle; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.function.NoiseProvider; -import com.volmit.iris.util.interpolation.InterpolationMethod; -import com.volmit.iris.util.interpolation.IrisInterpolation; -import com.volmit.iris.util.math.RNG; -import com.volmit.iris.util.noise.CNG; -import com.volmit.iris.util.parallel.BurstExecutor; -import com.volmit.iris.util.scheduling.ChronoLatch; -import com.volmit.iris.util.scheduling.J; -import com.volmit.iris.util.scheduling.PrecisionStopwatch; -import com.volmit.iris.util.stream.ProceduralStream; -import com.volmit.iris.util.uniques.features.*; - -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicInteger; - -public class UniqueRenderer { - static final List backgrounds = List.of(new UFWarpedBackground()); - static final List interpolators = List.of(new UFInterpolator(), new UFNOOP()); - static final List features = List.of(new UFWarpedLines(), new UFWarpedDisc(), new UFWarpedDots(), new UFWarpedCircle()); - static UniqueRenderer renderer; - private final String seed; - private final ProceduralStream spatialSeed; - private final int width; - private final int height; - private final KMap writing = new KMap<>(); - private final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); - int cores = Runtime.getRuntime().availableProcessors(); - private final KList sortedStyles = new KList(); - private final KList sortedInterpolators = new KList(); - - public UniqueRenderer(String seed, int width, int height) { - renderer = this; - computeNoiseStyles(3000, 2); - computeInterpolationMethods(3000, 2); - this.seed = seed; - this.width = width; - this.height = height; - spatialSeed = NoiseStyle.FRACTAL_WATER.stream(new RNG(seed)).convert((d) -> new RNG(Math.round(seed.hashCode() + (d * 934321234D)))); - new Thread(() -> { - while (true) { - J.sleep(5000); - - if (!writing.isEmpty()) { - System.out.println(Form.repeat("\n", 60)); - System.out.println(Form.memSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(), 2) + " of " + Form.memSize(Runtime.getRuntime().totalMemory(), 2)); - KMap c = writing.copy(); - - for (String i : writing.k().sort()) { - String prog = ""; - String f = writing.get(i); - - if (f.contains("%")) { - String v = f.split("\\Q%\\E")[0]; - try { - prog = drawProgress(Double.valueOf(Integer.parseInt(v.substring(v.length() - 2))) / 100D, 30); - } catch (Throwable e) { - try { - prog = drawProgress(Double.valueOf(Integer.parseInt(v.substring(v.length() - 1))) / 100D, 30); - } catch (Throwable ee) { - try { - prog = drawProgress(Double.valueOf(Integer.parseInt(v.substring(v.length() - 3))) / 100D, 30); - } catch (Throwable eee) { - - } - } - } - } - - System.out.println(prog + " " + i + " => " + f); - } - } - } - }).start(); - } - - public UMeta renderFrameBuffer(long id, double t) { - UMeta meta = new UMeta(); - meta.setId(id); - meta.setTime(t); - RNG rng = spatialSeed.get(id, id + ((id * id) % (id / 3D))); - RNG rngbg = spatialSeed.get(id, -id + ((id * id) % (id / 4D))); - BufferedImage buf = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - BufferedImage bufFG = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - UImage image = new UBufferedImage(buf); - UImage imageFG = new UBufferedImage(bufFG); - ChronoLatch cl = new ChronoLatch(250); - UFeature background = rng.pick(backgrounds); - UFeature interpolator = rng.pick(interpolators); - UFeature foreground = rng.pick(features); - UFeature foregroundInterpolator = rng.pick(interpolators); - UFeatureMeta backgroundMeta = new UFeatureMeta(); - UFeatureMeta foregroundMeta = new UFeatureMeta(); - UFeatureMeta backgroundInterpolatorMeta = new UFeatureMeta(); - UFeatureMeta foregroundInterpolatorMeta = new UFeatureMeta(); - background.render(image, rngbg, t, (p) -> { - if (cl.flip()) { - writing.put("#" + id + ":" + t, Form.pc(p / 4D) + " [" + background.getClass().getSimpleName() + " " + Form.pc(p) + "]"); - } - }, backgroundMeta); - backgroundMeta.setFeature(background.getClass().getSimpleName()); - meta.registerFeature("background", backgroundMeta); - interpolator.render(image, rng, t, (p) -> { - if (cl.flip()) { - writing.put("#" + id + ":" + t, Form.pc(0.25 + (p / 4d)) + " [" + interpolator.getClass().getSimpleName() + " " + Form.pc(p) + "]"); - } - }, backgroundInterpolatorMeta); - backgroundInterpolatorMeta.setFeature(interpolator.getClass().getSimpleName()); - meta.registerFeature("backgroundInterpolator", backgroundInterpolatorMeta); - foreground.render(imageFG, rng, t, (p) -> { - if (cl.flip()) { - writing.put("#" + id + ":" + t, Form.pc(0.5 + (p / 4d)) + " [" + foreground.getClass().getSimpleName() + " " + Form.pc(p) + "]"); - } - }, foregroundMeta); - foregroundMeta.setFeature(foreground.getClass().getSimpleName()); - meta.registerFeature("foreground", foregroundMeta); - overlay(imageFG, bufFG, image); - foregroundInterpolator.render(image, rng, t, (p) -> { - if (cl.flip()) { - writing.put("#" + id + ":" + t, Form.pc(0.75 + (p / 4d)) + " [" + interpolator.getClass().getSimpleName() + " " + Form.pc(p) + "]"); - } - }, foregroundInterpolatorMeta); - foregroundInterpolatorMeta.setFeature(foregroundInterpolator.getClass().getSimpleName()); - meta.registerFeature("foregroundInterpolator", foregroundInterpolatorMeta); - overlay(imageFG, bufFG, image); - meta.setImage(buf); - writing.remove("#" + id + ":" + t); - return meta; - } - - private void overlay(UImage layer, BufferedImage layerBuf, UImage onto) { - for (int i = 0; i < onto.getWidth(); i++) { - for (int j = 0; j < onto.getHeight(); j++) { - if (layerBuf.getRGB(i, j) != 0) { - onto.set(i, j, layer.get(i, j)); - } - } - } - } - - private String drawProgress(double progress, int len) { - int max = len; - int in = (int) Math.round(progress * max); - max -= in; - - return "[" + Form.repeat("=", in) + Form.repeat(" ", max) + "]"; - } - - private void computeNoiseStyles(double time, double scope) { - List allowedStyles = new KList<>(NoiseStyle.values()); - allowedStyles.remove(NoiseStyle.FLAT); - KMap speeds = new KMap<>(); - double allocateMS = time; - double maxTestDuration = allocateMS / allowedStyles.size(); - System.out.println("Running Noise Style Benchmark for " + Form.duration(allocateMS, 0) + "."); - System.out.println("Benchmarking " + allowedStyles.size() + " + Noise Styles for " + Form.duration(maxTestDuration, 1) + " each."); - System.out.println(); - - for (NoiseStyle i : allowedStyles) { - int score = 0; - CNG cng = i.create(new RNG("renderspeedtest")); - PrecisionStopwatch p = PrecisionStopwatch.start(); - double g = 0; - while (p.getMilliseconds() < maxTestDuration) { - cng.noise(g, -g * 2); - g += 0.1; - g *= 1.25; - score++; - } - - speeds.put(i, score); - } - - for (NoiseStyle i : speeds.sortKNumber()) { - System.out.println(Form.capitalizeWords(i.name().toLowerCase(Locale.ROOT).replaceAll("\\Q_\\E", " ")) + " => " + Form.f(speeds.get(i))); - } - System.out.println(); - int takeUpTo = (int) Math.max(1, scope * speeds.size()); - System.out.println("Choosing the fastest " + Form.pc(scope) + " styles (" + takeUpTo + ")"); - - for (NoiseStyle i : speeds.sortKNumber().reverse()) { - if (takeUpTo-- <= 0) { - break; - } - - sortedStyles.add(i); - System.out.println("- " + Form.capitalizeWords(i.name().toLowerCase(Locale.ROOT).replaceAll("\\Q_\\E", " "))); - } - } - - private void computeInterpolationMethods(double time, double scope) { - List allowedStyles = new KList<>(InterpolationMethod.values()); - allowedStyles.remove(InterpolationMethod.NONE); - KMap speeds = new KMap<>(); - double allocateMS = time; - double maxTestDuration = allocateMS / allowedStyles.size(); - System.out.println("Running Interpolation Method Benchmark for " + Form.duration(allocateMS, 0) + "."); - System.out.println("Benchmarking " + allowedStyles.size() + " + Interpolation Methods for " + Form.duration(maxTestDuration, 1) + " each."); - System.out.println(); - - RNG r = new RNG("renderspeedtestinterpolation"); - CNG cng = NoiseStyle.SIMPLEX.create(r); - NoiseProvider np = (x, z) -> cng.noise(x, z); - - for (InterpolationMethod i : allowedStyles) { - int score = 0; - - PrecisionStopwatch p = PrecisionStopwatch.start(); - double g = 0; - while (p.getMilliseconds() < maxTestDuration) { - IrisInterpolation.getNoise(i, (int) g, (int) (-g * 2.225), r.d(4, 64), np); - cng.noise(g, -g * 2); - g += 1.1; - g *= 1.25; - score++; - } - - speeds.put(i, score); - } - - for (InterpolationMethod i : speeds.sortKNumber()) { - System.out.println(Form.capitalizeWords(i.name().toLowerCase(Locale.ROOT).replaceAll("\\Q_\\E", " ")) + " => " + Form.f(speeds.get(i))); - } - System.out.println(); - int takeUpTo = (int) Math.max(1, scope * speeds.size()); - System.out.println("Choosing the fastest " + Form.pc(scope) + " interpolators (" + takeUpTo + ")"); - - for (InterpolationMethod i : speeds.sortKNumber().reverse()) { - if (takeUpTo-- <= 0) { - break; - } - - sortedInterpolators.add(i); - System.out.println("- " + Form.capitalizeWords(i.name().toLowerCase(Locale.ROOT).replaceAll("\\Q_\\E", " "))); - } - } - - public void writeCollectionFrames(File folder, int fromId, int toId) { - folder.mkdirs(); - BurstExecutor burst = new BurstExecutor(executor, Math.min(toId - fromId, 1000)); - burst.setMulticore(true); - AtomicInteger ai = new AtomicInteger(0); - int max = toId - fromId; - - for (int i = fromId; i <= toId; i++) { - int ii = i; - burst.queue(() -> { - - writing.put("!#[" + fromId + "-" + toId + "] Collection", ai.get() + " of " + max + " (" + Form.pc(ai.get() / (double) max, 0) + ")"); - writeFrame(new File(folder, ii + ".png"), ii, 0); - ai.incrementAndGet(); - writing.put("!#[" + fromId + "-" + toId + "] Collection", ai.get() + " of " + max + " (" + Form.pc(ai.get() / (double) max, 0) + ")"); - }); - } - - burst.complete(); - writing.remove("!#[" + fromId + "-" + toId + "] Collection"); - } - - public void writeFrame(File destination, long id, double t) { - try { - renderFrameBuffer(id, t).export(destination); - } catch (Throwable e) { - e.printStackTrace(); - } - } - - public void report(String s) { - System.out.println(s); - } - - public KList getStyles() { - return sortedStyles; - } - - public List getInterpolators() { - return sortedInterpolators; - } -} +package com.volmit.iris.util.uniques; + +import com.volmit.iris.engine.object.NoiseStyle; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.function.NoiseProvider; +import com.volmit.iris.util.interpolation.InterpolationMethod; +import com.volmit.iris.util.interpolation.IrisInterpolation; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.noise.CNG; +import com.volmit.iris.util.parallel.BurstExecutor; +import com.volmit.iris.util.scheduling.ChronoLatch; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import com.volmit.iris.util.stream.ProceduralStream; +import com.volmit.iris.util.uniques.features.*; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +public class UniqueRenderer { + static final List backgrounds = List.of(new UFWarpedBackground()); + static final List interpolators = List.of(new UFInterpolator(), new UFNOOP()); + static final List features = List.of(new UFWarpedLines(), new UFWarpedDisc(), new UFWarpedDots(), new UFWarpedCircle()); + static UniqueRenderer renderer; + private final String seed; + private final ProceduralStream spatialSeed; + private final int width; + private final int height; + private final KMap writing = new KMap<>(); + private final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); + private final KList sortedStyles = new KList(); + private final KList sortedInterpolators = new KList(); + int cores = Runtime.getRuntime().availableProcessors(); + + public UniqueRenderer(String seed, int width, int height) { + renderer = this; + computeNoiseStyles(3000, 2); + computeInterpolationMethods(3000, 2); + this.seed = seed; + this.width = width; + this.height = height; + spatialSeed = NoiseStyle.FRACTAL_WATER.stream(new RNG(seed)).convert((d) -> new RNG(Math.round(seed.hashCode() + (d * 934321234D)))); + new Thread(() -> { + while (true) { + J.sleep(5000); + + if (!writing.isEmpty()) { + System.out.println(Form.repeat("\n", 60)); + System.out.println(Form.memSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(), 2) + " of " + Form.memSize(Runtime.getRuntime().totalMemory(), 2)); + KMap c = writing.copy(); + + for (String i : writing.k().sort()) { + String prog = ""; + String f = writing.get(i); + + if (f.contains("%")) { + String v = f.split("\\Q%\\E")[0]; + try { + prog = drawProgress(Double.valueOf(Integer.parseInt(v.substring(v.length() - 2))) / 100D, 30); + } catch (Throwable e) { + try { + prog = drawProgress(Double.valueOf(Integer.parseInt(v.substring(v.length() - 1))) / 100D, 30); + } catch (Throwable ee) { + try { + prog = drawProgress(Double.valueOf(Integer.parseInt(v.substring(v.length() - 3))) / 100D, 30); + } catch (Throwable eee) { + + } + } + } + } + + System.out.println(prog + " " + i + " => " + f); + } + } + } + }).start(); + } + + public UMeta renderFrameBuffer(long id, double t) { + UMeta meta = new UMeta(); + meta.setId(id); + meta.setTime(t); + RNG rng = spatialSeed.get(id, id + ((id * id) % (id / 3D))); + RNG rngbg = spatialSeed.get(id, -id + ((id * id) % (id / 4D))); + BufferedImage buf = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + BufferedImage bufFG = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + UImage image = new UBufferedImage(buf); + UImage imageFG = new UBufferedImage(bufFG); + ChronoLatch cl = new ChronoLatch(250); + UFeature background = rng.pick(backgrounds); + UFeature interpolator = rng.pick(interpolators); + UFeature foreground = rng.pick(features); + UFeature foregroundInterpolator = rng.pick(interpolators); + UFeatureMeta backgroundMeta = new UFeatureMeta(); + UFeatureMeta foregroundMeta = new UFeatureMeta(); + UFeatureMeta backgroundInterpolatorMeta = new UFeatureMeta(); + UFeatureMeta foregroundInterpolatorMeta = new UFeatureMeta(); + background.render(image, rngbg, t, (p) -> { + if (cl.flip()) { + writing.put("#" + id + ":" + t, Form.pc(p / 4D) + " [" + background.getClass().getSimpleName() + " " + Form.pc(p) + "]"); + } + }, backgroundMeta); + backgroundMeta.setFeature(background.getClass().getSimpleName()); + meta.registerFeature("background", backgroundMeta); + interpolator.render(image, rng, t, (p) -> { + if (cl.flip()) { + writing.put("#" + id + ":" + t, Form.pc(0.25 + (p / 4d)) + " [" + interpolator.getClass().getSimpleName() + " " + Form.pc(p) + "]"); + } + }, backgroundInterpolatorMeta); + backgroundInterpolatorMeta.setFeature(interpolator.getClass().getSimpleName()); + meta.registerFeature("backgroundInterpolator", backgroundInterpolatorMeta); + foreground.render(imageFG, rng, t, (p) -> { + if (cl.flip()) { + writing.put("#" + id + ":" + t, Form.pc(0.5 + (p / 4d)) + " [" + foreground.getClass().getSimpleName() + " " + Form.pc(p) + "]"); + } + }, foregroundMeta); + foregroundMeta.setFeature(foreground.getClass().getSimpleName()); + meta.registerFeature("foreground", foregroundMeta); + overlay(imageFG, bufFG, image); + foregroundInterpolator.render(image, rng, t, (p) -> { + if (cl.flip()) { + writing.put("#" + id + ":" + t, Form.pc(0.75 + (p / 4d)) + " [" + interpolator.getClass().getSimpleName() + " " + Form.pc(p) + "]"); + } + }, foregroundInterpolatorMeta); + foregroundInterpolatorMeta.setFeature(foregroundInterpolator.getClass().getSimpleName()); + meta.registerFeature("foregroundInterpolator", foregroundInterpolatorMeta); + overlay(imageFG, bufFG, image); + meta.setImage(buf); + writing.remove("#" + id + ":" + t); + return meta; + } + + private void overlay(UImage layer, BufferedImage layerBuf, UImage onto) { + for (int i = 0; i < onto.getWidth(); i++) { + for (int j = 0; j < onto.getHeight(); j++) { + if (layerBuf.getRGB(i, j) != 0) { + onto.set(i, j, layer.get(i, j)); + } + } + } + } + + private String drawProgress(double progress, int len) { + int max = len; + int in = (int) Math.round(progress * max); + max -= in; + + return "[" + Form.repeat("=", in) + Form.repeat(" ", max) + "]"; + } + + private void computeNoiseStyles(double time, double scope) { + List allowedStyles = new KList<>(NoiseStyle.values()); + allowedStyles.remove(NoiseStyle.FLAT); + KMap speeds = new KMap<>(); + double allocateMS = time; + double maxTestDuration = allocateMS / allowedStyles.size(); + System.out.println("Running Noise Style Benchmark for " + Form.duration(allocateMS, 0) + "."); + System.out.println("Benchmarking " + allowedStyles.size() + " + Noise Styles for " + Form.duration(maxTestDuration, 1) + " each."); + System.out.println(); + + for (NoiseStyle i : allowedStyles) { + int score = 0; + CNG cng = i.create(new RNG("renderspeedtest")); + PrecisionStopwatch p = PrecisionStopwatch.start(); + double g = 0; + while (p.getMilliseconds() < maxTestDuration) { + cng.noise(g, -g * 2); + g += 0.1; + g *= 1.25; + score++; + } + + speeds.put(i, score); + } + + for (NoiseStyle i : speeds.sortKNumber()) { + System.out.println(Form.capitalizeWords(i.name().toLowerCase(Locale.ROOT).replaceAll("\\Q_\\E", " ")) + " => " + Form.f(speeds.get(i))); + } + System.out.println(); + int takeUpTo = (int) Math.max(1, scope * speeds.size()); + System.out.println("Choosing the fastest " + Form.pc(scope) + " styles (" + takeUpTo + ")"); + + for (NoiseStyle i : speeds.sortKNumber().reverse()) { + if (takeUpTo-- <= 0) { + break; + } + + sortedStyles.add(i); + System.out.println("- " + Form.capitalizeWords(i.name().toLowerCase(Locale.ROOT).replaceAll("\\Q_\\E", " "))); + } + } + + private void computeInterpolationMethods(double time, double scope) { + List allowedStyles = new KList<>(InterpolationMethod.values()); + allowedStyles.remove(InterpolationMethod.NONE); + KMap speeds = new KMap<>(); + double allocateMS = time; + double maxTestDuration = allocateMS / allowedStyles.size(); + System.out.println("Running Interpolation Method Benchmark for " + Form.duration(allocateMS, 0) + "."); + System.out.println("Benchmarking " + allowedStyles.size() + " + Interpolation Methods for " + Form.duration(maxTestDuration, 1) + " each."); + System.out.println(); + + RNG r = new RNG("renderspeedtestinterpolation"); + CNG cng = NoiseStyle.SIMPLEX.create(r); + NoiseProvider np = (x, z) -> cng.noise(x, z); + + for (InterpolationMethod i : allowedStyles) { + int score = 0; + + PrecisionStopwatch p = PrecisionStopwatch.start(); + double g = 0; + while (p.getMilliseconds() < maxTestDuration) { + IrisInterpolation.getNoise(i, (int) g, (int) (-g * 2.225), r.d(4, 64), np); + cng.noise(g, -g * 2); + g += 1.1; + g *= 1.25; + score++; + } + + speeds.put(i, score); + } + + for (InterpolationMethod i : speeds.sortKNumber()) { + System.out.println(Form.capitalizeWords(i.name().toLowerCase(Locale.ROOT).replaceAll("\\Q_\\E", " ")) + " => " + Form.f(speeds.get(i))); + } + System.out.println(); + int takeUpTo = (int) Math.max(1, scope * speeds.size()); + System.out.println("Choosing the fastest " + Form.pc(scope) + " interpolators (" + takeUpTo + ")"); + + for (InterpolationMethod i : speeds.sortKNumber().reverse()) { + if (takeUpTo-- <= 0) { + break; + } + + sortedInterpolators.add(i); + System.out.println("- " + Form.capitalizeWords(i.name().toLowerCase(Locale.ROOT).replaceAll("\\Q_\\E", " "))); + } + } + + public void writeCollectionFrames(File folder, int fromId, int toId) { + folder.mkdirs(); + BurstExecutor burst = new BurstExecutor(executor, Math.min(toId - fromId, 1000)); + burst.setMulticore(true); + AtomicInteger ai = new AtomicInteger(0); + int max = toId - fromId; + + for (int i = fromId; i <= toId; i++) { + int ii = i; + burst.queue(() -> { + + writing.put("!#[" + fromId + "-" + toId + "] Collection", ai.get() + " of " + max + " (" + Form.pc(ai.get() / (double) max, 0) + ")"); + writeFrame(new File(folder, ii + ".png"), ii, 0); + ai.incrementAndGet(); + writing.put("!#[" + fromId + "-" + toId + "] Collection", ai.get() + " of " + max + " (" + Form.pc(ai.get() / (double) max, 0) + ")"); + }); + } + + burst.complete(); + writing.remove("!#[" + fromId + "-" + toId + "] Collection"); + } + + public void writeFrame(File destination, long id, double t) { + try { + renderFrameBuffer(id, t).export(destination); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + public void report(String s) { + System.out.println(s); + } + + public KList getStyles() { + return sortedStyles; + } + + public List getInterpolators() { + return sortedInterpolators; + } +}