mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-05-20 08:30:37 +00:00
Compare commits
439 Commits
feat/map
...
unification
| Author | SHA1 | Date | |
|---|---|---|---|
| 821cc027db | |||
| 1968ef2f2c | |||
| ce29b70618 | |||
| 3d128b70a7 | |||
| 23fad24fb7 | |||
| 6df718e6ca | |||
| 787c728060 | |||
| 88bbce82fe | |||
| e6a8351e57 | |||
| b82472d521 | |||
| 8c05f1bf1d | |||
| 6a5616abae | |||
| 6096c39192 | |||
| b42d0e4710 | |||
| dcb3306197 | |||
| 9a231b2bcf | |||
| aa706d027b | |||
| 568fb07f66 | |||
| bf207b7062 | |||
| 18d4dce1db | |||
| 651dfa247e | |||
| 5b1ff6d2f7 | |||
| 589baafc28 | |||
| 130073989d | |||
| 2ca8cc7ad3 | |||
| 9f35855599 | |||
| 5110a33857 | |||
| a38e87cce3 | |||
| 1d09cd5f0f | |||
| 72c891ce5b | |||
| 3964185a81 | |||
| afc5f67bc2 | |||
| 1c72d6410f | |||
| c2c612eb35 | |||
| 415c2f5837 | |||
| b491d9efd0 | |||
| d643461e2e | |||
| 1f41e195cf | |||
| 7b9280083f | |||
| 73525c170d | |||
| 16c861a781 | |||
| d6bd5ec82f | |||
| 1829fa3eb1 | |||
| 8d215e3911 | |||
| ba819e4899 | |||
| 05d79b6d40 | |||
| cf64fbb730 | |||
| 43a65135b1 | |||
| 440c1bec6c | |||
| 0fd8bcc488 | |||
| bb1c0b8f68 | |||
| fc25acdea2 | |||
| 0aa6f6d523 | |||
| a79c1a7954 | |||
| 7885762cd7 | |||
| 86ed3f0095 | |||
| 99fcc8fd03 | |||
| 41888e33f6 | |||
| 25fa2553e5 | |||
| 86f78baecf | |||
| c31158578f | |||
| 4e86d7d634 | |||
| 62fe29cf34 | |||
| a3bcea4f3e | |||
| 7befce1084 | |||
| 351a1aa495 | |||
| d2cb8a9032 | |||
| 509715087c | |||
| 12527ecdd8 | |||
| 01a2999e03 | |||
| dbafe84fa5 | |||
| 40b020fc5d | |||
| d15f7d62d1 | |||
| f9c062c794 | |||
| 6a89b8bd06 | |||
| 194fcb2ea7 | |||
| 3b68f855b2 | |||
| cfbf68d37a | |||
| 9999f3e429 | |||
| 97fa0562a4 | |||
| 3abe671851 | |||
| e164a3bb5c | |||
| 4dfb4441e4 | |||
| e686f67453 | |||
| e2eed4812a | |||
| 1737833bfe | |||
| a4c5f46c37 | |||
| 025fa833c4 | |||
| 98cc82cc3d | |||
| 298365f588 | |||
| 90e5720e2e | |||
| 7cd43791f4 | |||
| a251d192ad | |||
| abfff28f43 | |||
| fbdae4c928 | |||
| 93ca26e368 | |||
| 8c1db1c223 | |||
| 123708601f | |||
| b5ab4968ba | |||
| 93ae6037bd | |||
| dd8e487a3b | |||
| d3c8377a12 | |||
| 84f3687c0c | |||
| ed1e1f1181 | |||
| 528c97f367 | |||
| f7a459f3bc | |||
| cd179b4321 | |||
| 6373dbb1b8 | |||
| 0b0797f876 | |||
| 446acefc91 | |||
| 7a44e555b2 | |||
| 57d4c2935c | |||
| 234fb1b0c4 | |||
| 0882b5acc4 | |||
| 18da26e1fa | |||
| 65aa95f2a5 | |||
| 5330ddc4ec | |||
| a7fdd37569 | |||
| a226fea9e2 | |||
| 8cea165a29 | |||
| 1d7cba184c | |||
| 4ca7ea3911 | |||
| ea5919def2 | |||
| be35e49302 | |||
| aadd03990a | |||
| 38a579453d | |||
| 0bf5da2ca1 | |||
| 317848692e | |||
| 22118de9e9 | |||
| d7039d120b | |||
| 979ee4e7d8 | |||
| f68d45bd30 | |||
| b86d7f303e | |||
| c573843314 | |||
| 51a7bef18e | |||
| 0e237aa1ad | |||
| b46c413f6b | |||
| f3ef1ca2ae | |||
| 703e61dd54 | |||
| e1ec6b7827 | |||
| f94292fdac | |||
| 7d153bf985 | |||
| f85f15ed02 | |||
| 867686eced | |||
| 526efd3ae1 | |||
| 9d796bd2a0 | |||
| 1a9a5d80ad | |||
| c5c7f9bdc5 | |||
| 01a421b732 | |||
| ae92bcf194 | |||
| 7e7933858b | |||
| 9c073ecbcb | |||
| f4617c1996 | |||
| 21a2e4feef | |||
| 258d0d3aaa | |||
| 27b2fd0823 | |||
| 0524adb0df | |||
| 3981b0976d | |||
| b5811cae08 | |||
| c998fd1fd9 | |||
| a7d874d37f | |||
| 7c41f86fb3 | |||
| e5e0561d5a | |||
| d50cdfec3e | |||
| 00997c1902 | |||
| 3095a92522 | |||
| fca309dec7 | |||
| 2793ed1035 | |||
| e5908285af | |||
| a8ee321eb8 | |||
| aa14242b54 | |||
| f6968269b4 | |||
| 0d0251e2f1 | |||
| 81b8fb02ae | |||
| 77842489e5 | |||
| eda1f59d3a | |||
| 5418868559 | |||
| 1d81daafbb | |||
| 609a3585c1 | |||
| 571dde608c | |||
| 3a13f5a7c1 | |||
| e5654b74d4 | |||
| a75738dd7a | |||
| 003be1f88b | |||
| 1eaafae20d | |||
| 79088c0305 | |||
| 9e147774bd | |||
| e51b632c8f | |||
| 1aa64c9a02 | |||
| 1e148d8fcd | |||
| f63cabd8b8 | |||
| 176d3a5f9f | |||
| eb184983de | |||
| d1c307865d | |||
| 4a26b8b34f | |||
| 33fd01c3ac | |||
| 34874080e7 | |||
| 43131ed8f6 | |||
| 9ea425aee4 | |||
| 709f05c1a5 | |||
| ec74add5de | |||
| d5b706764a | |||
| ca4c205a4a | |||
| 7b9c2ae6ad | |||
| d0e9d44152 | |||
| 2cdffaae33 | |||
| d4a8beac95 | |||
| 0e0e4075d8 | |||
| 9c492a2e66 | |||
| 96bf83684c | |||
| 25ea9ae62d | |||
| 7938c150dd | |||
| a7b4bf3ff2 | |||
| 693a05f2cb | |||
| e48cbe1f69 | |||
| 558f6fa8dd | |||
| 67b29cc363 | |||
| 4702534f9c | |||
| 29390c5e0a | |||
| e8bfce469d | |||
| 770e2f47a2 | |||
| 747e7b3330 | |||
| 8662f3b47a | |||
| 6e738e69e7 | |||
| 4bf14c83e1 | |||
| 837674c295 | |||
| e5f3bbd69e | |||
| 2885a39299 | |||
| bd1b06c761 | |||
| 3e1143112a | |||
| 834b214fbf | |||
| 768e569400 | |||
| a5d04333dd | |||
| ebe8da0fd5 | |||
| 5eb25f3977 | |||
| a5bca0a9bb | |||
| a82882c1c1 | |||
| 05193bd0d9 | |||
| 96efc15c36 | |||
| 67f456cf53 | |||
| 58b1bd115f | |||
| 8ddc8abdb9 | |||
| 12c2c71739 | |||
| 8705ca6e47 | |||
| a610d0a7a9 | |||
| 0648cfd3fa | |||
| fba9c17e3f | |||
| 74128d58cf | |||
| 70130e976d | |||
| 501c426302 | |||
| 8471f15bc8 | |||
| c4539441a0 | |||
| 472a98da16 | |||
| 76a6f1465a | |||
| b0ca0e3617 | |||
| 08b9058c8f | |||
| 556bef6e43 | |||
| 16a4d20c90 | |||
| e5f2fee926 | |||
| 3949468a60 | |||
| fc54fcb7eb | |||
| 9d6e4e87d8 | |||
| f50e964d4f | |||
| 8262e52893 | |||
| 1c41dc69ce | |||
| dee46a4284 | |||
| a8892b04ef | |||
| d49f7d7821 | |||
| 106a3834ab | |||
| 387e8adfe2 | |||
| 9ade90d9ca | |||
| 3d2392843a | |||
| a9891e819a | |||
| 02c13a9391 | |||
| f6590c26e7 | |||
| 64bb81626c | |||
| 343dc429d5 | |||
| 8f5f44bc96 | |||
| 6964b99744 | |||
| 11cfd85f6a | |||
| c5416f54fa | |||
| 94c5782490 | |||
| cc49b0f540 | |||
| 1bc6192c8b | |||
| 4b0766c097 | |||
| ec5cb2d646 | |||
| 0edaeeec99 | |||
| a0543bbbf2 | |||
| 0b1af51227 | |||
| 03c5998c02 | |||
| 6b193f695a | |||
| 0a30881f87 | |||
| c01a7def5d | |||
| 50db1d11a7 | |||
| 2e2ea8f1e4 | |||
| badf108d56 | |||
| f9888d19a5 | |||
| fbc5cee300 | |||
| 12777bc3f0 | |||
| 9324b1b5c0 | |||
| 2ecb555619 | |||
| 20c7891c2f | |||
| c35c858eee | |||
| cb93e78242 | |||
| c9ed4519a8 | |||
| 86d986dfbc | |||
| 54402faea8 | |||
| 77b4253624 | |||
| 44af23ba2e | |||
| 4e8079e431 | |||
| 39f65d02bf | |||
| 8c025a9ba2 | |||
| 7119fa8ef7 | |||
| 6f0b2b6bba | |||
| 0ae1334a57 | |||
| 5d28563b7c | |||
| cf8243a000 | |||
| cca0bed482 | |||
| a802edc375 | |||
| 3677931114 | |||
| e38dae0a32 | |||
| d537459c5a | |||
| 67398174bb | |||
| 0a0f77ff58 | |||
| e5cb4d82a3 | |||
| 9d44ac0b47 | |||
| 7f6d65a13e | |||
| ad720f4aa2 | |||
| 80548f753c | |||
| c9c8a9e412 | |||
| d048c073ac | |||
| 2929a1f0a7 | |||
| b6f9f68b9f | |||
| da2dd42e28 | |||
| 88360ef772 | |||
| 73787e21d2 | |||
| 840608a40f | |||
| fcedc35635 | |||
| 32d9a5e40a | |||
| 8df6253604 | |||
| 01b62c13b6 | |||
| f32f73e65a | |||
| 7b7118fe0d | |||
| 851ac18f0d | |||
| 37be7ca847 | |||
| ed67b4d3c2 | |||
| b62ac875d5 | |||
| 4c3f95b0e1 | |||
| 8712c8874c | |||
| ccc3bab8e0 | |||
| 113f25dab8 | |||
| cd80acdc7d | |||
| 9316ea9e5b | |||
| e2a3f25dcb | |||
| 944cc19ebc | |||
| 172e234514 | |||
| f9dac8a3a1 | |||
| eefbbab7ee | |||
| 9cf567e1ff | |||
| b8ee7561dd | |||
| c0d17742e8 | |||
| a88d389e0f | |||
| b341089996 | |||
| 9cbfd5a10b | |||
| fdaf8ff9d3 | |||
| e63d84c052 | |||
| 0d103a934a | |||
| 8119207254 | |||
| b66e6d8335 | |||
| ce2b62f5ae | |||
| c767b6c8e8 | |||
| 67328d7d10 | |||
| 329e136a66 | |||
| 52f87befa2 | |||
| 2ee22db072 | |||
| cc27e87376 | |||
| abb1d9cd62 | |||
| e0ad029c3d | |||
| 5705caa1ba | |||
| a56cd4c268 | |||
| 48cc6bb49c | |||
| 635ee02459 | |||
| 2c7b7c8c91 | |||
| ad0662be54 | |||
| 835c8422f1 | |||
| 2c60192de3 | |||
| adb7188eb9 | |||
| 2436ebb857 | |||
| ad85a0bbd1 | |||
| 22ac9ebf47 | |||
| 3cb9585dd8 | |||
| f4d1177c51 | |||
| b132862a60 | |||
| 2452a2f633 | |||
| f0476fea9b | |||
| 90c6457d37 | |||
| 61301ffd4d | |||
| e42317139d | |||
| f68600464b | |||
| 97ddfd309b | |||
| 5d42c5cae0 | |||
| 52fecb48d8 | |||
| ce29dc98c3 | |||
| 463e3d9658 | |||
| 9152b25d51 | |||
| bb9c72e414 | |||
| 976648340e | |||
| 5958bcb22e | |||
| 8eb35aa8be | |||
| 49ce84a9e9 | |||
| 6724b0f4c5 | |||
| e72abc8c39 | |||
| 0a2f35dd8d | |||
| c597c55c2c | |||
| f93995e152 | |||
| 7a5a2e5909 | |||
| e15d31a0ed | |||
| 09cdd61a68 | |||
| 0575cd85c8 | |||
| 2008975a8a | |||
| aad1d3815a | |||
| 86c64f99e9 | |||
| 6577f4a5de | |||
| 3415e7c7af | |||
| f1c72974fd | |||
| 26e2e20840 | |||
| c00dcf205b | |||
| a10a784c3b | |||
| f99cc61042 | |||
| d2a1e5cc1e | |||
| 3e2c0fa025 | |||
| 9c151abac7 | |||
| ab4770400e | |||
| 1a810d5d62 | |||
| 536d20bca7 | |||
| 9a691ac5b4 | |||
| 71078a20a9 | |||
| 566fca2b52 | |||
| 74e2576ca2 |
+3
-1
@@ -10,4 +10,6 @@ libs/
|
||||
|
||||
collection/
|
||||
|
||||
/core/src/main/java/com/volmit/iris/util/uniques/
|
||||
/core/src/main/java/art/arcane/iris/util/uniques/
|
||||
|
||||
DataPackExamples/
|
||||
|
||||
@@ -7,7 +7,7 @@ The master branch is for the latest version of minecraft.
|
||||
# Building
|
||||
|
||||
Building Iris is fairly simple, though you will need to setup a few things if your system has never been used for java
|
||||
development.
|
||||
development.[README.md](README.md)
|
||||
|
||||
Consider supporting our development by buying Iris on spigot! We work hard to make Iris the best it can be for everyone.
|
||||
|
||||
@@ -15,17 +15,17 @@ Consider supporting our development by buying Iris on spigot! We work hard to ma
|
||||
|
||||
### Command Line Builds
|
||||
|
||||
1. Install [Java JDK 21](https://www.oracle.com/java/technologies/javase/jdk21-archive-downloads.html)
|
||||
1. Install [Java JDK 25](https://adoptium.net/temurin/releases/?version=25)
|
||||
2. Set the JDK installation path to `JAVA_HOME` as an environment variable.
|
||||
* Windows
|
||||
1. Start > Type `env` and press Enter
|
||||
2. Advanced > Environment Variables
|
||||
3. Under System Variables, click `New...`
|
||||
4. Variable Name: `JAVA_HOME`
|
||||
5. Variable Value: `C:\Program Files\Java\jdk-21.0.1` (verify this exists after installing java don't just copy
|
||||
5. Variable Value: `C:\Program Files\Java\jdk-25` (verify this exists after installing java don't just copy
|
||||
the example text)
|
||||
* MacOS
|
||||
1. Run `/usr/libexec/java_home -V` and look for Java 21
|
||||
1. Run `/usr/libexec/java_home -V` and look for Java 25
|
||||
2. Run `sudo nano ~/.zshenv`
|
||||
3. Add `export JAVA_HOME=$(/usr/libexec/java_home)` as a new line
|
||||
4. Use `CTRL + X`, then Press `Y`, Then `ENTER`
|
||||
@@ -45,7 +45,7 @@ Consider supporting our development by buying Iris on spigot! We work hard to ma
|
||||
Everyone needs a tool-belt.
|
||||
|
||||
```java
|
||||
package com.volmit.iris.core.tools;
|
||||
package art.arcane.iris.core.tools;
|
||||
|
||||
// Get IrisDataManager from a world
|
||||
IrisToolbelt.access(anyWorld).getCompound().getData();
|
||||
|
||||
+270
-254
@@ -1,254 +1,270 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
buildscript() {
|
||||
repositories {
|
||||
maven { url 'https://jitpack.io'}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.github.VolmitSoftware:NMSTools:1.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'java-library'
|
||||
id "io.github.goooler.shadow" version "8.1.7"
|
||||
id "de.undercouch.download" version "5.0.1"
|
||||
}
|
||||
|
||||
|
||||
version '3.6.5-1.20.1-1.21.4'
|
||||
|
||||
// ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED
|
||||
// ======================== WINDOWS =============================
|
||||
registerCustomOutputTask('Cyberpwn', 'C://Users/cyberpwn/Documents/development/server/plugins')
|
||||
registerCustomOutputTask('Psycho', 'C://Dan/MinecraftDevelopment/Server/plugins')
|
||||
registerCustomOutputTask('ArcaneArts', 'C://Users/arcane/Documents/development/server/plugins')
|
||||
registerCustomOutputTask('Coco', 'D://mcsm/plugins')
|
||||
registerCustomOutputTask('Strange', 'D://Servers/1.17 Test Server/plugins')
|
||||
registerCustomOutputTask('Vatuu', 'D://Minecraft/Servers/1.19.4/plugins')
|
||||
registerCustomOutputTask('CrazyDev22', 'C://Users/Julian/Desktop/server/plugins')
|
||||
registerCustomOutputTask('PixelFury', 'C://Users/repix/workplace/Iris/1.21.3 - Development-Public-v3/plugins')
|
||||
registerCustomOutputTask('PixelFuryDev', 'C://Users/repix/workplace/Iris/1.21 - Development-v3/plugins')
|
||||
// ========================== UNIX ==============================
|
||||
registerCustomOutputTaskUnix('CyberpwnLT', '/Users/danielmills/development/server/plugins')
|
||||
registerCustomOutputTaskUnix('PsychoLT', '/Users/brianfopiano/Developer/RemoteGit/Server/plugins')
|
||||
registerCustomOutputTaskUnix('PixelMac', '/Users/test/Desktop/mcserver/plugins')
|
||||
registerCustomOutputTaskUnix('CrazyDev22LT', '/home/julian/Desktop/server/plugins')
|
||||
// ==============================================================
|
||||
|
||||
def NMS_BINDINGS = Map.of(
|
||||
"v1_21_R3", "1.21.4-R0.1-SNAPSHOT",
|
||||
"v1_21_R2", "1.21.3-R0.1-SNAPSHOT",
|
||||
"v1_21_R1", "1.21.1-R0.1-SNAPSHOT",
|
||||
"v1_20_R4", "1.20.6-R0.1-SNAPSHOT",
|
||||
"v1_20_R3", "1.20.4-R0.1-SNAPSHOT",
|
||||
"v1_20_R2", "1.20.2-R0.1-SNAPSHOT",
|
||||
"v1_20_R1", "1.20.1-R0.1-SNAPSHOT",
|
||||
)
|
||||
def JVM_VERSION = Map.of()
|
||||
NMS_BINDINGS.each { nms ->
|
||||
project(":nms:${nms.key}") {
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'com.volmit.nmstools'
|
||||
|
||||
nmsTools {
|
||||
it.jvm = JVM_VERSION.getOrDefault(nms.key, 21)
|
||||
it.version = nms.value
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":core")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
NMS_BINDINGS.each {
|
||||
dependsOn(":nms:${it.key}:remap")
|
||||
from("${project(":nms:${it.key}").layout.buildDirectory.asFile.get()}/libs/${it.key}-mapped.jar")
|
||||
}
|
||||
|
||||
//minimize()
|
||||
append("plugin.yml")
|
||||
relocate 'com.dfsek.paralithic', 'com.volmit.iris.util.paralithic'
|
||||
relocate 'io.papermc.lib', 'com.volmit.iris.util.paper'
|
||||
relocate 'net.kyori', 'com.volmit.iris.util.kyori'
|
||||
relocate 'org.bstats', 'com.volmit.util.metrics'
|
||||
archiveFileName.set("Iris-${project.version}.jar")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
}
|
||||
|
||||
configurations.configureEach {
|
||||
resolutionStrategy.cacheChangingModulesFor 60, 'minutes'
|
||||
resolutionStrategy.cacheDynamicVersionsFor 60, 'minutes'
|
||||
}
|
||||
|
||||
allprojects {
|
||||
apply plugin: 'java'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "https://repo.papermc.io/repository/maven-public/" }
|
||||
maven { url "https://repo.codemc.org/repository/maven-public" }
|
||||
maven { url "https://mvn.lumine.io/repository/maven-public/" }
|
||||
maven { url "https://jitpack.io" }
|
||||
|
||||
maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots" }
|
||||
maven { url "https://mvn.lumine.io/repository/maven/" }
|
||||
maven { url "https://repo.triumphteam.dev/snapshots" }
|
||||
maven { url "https://repo.mineinabyss.com/releases" }
|
||||
maven { url 'https://hub.jeff-media.com/nexus/repository/jeff-media-public/' }
|
||||
maven { url "https://repo.nexomc.com/snapshots/" }
|
||||
maven { url "https://libraries.minecraft.net" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Provided or Classpath
|
||||
compileOnly 'org.projectlombok:lombok:1.18.36'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.36'
|
||||
|
||||
// Shaded
|
||||
implementation 'com.dfsek:paralithic:0.8.1'
|
||||
implementation 'io.papermc:paperlib:1.0.5'
|
||||
implementation "net.kyori:adventure-text-minimessage:4.17.0"
|
||||
implementation 'net.kyori:adventure-platform-bukkit:4.3.4'
|
||||
implementation 'net.kyori:adventure-api:4.17.0'
|
||||
implementation 'org.bstats:bstats-bukkit:3.1.0'
|
||||
//implementation 'org.bytedeco:javacpp:1.5.10'
|
||||
//implementation 'org.bytedeco:cuda-platform:12.3-8.9-1.5.10'
|
||||
compileOnly 'io.lumine:Mythic-Dist:5.2.1'
|
||||
compileOnly 'io.lumine:MythicCrucible-Dist:2.0.0'
|
||||
|
||||
// Dynamically Loaded
|
||||
compileOnly 'io.timeandspace:smoothie-map:2.0.2'
|
||||
compileOnly 'it.unimi.dsi:fastutil:8.5.8'
|
||||
compileOnly 'com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2'
|
||||
compileOnly 'org.zeroturnaround:zt-zip:1.14'
|
||||
compileOnly 'com.google.code.gson:gson:2.10.1'
|
||||
compileOnly 'org.ow2.asm:asm:9.2'
|
||||
compileOnly 'com.google.guava:guava:33.0.0-jre'
|
||||
compileOnly 'bsf:bsf:2.4.0'
|
||||
compileOnly 'rhino:js:1.7R2'
|
||||
compileOnly 'com.github.ben-manes.caffeine:caffeine:3.0.6'
|
||||
compileOnly 'org.apache.commons:commons-lang3:3.12.0'
|
||||
compileOnly 'com.github.oshi:oshi-core:6.6.5'
|
||||
}
|
||||
|
||||
/**
|
||||
* We need parameter meta for the decree command system
|
||||
*/
|
||||
compileJava {
|
||||
options.compilerArgs << '-parameters'
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
javadoc {
|
||||
options.encoding = "UTF-8"
|
||||
options.addStringOption('Xdoclint:none', '-quiet')
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
archiveClassifier.set('sources')
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
archiveClassifier.set('javadoc')
|
||||
from javadoc.destinationDir
|
||||
}
|
||||
}
|
||||
|
||||
if (JavaVersion.current().toString() != "21") {
|
||||
System.err.println()
|
||||
System.err.println("=========================================================================================================")
|
||||
System.err.println("You must run gradle on Java 21. You are using " + JavaVersion.current())
|
||||
System.err.println()
|
||||
System.err.println("=== For IDEs ===")
|
||||
System.err.println("1. Configure the project for Java 21")
|
||||
System.err.println("2. Configure the bundled gradle to use Java 21 in settings")
|
||||
System.err.println()
|
||||
System.err.println("=== For Command Line (gradlew) ===")
|
||||
System.err.println("1. Install JDK 21 from https://www.oracle.com/java/technologies/javase/jdk21-archive-downloads.html")
|
||||
System.err.println("2. Set JAVA_HOME environment variable to the new jdk installation folder such as C:\\Program Files\\Java\\jdk-21.0.4")
|
||||
System.err.println("3. Open a new command prompt window to get the new environment variables if need be.")
|
||||
System.err.println("=========================================================================================================")
|
||||
System.err.println()
|
||||
System.exit(69);
|
||||
}
|
||||
|
||||
task iris(type: Copy) {
|
||||
group "iris"
|
||||
from new File(layout.buildDirectory.asFile.get(), "libs/Iris-${version}.jar")
|
||||
into layout.buildDirectory.asFile.get()
|
||||
dependsOn(build)
|
||||
}
|
||||
|
||||
// with classifier: 'javadoc' and 'sources'
|
||||
task irisDev(type: Copy) {
|
||||
group "iris"
|
||||
from("core/build/libs/core-javadoc.jar", "core/build/libs/core-sources.jar")
|
||||
rename { String fileName ->
|
||||
fileName.replace("core", "Iris-${version}")
|
||||
}
|
||||
into layout.buildDirectory.asFile.get()
|
||||
dependsOn(iris)
|
||||
dependsOn("core:sourcesJar")
|
||||
dependsOn("core:javadocJar")
|
||||
}
|
||||
|
||||
|
||||
def registerCustomOutputTask(name, path) {
|
||||
if (!System.properties['os.name'].toLowerCase().contains('windows')) {
|
||||
return;
|
||||
}
|
||||
|
||||
tasks.register('build' + name, Copy) {
|
||||
group('development')
|
||||
outputs.upToDateWhen { false }
|
||||
dependsOn(iris)
|
||||
from(new File(buildDir, "Iris-" + version + ".jar"))
|
||||
into(file(path))
|
||||
rename { String fileName ->
|
||||
fileName.replace("Iris-" + version + ".jar", "Iris.jar")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def registerCustomOutputTaskUnix(name, path) {
|
||||
if (System.properties['os.name'].toLowerCase().contains('windows')) {
|
||||
return;
|
||||
}
|
||||
|
||||
tasks.register('build' + name, Copy) {
|
||||
group('development')
|
||||
outputs.upToDateWhen { false }
|
||||
dependsOn(iris)
|
||||
from(new File(buildDir, "Iris-" + version + ".jar"))
|
||||
into(file(path))
|
||||
rename { String fileName ->
|
||||
fileName.replace("Iris-" + version + ".jar", "Iris.jar")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.build.dependsOn(shadowJar)
|
||||
import de.undercouch.gradle.tasks.download.Download
|
||||
import org.gradle.api.plugins.JavaPlugin
|
||||
import org.gradle.api.tasks.Copy
|
||||
import org.gradle.api.tasks.compile.JavaCompile
|
||||
import org.gradle.jvm.tasks.Jar
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2021 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
maven {
|
||||
url = uri('https://jitpack.io')
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath('com.github.VolmitSoftware:NMSTools:c88961416f')
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'java-library'
|
||||
alias(libs.plugins.download)
|
||||
}
|
||||
|
||||
group = 'art.arcane'
|
||||
version = '4.0.0-26.1'
|
||||
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
|
||||
.orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT')
|
||||
.get()
|
||||
|
||||
apply plugin: ApiGenerator
|
||||
|
||||
// ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED
|
||||
// ======================== WINDOWS =============================
|
||||
registerCustomOutputTask('Cyberpwn', 'C://Users/cyberpwn/Documents/development/server/plugins')
|
||||
registerCustomOutputTask('Psycho', 'C://Dan/MinecraftDevelopment/Server/plugins')
|
||||
registerCustomOutputTask('ArcaneArts', 'C://Users/arcane/Documents/development/server/plugins')
|
||||
registerCustomOutputTask('Coco', 'D://mcsm/plugins')
|
||||
registerCustomOutputTask('Strange', 'D://Servers/1.17 Test Server/plugins')
|
||||
registerCustomOutputTask('Vatuu', 'D://Minecraft/Servers/1.19.4/plugins')
|
||||
registerCustomOutputTask('CrazyDev22', 'C://Users/Julian/Desktop/server/plugins')
|
||||
registerCustomOutputTask('PixelFury', 'C://Users/repix/workplace/Iris/1.21.3 - Development-Public-v3/plugins')
|
||||
registerCustomOutputTask('PixelFuryDev', 'C://Users/repix/workplace/Iris/1.21 - Development-v3/plugins')
|
||||
// ========================== UNIX ==============================
|
||||
registerCustomOutputTaskUnix('CyberpwnLT', '/Users/danielmills/development/server/plugins')
|
||||
registerCustomOutputTaskUnix('PsychoLT', '/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/consumers/plugin-consumers/dropins/plugins')
|
||||
registerCustomOutputTaskUnix('PixelMac', '/Users/test/Desktop/mcserver/plugins')
|
||||
registerCustomOutputTaskUnix('CrazyDev22LT', '/home/julian/Desktop/server/plugins')
|
||||
// ==============================================================
|
||||
|
||||
def nmsBindings = [
|
||||
v1_21_R7: '1.21.11-R0.1-SNAPSHOT',
|
||||
]
|
||||
Class nmsTypeClass = Class.forName('NMSBinding$Type')
|
||||
nmsBindings.each { key, value ->
|
||||
project(":nms:${key}") {
|
||||
apply plugin: JavaPlugin
|
||||
|
||||
def nmsConfig = new Config()
|
||||
nmsConfig.jvm = 25
|
||||
nmsConfig.version = value
|
||||
nmsConfig.type = Enum.valueOf(nmsTypeClass, 'USER_DEV')
|
||||
extensions.extraProperties.set('nms', nmsConfig)
|
||||
plugins.apply(NMSBinding)
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(':core'))
|
||||
compileOnly(volmLibCoordinate) {
|
||||
changing = true
|
||||
transitive = false
|
||||
}
|
||||
compileOnly(rootProject.libs.annotations)
|
||||
compileOnly(rootProject.libs.byteBuddy.core)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def included = configurations.create('included')
|
||||
def jarJar = configurations.create('jarJar')
|
||||
dependencies {
|
||||
nmsBindings.keySet().each { key ->
|
||||
add('included', project(path: ":nms:${key}", configuration: 'runtimeElements'))
|
||||
}
|
||||
add('included', project(path: ':core', configuration: 'shadow'))
|
||||
add('jarJar', project(':core:agent'))
|
||||
}
|
||||
|
||||
tasks.named('jar', Jar).configure {
|
||||
inputs.files(included)
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
from(jarJar, provider { included.resolve().collect { zipTree(it) } })
|
||||
archiveFileName.set("Iris-${project.version}.jar")
|
||||
}
|
||||
|
||||
tasks.register('iris', Copy) {
|
||||
group = 'iris'
|
||||
dependsOn('jar')
|
||||
from(layout.buildDirectory.file("libs/Iris-${project.version}.jar"))
|
||||
into(layout.buildDirectory)
|
||||
}
|
||||
|
||||
tasks.register('irisDev', Copy) {
|
||||
group = 'iris'
|
||||
from(project(':core').layout.buildDirectory.files('libs/core-javadoc.jar', 'libs/core-sources.jar'))
|
||||
rename { String fileName -> fileName.replace('core', "Iris-${project.version}") }
|
||||
into(layout.buildDirectory)
|
||||
dependsOn(':core:sourcesJar')
|
||||
dependsOn(':core:javadocJar')
|
||||
}
|
||||
|
||||
def cli = file('sentry-cli.exe')
|
||||
tasks.register('downloadCli', Download) {
|
||||
group = 'io.sentry'
|
||||
src("https://release-registry.services.sentry.io/apps/sentry-cli/latest?response=download&arch=x86_64&platform=${System.getProperty('os.name')}&package=sentry-cli")
|
||||
dest(cli)
|
||||
|
||||
doLast {
|
||||
cli.setExecutable(true)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('release') {
|
||||
group = 'io.sentry'
|
||||
dependsOn('downloadCli')
|
||||
doLast {
|
||||
String url = 'http://sentry.volmit.com:8080'
|
||||
def authToken = project.findProperty('sentry.auth.token') ?: System.getenv('SENTRY_AUTH_TOKEN')
|
||||
String org = 'sentry'
|
||||
String projectName = 'iris'
|
||||
runCommand(cli, '--url', url, '--auth-token', authToken, 'releases', 'new', '-o', org, '-p', projectName, version)
|
||||
runCommand(cli, '--url', url, '--auth-token', authToken, 'releases', 'set-commits', '-o', org, '-p', projectName, version, '--auto', '--ignore-missing')
|
||||
//exec(cli, "--url", url, "--auth-token", authToken, "releases", "finalize", "-o", org, "-p", projectName, version)
|
||||
cli.delete()
|
||||
}
|
||||
}
|
||||
|
||||
void runCommand(Object... command) {
|
||||
Process process = new ProcessBuilder(command.collect { it.toString() }).start()
|
||||
process.inputStream.readLines().each { println(it) }
|
||||
process.errorStream.readLines().each { println(it) }
|
||||
process.waitFor()
|
||||
}
|
||||
|
||||
configurations.configureEach {
|
||||
resolutionStrategy.cacheChangingModulesFor(0, 'seconds')
|
||||
resolutionStrategy.cacheDynamicVersionsFor(0, 'seconds')
|
||||
}
|
||||
|
||||
allprojects {
|
||||
apply plugin: 'java'
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(25)
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url = uri('https://repo.papermc.io/repository/maven-public/') }
|
||||
maven { url = uri('https://repo.codemc.org/repository/maven-public/') }
|
||||
|
||||
maven { url = uri('https://jitpack.io') } // EcoItems, score
|
||||
maven { url = uri('https://repo.nexomc.com/releases/') } // nexo
|
||||
maven { url = uri('https://maven.devs.beer/') } // itemsadder
|
||||
maven { url = uri('https://repo.extendedclip.com/releases/') } // placeholderapi
|
||||
maven { url = uri('https://mvn.lumine.io/repository/maven-public/') } // mythic
|
||||
maven { url = uri('https://nexus.phoenixdevt.fr/repository/maven-public/') } //MMOItems
|
||||
maven { url = uri('https://repo.onarandombox.com/content/groups/public/') } //Multiverse Core
|
||||
maven { url = uri('https://repo.momirealms.net/releases/') } // CraftEngine
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Provided or Classpath
|
||||
compileOnly(rootProject.libs.lombok)
|
||||
annotationProcessor(rootProject.libs.lombok)
|
||||
}
|
||||
|
||||
/**
|
||||
* We need parameter meta for the decree command system
|
||||
*/
|
||||
tasks.named('compileJava', JavaCompile).configure {
|
||||
options.compilerArgs.add('-parameters')
|
||||
options.encoding = 'UTF-8'
|
||||
options.debugOptions.debugLevel = 'none'
|
||||
options.release.set(25)
|
||||
}
|
||||
|
||||
tasks.named('javadoc').configure {
|
||||
options.encoding = 'UTF-8'
|
||||
options.quiet()
|
||||
//options.addStringOption("Xdoclint:none") // TODO: Re-enable this
|
||||
}
|
||||
|
||||
tasks.register('sourcesJar', Jar) {
|
||||
archiveClassifier.set('sources')
|
||||
from(sourceSets.main.allSource)
|
||||
}
|
||||
|
||||
tasks.register('javadocJar', Jar) {
|
||||
archiveClassifier.set('javadoc')
|
||||
from(tasks.named('javadoc').map { it.destinationDir })
|
||||
}
|
||||
}
|
||||
|
||||
if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25)) {
|
||||
System.err.println()
|
||||
System.err.println('=========================================================================================================')
|
||||
System.err.println('You must run gradle on Java 25 or newer. You are using ' + JavaVersion.current())
|
||||
System.err.println()
|
||||
System.err.println('=== For IDEs ===')
|
||||
System.err.println('1. Configure the project for Java 25 toolchain')
|
||||
System.err.println('2. Configure the bundled gradle to use Java 25+ in settings')
|
||||
System.err.println()
|
||||
System.err.println('=== For Command Line (gradlew) ===')
|
||||
System.err.println('1. Install JDK 25 from https://adoptium.net/temurin/releases/?version=25')
|
||||
System.err.println('2. Set JAVA_HOME environment variable to the new jdk installation folder such as C:\\Program Files\\Java\\jdk-25')
|
||||
System.err.println('3. Open a new command prompt window to get the new environment variables if need be.')
|
||||
System.err.println('=========================================================================================================')
|
||||
System.err.println()
|
||||
System.exit(69)
|
||||
}
|
||||
|
||||
void registerCustomOutputTask(String name, String path) {
|
||||
if (!System.getProperty('os.name').toLowerCase().contains('windows')) {
|
||||
return
|
||||
}
|
||||
|
||||
tasks.register("build${name}", Copy) {
|
||||
group = 'development'
|
||||
outputs.upToDateWhen { false }
|
||||
dependsOn('iris')
|
||||
from(layout.buildDirectory.file("Iris-${project.version}.jar"))
|
||||
into(file(path))
|
||||
rename { String ignored -> 'Iris.jar' }
|
||||
}
|
||||
}
|
||||
|
||||
void registerCustomOutputTaskUnix(String name, String path) {
|
||||
if (System.getProperty('os.name').toLowerCase().contains('windows')) {
|
||||
return
|
||||
}
|
||||
|
||||
tasks.register("build${name}", Copy) {
|
||||
group = 'development'
|
||||
outputs.upToDateWhen { false }
|
||||
dependsOn('iris')
|
||||
from(layout.buildDirectory.file("Iris-${project.version}.jar"))
|
||||
into(file(path))
|
||||
rename { String ignored -> 'Iris.jar' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(25)
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
maven {
|
||||
url = uri('https://jitpack.io')
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation('org.ow2.asm:asm:9.8')
|
||||
implementation('com.github.VolmitSoftware:NMSTools:c88961416f')
|
||||
implementation('io.papermc.paperweight:paperweight-userdev:2.0.0-beta.18')
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.publish.PublishingExtension;
|
||||
import org.gradle.api.publish.maven.MavenPublication;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.jvm.tasks.Jar;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
public class ApiGenerator implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project target) {
|
||||
target.getPlugins().apply("maven-publish");
|
||||
TaskProvider<GenerateApiTask> task = target.getTasks().register("irisApi", GenerateApiTask.class);
|
||||
|
||||
PublishingExtension publishing = target.getExtensions().findByType(PublishingExtension.class);
|
||||
if (publishing == null) {
|
||||
throw new GradleException("Publishing extension not found");
|
||||
}
|
||||
|
||||
publishing.getRepositories().maven(repository -> {
|
||||
repository.setName("deployDir");
|
||||
repository.setUrl(targetDirectory(target).toURI());
|
||||
});
|
||||
|
||||
publishing.getPublications().create("maven", MavenPublication.class, publication -> {
|
||||
publication.setGroupId(target.getName());
|
||||
publication.setVersion(target.getVersion().toString());
|
||||
publication.artifact(task);
|
||||
});
|
||||
}
|
||||
|
||||
public static File targetDirectory(Project project) {
|
||||
String dir = System.getenv("DEPLOY_DIR");
|
||||
if (dir == null) {
|
||||
return project.getLayout().getBuildDirectory().dir("api").get().getAsFile();
|
||||
}
|
||||
return new File(dir);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class GenerateApiTask extends DefaultTask {
|
||||
private final File inputFile;
|
||||
private final File outputFile;
|
||||
|
||||
public GenerateApiTask() {
|
||||
setGroup("iris");
|
||||
dependsOn("jar");
|
||||
finalizedBy("publishMavenPublicationToDeployDirRepository");
|
||||
doLast(task -> getLogger().lifecycle("The API is located at " + getOutputFile().getAbsolutePath()));
|
||||
|
||||
TaskProvider<Jar> jarTask = getProject().getTasks().named("jar", Jar.class);
|
||||
this.inputFile = jarTask.get().getArchiveFile().get().getAsFile();
|
||||
this.outputFile = ApiGenerator.targetDirectory(getProject()).toPath().resolve(this.inputFile.getName()).toFile();
|
||||
}
|
||||
|
||||
@InputFile
|
||||
public File getInputFile() {
|
||||
return inputFile;
|
||||
}
|
||||
|
||||
@OutputFile
|
||||
public File getOutputFile() {
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void generate() throws IOException {
|
||||
File parent = outputFile.getParentFile();
|
||||
if (parent != null) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
|
||||
try (JarFile jar = new JarFile(inputFile);
|
||||
JarOutputStream out = new JarOutputStream(new FileOutputStream(outputFile))) {
|
||||
jar.stream()
|
||||
.parallel()
|
||||
.filter(entry -> !entry.isDirectory())
|
||||
.filter(entry -> entry.getName().endsWith(".class"))
|
||||
.forEach(entry -> writeStrippedClass(jar, out, entry));
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeStrippedClass(JarFile jar, JarOutputStream out, JarEntry entry) {
|
||||
byte[] bytes;
|
||||
try (InputStream input = jar.getInputStream(entry)) {
|
||||
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
ClassVisitor visitor = new MethodClearingVisitor(writer);
|
||||
ClassReader reader = new ClassReader(input);
|
||||
reader.accept(visitor, 0);
|
||||
bytes = writer.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
synchronized (out) {
|
||||
try {
|
||||
JarEntry outputEntry = new JarEntry(entry.getName());
|
||||
out.putNextEntry(outputEntry);
|
||||
out.write(bytes);
|
||||
out.closeEntry();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MethodClearingVisitor extends ClassVisitor {
|
||||
public MethodClearingVisitor(ClassVisitor cv) {
|
||||
super(Opcodes.ASM9, cv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
return new ExceptionThrowingMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions));
|
||||
}
|
||||
}
|
||||
|
||||
class ExceptionThrowingMethodVisitor extends MethodVisitor {
|
||||
public ExceptionThrowingMethodVisitor(MethodVisitor mv) {
|
||||
super(Opcodes.ASM9, mv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCode() {
|
||||
if (mv == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mv.visitCode();
|
||||
mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException");
|
||||
mv.visitInsn(Opcodes.DUP);
|
||||
mv.visitLdcInsn("Only API");
|
||||
mv.visitMethodInsn(
|
||||
Opcodes.INVOKESPECIAL,
|
||||
"java/lang/IllegalStateException",
|
||||
"<init>",
|
||||
"(Ljava/lang/String;)V",
|
||||
false
|
||||
);
|
||||
mv.visitInsn(Opcodes.ATHROW);
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
public class Config {
|
||||
public int jvm = 25;
|
||||
public NMSBinding.Type type = NMSBinding.Type.DIRECT;
|
||||
public String version;
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
import com.volmit.nmstools.NMSToolsExtension;
|
||||
import com.volmit.nmstools.NMSToolsPlugin;
|
||||
import io.papermc.paperweight.userdev.PaperweightUser;
|
||||
import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension;
|
||||
import io.papermc.paperweight.userdev.PaperweightUserExtension;
|
||||
import io.papermc.paperweight.userdev.attribute.Obfuscation;
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Named;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.attributes.Bundling;
|
||||
import org.gradle.api.attributes.Category;
|
||||
import org.gradle.api.attributes.LibraryElements;
|
||||
import org.gradle.api.attributes.Usage;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.plugins.ExtraPropertiesExtension;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion;
|
||||
import org.gradle.jvm.toolchain.JavaToolchainService;
|
||||
import org.gradle.work.DisableCachingByDefault;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static io.papermc.paperweight.util.constants.ConstantsKt.REOBF_CONFIG;
|
||||
|
||||
public class NMSBinding implements Plugin<Project> {
|
||||
private static final String NEW_LINE = System.lineSeparator();
|
||||
private static final byte[] NEW_LINE_BYTES = NEW_LINE.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
@Override
|
||||
public void apply(Project target) {
|
||||
ExtraPropertiesExtension extra = target.getExtensions().getExtraProperties();
|
||||
Object configValue = extra.has("nms") ? extra.get("nms") : null;
|
||||
if (!(configValue instanceof Config)) {
|
||||
throw new GradleException("No NMS binding configuration found");
|
||||
}
|
||||
|
||||
Config config = (Config) configValue;
|
||||
int jvm = config.jvm;
|
||||
Type type = config.type;
|
||||
|
||||
if (type == Type.USER_DEV) {
|
||||
target.getPlugins().apply(PaperweightUser.class);
|
||||
|
||||
PaperweightUserDependenciesExtension dependenciesExtension =
|
||||
target.getDependencies().getExtensions().findByType(PaperweightUserDependenciesExtension.class);
|
||||
if (dependenciesExtension != null) {
|
||||
dependenciesExtension.paperDevBundle(config.version);
|
||||
}
|
||||
|
||||
JavaPluginExtension java = target.getExtensions().findByType(JavaPluginExtension.class);
|
||||
if (java == null) {
|
||||
throw new GradleException("Java plugin not found");
|
||||
}
|
||||
|
||||
java.getToolchain().getLanguageVersion().set(JavaLanguageVersion.of(jvm));
|
||||
JavaToolchainService javaToolchains = target.getExtensions().getByType(JavaToolchainService.class);
|
||||
target.getExtensions().configure(PaperweightUserExtension.class,
|
||||
extension -> extension.getJavaLauncher().set(javaToolchains.launcherFor(java.getToolchain())));
|
||||
} else {
|
||||
extra.set("nmsTools.useBuildTools", type == Type.BUILD_TOOLS);
|
||||
target.getPlugins().apply(NMSToolsPlugin.class);
|
||||
target.getExtensions().configure(NMSToolsExtension.class, extension -> {
|
||||
extension.getJvm().set(jvm);
|
||||
extension.getVersion().set(config.version);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
String outgoingArtifactTask = type == Type.USER_DEV ? "jar" : "remap";
|
||||
ObjectFactory objects = target.getObjects();
|
||||
Configuration reobfConfiguration = target.getConfigurations().findByName(REOBF_CONFIG);
|
||||
if (reobfConfiguration == null) {
|
||||
reobfConfiguration = target.getConfigurations().create(REOBF_CONFIG);
|
||||
}
|
||||
|
||||
target.getConfigurations().named(REOBF_CONFIG).configure(configuration -> {
|
||||
configuration.setCanBeConsumed(true);
|
||||
configuration.setCanBeResolved(false);
|
||||
configuration.getAttributes().attribute(Usage.USAGE_ATTRIBUTE, named(objects, Usage.class, Usage.JAVA_RUNTIME));
|
||||
configuration.getAttributes().attribute(Category.CATEGORY_ATTRIBUTE, named(objects, Category.class, Category.LIBRARY));
|
||||
configuration.getAttributes().attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, named(objects, LibraryElements.class, LibraryElements.JAR));
|
||||
configuration.getAttributes().attribute(Bundling.BUNDLING_ATTRIBUTE, named(objects, Bundling.class, Bundling.EXTERNAL));
|
||||
configuration.getAttributes().attribute(Obfuscation.Companion.getOBFUSCATION_ATTRIBUTE(), named(objects, Obfuscation.class, Obfuscation.OBFUSCATED));
|
||||
configuration.getOutgoing().artifact(target.getTasks().named(outgoingArtifactTask));
|
||||
});
|
||||
|
||||
int[] version = parseVersion(config.version);
|
||||
int major = version[0];
|
||||
int minor = version[1];
|
||||
if (major <= 20 && minor <= 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
target.getTasks().register("convert", ConversionTask.class, type);
|
||||
target.getTasks().named("compileJava").configure(task -> task.dependsOn("convert"));
|
||||
target.getRootProject().getTasks()
|
||||
.matching(task -> task.getName().equals("prepareKotlinBuildScriptModel"))
|
||||
.configureEach(task -> task.dependsOn(target.getPath() + ":convert"));
|
||||
}
|
||||
|
||||
public static void nmsBinding(Project project, Action<Config> action) {
|
||||
Config config = new Config();
|
||||
action.execute(config);
|
||||
project.getExtensions().getExtraProperties().set("nms", config);
|
||||
project.getPlugins().apply(NMSBinding.class);
|
||||
}
|
||||
|
||||
private static int[] parseVersion(String version) {
|
||||
String trimmed = version;
|
||||
int suffix = trimmed.indexOf('-');
|
||||
if (suffix >= 0) {
|
||||
trimmed = trimmed.substring(0, suffix);
|
||||
}
|
||||
|
||||
String[] parts = trimmed.split("\\.");
|
||||
return new int[]{Integer.parseInt(parts[1]), Integer.parseInt(parts[2])};
|
||||
}
|
||||
|
||||
private static <T extends Named> T named(ObjectFactory objects, Class<T> type, String name) {
|
||||
return objects.named(type, name);
|
||||
}
|
||||
|
||||
@DisableCachingByDefault
|
||||
public abstract static class ConversionTask extends DefaultTask {
|
||||
private final Pattern pattern;
|
||||
private final String replacement;
|
||||
|
||||
@Inject
|
||||
public ConversionTask(Type type) {
|
||||
setGroup("nms");
|
||||
getInputs().property("type", type);
|
||||
|
||||
JavaPluginExtension java = getProject().getExtensions().findByType(JavaPluginExtension.class);
|
||||
if (java == null) {
|
||||
throw new GradleException("Java plugin not found");
|
||||
}
|
||||
|
||||
Provider<FileTree> source = java.getSourceSets().named(SourceSet.MAIN_SOURCE_SET_NAME).map(SourceSet::getAllJava);
|
||||
getInputs().files(source);
|
||||
getOutputs().files(source);
|
||||
|
||||
if (type == Type.USER_DEV) {
|
||||
this.pattern = Pattern.compile("org\\.bukkit\\.craftbukkit\\." + getProject().getName());
|
||||
this.replacement = "org.bukkit.craftbukkit";
|
||||
} else {
|
||||
this.pattern = Pattern.compile("org\\.bukkit\\.craftbukkit\\.(?!" + getProject().getName() + ")");
|
||||
this.replacement = "org.bukkit.craftbukkit." + getProject().getName() + ".";
|
||||
}
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void process() {
|
||||
ExecutorService executor = Executors.newFixedThreadPool(16);
|
||||
try {
|
||||
Set<File> files = getInputs().getFiles().getFiles();
|
||||
List<Future<?>> futures = new ArrayList<>(files.size());
|
||||
for (File file : files) {
|
||||
if (!file.getName().endsWith(".java")) {
|
||||
continue;
|
||||
}
|
||||
futures.add(executor.submit(() -> processFile(file)));
|
||||
}
|
||||
|
||||
for (Future<?> future : futures) {
|
||||
future.get();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e.getCause());
|
||||
} finally {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private void processFile(File file) {
|
||||
List<String> output = new ArrayList<>();
|
||||
boolean changed = false;
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("package") || line.isBlank()) {
|
||||
output.add(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!line.startsWith("import")) {
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
output.add(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
if (!matcher.find()) {
|
||||
output.add(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
output.add(matcher.replaceAll(replacement));
|
||||
changed = true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (hasTrailingNewLine(file)) {
|
||||
output.add("");
|
||||
}
|
||||
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
|
||||
for (int i = 0; i < output.size(); i++) {
|
||||
writer.append(output.get(i));
|
||||
if (i + 1 < output.size()) {
|
||||
writer.append(NEW_LINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasTrailingNewLine(File file) throws IOException {
|
||||
if (NEW_LINE_BYTES.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
|
||||
if (raf.length() < NEW_LINE_BYTES.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[NEW_LINE_BYTES.length];
|
||||
raf.seek(raf.length() - bytes.length);
|
||||
raf.readFully(bytes);
|
||||
return Arrays.equals(bytes, NEW_LINE_BYTES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
USER_DEV,
|
||||
BUILD_TOOLS,
|
||||
DIRECT
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import org.gradle.jvm.tasks.Jar
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
tasks.named('jar', Jar).configure {
|
||||
manifest.attributes(
|
||||
'Agent-Class': 'art.arcane.iris.util.project.agent.Installer',
|
||||
'Premain-Class': 'art.arcane.iris.util.project.agent.Installer',
|
||||
'Can-Redefine-Classes': true,
|
||||
'Can-Retransform-Classes': true
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package art.arcane.iris.util.project.agent;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
|
||||
public class Installer {
|
||||
private static volatile Instrumentation instrumentation;
|
||||
|
||||
public static Instrumentation getInstrumentation() {
|
||||
Instrumentation instrumentation = Installer.instrumentation;
|
||||
if (instrumentation == null) {
|
||||
throw new IllegalStateException("The agent is not loaded or this method is not called via the system class loader");
|
||||
}
|
||||
return instrumentation;
|
||||
}
|
||||
|
||||
public static void premain(String arguments, Instrumentation instrumentation) {
|
||||
doMain(instrumentation);
|
||||
}
|
||||
|
||||
public static void agentmain(String arguments, Instrumentation instrumentation) {
|
||||
doMain(instrumentation);
|
||||
}
|
||||
|
||||
private static synchronized void doMain(Instrumentation instrumentation) {
|
||||
if (Installer.instrumentation != null)
|
||||
return;
|
||||
Installer.instrumentation = instrumentation;
|
||||
}
|
||||
}
|
||||
+252
-49
@@ -1,3 +1,15 @@
|
||||
import io.github.slimjar.resolver.data.Mirror
|
||||
import org.ajoberstar.grgit.Grgit
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.tasks.Copy
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.gradle.api.tasks.compile.JavaCompile
|
||||
import org.gradle.jvm.tasks.Jar
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
import java.net.URI
|
||||
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2021 Arcane Arts (Volmit Software)
|
||||
@@ -19,24 +31,22 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'java-library'
|
||||
id "io.freefair.lombok" version "8.6"
|
||||
alias(libs.plugins.shadow)
|
||||
alias(libs.plugins.sentry)
|
||||
alias(libs.plugins.slimjar)
|
||||
alias(libs.plugins.grgit)
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kotlin.lombok)
|
||||
}
|
||||
|
||||
def apiVersion = '1.19'
|
||||
def main = 'com.volmit.iris.Iris'
|
||||
|
||||
/**
|
||||
* We need parameter meta for the decree command system
|
||||
*/
|
||||
compileJava {
|
||||
options.compilerArgs << '-parameters'
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url 'https://nexus.phoenixdevt.fr/repository/maven-public/'}
|
||||
maven { url 'https://repo.auxilor.io/repository/maven-public/' }
|
||||
}
|
||||
def apiVersion = '1.21'
|
||||
def mainClass = 'art.arcane.iris.Iris'
|
||||
def lib = 'art.arcane.iris.util'
|
||||
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
|
||||
.orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT')
|
||||
.get()
|
||||
String sentryAuthToken = findProperty('sentry.auth.token') as String ?: System.getenv('SENTRY_AUTH_TOKEN')
|
||||
boolean hasSentryAuthToken = sentryAuthToken != null && !sentryAuthToken.isBlank()
|
||||
|
||||
/**
|
||||
* Dependencies.
|
||||
@@ -52,44 +62,237 @@ repositories {
|
||||
*/
|
||||
dependencies {
|
||||
// Provided or Classpath
|
||||
compileOnly 'org.spigotmc:spigot-api:1.20.1-R0.1-SNAPSHOT'
|
||||
compileOnly 'org.apache.logging.log4j:log4j-api:2.19.0'
|
||||
compileOnly 'org.apache.logging.log4j:log4j-core:2.19.0'
|
||||
compileOnly 'commons-io:commons-io:2.13.0'
|
||||
compileOnly 'commons-lang:commons-lang:2.6'
|
||||
compileOnly 'com.github.oshi:oshi-core:5.8.5'
|
||||
compileOnly 'org.lz4:lz4-java:1.8.0'
|
||||
compileOnly(libs.spigot)
|
||||
compileOnly(libs.log4j.api)
|
||||
compileOnly(libs.log4j.core)
|
||||
|
||||
// Third Party Integrations
|
||||
compileOnly 'com.ticxo.playeranimator:PlayerAnimator:R1.2.7'
|
||||
compileOnly 'com.nexomc:nexo:1.0.0-dev.38'
|
||||
compileOnly 'com.github.LoneDev6:api-itemsadder:3.4.1-r4'
|
||||
compileOnly 'com.github.PlaceholderAPI:placeholderapi:2.11.3'
|
||||
compileOnly 'com.github.Ssomar-Developement:SCore:4.23.10.8'
|
||||
compileOnly 'net.Indyuce:MMOItems-API:6.9.5-SNAPSHOT'
|
||||
compileOnly 'com.willfp:EcoItems:5.44.0'
|
||||
//implementation files('libs/CustomItems.jar')
|
||||
compileOnly(libs.nexo)
|
||||
compileOnly(libs.itemsadder)
|
||||
compileOnly(libs.placeholderApi)
|
||||
compileOnly(libs.score)
|
||||
compileOnly(libs.mmoitems)
|
||||
compileOnly(libs.ecoitems)
|
||||
compileOnly(libs.mythic)
|
||||
compileOnly(libs.mythicChrucible)
|
||||
compileOnly(libs.kgenerators) {
|
||||
transitive = false
|
||||
}
|
||||
compileOnly(libs.multiverseCore)
|
||||
compileOnly(libs.craftengine.core)
|
||||
compileOnly(libs.craftengine.bukkit)
|
||||
|
||||
// Shaded
|
||||
implementation('de.crazydev22.slimjar.helper:spigot:2.1.9')
|
||||
implementation(volmLibCoordinate) {
|
||||
changing = true
|
||||
transitive = false
|
||||
}
|
||||
|
||||
// Dynamically Loaded
|
||||
slim(libs.paralithic)
|
||||
slim(libs.paperlib)
|
||||
slim(libs.adventure.api)
|
||||
slim(libs.adventure.minimessage)
|
||||
slim(libs.adventure.platform)
|
||||
slim(libs.bstats)
|
||||
slim(libs.sentry)
|
||||
|
||||
slim(libs.commons.io)
|
||||
slim(libs.commons.lang)
|
||||
slim(libs.commons.lang3)
|
||||
slim(libs.commons.math3)
|
||||
slim(libs.oshi)
|
||||
slim(libs.lz4)
|
||||
slim(libs.fastutil)
|
||||
slim(libs.lru)
|
||||
slim(libs.zip)
|
||||
slim(libs.gson)
|
||||
slim(libs.asm)
|
||||
slim(libs.caffeine)
|
||||
slim(libs.byteBuddy.core)
|
||||
slim(libs.byteBuddy.agent)
|
||||
slim(libs.dom4j)
|
||||
slim(libs.jaxen)
|
||||
|
||||
// Script Engine
|
||||
slim(libs.kotlin.stdlib)
|
||||
slim(libs.kotlin.coroutines)
|
||||
|
||||
testImplementation('junit:junit:4.13.2')
|
||||
testImplementation('org.mockito:mockito-core:5.16.1')
|
||||
testImplementation(libs.spigot)
|
||||
testRuntimeOnly(libs.spigot)
|
||||
}
|
||||
|
||||
java {
|
||||
disableAutoTargetJvm()
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(25)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gradle is weird sometimes, we need to delete the plugin yml from the build folder to actually filter properly.
|
||||
*/
|
||||
file(jar.archiveFile.get().getAsFile().getParentFile().getParentFile().getParentFile().getAbsolutePath() + '/build/resources/main/plugin.yml').delete()
|
||||
|
||||
/**
|
||||
* Expand properties into plugin yml
|
||||
*/
|
||||
processResources {
|
||||
filesMatching('**/plugin.yml') {
|
||||
expand(
|
||||
'name': rootProject.name.toString(),
|
||||
'version': rootProject.version.toString(),
|
||||
'main': main.toString(),
|
||||
'apiversion': apiVersion.toString()
|
||||
)
|
||||
kotlin {
|
||||
jvmToolchain(25)
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.fromTarget('25'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sentry {
|
||||
url = 'http://sentry.volmit.com:8080'
|
||||
autoInstallation.enabled = false
|
||||
includeSourceContext = true
|
||||
|
||||
org = 'sentry'
|
||||
projectName = 'iris'
|
||||
authToken = sentryAuthToken
|
||||
}
|
||||
|
||||
slimJar {
|
||||
mirrors = [
|
||||
new Mirror(
|
||||
URI.create('https://maven-central.storage-download.googleapis.com/maven2').toURL(),
|
||||
URI.create('https://repo.maven.apache.org/maven2/').toURL()
|
||||
)
|
||||
]
|
||||
|
||||
relocate('com.dfsek.paralithic', "${lib}.paralithic")
|
||||
relocate('io.papermc.lib', "${lib}.paper")
|
||||
relocate('net.kyori', "${lib}.kyori")
|
||||
relocate('org.bstats', "${lib}.metrics")
|
||||
relocate('io.sentry', "${lib}.sentry")
|
||||
relocate('org.apache.maven', "${lib}.maven")
|
||||
relocate('org.codehaus.plexus', "${lib}.plexus")
|
||||
relocate('org.eclipse.sisu', "${lib}.sisu")
|
||||
relocate('org.eclipse.aether', "${lib}.aether")
|
||||
relocate('com.google.inject', "${lib}.guice")
|
||||
relocate('org.dom4j', "${lib}.dom4j")
|
||||
relocate('org.jaxen', "${lib}.jaxen")
|
||||
relocate('com.github.benmanes.caffeine', "${lib}.caffeine")
|
||||
}
|
||||
|
||||
def embeddedAgentJar = project(':core:agent').tasks.named('jar', Jar)
|
||||
def templateSource = file('src/main/templates')
|
||||
def templateDest = layout.buildDirectory.dir('generated/sources/templates')
|
||||
def generateTemplates = tasks.register('generateTemplates', Copy) {
|
||||
inputs.properties([
|
||||
environment: providers.provider {
|
||||
if (project.hasProperty('release')) {
|
||||
return 'production'
|
||||
}
|
||||
if (project.hasProperty('argghh')) {
|
||||
return 'Argghh!'
|
||||
}
|
||||
return 'development'
|
||||
},
|
||||
commit: providers.provider {
|
||||
String commitId = null
|
||||
Exception failure = null
|
||||
try {
|
||||
commitId = project.extensions.getByType(Grgit).head().id
|
||||
} catch (Exception ex) {
|
||||
failure = ex
|
||||
}
|
||||
|
||||
if (commitId != null && commitId.length() == 40) {
|
||||
return commitId
|
||||
}
|
||||
|
||||
logger.error('Git commit hash not found', failure)
|
||||
return 'unknown'
|
||||
},
|
||||
])
|
||||
|
||||
from(templateSource)
|
||||
into(templateDest)
|
||||
rename { String fileName -> "art/arcane/iris/${fileName}" }
|
||||
expand(inputs.properties)
|
||||
}
|
||||
|
||||
tasks.named('compileJava', JavaCompile).configure {
|
||||
/**
|
||||
* We need parameter meta for the decree command system
|
||||
*/
|
||||
options.compilerArgs.add('-parameters')
|
||||
options.encoding = 'UTF-8'
|
||||
options.debugOptions.debugLevel = 'none'
|
||||
}
|
||||
|
||||
tasks.named('processResources').configure {
|
||||
/**
|
||||
* Expand properties into plugin yml
|
||||
*/
|
||||
def pluginProperties = [
|
||||
name : rootProject.name,
|
||||
version : rootProject.version,
|
||||
apiVersion: apiVersion,
|
||||
main : mainClass,
|
||||
]
|
||||
inputs.properties(pluginProperties)
|
||||
filesMatching('**/plugin.yml') {
|
||||
expand(pluginProperties)
|
||||
}
|
||||
}
|
||||
|
||||
def runningTestTasks = gradle.startParameter.taskNames.any { String taskName -> taskName.toLowerCase().contains('test') }
|
||||
if (runningTestTasks) {
|
||||
TaskProvider<Task> processResourcesTask = tasks.named('processResources')
|
||||
tasks.named('classes').configure { Task classesTask ->
|
||||
Set<Object> dependencies = new LinkedHashSet<Object>(classesTask.getDependsOn())
|
||||
dependencies.removeIf { Object dependency ->
|
||||
if (dependency instanceof TaskProvider) {
|
||||
return ((TaskProvider<?>) dependency).name == processResourcesTask.name
|
||||
}
|
||||
if (dependency instanceof Task) {
|
||||
return ((Task) dependency).name == processResourcesTask.name
|
||||
}
|
||||
String dependencyName = String.valueOf(dependency)
|
||||
return dependencyName == 'processResources' || dependencyName.endsWith(':processResources')
|
||||
}
|
||||
classesTask.setDependsOn(dependencies)
|
||||
}
|
||||
processResourcesTask.configure { Task task ->
|
||||
task.enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar).configure {
|
||||
dependsOn(embeddedAgentJar)
|
||||
mergeServiceFiles()
|
||||
//minimize()
|
||||
relocate('io.github.slimjar', "${lib}.slimjar")
|
||||
exclude('modules/loader-agent.isolated-jar')
|
||||
from(embeddedAgentJar.map { it.archiveFile }) {
|
||||
rename { String ignored -> 'agent.jar' }
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('sentryCollectSourcesJava').configure {
|
||||
dependsOn(generateTemplates)
|
||||
}
|
||||
|
||||
tasks.named('generateSentryBundleIdJava').configure {
|
||||
dependsOn(generateTemplates)
|
||||
}
|
||||
|
||||
tasks.matching { Task task ->
|
||||
task.name.startsWith('sentry') || task.name.startsWith('generateSentry')
|
||||
}.configureEach {
|
||||
onlyIf {
|
||||
hasSentryAuthToken
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.tasks.matching {
|
||||
it.name == 'prepareKotlinBuildScriptModel'
|
||||
}.configureEach {
|
||||
dependsOn(generateTemplates)
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir(generateTemplates.map { it.outputs })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
1435163759
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
package art.arcane.iris.core;
|
||||
|
||||
public enum IrisPaperLikeBackendMode {
|
||||
AUTO,
|
||||
TICKET,
|
||||
SERVICE
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package art.arcane.iris.core;
|
||||
|
||||
import art.arcane.volmlib.util.scheduling.FoliaScheduler;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum IrisRuntimeSchedulerMode {
|
||||
AUTO,
|
||||
PAPER_LIKE,
|
||||
FOLIA;
|
||||
|
||||
public static IrisRuntimeSchedulerMode resolve(IrisSettings.IrisSettingsPregen pregen) {
|
||||
Server server = Bukkit.getServer();
|
||||
boolean regionizedRuntime = FoliaScheduler.isRegionizedRuntime(server);
|
||||
if (regionizedRuntime) {
|
||||
return FOLIA;
|
||||
}
|
||||
|
||||
IrisRuntimeSchedulerMode configuredMode = pregen == null ? null : pregen.getRuntimeSchedulerMode();
|
||||
if (configuredMode != null && configuredMode != AUTO) {
|
||||
if (configuredMode == FOLIA) {
|
||||
return PAPER_LIKE;
|
||||
}
|
||||
return configuredMode;
|
||||
}
|
||||
|
||||
String bukkitName = Bukkit.getName();
|
||||
String bukkitVersion = Bukkit.getVersion();
|
||||
String serverClassName = server == null ? "" : server.getClass().getName();
|
||||
if (containsIgnoreCase(bukkitName, "folia")
|
||||
|| containsIgnoreCase(bukkitVersion, "folia")
|
||||
|| containsIgnoreCase(serverClassName, "folia")) {
|
||||
return FOLIA;
|
||||
}
|
||||
|
||||
if (containsIgnoreCase(bukkitName, "purpur")
|
||||
|| containsIgnoreCase(bukkitVersion, "purpur")
|
||||
|| containsIgnoreCase(serverClassName, "purpur")
|
||||
|| containsIgnoreCase(bukkitName, "canvas")
|
||||
|| containsIgnoreCase(bukkitVersion, "canvas")
|
||||
|| containsIgnoreCase(serverClassName, "canvas")
|
||||
|| containsIgnoreCase(bukkitName, "paper")
|
||||
|| containsIgnoreCase(bukkitVersion, "paper")
|
||||
|| containsIgnoreCase(serverClassName, "paper")
|
||||
|| containsIgnoreCase(bukkitName, "pufferfish")
|
||||
|| containsIgnoreCase(bukkitVersion, "pufferfish")
|
||||
|| containsIgnoreCase(serverClassName, "pufferfish")
|
||||
|| containsIgnoreCase(bukkitName, "spigot")
|
||||
|| containsIgnoreCase(bukkitVersion, "spigot")
|
||||
|| containsIgnoreCase(serverClassName, "spigot")
|
||||
|| containsIgnoreCase(bukkitName, "craftbukkit")
|
||||
|| containsIgnoreCase(bukkitVersion, "craftbukkit")
|
||||
|| containsIgnoreCase(serverClassName, "craftbukkit")) {
|
||||
return PAPER_LIKE;
|
||||
}
|
||||
|
||||
if (regionizedRuntime) {
|
||||
return FOLIA;
|
||||
}
|
||||
|
||||
return PAPER_LIKE;
|
||||
}
|
||||
|
||||
private static boolean containsIgnoreCase(String value, String contains) {
|
||||
if (value == null || contains == null || contains.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return value.toLowerCase(Locale.ROOT).contains(contains.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
+92
-36
@@ -16,17 +16,16 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core;
|
||||
package art.arcane.iris.core;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.json.JSONException;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import lombok.AllArgsConstructor;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.volmlib.util.json.JSONException;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import art.arcane.iris.util.common.misc.getHardware;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -43,14 +42,14 @@ public class IrisSettings {
|
||||
private IrisSettingsConcurrency concurrency = new IrisSettingsConcurrency();
|
||||
private IrisSettingsStudio studio = new IrisSettingsStudio();
|
||||
private IrisSettingsPerformance performance = new IrisSettingsPerformance();
|
||||
private IrisSettingsUpdater updater = new IrisSettingsUpdater();
|
||||
private IrisSettingsPregen pregen = new IrisSettingsPregen();
|
||||
private IrisSettingsSentry sentry = new IrisSettingsSentry();
|
||||
|
||||
public static int getThreadCount(int c) {
|
||||
return switch (c) {
|
||||
return Math.max(switch (c) {
|
||||
case -1, -2, -4 -> Runtime.getRuntime().availableProcessors() / -c;
|
||||
case 0, 1, 2 -> 1;
|
||||
default -> Math.max(c, 2);
|
||||
};
|
||||
}, 1);
|
||||
}
|
||||
|
||||
public static IrisSettings get() {
|
||||
@@ -129,11 +128,13 @@ public class IrisSettings {
|
||||
public boolean markerEntitySpawningSystem = true;
|
||||
public boolean effectSystem = true;
|
||||
public boolean worldEditWandCUI = true;
|
||||
public boolean globalPregenCache = false;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsConcurrency {
|
||||
public int parallelism = -1;
|
||||
public int ioParallelism = -2;
|
||||
public int worldGenParallelism = -1;
|
||||
|
||||
public int getWorldGenThreads() {
|
||||
@@ -141,45 +142,78 @@ public class IrisSettings {
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsPregen {
|
||||
public boolean useCacheByDefault = true;
|
||||
public boolean useHighPriority = false;
|
||||
public boolean useVirtualThreads = false;
|
||||
public boolean useTicketQueue = true;
|
||||
public IrisRuntimeSchedulerMode runtimeSchedulerMode = IrisRuntimeSchedulerMode.AUTO;
|
||||
public IrisPaperLikeBackendMode paperLikeBackendMode = IrisPaperLikeBackendMode.AUTO;
|
||||
public int maxConcurrency = 256;
|
||||
public int paperLikeMaxConcurrency = 96;
|
||||
public int foliaMaxConcurrency = 32;
|
||||
public int chunkLoadTimeoutSeconds = 15;
|
||||
public int timeoutWarnIntervalMs = 500;
|
||||
public int saveIntervalMs = 30_000;
|
||||
public boolean enablePregenPerformanceProfile = true;
|
||||
public int pregenProfileNoiseCacheSize = 4_096;
|
||||
public boolean pregenProfileEnableFastCache = true;
|
||||
public boolean pregenProfileLogJvmHints = true;
|
||||
|
||||
public int getChunkLoadTimeoutSeconds() {
|
||||
return Math.max(5, Math.min(chunkLoadTimeoutSeconds, 120));
|
||||
}
|
||||
|
||||
public int getTimeoutWarnIntervalMs() {
|
||||
return Math.max(timeoutWarnIntervalMs, 250);
|
||||
}
|
||||
|
||||
public int getPaperLikeMaxConcurrency() {
|
||||
return Math.max(1, paperLikeMaxConcurrency);
|
||||
}
|
||||
|
||||
public int getFoliaMaxConcurrency() {
|
||||
return Math.max(1, foliaMaxConcurrency);
|
||||
}
|
||||
|
||||
public IrisPaperLikeBackendMode getPaperLikeBackendMode() {
|
||||
if (paperLikeBackendMode == null) {
|
||||
return IrisPaperLikeBackendMode.AUTO;
|
||||
}
|
||||
|
||||
return paperLikeBackendMode;
|
||||
}
|
||||
|
||||
public int getSaveIntervalMs() {
|
||||
return Math.max(5_000, Math.min(saveIntervalMs, 900_000));
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsPerformance {
|
||||
private IrisSettingsEngineSVC engineSVC = new IrisSettingsEngineSVC();
|
||||
public boolean trimMantleInStudio = false;
|
||||
public int mantleKeepAlive = 30;
|
||||
public int cacheSize = 4_096;
|
||||
public int noiseCacheSize = 1_024;
|
||||
public int resourceLoaderCacheSize = 1_024;
|
||||
public int objectLoaderCacheSize = 4_096;
|
||||
public int scriptLoaderCacheSize = 512;
|
||||
}
|
||||
public int tectonicPlateSize = -1;
|
||||
public int mantleCleanupDelay = 200;
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsUpdater {
|
||||
public double threadMultiplier = 2;
|
||||
public double chunkLoadSensitivity = 0.7;
|
||||
public MsRange emptyMsRange = new MsRange(80, 100);
|
||||
public MsRange defaultMsRange = new MsRange(20, 40);
|
||||
public int getTectonicPlateSize() {
|
||||
if (tectonicPlateSize > 0)
|
||||
return tectonicPlateSize;
|
||||
|
||||
public double getThreadMultiplier() {
|
||||
return Math.min(Math.abs(threadMultiplier), 0.1);
|
||||
return (int) (getHardware.getProcessMemory() / 512L);
|
||||
}
|
||||
|
||||
public double getChunkLoadSensitivity() {
|
||||
return Math.min(chunkLoadSensitivity, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class MsRange {
|
||||
public int min = 20;
|
||||
public int max = 40;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsGeneral {
|
||||
public boolean DoomsdayAnnihilationSelfDestructMode = false;
|
||||
public boolean commandSounds = true;
|
||||
public boolean debug = false;
|
||||
public boolean dumpMantleOnError = false;
|
||||
public boolean disableNMS = false;
|
||||
public boolean pluginMetrics = true;
|
||||
public boolean splashLogoStartup = true;
|
||||
@@ -199,6 +233,13 @@ public class IrisSettings {
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsSentry {
|
||||
public boolean includeServerId = true;
|
||||
public boolean disableAutoReporting = false;
|
||||
public boolean debug = false;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsGUI {
|
||||
public boolean useServerLaunchedGuis = true;
|
||||
@@ -211,6 +252,9 @@ public class IrisSettings {
|
||||
public String defaultWorldType = "overworld";
|
||||
public int maxBiomeChildDepth = 4;
|
||||
public boolean preventLeafDecay = true;
|
||||
public boolean useMulticore = false;
|
||||
public boolean useMulticoreMantle = false;
|
||||
public boolean earlyCustomBlocks = false;
|
||||
}
|
||||
|
||||
@Data
|
||||
@@ -218,6 +262,18 @@ public class IrisSettings {
|
||||
public boolean studio = true;
|
||||
public boolean openVSCode = true;
|
||||
public boolean disableTimeAndWeather = true;
|
||||
public boolean enableEntitySpawning = false;
|
||||
public boolean autoStartDefaultStudio = false;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class IrisSettingsEngineSVC {
|
||||
public boolean useVirtualThreads = true;
|
||||
public boolean forceMulticoreWrite = false;
|
||||
public int priority = Thread.NORM_PRIORITY;
|
||||
|
||||
public int getPriority() {
|
||||
return Math.max(Math.min(priority, Thread.MAX_PRIORITY), Thread.MIN_PRIORITY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package art.arcane.iris.core;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.iris.util.common.misc.ServerProperties;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class IrisWorlds {
|
||||
private static final AtomicCache<IrisWorlds> cache = new AtomicCache<>();
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
private static final Type TYPE = TypeToken.getParameterized(KMap.class, String.class, String.class).getType();
|
||||
private final KMap<String, String> worlds;
|
||||
private volatile boolean dirty = false;
|
||||
|
||||
private IrisWorlds(KMap<String, String> worlds) {
|
||||
this.worlds = worlds;
|
||||
readBukkitWorlds().forEach(this::put0);
|
||||
save();
|
||||
}
|
||||
|
||||
public static IrisWorlds get() {
|
||||
return cache.aquire(() -> {
|
||||
File file = Iris.instance.getDataFile("worlds.json");
|
||||
if (!file.exists()) {
|
||||
return new IrisWorlds(new KMap<>());
|
||||
}
|
||||
|
||||
try {
|
||||
String json = IO.readAll(file);
|
||||
KMap<String, String> worlds = GSON.fromJson(json, TYPE);
|
||||
return new IrisWorlds(Objects.requireNonNullElseGet(worlds, KMap::new));
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Failed to load worlds.json!");
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
}
|
||||
|
||||
return new IrisWorlds(new KMap<>());
|
||||
});
|
||||
}
|
||||
|
||||
public void put(String name, String type) {
|
||||
put0(name, type);
|
||||
save();
|
||||
}
|
||||
|
||||
private void put0(String name, String type) {
|
||||
String old = worlds.put(name, type);
|
||||
if (!type.equals(old))
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
public KMap<String, String> getWorlds() {
|
||||
clean();
|
||||
return readBukkitWorlds().put(worlds);
|
||||
}
|
||||
|
||||
public Stream<IrisData> getPacks() {
|
||||
return getDimensions()
|
||||
.map(IrisDimension::getLoader)
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
public Stream<IrisDimension> getDimensions() {
|
||||
return getWorlds()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(entry -> Iris.loadDimension(entry.getKey(), entry.getValue()))
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
public void clean() {
|
||||
dirty = worlds.entrySet().removeIf(entry -> !new File(Bukkit.getWorldContainer(), entry.getKey() + "/iris/pack/dimensions/" + entry.getValue() + ".json").exists());
|
||||
}
|
||||
|
||||
public synchronized void save() {
|
||||
clean();
|
||||
if (!dirty) return;
|
||||
try {
|
||||
IO.write(Iris.instance.getDataFile("worlds.json"), OutputStreamWriter::new, writer -> GSON.toJson(worlds, TYPE, writer));
|
||||
dirty = false;
|
||||
} catch (IOException e) {
|
||||
Iris.error("Failed to save worlds.json!");
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static KMap<String, String> readBukkitWorlds() {
|
||||
var bukkit = YamlConfiguration.loadConfiguration(ServerProperties.BUKKIT_YML);
|
||||
var worlds = bukkit.getConfigurationSection("worlds");
|
||||
if (worlds == null) return new KMap<>();
|
||||
|
||||
var result = new KMap<String, String>();
|
||||
for (String world : worlds.getKeys(false)) {
|
||||
var gen = worlds.getString(world + ".generator");
|
||||
if (gen == null) continue;
|
||||
|
||||
String loadKey;
|
||||
if (gen.equalsIgnoreCase("iris")) {
|
||||
loadKey = IrisSettings.get().getGenerator().getDefaultWorldType();
|
||||
} else if (gen.startsWith("Iris:")) {
|
||||
loadKey = gen.substring(5);
|
||||
} else continue;
|
||||
|
||||
result.put(world, loadKey);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.iris.core.nms.datapack.DataVersion;
|
||||
import art.arcane.iris.core.nms.datapack.IDataFixer;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.iris.util.common.misc.ServerProperties;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import lombok.NonNull;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicIntegerArray;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ServerConfigurator {
|
||||
private static volatile boolean deferredInstallPending = false;
|
||||
|
||||
public static void configure() {
|
||||
IrisSettings.IrisSettingsAutoconfiguration s = IrisSettings.get().getAutoConfiguration();
|
||||
if (s.isConfigureSpigotTimeoutTime()) {
|
||||
J.attempt(ServerConfigurator::increaseKeepAliveSpigot);
|
||||
}
|
||||
|
||||
if (s.isConfigurePaperWatchdogDelay()) {
|
||||
J.attempt(ServerConfigurator::increasePaperWatchdog);
|
||||
}
|
||||
|
||||
if (shouldDeferInstallUntilWorldsReady()) {
|
||||
deferredInstallPending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
deferredInstallPending = false;
|
||||
installDataPacks(true);
|
||||
}
|
||||
|
||||
public static void configureIfDeferred() {
|
||||
if (!deferredInstallPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
configure();
|
||||
if (deferredInstallPending) {
|
||||
J.a(ServerConfigurator::configureIfDeferred, 20);
|
||||
}
|
||||
}
|
||||
|
||||
private static void increaseKeepAliveSpigot() throws IOException, InvalidConfigurationException {
|
||||
File spigotConfig = new File("spigot.yml");
|
||||
FileConfiguration f = new YamlConfiguration();
|
||||
f.load(spigotConfig);
|
||||
long tt = f.getLong("settings.timeout-time");
|
||||
|
||||
long spigotTimeout = TimeUnit.MINUTES.toSeconds(5);
|
||||
|
||||
if (tt < spigotTimeout) {
|
||||
Iris.warn("Updating spigot.yml timeout-time: " + tt + " -> " + spigotTimeout + " (5 minutes)");
|
||||
Iris.warn("You can disable this change (autoconfigureServer) in Iris settings, then change back the value.");
|
||||
f.set("settings.timeout-time", spigotTimeout);
|
||||
f.save(spigotConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private static void increasePaperWatchdog() throws IOException, InvalidConfigurationException {
|
||||
File spigotConfig = new File("config/paper-global.yml");
|
||||
FileConfiguration f = new YamlConfiguration();
|
||||
f.load(spigotConfig);
|
||||
long tt = f.getLong("watchdog.early-warning-delay");
|
||||
|
||||
long watchdog = TimeUnit.MINUTES.toMillis(3);
|
||||
if (tt < watchdog) {
|
||||
Iris.warn("Updating paper.yml watchdog early-warning-delay: " + tt + " -> " + watchdog + " (3 minutes)");
|
||||
Iris.warn("You can disable this change (autoconfigureServer) in Iris settings, then change back the value.");
|
||||
f.set("watchdog.early-warning-delay", watchdog);
|
||||
f.save(spigotConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private static KList<File> getDatapacksFolder() {
|
||||
if (!IrisSettings.get().getGeneral().forceMainWorld.isEmpty()) {
|
||||
return new KList<File>().qadd(new File(Bukkit.getWorldContainer(), IrisSettings.get().getGeneral().forceMainWorld + "/datapacks"));
|
||||
}
|
||||
KList<File> worlds = new KList<>();
|
||||
Bukkit.getServer().getWorlds().forEach(w -> {
|
||||
File folder = resolveDatapacksFolder(w.getWorldFolder());
|
||||
if (!worlds.contains(folder)) {
|
||||
worlds.add(folder);
|
||||
}
|
||||
});
|
||||
if (worlds.isEmpty()) {
|
||||
worlds.add(new File(Bukkit.getWorldContainer(), ServerProperties.LEVEL_NAME + "/datapacks"));
|
||||
}
|
||||
return worlds;
|
||||
}
|
||||
|
||||
public static boolean installDataPacks(boolean fullInstall) {
|
||||
IDataFixer fixer = DataVersion.getDefault();
|
||||
if (fixer == null) {
|
||||
DataVersion fallback = DataVersion.getLatest();
|
||||
Iris.warn("Primary datapack fixer was null, forcing latest fixer: " + fallback.getVersion());
|
||||
fixer = fallback.get();
|
||||
}
|
||||
return installDataPacks(fixer, fullInstall);
|
||||
}
|
||||
|
||||
public static boolean installDataPacks(IDataFixer fixer, boolean fullInstall) {
|
||||
if (fixer == null) {
|
||||
Iris.error("Unable to install datapacks, fixer is null!");
|
||||
return false;
|
||||
}
|
||||
if (fullInstall) {
|
||||
Iris.info("Checking Data Packs...");
|
||||
} else {
|
||||
Iris.verbose("Checking Data Packs...");
|
||||
}
|
||||
DimensionHeight height = new DimensionHeight(fixer);
|
||||
KList<File> folders = getDatapacksFolder();
|
||||
java.util.concurrent.ConcurrentMap<String, KSet<String>> biomes = new java.util.concurrent.ConcurrentHashMap<>();
|
||||
|
||||
try (Stream<IrisData> stream = allPacks()) {
|
||||
stream.flatMap(height::merge)
|
||||
.parallel()
|
||||
.forEach(dim -> {
|
||||
Iris.verbose(" Checking Dimension " + dim.getLoadFile().getPath());
|
||||
dim.installBiomes(fixer, dim::getLoader, folders, biomes.computeIfAbsent(dim.getLoadKey(), k -> new KSet<>()));
|
||||
dim.installDimensionType(fixer, folders);
|
||||
});
|
||||
}
|
||||
IrisDimension.writeShared(folders, height);
|
||||
if (fullInstall) {
|
||||
Iris.info("Data Packs Setup!");
|
||||
} else {
|
||||
Iris.verbose("Data Packs Setup!");
|
||||
}
|
||||
|
||||
return fullInstall && verifyDataPacksPost(IrisSettings.get().getAutoConfiguration().isAutoRestartOnCustomBiomeInstall());
|
||||
}
|
||||
|
||||
public static boolean installDataPacksIfChanged(boolean fullInstall) {
|
||||
File packsDir = Iris.instance.getDataFolder("packs");
|
||||
String current = computePackFingerprint(packsDir);
|
||||
File cacheFile = new File(Iris.instance.getDataFolder("cache"), "datapack-fingerprint");
|
||||
String cached = "";
|
||||
if (cacheFile.exists()) {
|
||||
try {
|
||||
cached = Files.readString(cacheFile.toPath(), StandardCharsets.UTF_8).trim();
|
||||
} catch (IOException e) {
|
||||
cached = "";
|
||||
}
|
||||
}
|
||||
if (!current.isEmpty() && current.equals(cached)) {
|
||||
Iris.verbose("Data packs unchanged, skipping install.");
|
||||
return false;
|
||||
}
|
||||
boolean result = installDataPacks(fullInstall);
|
||||
try {
|
||||
cacheFile.getParentFile().mkdirs();
|
||||
Files.writeString(cacheFile.toPath(), current, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
Iris.warn("Failed to write datapack fingerprint cache: " + e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String computePackFingerprint(File packsDir) {
|
||||
if (packsDir == null || !packsDir.isDirectory()) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
List<String> entries = new ArrayList<>();
|
||||
collectFingerprintEntries(packsDir, packsDir.getAbsolutePath(), entries);
|
||||
Collections.sort(entries);
|
||||
for (String entry : entries) {
|
||||
digest.update(entry.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
byte[] hash = digest.digest();
|
||||
StringBuilder sb = new StringBuilder(hash.length * 2);
|
||||
for (byte b : hash) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("SHA-256 not available", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectFingerprintEntries(File dir, String rootPath, List<String> entries) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
collectFingerprintEntries(file, rootPath, entries);
|
||||
} else {
|
||||
String relative = file.getAbsolutePath().substring(rootPath.length());
|
||||
entries.add(relative + "|" + file.length() + "|" + file.lastModified());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldDeferInstallUntilWorldsReady() {
|
||||
String forcedMainWorld = IrisSettings.get().getGeneral().forceMainWorld;
|
||||
if (forcedMainWorld != null && !forcedMainWorld.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Bukkit.getServer().getWorlds().isEmpty();
|
||||
}
|
||||
|
||||
public static File resolveDatapacksFolder(File worldFolder) {
|
||||
File rootFolder = resolveWorldRootFolder(worldFolder);
|
||||
return new File(rootFolder, "datapacks");
|
||||
}
|
||||
|
||||
static File resolveWorldRootFolder(File worldFolder) {
|
||||
if (worldFolder == null) {
|
||||
return new File(Bukkit.getWorldContainer(), ServerProperties.LEVEL_NAME);
|
||||
}
|
||||
|
||||
File current = worldFolder.getAbsoluteFile();
|
||||
while (current != null) {
|
||||
if ("dimensions".equals(current.getName())) {
|
||||
File parent = current.getParentFile();
|
||||
if (parent != null) {
|
||||
return parent;
|
||||
}
|
||||
break;
|
||||
}
|
||||
current = current.getParentFile();
|
||||
}
|
||||
|
||||
return worldFolder.getAbsoluteFile();
|
||||
}
|
||||
|
||||
private static boolean verifyDataPacksPost(boolean allowRestarting) {
|
||||
try (Stream<IrisData> stream = allPacks()) {
|
||||
boolean bad = stream
|
||||
.map(data -> {
|
||||
Iris.verbose("Checking Pack: " + data.getDataFolder().getPath());
|
||||
var loader = data.getDimensionLoader();
|
||||
return loader.loadAll(loader.getPossibleKeys())
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(ServerConfigurator::verifyDataPackInstalled)
|
||||
.toList()
|
||||
.contains(false);
|
||||
})
|
||||
.toList()
|
||||
.contains(true);
|
||||
if (!bad) return false;
|
||||
}
|
||||
|
||||
|
||||
if (allowRestarting) {
|
||||
restart();
|
||||
} else if (INMS.get().supportsDataPacks()) {
|
||||
Iris.error("============================================================================");
|
||||
Iris.error(C.ITALIC + "You need to restart your server to properly generate custom biomes.");
|
||||
Iris.error(C.ITALIC + "By continuing, Iris will use backup biomes in place of the custom biomes.");
|
||||
Iris.error("----------------------------------------------------------------------------");
|
||||
Iris.error(C.UNDERLINE + "IT IS HIGHLY RECOMMENDED YOU RESTART THE SERVER BEFORE GENERATING!");
|
||||
Iris.error("============================================================================");
|
||||
|
||||
for (Player i : Bukkit.getOnlinePlayers()) {
|
||||
if (i.isOp() || i.hasPermission("iris.all")) {
|
||||
VolmitSender sender = new VolmitSender(i, Iris.instance.getTag("WARNING"));
|
||||
sender.sendMessage("There are some Iris Packs that have custom biomes in them");
|
||||
sender.sendMessage("You need to restart your server to use these packs.");
|
||||
}
|
||||
}
|
||||
|
||||
J.sleep(3000);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void restart() {
|
||||
J.s(() -> {
|
||||
Iris.warn("New data pack entries have been installed in Iris! Restarting server!");
|
||||
Iris.warn("This will only happen when your pack changes (updates/first time setup)");
|
||||
Iris.warn("(You can disable this auto restart in iris settings)");
|
||||
J.s(() -> {
|
||||
Iris.warn("Looks like the restart command didn't work. Stopping the server instead!");
|
||||
Bukkit.shutdown();
|
||||
}, 100);
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "restart");
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean verifyDataPackInstalled(IrisDimension dimension) {
|
||||
KSet<String> keys = new KSet<>();
|
||||
boolean warn = false;
|
||||
|
||||
for (IrisBiome i : dimension.getAllBiomes(dimension::getLoader)) {
|
||||
if (i.isCustom()) {
|
||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||
keys.add(dimension.getLoadKey() + ":" + j.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
String key = getWorld(dimension.getLoader());
|
||||
if (key == null) key = dimension.getLoadKey();
|
||||
else key += "/" + dimension.getLoadKey();
|
||||
|
||||
if (!INMS.get().supportsDataPacks()) {
|
||||
if (!keys.isEmpty()) {
|
||||
Iris.warn("===================================================================================");
|
||||
Iris.warn("Pack " + key + " has " + keys.size() + " custom biome(s). ");
|
||||
Iris.warn("Your server version does not yet support datapacks for iris.");
|
||||
Iris.warn("The world will generate these biomes as backup biomes.");
|
||||
Iris.warn("====================================================================================");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String i : keys) {
|
||||
Object o = INMS.get().getCustomBiomeBaseFor(i);
|
||||
|
||||
if (o == null) {
|
||||
Iris.warn("The Biome " + i + " is not registered on the server.");
|
||||
warn = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (INMS.get().missingDimensionTypes(dimension.getDimensionTypeKey())) {
|
||||
Iris.warn("The Dimension Type for " + dimension.getLoadFile() + " is not registered on the server.");
|
||||
warn = true;
|
||||
}
|
||||
|
||||
if (warn) {
|
||||
Iris.error("The Pack " + key + " is INCAPABLE of generating custom biomes");
|
||||
Iris.error("If not done automatically, restart your server before generating with this pack!");
|
||||
}
|
||||
|
||||
return !warn;
|
||||
}
|
||||
|
||||
public static Stream<IrisData> allPacks() {
|
||||
File[] packs = Iris.instance.getDataFolder("packs").listFiles(File::isDirectory);
|
||||
Stream<File> locals = packs == null ? Stream.empty() : Arrays.stream(packs);
|
||||
return Stream.concat(locals
|
||||
.filter( base -> {
|
||||
var content = new File(base, "dimensions").listFiles();
|
||||
return content != null && content.length > 0;
|
||||
})
|
||||
.map(IrisData::get), IrisWorlds.get().getPacks());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getWorld(@NonNull IrisData data) {
|
||||
String worldContainer = Bukkit.getWorldContainer().getAbsolutePath();
|
||||
if (!worldContainer.endsWith(File.separator)) worldContainer += File.separator;
|
||||
|
||||
String path = data.getDataFolder().getAbsolutePath();
|
||||
if (!path.startsWith(worldContainer)) return null;
|
||||
int l = path.endsWith(File.separator) ? 11 : 10;
|
||||
return path.substring(worldContainer.length(), path.length() - l);
|
||||
}
|
||||
|
||||
public static class DimensionHeight {
|
||||
private final IDataFixer fixer;
|
||||
private final AtomicIntegerArray[] dimensions = new AtomicIntegerArray[3];
|
||||
|
||||
public DimensionHeight(IDataFixer fixer) {
|
||||
this.fixer = fixer;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
dimensions[i] = new AtomicIntegerArray(new int[]{
|
||||
Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Stream<IrisDimension> merge(IrisData data) {
|
||||
Iris.verbose("Checking Pack: " + data.getDataFolder().getPath());
|
||||
var loader = data.getDimensionLoader();
|
||||
return loader.loadAll(loader.getPossibleKeys())
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.peek(this::merge);
|
||||
}
|
||||
|
||||
public void merge(IrisDimension dimension) {
|
||||
AtomicIntegerArray array = dimensions[dimension.getBaseDimension().ordinal()];
|
||||
array.updateAndGet(0, min -> Math.min(min, dimension.getMinHeight()));
|
||||
array.updateAndGet(1, max -> Math.max(max, dimension.getMaxHeight()));
|
||||
array.updateAndGet(2, logical -> Math.max(logical, dimension.getLogicalHeight()));
|
||||
}
|
||||
|
||||
public String[] jsonStrings() {
|
||||
var dims = IDataFixer.Dimension.values();
|
||||
var arr = new String[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
arr[i] = jsonString(dims[i]);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
public String jsonString(IDataFixer.Dimension dimension) {
|
||||
var data = dimensions[dimension.ordinal()];
|
||||
int minY = data.get(0);
|
||||
int maxY = data.get(1);
|
||||
int logicalHeight = data.get(2);
|
||||
if (minY == Integer.MAX_VALUE || maxY == Integer.MIN_VALUE || Integer.MIN_VALUE == logicalHeight)
|
||||
return null;
|
||||
return fixer.createDimension(dimension, minY, maxY - minY, logicalHeight, null).toString(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.service.StudioSVC;
|
||||
import art.arcane.iris.engine.object.*;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.iris.util.common.director.DirectorHelp;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
|
||||
@Director(name = "edit", origin = DirectorOrigin.PLAYER, studio = true, description = "Edit something")
|
||||
public class CommandEdit implements DirectorExecutor {
|
||||
@Director(description = "Show help tree for this command group", aliases = {"?"})
|
||||
public void help() {
|
||||
DirectorHelp.print(sender(), getClass());
|
||||
}
|
||||
|
||||
private boolean noStudio() {
|
||||
if (!sender().isPlayer()) {
|
||||
sender().sendMessage(C.RED + "Players only!");
|
||||
return true;
|
||||
}
|
||||
if (!Iris.service(StudioSVC.class).isProjectOpen()) {
|
||||
sender().sendMessage(C.RED + "No studio world is open!");
|
||||
return true;
|
||||
}
|
||||
if (!engine().isStudio()) {
|
||||
sender().sendMessage(C.RED + "You must be in a studio world!");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GraphicsEnvironment.isHeadless()) {
|
||||
sender().sendMessage(C.RED + "Cannot open files in headless environments!");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Desktop.isDesktopSupported()) {
|
||||
sender().sendMessage(C.RED + "Desktop is not supported by this environment!");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Director(description = "Edit the biome you specified", aliases = {"b"}, origin = DirectorOrigin.PLAYER)
|
||||
public void biome(@Param(contextual = false, description = "The biome to edit") IrisBiome biome) {
|
||||
if (noStudio()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (biome == null || biome.getLoadFile() == null) {
|
||||
sender().sendMessage(C.GOLD + "Cannot find the file; Perhaps it was not loaded directly from a file?");
|
||||
return;
|
||||
}
|
||||
Desktop.getDesktop().open(biome.getLoadFile());
|
||||
sender().sendMessage(C.GREEN + "Opening " + biome.getTypeName() + " " + biome.getLoadFile().getName().split("\\Q.\\E")[0] + " in VSCode! ");
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
sender().sendMessage(C.RED + "Cant find the file. Or registrant does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
@Director(description = "Edit the region you specified", aliases = {"r"}, origin = DirectorOrigin.PLAYER)
|
||||
public void region(@Param(contextual = false, description = "The region to edit") IrisRegion region) {
|
||||
if (noStudio()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (region == null || region.getLoadFile() == null) {
|
||||
sender().sendMessage(C.GOLD + "Cannot find the file; Perhaps it was not loaded directly from a file?");
|
||||
return;
|
||||
}
|
||||
Desktop.getDesktop().open(region.getLoadFile());
|
||||
sender().sendMessage(C.GREEN + "Opening " + region.getTypeName() + " " + region.getLoadFile().getName().split("\\Q.\\E")[0] + " in VSCode! ");
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
sender().sendMessage(C.RED + "Cant find the file. Or registrant does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
@Director(description = "Edit the dimension you specified", aliases = {"d"}, origin = DirectorOrigin.PLAYER)
|
||||
public void dimension(@Param(contextual = false, description = "The dimension to edit") IrisDimension dimension) {
|
||||
if (noStudio()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (dimension == null || dimension.getLoadFile() == null) {
|
||||
sender().sendMessage(C.GOLD + "Cannot find the file; Perhaps it was not loaded directly from a file?");
|
||||
return;
|
||||
}
|
||||
Desktop.getDesktop().open(dimension.getLoadFile());
|
||||
sender().sendMessage(C.GREEN + "Opening " + dimension.getTypeName() + " " + dimension.getLoadFile().getName().split("\\Q.\\E")[0] + " in VSCode! ");
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
sender().sendMessage(C.RED + "Cant find the file. Or registrant does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+37
-35
@@ -16,22 +16,24 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.commands;
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.engine.object.IrisJigsawStructure;
|
||||
import com.volmit.iris.engine.object.IrisRegion;
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import com.volmit.iris.util.decree.DecreeOrigin;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
import com.volmit.iris.util.decree.annotations.Param;
|
||||
import com.volmit.iris.util.decree.specialhandlers.ObjectHandler;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.service.ObjectStudioSaveService;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.iris.util.common.director.specialhandlers.ObjectHandler;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@Decree(name = "find", origin = DecreeOrigin.PLAYER, description = "Iris Find commands", aliases = "goto")
|
||||
public class CommandFind implements DecreeExecutor {
|
||||
@Decree(description = "Find a biome")
|
||||
@Director(name = "find", origin = DirectorOrigin.PLAYER, description = "Iris Find commands", aliases = "goto")
|
||||
public class CommandFind implements DirectorExecutor {
|
||||
@Director(description = "Find a biome")
|
||||
public void biome(
|
||||
@Param(description = "The biome to look for")
|
||||
IrisBiome biome,
|
||||
@@ -48,7 +50,7 @@ public class CommandFind implements DecreeExecutor {
|
||||
e.gotoBiome(biome, player(), teleport);
|
||||
}
|
||||
|
||||
@Decree(description = "Find a region")
|
||||
@Director(description = "Find a region")
|
||||
public void region(
|
||||
@Param(description = "The region to look for")
|
||||
IrisRegion region,
|
||||
@@ -65,24 +67,7 @@ public class CommandFind implements DecreeExecutor {
|
||||
e.gotoRegion(region, player(), teleport);
|
||||
}
|
||||
|
||||
@Decree(description = "Find a structure")
|
||||
public void structure(
|
||||
@Param(description = "The structure to look for")
|
||||
IrisJigsawStructure structure,
|
||||
@Param(description = "Should you be teleported", defaultValue = "true")
|
||||
boolean teleport
|
||||
) {
|
||||
Engine e = engine();
|
||||
|
||||
if (e == null) {
|
||||
sender().sendMessage(C.GOLD + "Not in an Iris World!");
|
||||
return;
|
||||
}
|
||||
|
||||
e.gotoJigsaw(structure, player(), teleport);
|
||||
}
|
||||
|
||||
@Decree(description = "Find a point of interest.")
|
||||
@Director(description = "Find a point of interest.")
|
||||
public void poi(
|
||||
@Param(description = "The type of PoI to look for.")
|
||||
String type,
|
||||
@@ -98,7 +83,7 @@ public class CommandFind implements DecreeExecutor {
|
||||
e.gotoPOI(type, player(), teleport);
|
||||
}
|
||||
|
||||
@Decree(description = "Find an object")
|
||||
@Director(description = "Find an object")
|
||||
public void object(
|
||||
@Param(description = "The object to look for", customHandler = ObjectHandler.class)
|
||||
String object,
|
||||
@@ -112,6 +97,23 @@ public class CommandFind implements DecreeExecutor {
|
||||
return;
|
||||
}
|
||||
|
||||
e.gotoObject(object, player(), teleport);
|
||||
Player studioPlayer = player();
|
||||
if (studioPlayer != null) {
|
||||
try {
|
||||
if (ObjectStudioSaveService.get().teleportTo(studioPlayer, object)) {
|
||||
sender().sendMessage(C.GREEN + "Object Studio: teleporting to " + object);
|
||||
return;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Iris.reportError(t);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.hasObjectPlacement(object)) {
|
||||
e.gotoObject(object, player(), teleport);
|
||||
return;
|
||||
}
|
||||
|
||||
sender().sendMessage(C.RED + object + " is not configured in any region/biome object placements.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,793 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.IrisWorlds;
|
||||
import art.arcane.iris.core.lifecycle.WorldLifecycleService;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.iris.core.service.StudioSVC;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.platform.ChunkReplacementListener;
|
||||
import art.arcane.iris.engine.platform.ChunkReplacementOptions;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.common.director.DirectorContext;
|
||||
import art.arcane.volmlib.util.director.DirectorParameterHandler;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.iris.util.common.director.DirectorHelp;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
|
||||
import art.arcane.iris.util.common.director.specialhandlers.NullablePlayerHandler;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import art.arcane.iris.util.common.parallel.SyncExecutor;
|
||||
import art.arcane.iris.util.common.misc.ServerProperties;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.boss.BarColor;
|
||||
import org.bukkit.boss.BarStyle;
|
||||
import org.bukkit.boss.BossBar;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static art.arcane.iris.core.service.EditSVC.deletingWorld;
|
||||
import static art.arcane.iris.util.common.misc.ServerProperties.BUKKIT_YML;
|
||||
import static org.bukkit.Bukkit.getServer;
|
||||
|
||||
@Director(name = "iris", aliases = {"ir", "irs"}, description = "Basic Command")
|
||||
public class CommandIris implements DirectorExecutor {
|
||||
@Director(description = "Show help tree for this command group", aliases = {"?"})
|
||||
public void help() {
|
||||
DirectorHelp.print(sender(), getClass());
|
||||
}
|
||||
|
||||
private CommandStudio studio;
|
||||
private CommandPregen pregen;
|
||||
private CommandSettings settings;
|
||||
private CommandObject object;
|
||||
private CommandWhat what;
|
||||
private CommandEdit edit;
|
||||
private CommandDeveloper developer;
|
||||
private CommandPack pack;
|
||||
private CommandFind find;
|
||||
public static boolean worldCreation = false;
|
||||
private static final AtomicReference<Thread> mainWorld = new AtomicReference<>();
|
||||
String WorldEngine;
|
||||
String worldNameToCheck = "YourWorldName";
|
||||
VolmitSender sender = Iris.getSender();
|
||||
|
||||
@Director(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", "pack"},
|
||||
description = "The dimension/pack to create the world with",
|
||||
defaultValue = "default",
|
||||
customHandler = PackDimensionTypeHandler.class
|
||||
)
|
||||
String type,
|
||||
@Param(description = "The seed to generate the world with", defaultValue = "1337")
|
||||
long seed,
|
||||
@Param(aliases = "main-world", description = "Whether or not to automatically use this world as the main world", defaultValue = "false")
|
||||
boolean main,
|
||||
@Param(aliases = {"remove-others", "removeothers"}, description = "When main-world is true, remove other Iris worlds from bukkit.yml and queue deletion on startup", defaultValue = "false")
|
||||
boolean removeOthers,
|
||||
@Param(aliases = {"remove-worlds", "removeworlds"}, description = "Comma-separated world names to remove from Iris control and delete on next startup (main-world only)", defaultValue = "none")
|
||||
String removeWorlds
|
||||
) {
|
||||
if (name.equalsIgnoreCase("iris")) {
|
||||
sender().sendMessage(C.RED + "You cannot use the world name \"iris\" for creating worlds as Iris uses this directory for studio worlds.");
|
||||
sender().sendMessage(C.RED + "May we suggest the name \"IrisWorld\" instead?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (name.equalsIgnoreCase("benchmark")) {
|
||||
sender().sendMessage(C.RED + "You cannot use the world name \"benchmark\" for creating worlds as Iris uses this directory for Benchmarking Packs.");
|
||||
sender().sendMessage(C.RED + "May we suggest the name \"IrisWorld\" instead?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (new File(Bukkit.getWorldContainer(), name).exists()) {
|
||||
sender().sendMessage(C.RED + "That folder already exists!");
|
||||
return;
|
||||
}
|
||||
|
||||
String resolvedType = type.equalsIgnoreCase("default")
|
||||
? IrisSettings.get().getGenerator().getDefaultWorldType()
|
||||
: type;
|
||||
|
||||
IrisDimension dimension = IrisToolbelt.getDimension(resolvedType);
|
||||
if (dimension == null) {
|
||||
sender().sendMessage(C.RED + "Could not find or download dimension \"" + resolvedType + "\".");
|
||||
sender().sendMessage(C.YELLOW + "Try one of: overworld, vanilla, flat, theend");
|
||||
sender().sendMessage(C.YELLOW + "Or download manually: /iris download IrisDimensions/" + resolvedType);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!main && (removeOthers || hasExplicitCleanupWorlds(removeWorlds))) {
|
||||
sender().sendMessage(C.YELLOW + "remove-others/remove-worlds only apply when main-world=true. Ignoring cleanup options.");
|
||||
removeOthers = false;
|
||||
removeWorlds = "none";
|
||||
}
|
||||
|
||||
if (J.isFolia()) {
|
||||
if (stageFoliaWorldCreation(name, dimension, seed, main, removeOthers, removeWorlds)) {
|
||||
sender().sendMessage(C.GREEN + "World staging completed. Restart the server to generate/load \"" + name + "\".");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
worldCreation = true;
|
||||
IrisToolbelt.createWorld()
|
||||
.dimension(dimension.getLoadKey())
|
||||
.name(name)
|
||||
.seed(seed)
|
||||
.sender(sender())
|
||||
.studio(false)
|
||||
.create();
|
||||
if (main) {
|
||||
Runtime.getRuntime().addShutdownHook(mainWorld.updateAndGet(old -> {
|
||||
if (old != null) Runtime.getRuntime().removeShutdownHook(old);
|
||||
return new Thread(() -> updateMainWorld(name));
|
||||
}));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
sender().sendMessage(C.RED + "Exception raised during creation. See the console for more details.");
|
||||
Iris.reportError("Exception raised during world creation for \"" + name + "\".", e);
|
||||
worldCreation = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (main && !applyMainWorldCleanup(name, removeOthers, removeWorlds)) {
|
||||
worldCreation = false;
|
||||
return;
|
||||
}
|
||||
|
||||
worldCreation = false;
|
||||
sender().sendMessage(C.GREEN + "Successfully created your world!");
|
||||
if (main) sender().sendMessage(C.GREEN + "Your world will automatically be set as the main world when the server restarts.");
|
||||
}
|
||||
|
||||
private boolean updateMainWorld(String newName) {
|
||||
try {
|
||||
File worlds = Bukkit.getWorldContainer();
|
||||
var data = ServerProperties.DATA;
|
||||
try (var in = new FileInputStream(ServerProperties.SERVER_PROPERTIES)) {
|
||||
data.load(in);
|
||||
}
|
||||
|
||||
File oldWorldFolder = new File(worlds, ServerProperties.LEVEL_NAME);
|
||||
File newWorldFolder = new File(worlds, newName);
|
||||
if (!newWorldFolder.exists() && !newWorldFolder.mkdirs()) {
|
||||
Iris.warn("Could not create target main world folder: " + newWorldFolder.getAbsolutePath());
|
||||
}
|
||||
|
||||
for (String sub : List.of("datapacks", "playerdata", "advancements", "stats")) {
|
||||
File source = new File(oldWorldFolder, sub);
|
||||
if (!source.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IO.copyDirectory(source.toPath(), new File(newWorldFolder, sub).toPath());
|
||||
}
|
||||
|
||||
data.setProperty("level-name", newName);
|
||||
try (var out = new FileOutputStream(ServerProperties.SERVER_PROPERTIES)) {
|
||||
data.store(out, null);
|
||||
}
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Failed to update server.properties main world to \"" + newName + "\"");
|
||||
Iris.reportError(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean stageFoliaWorldCreation(String name, IrisDimension dimension, long seed, boolean main, boolean removeOthers, String removeWorlds) {
|
||||
sender().sendMessage(C.YELLOW + "Runtime world creation is disabled on Folia.");
|
||||
sender().sendMessage(C.YELLOW + "Preparing world files and bukkit.yml for next startup...");
|
||||
|
||||
File worldFolder = new File(Bukkit.getWorldContainer(), name);
|
||||
IrisDimension installed = Iris.service(StudioSVC.class).installIntoWorld(sender(), dimension.getLoadKey(), worldFolder);
|
||||
if (installed == null) {
|
||||
sender().sendMessage(C.RED + "Failed to stage world files for dimension \"" + dimension.getLoadKey() + "\".");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!registerWorldInBukkitYml(name, dimension.getLoadKey(), seed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (main) {
|
||||
if (updateMainWorld(name)) {
|
||||
sender().sendMessage(C.GREEN + "Updated server.properties level-name to \"" + name + "\".");
|
||||
} else {
|
||||
sender().sendMessage(C.RED + "World was staged, but failed to update server.properties main world.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!applyMainWorldCleanup(name, removeOthers, removeWorlds)) {
|
||||
sender().sendMessage(C.RED + "World was staged, but failed to apply main-world cleanup options.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sender().sendMessage(C.GREEN + "Staged Iris world \"" + name + "\" with generator Iris:" + dimension.getLoadKey() + " and seed " + seed + ".");
|
||||
if (main) {
|
||||
sender().sendMessage(C.GREEN + "This world is now configured as main for next restart.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean registerWorldInBukkitYml(String worldName, String dimension, Long seed) {
|
||||
YamlConfiguration yml = YamlConfiguration.loadConfiguration(BUKKIT_YML);
|
||||
ConfigurationSection worlds = yml.getConfigurationSection("worlds");
|
||||
if (worlds == null) {
|
||||
worlds = yml.createSection("worlds");
|
||||
}
|
||||
ConfigurationSection worldSection = worlds.getConfigurationSection(worldName);
|
||||
if (worldSection == null) {
|
||||
worldSection = worlds.createSection(worldName);
|
||||
}
|
||||
|
||||
String generator = "Iris:" + dimension;
|
||||
worldSection.set("generator", generator);
|
||||
if (seed != null) {
|
||||
worldSection.set("seed", seed);
|
||||
}
|
||||
|
||||
try {
|
||||
yml.save(BUKKIT_YML);
|
||||
Iris.info("Registered \"" + worldName + "\" in bukkit.yml");
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
sender().sendMessage(C.RED + "Failed to update bukkit.yml: " + e.getMessage());
|
||||
Iris.error("Failed to update bukkit.yml!");
|
||||
Iris.reportError(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean applyMainWorldCleanup(String mainWorld, boolean removeOthers, String removeWorlds) {
|
||||
Set<String> targets = resolveCleanupTargets(mainWorld, removeOthers, removeWorlds);
|
||||
if (targets.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
sender().sendMessage(C.YELLOW + "Applying main-world cleanup for " + targets.size() + " world(s).");
|
||||
|
||||
YamlConfiguration yml = YamlConfiguration.loadConfiguration(BUKKIT_YML);
|
||||
ConfigurationSection worlds = yml.getConfigurationSection("worlds");
|
||||
|
||||
Set<String> removedFromBukkit = new LinkedHashSet<>();
|
||||
Set<String> notRemoved = new LinkedHashSet<>();
|
||||
for (String target : targets) {
|
||||
String key = findWorldKeyIgnoreCase(worlds, target);
|
||||
if (key == null) {
|
||||
notRemoved.add(target);
|
||||
continue;
|
||||
}
|
||||
|
||||
String generator = worlds.getString(key + ".generator");
|
||||
if (generator == null || !(generator.equalsIgnoreCase("iris") || generator.startsWith("Iris:"))) {
|
||||
notRemoved.add(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
worlds.set(key, null);
|
||||
removedFromBukkit.add(key);
|
||||
}
|
||||
|
||||
try {
|
||||
if (worlds != null && worlds.getKeys(false).isEmpty()) {
|
||||
yml.set("worlds", null);
|
||||
}
|
||||
|
||||
if (!removedFromBukkit.isEmpty()) {
|
||||
yml.save(BUKKIT_YML);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
sender().sendMessage(C.RED + "Failed to update bukkit.yml while applying cleanup: " + e.getMessage());
|
||||
Iris.reportError(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
int queued = Iris.queueWorldDeletionOnStartup(targets);
|
||||
if (queued > 0) {
|
||||
sender().sendMessage(C.GREEN + "Queued " + queued + " world folder(s) for deletion on next startup.");
|
||||
} else {
|
||||
sender().sendMessage(C.YELLOW + "Cleanup queue already contained the requested world folder(s).");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
sender().sendMessage(C.RED + "Failed to queue startup world deletions: " + e.getMessage());
|
||||
Iris.reportError(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!removedFromBukkit.isEmpty()) {
|
||||
sender().sendMessage(C.GREEN + "Removed from Iris control in bukkit.yml: " + String.join(", ", removedFromBukkit));
|
||||
}
|
||||
|
||||
if (!notRemoved.isEmpty()) {
|
||||
sender().sendMessage(C.YELLOW + "Skipped from bukkit.yml removal (not found or non-Iris generator): " + String.join(", ", notRemoved));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Set<String> resolveCleanupTargets(String mainWorld, boolean removeOthers, String removeWorlds) {
|
||||
Set<String> targets = new LinkedHashSet<>();
|
||||
if (removeOthers) {
|
||||
IrisWorlds.readBukkitWorlds().keySet().stream()
|
||||
.filter(world -> !world.equalsIgnoreCase(mainWorld))
|
||||
.forEach(targets::add);
|
||||
}
|
||||
|
||||
if (hasExplicitCleanupWorlds(removeWorlds)) {
|
||||
for (String raw : removeWorlds.split("[,;\\s]+")) {
|
||||
if (raw == null || raw.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (raw.equalsIgnoreCase(mainWorld)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
targets.add(raw.trim());
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
private static boolean hasExplicitCleanupWorlds(String removeWorlds) {
|
||||
if (removeWorlds == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String trimmed = removeWorlds.trim();
|
||||
return !trimmed.isEmpty() && !trimmed.equalsIgnoreCase("none");
|
||||
}
|
||||
|
||||
private static String findWorldKeyIgnoreCase(ConfigurationSection worlds, String requested) {
|
||||
if (worlds == null || requested == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (worlds.contains(requested)) {
|
||||
return requested;
|
||||
}
|
||||
|
||||
for (String key : worlds.getKeys(false)) {
|
||||
if (key.equalsIgnoreCase(requested)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Director(description = "Teleport to another world", aliases = {"tp"}, sync = true)
|
||||
public void teleport(
|
||||
@Param(description = "World to teleport to")
|
||||
World world,
|
||||
@Param(description = "Player to teleport", defaultValue = "---", customHandler = NullablePlayerHandler.class)
|
||||
Player player
|
||||
) {
|
||||
if (player == null && sender().isPlayer())
|
||||
player = sender().player();
|
||||
|
||||
final Player target = player;
|
||||
if (target == null) {
|
||||
sender().sendMessage(C.RED + "The specified player does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
target.teleport(world.getSpawnLocation());
|
||||
new VolmitSender(target).sendMessage(C.GREEN + "You have been teleported to " + world.getName() + ".");
|
||||
}
|
||||
}.runTask(Iris.instance);
|
||||
}
|
||||
|
||||
@Director(description = "Print version information")
|
||||
public void version() {
|
||||
sender().sendMessage(C.GREEN + "Iris v" + Iris.instance.getDescription().getVersion() + " by Volmit Software");
|
||||
}
|
||||
|
||||
/*
|
||||
/todo
|
||||
@Director(description = "Benchmark a pack", origin = DirectorOrigin.CONSOLE)
|
||||
public void packbenchmark(
|
||||
@Param(description = "Dimension to benchmark")
|
||||
IrisDimension type
|
||||
) throws InterruptedException {
|
||||
|
||||
BenchDimension = type.getLoadKey();
|
||||
|
||||
IrisPackBenchmarking.runBenchmark();
|
||||
} */
|
||||
|
||||
@Director(description = "Print world height information", origin = DirectorOrigin.PLAYER)
|
||||
public void height() {
|
||||
if (sender().isPlayer()) {
|
||||
sender().sendMessage(C.GREEN + "" + sender().player().getWorld().getMinHeight() + " to " + sender().player().getWorld().getMaxHeight());
|
||||
sender().sendMessage(C.GREEN + "Total Height: " + (sender().player().getWorld().getMaxHeight() - sender().player().getWorld().getMinHeight()));
|
||||
} else {
|
||||
World mainWorld = getServer().getWorlds().get(0);
|
||||
Iris.info(C.GREEN + "" + mainWorld.getMinHeight() + " to " + mainWorld.getMaxHeight());
|
||||
Iris.info(C.GREEN + "Total Height: " + (mainWorld.getMaxHeight() - mainWorld.getMinHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
@Director(description = "Check access of all worlds.", aliases = {"accesslist"})
|
||||
public void worlds() {
|
||||
KList<World> IrisWorlds = new KList<>();
|
||||
KList<World> BukkitWorlds = new KList<>();
|
||||
|
||||
for (World w : Bukkit.getServer().getWorlds()) {
|
||||
try {
|
||||
Engine engine = IrisToolbelt.access(w).getEngine();
|
||||
if (engine != null) {
|
||||
IrisWorlds.add(w);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BukkitWorlds.add(w);
|
||||
}
|
||||
}
|
||||
|
||||
if (sender().isPlayer()) {
|
||||
sender().sendMessage(C.BLUE + "Iris Worlds: ");
|
||||
for (World IrisWorld : IrisWorlds.copy()) {
|
||||
sender().sendMessage(C.IRIS + "- " +IrisWorld.getName());
|
||||
}
|
||||
sender().sendMessage(C.GOLD + "Bukkit Worlds: ");
|
||||
for (World BukkitWorld : BukkitWorlds.copy()) {
|
||||
sender().sendMessage(C.GRAY + "- " +BukkitWorld.getName());
|
||||
}
|
||||
} else {
|
||||
Iris.info(C.BLUE + "Iris Worlds: ");
|
||||
for (World IrisWorld : IrisWorlds.copy()) {
|
||||
Iris.info(C.IRIS + "- " +IrisWorld.getName());
|
||||
}
|
||||
Iris.info(C.GOLD + "Bukkit Worlds: ");
|
||||
for (World BukkitWorld : BukkitWorlds.copy()) {
|
||||
Iris.info(C.GRAY + "- " +BukkitWorld.getName());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Director(description = "Remove an Iris world", aliases = {"del", "rm", "delete"}, sync = true)
|
||||
public void remove(
|
||||
@Param(description = "The world to remove")
|
||||
World world,
|
||||
@Param(description = "Whether to also remove the folder (if set to false, just does not load the world)", defaultValue = "true")
|
||||
boolean delete
|
||||
) {
|
||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||
sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList()));
|
||||
return;
|
||||
}
|
||||
sender().sendMessage(C.GREEN + "Removing world: " + world.getName());
|
||||
|
||||
if (!IrisToolbelt.evacuate(world)) {
|
||||
sender().sendMessage(C.RED + "Failed to evacuate world: " + world.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!WorldLifecycleService.get().unload(world, false)) {
|
||||
sender().sendMessage(C.RED + "Failed to unload world: " + world.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (IrisToolbelt.removeWorld(world)) {
|
||||
sender().sendMessage(C.GREEN + "Successfully removed " + world.getName() + " from bukkit.yml");
|
||||
} else {
|
||||
sender().sendMessage(C.YELLOW + "Looks like the world was already removed from bukkit.yml");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
sender().sendMessage(C.RED + "Failed to save bukkit.yml because of " + e.getMessage());
|
||||
Iris.reportError("Failed to remove world \"" + world.getName() + "\" from bukkit.yml.", e);
|
||||
}
|
||||
IrisToolbelt.evacuate(world, "Deleting world");
|
||||
deletingWorld = true;
|
||||
if (!delete) {
|
||||
deletingWorld = false;
|
||||
return;
|
||||
}
|
||||
VolmitSender sender = sender();
|
||||
J.a(() -> {
|
||||
int retries = 12;
|
||||
|
||||
if (deleteDirectory(world.getWorldFolder())) {
|
||||
sender.sendMessage(C.GREEN + "Successfully removed world folder");
|
||||
} else {
|
||||
while(true){
|
||||
if (deleteDirectory(world.getWorldFolder())){
|
||||
sender.sendMessage(C.GREEN + "Successfully removed world folder");
|
||||
break;
|
||||
}
|
||||
retries--;
|
||||
if (retries == 0){
|
||||
sender.sendMessage(C.RED + "Failed to remove world folder");
|
||||
break;
|
||||
}
|
||||
J.sleep(3000);
|
||||
}
|
||||
}
|
||||
deletingWorld = false;
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean deleteDirectory(File dir) {
|
||||
if (dir.isDirectory()) {
|
||||
File[] children = dir.listFiles();
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
boolean success = deleteDirectory(children[i]);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dir.delete();
|
||||
}
|
||||
|
||||
@Director(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);
|
||||
}
|
||||
|
||||
@Director(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 = "stable")
|
||||
String branch,
|
||||
@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 + (overwrite ? " overwriting" : ""));
|
||||
if (pack.equals("overworld")) {
|
||||
String url = "https://github.com/IrisDimensions/overworld/releases/download/" + INMS.OVERWORLD_TAG + "/overworld.zip";
|
||||
Iris.service(StudioSVC.class).downloadRelease(sender(), url, overwrite);
|
||||
} else {
|
||||
Iris.service(StudioSVC.class).downloadSearch(sender(), "IrisDimensions/" + pack + "/" + branch, overwrite);
|
||||
}
|
||||
}
|
||||
|
||||
@Director(description = "Get metrics for your world", aliases = "measure", origin = DirectorOrigin.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());
|
||||
}
|
||||
|
||||
@Director(description = "Reload configuration file (this is also done automatically)")
|
||||
public void reload() {
|
||||
IrisSettings.invalidate();
|
||||
IrisSettings.get();
|
||||
sender().sendMessage(C.GREEN + "Hotloaded settings");
|
||||
}
|
||||
|
||||
|
||||
@Director(description = "Unload an Iris World", origin = DirectorOrigin.PLAYER, sync = true)
|
||||
public void unloadWorld(
|
||||
@Param(description = "The world to unload")
|
||||
World world
|
||||
) {
|
||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||
sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList()));
|
||||
return;
|
||||
}
|
||||
sender().sendMessage(C.GREEN + "Unloading world: " + world.getName());
|
||||
try {
|
||||
IrisToolbelt.evacuate(world);
|
||||
boolean unloaded = WorldLifecycleService.get().unload(world, false);
|
||||
if (unloaded) {
|
||||
sender().sendMessage(C.GREEN + "World unloaded successfully.");
|
||||
} else {
|
||||
sender().sendMessage(C.RED + "Failed to unload the world.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
sender().sendMessage(C.RED + "Failed to unload the world: " + e.getMessage());
|
||||
Iris.reportError("Failed to unload world \"" + world.getName() + "\".", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Director(description = "Load an Iris World", origin = DirectorOrigin.PLAYER, sync = true, aliases = {"import"})
|
||||
public void loadWorld(
|
||||
@Param(description = "The name of the world to load")
|
||||
String world
|
||||
) {
|
||||
World worldloaded = Bukkit.getWorld(world);
|
||||
worldNameToCheck = world;
|
||||
boolean worldExists = doesWorldExist(worldNameToCheck);
|
||||
WorldEngine = world;
|
||||
|
||||
if (!worldExists) {
|
||||
sender().sendMessage(C.YELLOW + world + " Doesnt exist on the server.");
|
||||
return;
|
||||
}
|
||||
|
||||
String pathtodim = world + File.separator +"iris"+File.separator +"pack"+File.separator +"dimensions"+File.separator;
|
||||
File directory = new File(Bukkit.getWorldContainer(), pathtodim);
|
||||
|
||||
String dimension = null;
|
||||
if (directory.exists() && directory.isDirectory()) {
|
||||
File[] files = directory.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isFile()) {
|
||||
String fileName = file.getName();
|
||||
if (fileName.endsWith(".json")) {
|
||||
dimension = fileName.substring(0, fileName.length() - 5);
|
||||
sender().sendMessage(C.BLUE + "Generator: " + dimension);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sender().sendMessage(C.GOLD + world + " is not an iris world.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (dimension == null) {
|
||||
sender().sendMessage(C.RED + "Could not determine Iris dimension for " + world + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
sender().sendMessage(C.GREEN + "Loading world: " + world);
|
||||
|
||||
if (!registerWorldInBukkitYml(world, dimension, null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (J.isFolia()) {
|
||||
sender().sendMessage(C.YELLOW + "Folia cannot load new worlds at runtime. Restart the server to load \"" + world + "\".");
|
||||
return;
|
||||
}
|
||||
|
||||
Iris.instance.checkForBukkitWorlds(world::equals);
|
||||
sender().sendMessage(C.GREEN + world + " loaded successfully.");
|
||||
}
|
||||
@Director(description = "Evacuate an iris world", origin = DirectorOrigin.PLAYER, sync = true)
|
||||
public void evacuate(
|
||||
@Param(description = "Evacuate the world")
|
||||
World world
|
||||
) {
|
||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||
sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList()));
|
||||
return;
|
||||
}
|
||||
sender().sendMessage(C.GREEN + "Evacuating world" + world.getName());
|
||||
IrisToolbelt.evacuate(world);
|
||||
}
|
||||
|
||||
boolean doesWorldExist(String worldName) {
|
||||
File worldContainer = Bukkit.getWorldContainer();
|
||||
File worldDirectory = new File(worldContainer, worldName);
|
||||
return worldDirectory.exists() && worldDirectory.isDirectory();
|
||||
}
|
||||
|
||||
public static class PackDimensionTypeHandler implements DirectorParameterHandler<String> {
|
||||
@Override
|
||||
public KList<String> getPossibilities() {
|
||||
Set<String> options = new LinkedHashSet<>();
|
||||
options.add("default");
|
||||
|
||||
File packsFolder = Iris.instance.getDataFolder("packs");
|
||||
File[] packs = packsFolder.listFiles();
|
||||
if (packs != null) {
|
||||
for (File pack : packs) {
|
||||
if (pack == null || !pack.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
options.add(pack.getName());
|
||||
|
||||
try {
|
||||
IrisData data = IrisData.get(pack);
|
||||
for (String key : data.getDimensionLoader().getPossibleKeys()) {
|
||||
options.add(key);
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Iris.warn("Failed to read dimension keys from pack %s: %s%s",
|
||||
pack.getName(),
|
||||
ex.getClass().getSimpleName(),
|
||||
ex.getMessage() == null ? "" : " - " + ex.getMessage());
|
||||
Iris.reportError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new KList<>(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parse(String in, boolean force) throws DirectorParsingException {
|
||||
if (in == null || in.trim().isEmpty()) {
|
||||
throw new DirectorParsingException("World type cannot be empty");
|
||||
}
|
||||
|
||||
return in.trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> type) {
|
||||
return type == String.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
+375
-49
@@ -16,32 +16,43 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.commands;
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.link.WorldEditLink;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.service.ObjectSVC;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.core.service.WandSVC;
|
||||
import com.volmit.iris.core.tools.IrisConverter;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.*;
|
||||
import com.volmit.iris.util.data.Cuboid;
|
||||
import com.volmit.iris.util.data.IrisCustomData;
|
||||
import com.volmit.iris.util.data.registry.Materials;
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import com.volmit.iris.util.decree.DecreeOrigin;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
import com.volmit.iris.util.decree.annotations.Param;
|
||||
import com.volmit.iris.util.decree.specialhandlers.ObjectHandler;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.math.Direction;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.scheduling.Queue;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.link.WorldEditLink;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.loader.ResourceLoader;
|
||||
import art.arcane.iris.core.runtime.ObjectStudioActivation;
|
||||
import art.arcane.iris.core.runtime.WorldRuntimeControlService;
|
||||
import art.arcane.iris.core.service.ObjectSVC;
|
||||
import art.arcane.iris.core.service.StudioSVC;
|
||||
import art.arcane.iris.core.service.WandSVC;
|
||||
import art.arcane.iris.core.tools.IrisConverter;
|
||||
import art.arcane.iris.core.tools.PlausibilizeMode;
|
||||
import art.arcane.iris.core.tools.TreePlausibilizer;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.*;
|
||||
import art.arcane.volmlib.util.data.Cuboid;
|
||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||
import art.arcane.iris.util.common.data.registry.Materials;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.iris.util.common.director.DirectorHelp;
|
||||
import art.arcane.iris.util.common.director.specialhandlers.NullableDimensionHandler;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.common.director.specialhandlers.ObjectHandler;
|
||||
import art.arcane.iris.util.common.director.specialhandlers.ObjectTargetHandler;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.iris.util.common.math.Direction;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
@@ -50,8 +61,107 @@ import java.io.IOException;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.*;
|
||||
|
||||
@Decree(name = "object", aliases = "o", origin = DecreeOrigin.PLAYER, studio = true, description = "Iris object manipulation")
|
||||
public class CommandObject implements DecreeExecutor {
|
||||
@Director(name = "object", aliases = "o", origin = DirectorOrigin.PLAYER, studio = true, description = "Iris object manipulation")
|
||||
public class CommandObject implements DirectorExecutor {
|
||||
@Director(description = "Show help tree for this command group", aliases = {"?"})
|
||||
public void help() {
|
||||
DirectorHelp.print(sender(), getClass());
|
||||
}
|
||||
|
||||
@Director(description = "Open an object studio world (grid of every object; dimension optional, defaults to all packs)", aliases = {"std", "s"}, sync = true)
|
||||
public void studio(
|
||||
@Param(defaultValue = "null", description = "Optional dimension whose object pack to lay out; omit to aggregate objects from every pack", aliases = "dim", customHandler = NullableDimensionHandler.class)
|
||||
IrisDimension dimension,
|
||||
@Param(defaultValue = "1337", description = "The seed to generate the studio with", aliases = "s")
|
||||
long seed
|
||||
) {
|
||||
VolmitSender commandSender = sender();
|
||||
Map<String, IrisData> sources = new LinkedHashMap<>();
|
||||
IrisDimension hostDimension = dimension;
|
||||
|
||||
if (dimension != null) {
|
||||
IrisData data = dimension.getLoader();
|
||||
if (data == null) {
|
||||
data = IrisData.get(dimension.getLoadFile().getParentFile().getParentFile());
|
||||
}
|
||||
sources.put(data.getDataFolder().getName(), data);
|
||||
} else {
|
||||
File workspace = Iris.service(StudioSVC.class).getWorkspaceFolder();
|
||||
File[] packs = workspace == null ? null : workspace.listFiles();
|
||||
if (packs != null) {
|
||||
Arrays.sort(packs, Comparator.comparing(File::getName, String.CASE_INSENSITIVE_ORDER));
|
||||
for (File pack : packs) {
|
||||
if (!pack.isDirectory()) continue;
|
||||
File dimensionsDir = new File(pack, "dimensions");
|
||||
if (!dimensionsDir.isDirectory()) continue;
|
||||
IrisData data = IrisData.get(pack);
|
||||
String[] keys = data.getObjectLoader().getPossibleKeys();
|
||||
if (keys == null || keys.length == 0) continue;
|
||||
sources.put(pack.getName(), data);
|
||||
if (hostDimension == null) {
|
||||
File[] dimFiles = dimensionsDir.listFiles((f) -> f.isFile() && f.getName().endsWith(".json"));
|
||||
if (dimFiles != null && dimFiles.length > 0) {
|
||||
String loadKey = dimFiles[0].getName().replaceFirst("\\.json$", "");
|
||||
IrisDimension loaded = data.getDimensionLoader().load(loadKey);
|
||||
if (loaded != null) {
|
||||
hostDimension = loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hostDimension == null || sources.isEmpty()) {
|
||||
commandSender.sendMessage(C.RED + "No packs with objects were found on this server.");
|
||||
return;
|
||||
}
|
||||
|
||||
int totalObjects = 0;
|
||||
for (IrisData d : sources.values()) {
|
||||
String[] k = d.getObjectLoader().getPossibleKeys();
|
||||
if (k != null) totalObjects += k.length;
|
||||
}
|
||||
if (totalObjects == 0) {
|
||||
commandSender.sendMessage(C.RED + "No objects to place across the selected pack(s).");
|
||||
return;
|
||||
}
|
||||
|
||||
hostDimension.setStudioMode(StudioMode.OBJECT_BUFFET);
|
||||
ObjectStudioActivation.activate(hostDimension.getLoadKey());
|
||||
ObjectStudioActivation.setSources(hostDimension.getLoadKey(), sources);
|
||||
|
||||
String scope = dimension == null
|
||||
? ("all packs [" + sources.size() + "]")
|
||||
: ("\"" + hostDimension.getName() + "\"");
|
||||
commandSender.sendMessage(C.GREEN + "Opening Object Studio for " + scope + " ("
|
||||
+ totalObjects + " objects)");
|
||||
|
||||
IrisDimension finalHost = hostDimension;
|
||||
try {
|
||||
Iris.service(StudioSVC.class).open(commandSender, seed, hostDimension.getLoadKey(), world -> {
|
||||
if (world == null) return;
|
||||
try {
|
||||
WorldRuntimeControlService.get().applyObjectStudioWorldRules(world);
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("Failed to apply object studio world rules for " + world.getName(), e);
|
||||
}
|
||||
|
||||
if (commandSender.isPlayer()) {
|
||||
Player p = commandSender.player();
|
||||
if (p != null) {
|
||||
Location target = new Location(world, 0.5D, 66D, 0.5D);
|
||||
J.runEntity(p, () -> {
|
||||
PaperLib.teleportAsync(p, target).thenRun(() -> p.setGameMode(GameMode.CREATIVE));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("Failed to open object studio world \"" + finalHost.getLoadKey() + "\".", e);
|
||||
commandSender.sendMessage(C.RED + "Failed to open object studio: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static final Set<Material> skipBlocks = Set.of(Materials.GRASS, Material.SNOW, Material.VINE, Material.TORCH, Material.DEAD_BUSH,
|
||||
Material.POPPY, Material.DANDELION);
|
||||
@@ -79,9 +189,9 @@ public class CommandObject implements DecreeExecutor {
|
||||
futureBlockChanges.put(block, block.getBlockData());
|
||||
|
||||
if (d instanceof IrisCustomData data) {
|
||||
block.setBlockData(data.getBase());
|
||||
block.setBlockData(data.getBase(), false);
|
||||
Iris.warn("Tried to place custom block at " + x + ", " + y + ", " + z + " which is not supported!");
|
||||
} else block.setBlockData(d);
|
||||
} else block.setBlockData(d, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,6 +234,16 @@ public class CommandObject implements DecreeExecutor {
|
||||
tile.toBukkitTry(world.getBlockAt(xx, yy, zz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void setData(int xx, int yy, int zz, T data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getData(int xx, int yy, int zz, Class<T> t) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Engine getEngine() {
|
||||
return null;
|
||||
@@ -131,16 +251,16 @@ public class CommandObject implements DecreeExecutor {
|
||||
};
|
||||
}
|
||||
|
||||
@Decree(description = "Check the composition of an object")
|
||||
@Director(description = "Check the composition of an object")
|
||||
public void analyze(
|
||||
@Param(description = "The object to analyze", customHandler = ObjectHandler.class)
|
||||
String object
|
||||
) {
|
||||
IrisObject o = IrisData.loadAnyObject(object);
|
||||
IrisObject o = IrisData.loadAnyObject(object, data());
|
||||
sender().sendMessage("Object Size: " + o.getW() + " * " + o.getH() + " * " + o.getD() + "");
|
||||
sender().sendMessage("Blocks Used: " + NumberFormat.getIntegerInstance().format(o.getBlocks().size()));
|
||||
|
||||
Queue<BlockData> queue = o.getBlocks().enqueueValues();
|
||||
var queue = o.getBlocks().values();
|
||||
Map<Material, Set<BlockData>> unsorted = new HashMap<>();
|
||||
Map<BlockData, Integer> amounts = new HashMap<>();
|
||||
Map<Material, Integer> materials = new HashMap<>();
|
||||
@@ -199,9 +319,9 @@ public class CommandObject implements DecreeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Shrink an object to its minimum size")
|
||||
@Director(description = "Shrink an object to its minimum size")
|
||||
public void shrink(@Param(description = "The object to shrink", customHandler = ObjectHandler.class) String object) {
|
||||
IrisObject o = IrisData.loadAnyObject(object);
|
||||
IrisObject o = IrisData.loadAnyObject(object, data());
|
||||
sender().sendMessage("Current Object Size: " + o.getW() + " * " + o.getH() + " * " + o.getD());
|
||||
o.shrinkwrap();
|
||||
sender().sendMessage("New Object Size: " + o.getW() + " * " + o.getH() + " * " + o.getD());
|
||||
@@ -213,7 +333,200 @@ public class CommandObject implements DecreeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Convert .schem files in the 'convert' folder to .iob files.")
|
||||
@Director(description = "Make tree leaves vanilla-decay-plausible (every leaf within 6 blocks of a log)",
|
||||
origin = DirectorOrigin.BOTH, studio = false)
|
||||
public void plausibilize(
|
||||
@Param(description = "Object key, prefix (trees/), or filesystem path",
|
||||
customHandler = ObjectTargetHandler.class)
|
||||
String target,
|
||||
@Param(description = "DEFAULT: tentacle logs, delete orphans. NORMALIZE: + flip persistent=false. FOLIAGE_OVERATURE: add leaves to bridge orphans, no deletions. SMOKE: wipe & repaint canopy shell.",
|
||||
defaultValue = "DEFAULT")
|
||||
PlausibilizeMode mode,
|
||||
@Param(description = "Analyze only, do not write", defaultValue = "false")
|
||||
boolean dryRun,
|
||||
@Param(description = "Canopy shell radius (SMOKE only), clamped [0,5]", defaultValue = "2")
|
||||
int radius
|
||||
) {
|
||||
List<Target> targets = resolveTargets(target);
|
||||
if (targets.isEmpty()) {
|
||||
sender().sendMessage(C.RED + "No objects matched: " + target);
|
||||
return;
|
||||
}
|
||||
|
||||
sender().sendMessage(C.IRIS + "Plausibilize [" + mode.name()
|
||||
+ (dryRun ? " DRY" : "")
|
||||
+ (mode == PlausibilizeMode.SMOKE ? " r=" + radius : "")
|
||||
+ "] queued " + targets.size() + " object(s)");
|
||||
|
||||
org.bukkit.command.CommandSender s = sender();
|
||||
J.a(() -> runPlausibilize(targets, dryRun, mode, radius, s));
|
||||
}
|
||||
|
||||
private List<Target> resolveTargets(String target) {
|
||||
List<Target> out = new ArrayList<>();
|
||||
if (target == null || target.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
|
||||
File direct = new File(target);
|
||||
if (direct.isFile() && target.toLowerCase().endsWith(".iob")) {
|
||||
out.add(new Target(direct.getName().replaceAll("\\.iob$", ""), direct));
|
||||
return out;
|
||||
}
|
||||
if (direct.isDirectory()) {
|
||||
walkIob(direct, direct, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
IrisData irisData = data();
|
||||
if (irisData != null) {
|
||||
ResourceLoader<IrisObject> loader = irisData.getObjectLoader();
|
||||
if (!target.endsWith("/") && loader.findFile(target) != null) {
|
||||
out.add(new Target(target, null));
|
||||
return out;
|
||||
}
|
||||
String prefix = target.endsWith("/") ? target : target + "/";
|
||||
for (String k : loader.getPossibleKeys()) {
|
||||
if (k.startsWith(prefix)) {
|
||||
out.add(new Target(k, null));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
File packsFolder = Iris.instance.getDataFolder("packs");
|
||||
File[] packs = packsFolder.listFiles(File::isDirectory);
|
||||
if (packs != null) {
|
||||
for (File pack : packs) {
|
||||
File objectsRoot = new File(pack, "objects");
|
||||
if (!objectsRoot.isDirectory()) continue;
|
||||
File candidate = new File(objectsRoot, target + ".iob");
|
||||
if (candidate.isFile()) {
|
||||
out.add(new Target(pack.getName() + "/" + target, candidate));
|
||||
continue;
|
||||
}
|
||||
File candidateDir = new File(objectsRoot, target);
|
||||
if (candidateDir.isDirectory()) {
|
||||
walkIob(candidateDir, objectsRoot, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static void walkIob(File root, File keyRoot, List<Target> out) {
|
||||
File[] kids = root.listFiles();
|
||||
if (kids == null) return;
|
||||
for (File f : kids) {
|
||||
if (f.isDirectory()) {
|
||||
walkIob(f, keyRoot, out);
|
||||
} else if (f.getName().toLowerCase().endsWith(".iob")) {
|
||||
String rel = keyRoot.toPath().relativize(f.toPath()).toString()
|
||||
.replace(File.separatorChar, '/')
|
||||
.replaceAll("\\.iob$", "");
|
||||
out.add(new Target(rel, f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record Target(String key, File file) {
|
||||
}
|
||||
|
||||
private static void runPlausibilize(
|
||||
List<Target> targets,
|
||||
boolean dryRun,
|
||||
PlausibilizeMode mode,
|
||||
int radius,
|
||||
org.bukkit.command.CommandSender s
|
||||
) {
|
||||
int processed = 0;
|
||||
int skipped = 0;
|
||||
int failed = 0;
|
||||
int changed = 0;
|
||||
long totalLogsAdded = 0L;
|
||||
long totalLeavesAdded = 0L;
|
||||
long totalLeavesRemoved = 0L;
|
||||
long totalNormalized = 0L;
|
||||
long totalUnreachableAfter = 0L;
|
||||
|
||||
int progressStep = Math.max(1, targets.size() / 20);
|
||||
int index = 0;
|
||||
|
||||
for (Target t : targets) {
|
||||
index++;
|
||||
try {
|
||||
IrisObject o = loadTarget(t);
|
||||
if (o == null) {
|
||||
s.sendMessage(C.YELLOW + " skip " + t.key() + ": failed to load");
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
TreePlausibilizer.Result r = dryRun
|
||||
? TreePlausibilizer.analyze(o, mode, radius)
|
||||
: TreePlausibilizer.apply(o, mode, radius);
|
||||
|
||||
if (r.skipReason() != null) {
|
||||
s.sendMessage(C.YELLOW + " skip " + t.key() + ": " + r.skipReason());
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean touched = r.logsAdded() > 0 || r.leavesAdded() > 0
|
||||
|| r.leavesRemoved() > 0 || r.leavesNormalized() > 0;
|
||||
if (!dryRun && touched) {
|
||||
File dest = o.getLoadFile() != null ? o.getLoadFile() : t.file();
|
||||
if (dest != null) {
|
||||
o.write(dest);
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
|
||||
processed++;
|
||||
totalLogsAdded += r.logsAdded();
|
||||
totalLeavesAdded += r.leavesAdded();
|
||||
totalLeavesRemoved += r.leavesRemoved();
|
||||
totalNormalized += r.leavesNormalized();
|
||||
totalUnreachableAfter += r.unreachableAfter();
|
||||
|
||||
if (touched || targets.size() == 1) {
|
||||
s.sendMessage(C.GRAY + " " + t.key()
|
||||
+ C.WHITE + " +" + r.logsAdded() + " logs"
|
||||
+ C.WHITE + " +" + r.leavesAdded() + " leaves"
|
||||
+ C.WHITE + " -" + r.leavesRemoved() + " removed"
|
||||
+ (r.leavesNormalized() > 0 ? C.WHITE + " ~" + r.leavesNormalized() + " normalized" : "")
|
||||
+ (r.unreachableAfter() > 0 ? C.YELLOW + " " + r.unreachableAfter() + " unreachable" : ""));
|
||||
}
|
||||
|
||||
if (targets.size() > 1 && index % progressStep == 0) {
|
||||
s.sendMessage(C.IRIS + " [" + index + "/" + targets.size() + "]");
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
s.sendMessage(C.RED + " fail " + t.key() + ": " + e.getClass().getSimpleName()
|
||||
+ ": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
s.sendMessage(C.IRIS + "Done: " + processed + " processed, " + changed + " changed, "
|
||||
+ skipped + " skipped, " + failed + " failed");
|
||||
s.sendMessage(C.IRIS + "Totals: +" + totalLogsAdded + " logs, +" + totalLeavesAdded + " leaves, -"
|
||||
+ totalLeavesRemoved + " removed, ~" + totalNormalized + " normalized, "
|
||||
+ totalUnreachableAfter + " unreachable");
|
||||
}
|
||||
|
||||
private static IrisObject loadTarget(Target t) throws IOException {
|
||||
if (t.file() != null) {
|
||||
IrisObject o = new IrisObject();
|
||||
o.read(t.file());
|
||||
o.setLoadFile(t.file());
|
||||
return o;
|
||||
}
|
||||
return IrisData.loadAnyObject(t.key(), null);
|
||||
}
|
||||
|
||||
@Director(description = "Convert .schem files in the 'convert' folder to .iob files.")
|
||||
public void convert () {
|
||||
try {
|
||||
IrisConverter.convertSchematics(sender());
|
||||
@@ -223,13 +536,13 @@ public class CommandObject implements DecreeExecutor {
|
||||
|
||||
}
|
||||
|
||||
@Decree(description = "Get a powder that reveals objects", studio = true, aliases = "d")
|
||||
@Director(description = "Get a powder that reveals objects", studio = true, aliases = "d")
|
||||
public void dust() {
|
||||
player().getInventory().addItem(WandSVC.createDust());
|
||||
sender().playSound(Sound.AMBIENT_SOUL_SAND_VALLEY_ADDITIONS, 1f, 1.5f);
|
||||
}
|
||||
|
||||
@Decree(description = "Contract a selection based on your looking direction", aliases = "-")
|
||||
@Director(description = "Contract a selection based on your looking direction", aliases = "-")
|
||||
public void contract(
|
||||
@Param(description = "The amount to inset by", defaultValue = "1")
|
||||
int amount
|
||||
@@ -241,7 +554,8 @@ public class CommandObject implements DecreeExecutor {
|
||||
|
||||
|
||||
Location[] b = WandSVC.getCuboid(player());
|
||||
if (b == null) {
|
||||
if (b == null || b[0] == null || b[1] == null) {
|
||||
sender().sendMessage("No area selected.");
|
||||
return;
|
||||
}
|
||||
Location a1 = b[0].clone();
|
||||
@@ -249,7 +563,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
Cuboid cursor = new Cuboid(a1, a2);
|
||||
Direction d = Direction.closest(player().getLocation().getDirection()).reverse();
|
||||
assert d != null;
|
||||
cursor = cursor.expand(d, -amount);
|
||||
cursor = cursor.expand(d.f(), -amount);
|
||||
b[0] = cursor.getLowerNE();
|
||||
b[1] = cursor.getUpperSW();
|
||||
player().getInventory().setItemInMainHand(WandSVC.createWand(b[0], b[1]));
|
||||
@@ -257,7 +571,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
sender().playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f);
|
||||
}
|
||||
|
||||
@Decree(description = "Set point 1 to look", aliases = "p1")
|
||||
@Director(description = "Set point 1 to look", aliases = "p1")
|
||||
public void position1(
|
||||
@Param(description = "Whether to use your current position, or where you look", defaultValue = "true")
|
||||
boolean here
|
||||
@@ -279,11 +593,11 @@ public class CommandObject implements DecreeExecutor {
|
||||
} else {
|
||||
g[1] = player().getLocation().getBlock().getLocation().clone().add(0, -1, 0);
|
||||
}
|
||||
player().setItemInHand(WandSVC.createWand(g[0], g[1]));
|
||||
player().getInventory().setItemInMainHand(WandSVC.createWand(g[0], g[1]));
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Set point 2 to look", aliases = "p2")
|
||||
@Director(description = "Set point 2 to look", aliases = "p2")
|
||||
public void position2(
|
||||
@Param(description = "Whether to use your current position, or where you look", defaultValue = "true")
|
||||
boolean here
|
||||
@@ -306,11 +620,11 @@ public class CommandObject implements DecreeExecutor {
|
||||
} else {
|
||||
g[0] = player().getLocation().getBlock().getLocation().clone().add(0, -1, 0);
|
||||
}
|
||||
player().setItemInHand(WandSVC.createWand(g[0], g[1]));
|
||||
player().getInventory().setItemInMainHand(WandSVC.createWand(g[0], g[1]));
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Paste an object", sync = true)
|
||||
@Director(description = "Paste an object", sync = true)
|
||||
public void paste(
|
||||
@Param(description = "The object to paste", customHandler = ObjectHandler.class)
|
||||
String object,
|
||||
@@ -324,7 +638,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
// @Param(description = "The scale interpolator to use", defaultValue = "none")
|
||||
// IrisObjectPlacementScaleInterpolator interpolator
|
||||
) {
|
||||
IrisObject o = IrisData.loadAnyObject(object);
|
||||
IrisObject o = IrisData.loadAnyObject(object, data());
|
||||
double maxScale = Double.max(10 - o.getBlocks().size() / 10000d, 1);
|
||||
if (scale > maxScale) {
|
||||
sender().sendMessage(C.YELLOW + "Indicated scale exceeds maximum. Downscaled to maximum: " + maxScale);
|
||||
@@ -371,7 +685,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Save an object")
|
||||
@Director(description = "Save an object")
|
||||
public void save(
|
||||
@Param(description = "The dimension to store the object in", contextual = true)
|
||||
IrisDimension dimension,
|
||||
@@ -406,7 +720,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
sender().sendMessage(C.GREEN + "Successfully object to saved: " + dimension.getLoadKey() + "/objects/" + name);
|
||||
}
|
||||
|
||||
@Decree(description = "Shift a selection in your looking direction", aliases = "-")
|
||||
@Director(description = "Shift a selection in your looking direction", aliases = "-")
|
||||
public void shift(
|
||||
@Param(description = "The amount to shift by", defaultValue = "1")
|
||||
int amount
|
||||
@@ -417,6 +731,10 @@ public class CommandObject implements DecreeExecutor {
|
||||
}
|
||||
|
||||
Location[] b = WandSVC.getCuboid(player());
|
||||
if (b == null || b[0] == null || b[1] == null) {
|
||||
sender().sendMessage("No area selected.");
|
||||
return;
|
||||
}
|
||||
Location a1 = b[0].clone();
|
||||
Location a2 = b[1].clone();
|
||||
Direction d = Direction.closest(player().getLocation().getDirection()).reverse();
|
||||
@@ -433,7 +751,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
sender().playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f);
|
||||
}
|
||||
|
||||
@Decree(description = "Undo a number of pastes", aliases = "-")
|
||||
@Director(description = "Undo a number of pastes", aliases = "-")
|
||||
public void undo(
|
||||
@Param(description = "The amount of pastes to undo", defaultValue = "1")
|
||||
int amount
|
||||
@@ -444,7 +762,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
sender().sendMessage(C.BLUE + "Reverted " + actualReverts + C.BLUE +" pastes!");
|
||||
}
|
||||
|
||||
@Decree(description = "Gets an object wand and grabs the current WorldEdit selection.", aliases = "we", origin = DecreeOrigin.PLAYER, studio = true)
|
||||
@Director(description = "Gets an object wand and grabs the current WorldEdit selection.", aliases = "we", origin = DirectorOrigin.PLAYER, studio = true)
|
||||
public void we() {
|
||||
if (!Bukkit.getPluginManager().isPluginEnabled("WorldEdit")) {
|
||||
sender().sendMessage(C.RED + "You can't get a WorldEdit selection without WorldEdit, you know.");
|
||||
@@ -462,14 +780,14 @@ public class CommandObject implements DecreeExecutor {
|
||||
sender().sendMessage(C.GREEN + "A fresh wand with your current WorldEdit selection on it!");
|
||||
}
|
||||
|
||||
@Decree(description = "Get an object wand", sync = true)
|
||||
@Director(description = "Get an object wand", sync = true)
|
||||
public void wand() {
|
||||
player().getInventory().addItem(WandSVC.createWand());
|
||||
sender().playSound(Sound.ITEM_ARMOR_EQUIP_NETHERITE, 1f, 1.5f);
|
||||
sender().sendMessage(C.GREEN + "Poof! Good luck building!");
|
||||
}
|
||||
|
||||
@Decree(name = "x&y", description = "Autoselect up, down & out", sync = true)
|
||||
@Director(name = "x&y", description = "Autoselect up, down & out", sync = true)
|
||||
public void xay() {
|
||||
if (!WandSVC.isHoldingWand(player())) {
|
||||
sender().sendMessage(C.YELLOW + "Hold your wand!");
|
||||
@@ -477,6 +795,10 @@ public class CommandObject implements DecreeExecutor {
|
||||
}
|
||||
|
||||
Location[] b = WandSVC.getCuboid(player());
|
||||
if (b == null || b[0] == null || b[1] == null) {
|
||||
sender().sendMessage("No area selected.");
|
||||
return;
|
||||
}
|
||||
Location a1 = b[0].clone();
|
||||
Location a2 = b[1].clone();
|
||||
Location a1x = b[0].clone();
|
||||
@@ -516,7 +838,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
sender().sendMessage(C.GREEN + "Auto-select complete!");
|
||||
}
|
||||
|
||||
@Decree(name = "x+y", description = "Autoselect up & out", sync = true)
|
||||
@Director(name = "x+y", description = "Autoselect up & out", sync = true)
|
||||
public void xpy() {
|
||||
if (!WandSVC.isHoldingWand(player())) {
|
||||
sender().sendMessage(C.YELLOW + "Hold your wand!");
|
||||
@@ -524,6 +846,10 @@ public class CommandObject implements DecreeExecutor {
|
||||
}
|
||||
|
||||
Location[] b = WandSVC.getCuboid(player());
|
||||
if (b == null || b[0] == null || b[1] == null) {
|
||||
sender().sendMessage("No area selected.");
|
||||
return;
|
||||
}
|
||||
b[0].add(new Vector(0, 1, 0));
|
||||
b[1].add(new Vector(0, 1, 0));
|
||||
Location a1 = b[0].clone();
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.pack.PackValidationRegistry;
|
||||
import art.arcane.iris.core.pack.PackValidationResult;
|
||||
import art.arcane.iris.core.pack.PackValidator;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.iris.util.common.director.DirectorHelp;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@Director(name = "pack", aliases = {"pk"}, description = "Pack validation and maintenance")
|
||||
public class CommandPack implements DirectorExecutor {
|
||||
@Director(description = "Show help tree for this command group", aliases = {"?"})
|
||||
public void help() {
|
||||
DirectorHelp.print(sender(), getClass());
|
||||
}
|
||||
|
||||
@Director(description = "Validate a pack (or all packs) and re-publish results", aliases = {"v", "check"})
|
||||
public void validate(
|
||||
@Param(description = "The pack folder name to validate (leave empty for all)", defaultValue = "")
|
||||
String pack
|
||||
) {
|
||||
VolmitSender s = sender();
|
||||
File packsRoot = Iris.instance.getDataFolder("packs");
|
||||
if (!packsRoot.isDirectory()) {
|
||||
s.sendMessage(C.RED + "packs/ folder not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pack == null || pack.isBlank()) {
|
||||
File[] dirs = packsRoot.listFiles(File::isDirectory);
|
||||
if (dirs == null || dirs.length == 0) {
|
||||
s.sendMessage(C.YELLOW + "No packs to validate.");
|
||||
return;
|
||||
}
|
||||
int broken = 0;
|
||||
for (File dir : dirs) {
|
||||
PackValidationResult result = runValidate(s, dir);
|
||||
if (result != null && !result.isLoadable()) {
|
||||
broken++;
|
||||
}
|
||||
}
|
||||
s.sendMessage(C.GREEN + "Validation complete. Broken packs: " + broken + "/" + dirs.length);
|
||||
return;
|
||||
}
|
||||
|
||||
File target = new File(packsRoot, pack);
|
||||
if (!target.isDirectory()) {
|
||||
s.sendMessage(C.RED + "Pack '" + pack + "' not found under packs/.");
|
||||
return;
|
||||
}
|
||||
runValidate(s, target);
|
||||
}
|
||||
|
||||
@Director(description = "Restore most recent trashed files for a pack", aliases = {"r", "undelete"})
|
||||
public void restore(
|
||||
@Param(description = "The pack folder name to restore")
|
||||
String pack
|
||||
) {
|
||||
VolmitSender s = sender();
|
||||
if (pack == null || pack.isBlank()) {
|
||||
s.sendMessage(C.RED + "You must specify a pack name.");
|
||||
return;
|
||||
}
|
||||
File packFolder = new File(Iris.instance.getDataFolder("packs"), pack);
|
||||
if (!packFolder.isDirectory()) {
|
||||
s.sendMessage(C.RED + "Pack '" + pack + "' not found under packs/.");
|
||||
return;
|
||||
}
|
||||
int restored = PackValidator.restoreTrash(packFolder);
|
||||
if (restored == 0) {
|
||||
s.sendMessage(C.YELLOW + "Nothing to restore for pack '" + pack + "'.");
|
||||
return;
|
||||
}
|
||||
s.sendMessage(C.GREEN + "Restored " + restored + " file(s) from the most recent trash dump for pack '" + pack + "'.");
|
||||
s.sendMessage(C.GRAY + "Re-run /iris pack validate " + pack + " to re-check.");
|
||||
}
|
||||
|
||||
@Director(description = "Show cached validation status for a pack", aliases = {"s", "info"})
|
||||
public void status(
|
||||
@Param(description = "The pack folder name", defaultValue = "")
|
||||
String pack
|
||||
) {
|
||||
VolmitSender s = sender();
|
||||
if (pack == null || pack.isBlank()) {
|
||||
if (PackValidationRegistry.snapshot().isEmpty()) {
|
||||
s.sendMessage(C.YELLOW + "No validation results recorded. Run /iris pack validate first.");
|
||||
return;
|
||||
}
|
||||
PackValidationRegistry.snapshot().forEach((name, result) -> {
|
||||
String tag = result.isLoadable() ? (C.GREEN + "OK") : (C.RED + "BROKEN");
|
||||
s.sendMessage(tag + C.RESET + " " + name
|
||||
+ C.GRAY + " (blocking=" + result.getBlockingErrors().size()
|
||||
+ ", warnings=" + result.getWarnings().size()
|
||||
+ ", trashed=" + result.getRemovedUnusedFiles().size() + ")");
|
||||
});
|
||||
return;
|
||||
}
|
||||
PackValidationResult result = PackValidationRegistry.get(pack);
|
||||
if (result == null) {
|
||||
s.sendMessage(C.YELLOW + "No validation result for '" + pack + "'. Run /iris pack validate " + pack + ".");
|
||||
return;
|
||||
}
|
||||
reportResult(s, result);
|
||||
}
|
||||
|
||||
private PackValidationResult runValidate(VolmitSender s, File packFolder) {
|
||||
try {
|
||||
PackValidationResult result = PackValidator.validate(packFolder);
|
||||
PackValidationRegistry.publish(result);
|
||||
reportResult(s, result);
|
||||
return result;
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("Pack validation failed for '" + packFolder.getName() + "'", e);
|
||||
s.sendMessage(C.RED + "Validation of '" + packFolder.getName() + "' failed: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void reportResult(VolmitSender s, PackValidationResult result) {
|
||||
if (result.isLoadable()) {
|
||||
s.sendMessage(C.GREEN + "Pack '" + result.getPackName() + "' is loadable."
|
||||
+ C.GRAY + " (warnings=" + result.getWarnings().size()
|
||||
+ ", trashed=" + result.getRemovedUnusedFiles().size() + ")");
|
||||
} else {
|
||||
s.sendMessage(C.RED + "Pack '" + result.getPackName() + "' is BROKEN:");
|
||||
for (String reason : result.getBlockingErrors()) {
|
||||
s.sendMessage(C.RED + " - " + reason);
|
||||
}
|
||||
}
|
||||
int wMax = Math.min(10, result.getWarnings().size());
|
||||
for (int i = 0; i < wMax; i++) {
|
||||
s.sendMessage(C.YELLOW + " ! " + result.getWarnings().get(i));
|
||||
}
|
||||
if (result.getWarnings().size() > wMax) {
|
||||
s.sendMessage(C.GRAY + " ... and " + (result.getWarnings().size() - wMax) + " more warning(s).");
|
||||
}
|
||||
int tMax = Math.min(10, result.getRemovedUnusedFiles().size());
|
||||
for (int i = 0; i < tMax; i++) {
|
||||
s.sendMessage(C.GRAY + " ~ trashed " + result.getRemovedUnusedFiles().get(i));
|
||||
}
|
||||
if (result.getRemovedUnusedFiles().size() > tMax) {
|
||||
s.sendMessage(C.GRAY + " ... and " + (result.getRemovedUnusedFiles().size() - tMax) + " more trashed file(s).");
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-17
@@ -16,30 +16,38 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.commands;
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.gui.PregeneratorJob;
|
||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import com.volmit.iris.util.decree.annotations.Decree;
|
||||
import com.volmit.iris.util.decree.annotations.Param;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.gui.PregeneratorJob;
|
||||
import art.arcane.iris.core.pregenerator.PregenTask;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.iris.util.common.director.DirectorHelp;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
@Decree(name = "pregen", aliases = "pregenerate", description = "Pregenerate your Iris worlds!")
|
||||
public class CommandPregen implements DecreeExecutor {
|
||||
@Decree(description = "Pregenerate a world")
|
||||
@Director(name = "pregen", aliases = "pregenerate", description = "Pregenerate your Iris worlds!")
|
||||
public class CommandPregen implements DirectorExecutor {
|
||||
@Director(description = "Show help tree for this command group", aliases = {"?"})
|
||||
public void help() {
|
||||
DirectorHelp.print(sender(), getClass());
|
||||
}
|
||||
|
||||
@Director(description = "Pregenerate a world")
|
||||
public void start(
|
||||
@Param(description = "The radius of the pregen in blocks", aliases = "size")
|
||||
int radius,
|
||||
@Param(description = "The world to pregen", contextual = true)
|
||||
World world,
|
||||
@Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0")
|
||||
Vector center
|
||||
Vector center,
|
||||
@Param(description = "Open the Iris pregen gui", defaultValue = "true")
|
||||
boolean gui
|
||||
) {
|
||||
try {
|
||||
if (sender().isPlayer() && access() == null) {
|
||||
@@ -50,7 +58,7 @@ public class CommandPregen implements DecreeExecutor {
|
||||
IrisToolbelt.pregenerate(PregenTask
|
||||
.builder()
|
||||
.center(new Position2(center.getBlockX(), center.getBlockZ()))
|
||||
.gui(true)
|
||||
.gui(gui)
|
||||
.radiusX(radius)
|
||||
.radiusZ(radius)
|
||||
.build(), world);
|
||||
@@ -64,7 +72,7 @@ public class CommandPregen implements DecreeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Stop the active pregeneration task", aliases = "x")
|
||||
@Director(description = "Stop the active pregeneration task", aliases = "x")
|
||||
public void stop() {
|
||||
if (PregeneratorJob.shutdownInstance()) {
|
||||
Iris.info( C.BLUE + "Finishing up mca region...");
|
||||
@@ -73,7 +81,7 @@ public class CommandPregen implements DecreeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"})
|
||||
@Director(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"})
|
||||
public void pause() {
|
||||
if (PregeneratorJob.pauseResume()) {
|
||||
sender().sendMessage(C.GREEN + "Paused/unpaused pregeneration task, now: " + (PregeneratorJob.isPaused() ? "Paused" : "Running") + ".");
|
||||
+3
-3
@@ -16,10 +16,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.commands;
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
|
||||
public class CommandSettings implements DecreeExecutor {
|
||||
public class CommandSettings implements DirectorExecutor {
|
||||
|
||||
}
|
||||
+218
-261
@@ -16,50 +16,56 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.commands;
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.gui.NoiseExplorerGUI;
|
||||
import com.volmit.iris.core.gui.VisionGUI;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.project.IrisProject;
|
||||
import com.volmit.iris.core.service.ConversionSVC;
|
||||
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.*;
|
||||
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.collection.KSet;
|
||||
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.function.Function2;
|
||||
import com.volmit.iris.util.function.NoiseProvider;
|
||||
import com.volmit.iris.util.interpolation.InterpolationMethod;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.json.JSONArray;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.math.Spiraler;
|
||||
import com.volmit.iris.util.noise.CNG;
|
||||
import com.volmit.iris.util.parallel.BurstExecutor;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.O;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import com.volmit.iris.util.scheduling.jobs.QueueJob;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.gui.NoiseExplorerGUI;
|
||||
import art.arcane.iris.core.gui.VisionGUI;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.project.IrisProject;
|
||||
import art.arcane.iris.core.service.StudioSVC;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.*;
|
||||
import art.arcane.iris.engine.platform.ChunkReplacementListener;
|
||||
import art.arcane.iris.engine.platform.ChunkReplacementOptions;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import art.arcane.iris.util.common.director.DirectorContext;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.iris.util.common.director.DirectorHelp;
|
||||
import art.arcane.iris.util.common.director.handlers.DimensionHandler;
|
||||
import art.arcane.iris.util.common.director.specialhandlers.NullableDimensionHandler;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import art.arcane.volmlib.util.function.Function2;
|
||||
import art.arcane.volmlib.util.function.NoiseProvider;
|
||||
import art.arcane.iris.util.project.interpolation.InterpolationMethod;
|
||||
import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.math.Spiraler;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
||||
import art.arcane.iris.util.common.parallel.SyncExecutor;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.scheduling.O;
|
||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
import art.arcane.iris.util.common.scheduling.jobs.ParallelRadiusJob;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.util.BlockVector;
|
||||
@@ -73,17 +79,23 @@ import java.nio.file.Files;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Decree(name = "studio", aliases = {"std", "s"}, description = "Studio Commands", studio = true)
|
||||
public class CommandStudio implements DecreeExecutor {
|
||||
private CommandFind find;
|
||||
@Director(name = "studio", aliases = {"std", "s"}, description = "Studio Commands", studio = true)
|
||||
public class CommandStudio implements DirectorExecutor {
|
||||
@Director(description = "Show help tree for this command group", aliases = {"?"})
|
||||
public void help() {
|
||||
DirectorHelp.print(sender(), getClass());
|
||||
}
|
||||
|
||||
private CommandEdit edit;
|
||||
//private CommandDeepSearch deepSearch;
|
||||
|
||||
@@ -91,23 +103,24 @@ public class CommandStudio implements DecreeExecutor {
|
||||
return duration.toString().substring(2).replaceAll("(\\d[HMS])(?!$)", "$1 ").toLowerCase();
|
||||
}
|
||||
|
||||
@Decree(description = "Download a project.", aliases = "dl")
|
||||
//TODO fix pack trimming
|
||||
@Director(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 = "master")
|
||||
@Param(name = "branch", description = "The branch to download from", defaultValue = "stable")
|
||||
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 = "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
|
||||
) {
|
||||
new CommandIris().download(pack, branch, trim, overwrite);
|
||||
new CommandIris().download(pack, branch, overwrite);
|
||||
}
|
||||
|
||||
@Decree(description = "Open a new studio world", aliases = "o", sync = true)
|
||||
@Director(description = "Open a new studio world", aliases = "o", sync = true)
|
||||
public void open(
|
||||
@Param(defaultValue = "default", description = "The dimension to open a studio for", aliases = "dim")
|
||||
@Param(defaultValue = "default", description = "The dimension to open a studio for", aliases = "dim", customHandler = DimensionHandler.class)
|
||||
IrisDimension dimension,
|
||||
@Param(defaultValue = "1337", description = "The seed to generate the studio with", aliases = "s")
|
||||
long seed) {
|
||||
@@ -115,31 +128,53 @@ public class CommandStudio implements DecreeExecutor {
|
||||
Iris.service(StudioSVC.class).open(sender(), seed, dimension.getLoadKey());
|
||||
}
|
||||
|
||||
@Decree(description = "Open VSCode for a dimension", aliases = {"vsc", "edit"})
|
||||
@Director(description = "Open VSCode for a dimension", aliases = {"vsc", "edit"})
|
||||
public void vscode(
|
||||
@Param(defaultValue = "default", description = "The dimension to open VSCode for", aliases = "dim")
|
||||
@Param(defaultValue = "default", description = "The dimension to open VSCode for", aliases = "dim", customHandler = DimensionHandler.class)
|
||||
IrisDimension dimension
|
||||
) {
|
||||
sender().sendMessage(C.GREEN + "Opening VSCode for the \"" + dimension.getName() + "\" pack");
|
||||
Iris.service(StudioSVC.class).openVSCode(sender(), dimension.getLoadKey());
|
||||
}
|
||||
|
||||
@Decree(description = "Close an open studio project", aliases = {"x", "c"}, sync = true)
|
||||
@Director(description = "Close an open studio project", aliases = {"x", "c"}, sync = true)
|
||||
public void close() {
|
||||
VolmitSender commandSender = sender();
|
||||
if (!Iris.service(StudioSVC.class).isProjectOpen()) {
|
||||
sender().sendMessage(C.RED + "No open studio projects.");
|
||||
commandSender.sendMessage(C.RED + "No open studio projects.");
|
||||
return;
|
||||
}
|
||||
|
||||
Iris.service(StudioSVC.class).close();
|
||||
sender().sendMessage(C.GREEN + "Project Closed.");
|
||||
commandSender.sendMessage(C.YELLOW + "Closing studio...");
|
||||
Iris.service(StudioSVC.class).close().whenComplete((result, throwable) -> J.s(() -> {
|
||||
if (throwable != null) {
|
||||
commandSender.sendMessage(C.RED + "Studio close failed: " + throwable.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (result != null && result.failureCause() != null) {
|
||||
commandSender.sendMessage(C.RED + "Studio close failed: " + result.failureCause().getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (result != null && result.startupCleanupQueued()) {
|
||||
commandSender.sendMessage(C.YELLOW + "Studio closed. Remaining world-family cleanup was queued for startup fallback.");
|
||||
return;
|
||||
}
|
||||
|
||||
commandSender.sendMessage(C.GREEN + "Studio closed.");
|
||||
}));
|
||||
}
|
||||
|
||||
@Decree(description = "Create a new studio project", aliases = "+", sync = true)
|
||||
@Director(description = "Create a new studio project", aliases = "+", sync = true)
|
||||
public void create(
|
||||
@Param(description = "The name of this new Iris Project.")
|
||||
String name,
|
||||
@Param(description = "Copy the contents of an existing project in your packs folder and use it as a template in this new project.", contextual = true)
|
||||
@Param(
|
||||
description = "Copy the contents of an existing project in your packs folder and use it as a template in this new project.",
|
||||
contextual = true,
|
||||
customHandler = NullableDimensionHandler.class
|
||||
)
|
||||
IrisDimension template) {
|
||||
if (template != null) {
|
||||
Iris.service(StudioSVC.class).create(sender(), name, template.getLoadKey());
|
||||
@@ -148,148 +183,34 @@ public class CommandStudio implements DecreeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Get the version of a pack")
|
||||
@Director(description = "Get the version of a pack")
|
||||
public void version(
|
||||
@Param(defaultValue = "default", description = "The dimension get the version of", aliases = "dim", contextual = true)
|
||||
@Param(defaultValue = "default", description = "The dimension get the version of", aliases = "dim", contextual = true, customHandler = DimensionHandler.class)
|
||||
IrisDimension dimension
|
||||
) {
|
||||
sender().sendMessage(C.GREEN + "The \"" + dimension.getName() + "\" pack has version: " + dimension.getVersion());
|
||||
}
|
||||
|
||||
@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<Runnable> js = new KList<>();
|
||||
BurstExecutor b = MultiBurst.burst.burst();
|
||||
b.setMulticore(false);
|
||||
int rad = engine.getMantle().getRadius();
|
||||
for (int i = -(radius + rad); i <= radius + rad; i++) {
|
||||
for (int j = -(radius + rad); j <= radius + rad; j++) {
|
||||
engine.getMantle().getMantle().deleteChunk(i + cx.getX(), j + cx.getZ());
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = -radius; i <= radius; i++) {
|
||||
for (int j = -radius; j <= radius; j++) {
|
||||
int finalJ = j;
|
||||
int finalI = i;
|
||||
b.queue(() -> plat.injectChunkReplacement(player().getWorld(), finalI + cx.getX(), finalJ + cx.getZ(), (f) -> {
|
||||
synchronized (js) {
|
||||
js.add(f);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
b.complete();
|
||||
sender().sendMessage(C.GREEN + "Regenerating " + Form.f(js.size()) + " Sections");
|
||||
QueueJob<Runnable> r = new QueueJob<>() {
|
||||
final KList<Future<?>> 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 = "Convert objects in the \"convert\" folder")
|
||||
public void convert() {
|
||||
Iris.service(ConversionSVC.class).check(sender());
|
||||
//IrisConverter.convertSchematics(sender());
|
||||
}
|
||||
|
||||
@Decree(description = "Execute a script", aliases = "run", origin = DecreeOrigin.PLAYER)
|
||||
public void execute(
|
||||
@Param(description = "The script to run")
|
||||
IrisScript script
|
||||
) {
|
||||
engine().getExecution().execute(script.getLoadKey());
|
||||
}
|
||||
|
||||
@Decree(description = "Open the noise explorer (External GUI)", aliases = {"nmap", "n"})
|
||||
public void noise() {
|
||||
if (noGUI()) return;
|
||||
sender().sendMessage(C.GREEN + "Opening Noise Explorer!");
|
||||
NoiseExplorerGUI.launch();
|
||||
}
|
||||
|
||||
@Decree(description = "Charges all spawners in the area", aliases = "zzt", origin = DecreeOrigin.PLAYER)
|
||||
public void charge() {
|
||||
if (!IrisToolbelt.isIrisWorld(world())) {
|
||||
sender().sendMessage(C.RED + "You must be in an Iris world to charge spawners!");
|
||||
return;
|
||||
}
|
||||
sender().sendMessage(C.GREEN + "Charging spawners!");
|
||||
engine().getWorldManager().chargeEnergy();
|
||||
}
|
||||
|
||||
@Decree(description = "Preview noise gens (External GUI)", aliases = {"generator", "gen"})
|
||||
public void explore(
|
||||
@Param(description = "The generator to explore", contextual = true)
|
||||
@Director(description = "Open the noise explorer (External GUI)", aliases = {"nmap", "n", "generator", "gen"})
|
||||
public void noise(
|
||||
@Param(description = "Optional pack generator to preview", defaultValue = "null", contextual = true)
|
||||
IrisGenerator generator,
|
||||
@Param(description = "The seed to generate with", defaultValue = "12345")
|
||||
@Param(description = "The seed to preview the generator with", defaultValue = "12345")
|
||||
long seed
|
||||
) {
|
||||
if (noGUI()) return;
|
||||
sender().sendMessage(C.GREEN + "Opening Noise Explorer!");
|
||||
|
||||
Supplier<Function2<Double, Double, Double>> l = () -> {
|
||||
|
||||
if (generator == null) {
|
||||
return (x, z) -> 0D;
|
||||
}
|
||||
|
||||
return (x, z) -> generator.getHeight(x, z, new RNG(seed).nextParallelRNG(3245).lmax());
|
||||
};
|
||||
NoiseExplorerGUI.launch(l, "Custom Generator");
|
||||
}
|
||||
|
||||
@Decree(description = "Hotload a studio", aliases = {"reload", "h"})
|
||||
public void hotload() {
|
||||
if (!Iris.service(StudioSVC.class).isProjectOpen()) {
|
||||
sender().sendMessage(C.RED + "No studio world open!");
|
||||
if (generator == null) {
|
||||
NoiseExplorerGUI.launch();
|
||||
return;
|
||||
}
|
||||
Iris.service(StudioSVC.class).getActiveProject().getActiveProvider().getEngine().hotload();
|
||||
sender().sendMessage(C.GREEN + "Hotloaded");
|
||||
|
||||
Supplier<Function2<Double, Double, Double>> supplier = () -> (x, z) -> generator.getHeight(x, z, new RNG(seed).nextParallelRNG(3245).lmax());
|
||||
NoiseExplorerGUI.launch(supplier, "Custom Generator");
|
||||
}
|
||||
|
||||
@Decree(description = "Show loot if a chest were right here", origin = DecreeOrigin.PLAYER, sync = true)
|
||||
@Director(description = "Show loot if a chest were right here", origin = DirectorOrigin.PLAYER, sync = true)
|
||||
public void loot(
|
||||
@Param(description = "Fast insertion of items in virtual inventory (may cause performance drop)", defaultValue = "false")
|
||||
boolean fast,
|
||||
@@ -313,11 +234,15 @@ public class CommandStudio implements DecreeExecutor {
|
||||
O<Integer> ta = new O<>();
|
||||
ta.set(-1);
|
||||
|
||||
ta.set(Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, () ->
|
||||
var sender = sender();
|
||||
var player = player();
|
||||
var engine = engine();
|
||||
|
||||
ta.set(J.sr(() ->
|
||||
{
|
||||
if (!player().getOpenInventory().getType().equals(InventoryType.CHEST)) {
|
||||
Bukkit.getScheduler().cancelTask(ta.get());
|
||||
sender().sendMessage(C.GREEN + "Opened inventory!");
|
||||
if (!player.getOpenInventory().getType().equals(InventoryType.CHEST)) {
|
||||
J.csr(ta.get());
|
||||
sender.sendMessage(C.GREEN + "Opened inventory!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -325,81 +250,57 @@ public class CommandStudio implements DecreeExecutor {
|
||||
inv.clear();
|
||||
}
|
||||
|
||||
engine().addItems(true, inv, new RNG(RNG.r.imax()), tables, InventorySlotType.STORAGE, player().getWorld(), player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1);
|
||||
}, 0, fast ? 5 : 35));
|
||||
engine.addItems(true, inv, new RNG(RNG.r.imax()), tables, InventorySlotType.STORAGE, player.getWorld(), player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ(), 1);
|
||||
}, fast ? 5 : 35));
|
||||
|
||||
sender().sendMessage(C.GREEN + "Opening inventory now!");
|
||||
player().openInventory(inv);
|
||||
}
|
||||
|
||||
|
||||
@Decree(description = "Get all structures in a radius of chunks", aliases = "dist", origin = DecreeOrigin.PLAYER)
|
||||
public void distances(@Param(description = "The radius in chunks") int radius) {
|
||||
@Director(description = "Calculate the chance for each region to generate", origin = DirectorOrigin.PLAYER)
|
||||
public void regions(@Param(description = "The radius in chunks", defaultValue = "500") int radius) {
|
||||
var engine = engine();
|
||||
if (engine == null) {
|
||||
sender().sendMessage(C.RED + "Only works in an Iris world!");
|
||||
return;
|
||||
}
|
||||
var sender = sender();
|
||||
int d = radius * 2;
|
||||
KMap<String, KList<Position2>> data = new KMap<>();
|
||||
var multiBurst = new MultiBurst("Distance Sampler", Thread.MIN_PRIORITY);
|
||||
var executor = multiBurst.burst(radius * radius);
|
||||
var player = player();
|
||||
Thread.ofVirtual()
|
||||
.start(() -> {
|
||||
int d = radius * 2;
|
||||
KMap<String, AtomicInteger> data = new KMap<>();
|
||||
engine.getDimension().getRegions().forEach(key -> data.put(key, new AtomicInteger(0)));
|
||||
var multiBurst = new MultiBurst("Region Sampler");
|
||||
var executor = multiBurst.burst(radius * radius);
|
||||
sender.sendMessage(C.GRAY + "Generating data...");
|
||||
var loc = player.getLocation();
|
||||
int totalTasks = d * d;
|
||||
AtomicInteger completedTasks = new AtomicInteger(0);
|
||||
int c = J.ar(() -> sender.sendProgress((double) completedTasks.get() / totalTasks, "Finding regions"), 0);
|
||||
new Spiraler(d, d, (x, z) -> executor.queue(() -> {
|
||||
var region = engine.getRegion((x << 4) + 8, (z << 4) + 8);
|
||||
data.computeIfAbsent(region.getLoadKey(), (k) -> new AtomicInteger(0))
|
||||
.incrementAndGet();
|
||||
completedTasks.incrementAndGet();
|
||||
})).setOffset(loc.getBlockX(), loc.getBlockZ()).drain();
|
||||
executor.complete();
|
||||
multiBurst.close();
|
||||
J.car(c);
|
||||
|
||||
sender.sendMessage(C.GRAY + "Generating data...");
|
||||
var loc = player().getLocation();
|
||||
int totalTasks = d * d;
|
||||
AtomicInteger completedTasks = new AtomicInteger(0);
|
||||
int c = J.ar(() -> {
|
||||
sender.sendProgress((double) completedTasks.get() / totalTasks, "Finding structures");
|
||||
}, 0);
|
||||
sender.sendMessage(C.GREEN + "Done!");
|
||||
var loader = engine.getData().getRegionLoader();
|
||||
data.forEach((k, v) -> sender.sendMessage(C.GREEN + k + ": " + loader.load(k).getRarity() + " / " + Form.f((double) v.get() / totalTasks * 100, 2) + "%"));
|
||||
});
|
||||
}
|
||||
|
||||
new Spiraler(d, d, (x, z) -> executor.queue(() -> {
|
||||
var struct = engine.getStructureAt(x, z);
|
||||
if (struct != null) {
|
||||
data.computeIfAbsent(struct.getLoadKey(), (k) -> new KList<>()).add(new Position2(x, z));
|
||||
}
|
||||
completedTasks.incrementAndGet();
|
||||
})).setOffset(loc.getBlockX(), loc.getBlockZ()).drain();
|
||||
|
||||
executor.complete();
|
||||
multiBurst.close();
|
||||
J.car(c);
|
||||
|
||||
for (var key : data.keySet()) {
|
||||
var list = data.get(key);
|
||||
KList<Long> distances = new KList<>(list.size() - 1);
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
var pos = list.get(i);
|
||||
double dist = Integer.MAX_VALUE;
|
||||
for (var p : list) {
|
||||
if (p.equals(pos)) continue;
|
||||
dist = Math.min(dist, Math.sqrt(Math.pow(pos.getX() - p.getX(), 2) + Math.pow(pos.getZ() - p.getZ(), 2)));
|
||||
}
|
||||
if (dist == Integer.MAX_VALUE) continue;
|
||||
distances.add(Math.round(dist * 16));
|
||||
}
|
||||
long[] array = new long[distances.size()];
|
||||
for (int i = 0; i < distances.size(); i++) {
|
||||
array[i] = distances.get(i);
|
||||
}
|
||||
Arrays.sort(array);
|
||||
long min = array.length > 0 ? array[0] : 0;
|
||||
long max = array.length > 0 ? array[array.length - 1] : 0;
|
||||
long sum = Arrays.stream(array).sum();
|
||||
long avg = array.length > 0 ? Math.round(sum / (double) array.length) : 0;
|
||||
String msg = "%s: %s => min: %s/max: %s -> avg: %s".formatted(key, list.size(), min, max, avg);
|
||||
sender.sendMessage(msg);
|
||||
}
|
||||
if (data.isEmpty()) {
|
||||
sender.sendMessage(C.RED + "No data found!");
|
||||
} else {
|
||||
sender.sendMessage(C.GREEN + "Done!");
|
||||
}
|
||||
@Director(description = "Get all structures in a radius of chunks", aliases = "dist", origin = DirectorOrigin.PLAYER)
|
||||
public void distances(@Param(description = "The radius in chunks") int radius) {
|
||||
sender().sendMessage(C.YELLOW + "Structure distance sampling for legacy structure data has been removed.");
|
||||
}
|
||||
|
||||
|
||||
@Decree(description = "Render a world map (External GUI)", aliases = "render")
|
||||
@Director(description = "Render a world map (External GUI)", aliases = "render")
|
||||
public void map(
|
||||
@Param(name = "world", description = "The world to open the generator for", contextual = true)
|
||||
World world
|
||||
@@ -415,9 +316,9 @@ public class CommandStudio implements DecreeExecutor {
|
||||
sender().sendMessage(C.GREEN + "Opening map!");
|
||||
}
|
||||
|
||||
@Decree(description = "Package a dimension into a compressed format", aliases = "package")
|
||||
@Director(description = "Package a dimension into a compressed format", aliases = "package")
|
||||
public void pkg(
|
||||
@Param(name = "dimension", description = "The dimension pack to compress", contextual = true, defaultValue = "default")
|
||||
@Param(name = "dimension", description = "The dimension pack to compress", contextual = true, defaultValue = "default", customHandler = DimensionHandler.class)
|
||||
IrisDimension dimension,
|
||||
@Param(name = "obfuscate", description = "Whether or not to obfuscate the pack", defaultValue = "false")
|
||||
boolean obfuscate,
|
||||
@@ -427,9 +328,9 @@ public class CommandStudio implements DecreeExecutor {
|
||||
Iris.service(StudioSVC.class).compilePackage(sender(), dimension.getLoadKey(), obfuscate, minify);
|
||||
}
|
||||
|
||||
@Decree(description = "Profiles the performance of a dimension", origin = DecreeOrigin.PLAYER)
|
||||
@Director(description = "Profiles the performance of a dimension", origin = DirectorOrigin.PLAYER)
|
||||
public void profile(
|
||||
@Param(description = "The dimension to profile", contextual = true, defaultValue = "default")
|
||||
@Param(description = "The dimension to profile", contextual = true, defaultValue = "default", customHandler = DimensionHandler.class)
|
||||
IrisDimension dimension
|
||||
) {
|
||||
// Todo: Make this more accurate
|
||||
@@ -437,6 +338,14 @@ public class CommandStudio implements DecreeExecutor {
|
||||
File report = Iris.instance.getDataFile("profile.txt");
|
||||
IrisProject project = new IrisProject(pack);
|
||||
IrisData data = IrisData.get(pack);
|
||||
PlatformChunkGenerator activeGenerator = resolveProfileGenerator(dimension);
|
||||
Engine activeEngine = activeGenerator == null ? null : activeGenerator.getEngine();
|
||||
|
||||
if (activeEngine != null) {
|
||||
IrisToolbelt.applyPregenPerformanceProfile(activeEngine);
|
||||
} else {
|
||||
IrisToolbelt.applyPregenPerformanceProfile();
|
||||
}
|
||||
|
||||
KList<String> fileText = new KList<>();
|
||||
|
||||
@@ -616,7 +525,49 @@ public class CommandStudio implements DecreeExecutor {
|
||||
sender().sendMessage(C.GREEN + "Done! " + report.getPath());
|
||||
}
|
||||
|
||||
@Decree(description = "Spawn an Iris entity", aliases = "summon", origin = DecreeOrigin.PLAYER)
|
||||
private PlatformChunkGenerator resolveProfileGenerator(IrisDimension dimension) {
|
||||
StudioSVC studioService = Iris.service(StudioSVC.class);
|
||||
if (studioService != null && studioService.isProjectOpen()) {
|
||||
IrisProject activeProject = studioService.getActiveProject();
|
||||
if (activeProject != null) {
|
||||
PlatformChunkGenerator activeProvider = activeProject.getActiveProvider();
|
||||
if (isGeneratorDimension(activeProvider, dimension)) {
|
||||
return activeProvider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sender().isPlayer()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Player player = sender().player();
|
||||
if (player == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PlatformChunkGenerator worldAccess = IrisToolbelt.access(player.getWorld());
|
||||
if (isGeneratorDimension(worldAccess, dimension)) {
|
||||
return worldAccess;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isGeneratorDimension(PlatformChunkGenerator generator, IrisDimension dimension) {
|
||||
if (generator == null || generator.getEngine() == null || dimension == null || dimension.getLoadKey() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IrisDimension engineDimension = generator.getEngine().getDimension();
|
||||
if (engineDimension == null || engineDimension.getLoadKey() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return engineDimension.getLoadKey().equalsIgnoreCase(dimension.getLoadKey());
|
||||
}
|
||||
|
||||
@Director(description = "Spawn an Iris entity", aliases = "summon", origin = DirectorOrigin.PLAYER)
|
||||
public void spawn(
|
||||
@Param(description = "The entity to spawn")
|
||||
IrisEntity entity,
|
||||
@@ -629,7 +580,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
entity.spawn(engine(), new Location(world(), location.getX(), location.getY(), location.getZ()));
|
||||
}
|
||||
|
||||
@Decree(description = "Teleport to the active studio world", aliases = "stp", origin = DecreeOrigin.PLAYER, sync = true)
|
||||
@Director(description = "Teleport to the active studio world", aliases = "stp", origin = DirectorOrigin.PLAYER, sync = true)
|
||||
public void tpstudio() {
|
||||
if (!Iris.service(StudioSVC.class).isProjectOpen()) {
|
||||
sender().sendMessage(C.RED + "No studio world is open!");
|
||||
@@ -642,13 +593,19 @@ public class CommandStudio implements DecreeExecutor {
|
||||
}
|
||||
|
||||
sender().sendMessage(C.GREEN + "Sending you to the studio world!");
|
||||
player().teleport(Iris.service(StudioSVC.class).getActiveProject().getActiveProvider().getTarget().getWorld().spawnLocation());
|
||||
player().setGameMode(GameMode.SPECTATOR);
|
||||
var player = player();
|
||||
PaperLib.teleportAsync(player(), Iris.service(StudioSVC.class)
|
||||
.getActiveProject()
|
||||
.getActiveProvider()
|
||||
.getTarget()
|
||||
.getWorld()
|
||||
.spawnLocation()
|
||||
).thenRun(() -> player.setGameMode(GameMode.SPECTATOR));
|
||||
}
|
||||
|
||||
@Decree(description = "Update your dimension projects VSCode workspace")
|
||||
@Director(description = "Update your dimension projects VSCode workspace")
|
||||
public void update(
|
||||
@Param(description = "The dimension to update the workspace of", contextual = true, defaultValue = "default")
|
||||
@Param(description = "The dimension to update the workspace of", contextual = true, defaultValue = "default", customHandler = DimensionHandler.class)
|
||||
IrisDimension dimension
|
||||
) {
|
||||
sender().sendMessage(C.GOLD + "Updating Code Workspace for " + dimension.getName() + "...");
|
||||
@@ -659,7 +616,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(aliases = "find-objects", description = "Get information about nearby structures")
|
||||
@Director(aliases = "find-objects", description = "Get information about nearby structures")
|
||||
public void objects() {
|
||||
if (!IrisToolbelt.isIrisWorld(player().getWorld())) {
|
||||
sender().sendMessage(C.RED + "You must be in an Iris world");
|
||||
+70
-27
@@ -16,35 +16,42 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.commands;
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.edit.BlockSignal;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.engine.object.IrisRegion;
|
||||
import com.volmit.iris.util.data.B;
|
||||
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.matter.MatterMarker;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.edit.BlockSignal;
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||
import art.arcane.iris.util.common.director.DirectorHelp;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.volmlib.util.matter.MatterMarker;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.FluidCollisionMode;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Decree(name = "what", origin = DecreeOrigin.PLAYER, studio = true, description = "Iris What?")
|
||||
public class CommandWhat implements DecreeExecutor {
|
||||
@Decree(description = "What is in my hand?", origin = DecreeOrigin.PLAYER)
|
||||
@Director(name = "what", origin = DirectorOrigin.PLAYER, studio = true, description = "Iris What?")
|
||||
public class CommandWhat implements DirectorExecutor {
|
||||
@Director(description = "Show help tree for this command group", aliases = {"?"})
|
||||
public void help() {
|
||||
DirectorHelp.print(sender(), getClass());
|
||||
}
|
||||
|
||||
@Director(description = "What is in my hand?", origin = DirectorOrigin.PLAYER)
|
||||
public void hand() {
|
||||
try {
|
||||
BlockData bd = player().getInventory().getItemInMainHand().getType().createBlockData();
|
||||
@@ -65,17 +72,21 @@ public class CommandWhat implements DecreeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "What biome am i in?", origin = DecreeOrigin.PLAYER)
|
||||
@Director(description = "What biome am i in?", origin = DirectorOrigin.PLAYER)
|
||||
public void biome() {
|
||||
try {
|
||||
IrisBiome b = engine().getBiome(player().getLocation().getBlockX(), player().getLocation().getBlockY() - player().getWorld().getMinHeight(), player().getLocation().getBlockZ());
|
||||
sender().sendMessage("IBiome: " + b.getLoadKey() + " (" + b.getDerivative().name() + ")");
|
||||
Biome derivative = b.getDerivative();
|
||||
NamespacedKey derivativeKey = resolveBiomeKey(derivative);
|
||||
sender().sendMessage("IBiome: " + b.getLoadKey() + " (" + (derivativeKey == null ? "unregistered" : derivativeKey.getKey()) + ")");
|
||||
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
sender().sendMessage("Non-Iris Biome: " + player().getLocation().getBlock().getBiome().name());
|
||||
Biome biome = player().getLocation().getBlock().getBiome();
|
||||
NamespacedKey key = resolveBiomeKey(biome);
|
||||
sender().sendMessage("Non-Iris Biome: " + (key == null ? "unregistered" : key));
|
||||
|
||||
if (player().getLocation().getBlock().getBiome().equals(Biome.CUSTOM)) {
|
||||
if (key == null || key.getKey().equals("custom")) {
|
||||
try {
|
||||
sender().sendMessage("Data Pack Biome: " + INMS.get().getTrueBiomeBaseKey(player().getLocation()) + " (ID: " + INMS.get().getTrueBiomeBaseId(INMS.get().getTrueBiomeBase(player().getLocation())) + ")");
|
||||
} catch (Throwable ee) {
|
||||
@@ -85,7 +96,7 @@ public class CommandWhat implements DecreeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "What region am i in?", origin = DecreeOrigin.PLAYER)
|
||||
@Director(description = "What region am i in?", origin = DirectorOrigin.PLAYER)
|
||||
public void region() {
|
||||
try {
|
||||
Chunk chunk = world().getChunkAt(player().getLocation().getBlockZ() / 16, player().getLocation().getBlockZ() / 16);
|
||||
@@ -98,7 +109,7 @@ public class CommandWhat implements DecreeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "What block am i looking at?", origin = DecreeOrigin.PLAYER)
|
||||
@Director(description = "What block am i looking at?", origin = DirectorOrigin.PLAYER)
|
||||
public void block() {
|
||||
BlockData bd;
|
||||
try {
|
||||
@@ -143,7 +154,7 @@ public class CommandWhat implements DecreeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Show markers in chunk", origin = DecreeOrigin.PLAYER)
|
||||
@Director(description = "Show markers in chunk", origin = DirectorOrigin.PLAYER)
|
||||
public void markers(@Param(description = "Marker name such as cave_floor or cave_ceiling") String marker) {
|
||||
Chunk c = player().getLocation().getChunk();
|
||||
|
||||
@@ -155,7 +166,7 @@ public class CommandWhat implements DecreeExecutor {
|
||||
for (int zzz = c.getZ() - 4; zzz <= c.getZ() + 4; zzz++) {
|
||||
IrisToolbelt.access(c.getWorld()).getEngine().getMantle().findMarkers(xxx, zzz, new MatterMarker(marker))
|
||||
.convert((i) -> i.toLocation(c.getWorld())).forEach((i) -> {
|
||||
J.s(() -> BlockSignal.of(i.getBlock(), 100));
|
||||
BlockSignal.of(i.getWorld(), i.getBlockX(), i.getBlockY(), i.getBlockZ(), 100);
|
||||
v.incrementAndGet();
|
||||
});
|
||||
}
|
||||
@@ -166,4 +177,36 @@ public class CommandWhat implements DecreeExecutor {
|
||||
sender().sendMessage(C.IRIS + "Iris worlds only.");
|
||||
}
|
||||
}
|
||||
|
||||
private NamespacedKey resolveBiomeKey(Biome biome) {
|
||||
Object keyOrNullValue = invokeNoThrow(biome, "getKeyOrNull");
|
||||
if (keyOrNullValue instanceof NamespacedKey namespacedKey) {
|
||||
return namespacedKey;
|
||||
}
|
||||
|
||||
Object keyOrThrowValue = invokeNoThrow(biome, "getKeyOrThrow");
|
||||
if (keyOrThrowValue instanceof NamespacedKey namespacedKey) {
|
||||
return namespacedKey;
|
||||
}
|
||||
|
||||
Object keyValue = invokeNoThrow(biome, "getKey");
|
||||
if (keyValue instanceof NamespacedKey namespacedKey) {
|
||||
return namespacedKey;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object invokeNoThrow(Biome biome, String methodName) {
|
||||
if (biome == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Method method = biome.getClass().getMethod(methodName);
|
||||
return method.invoke(biome);
|
||||
} catch (Throwable ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.edit;
|
||||
package art.arcane.iris.core.edit;
|
||||
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
+75
-29
@@ -16,12 +16,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.edit;
|
||||
package art.arcane.iris.core.edit;
|
||||
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.SR;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.scheduling.SR;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.FallingBlock;
|
||||
@@ -46,26 +47,51 @@ public class BlockSignal {
|
||||
e.setSilent(true);
|
||||
e.setTicksLived(1);
|
||||
e.setVelocity(new Vector(0, 0, 0));
|
||||
J.s(() -> {
|
||||
e.remove();
|
||||
Location blockLocation = block.getLocation();
|
||||
Runnable removeTask = () -> {
|
||||
if (!J.runEntity(e, e::remove) && !e.isDead()) {
|
||||
e.remove();
|
||||
}
|
||||
active.decrementAndGet();
|
||||
BlockData type = block.getBlockData();
|
||||
MultiBurst.burst.lazy(() -> {
|
||||
for (Player i : block.getWorld().getPlayers()) {
|
||||
i.sendBlockChange(block.getLocation(), block.getBlockData());
|
||||
}
|
||||
});
|
||||
}, ticks);
|
||||
sendBlockRefresh(block);
|
||||
};
|
||||
if (!J.runAt(blockLocation, removeTask, ticks)) {
|
||||
if (!J.isFolia()) {
|
||||
J.s(removeTask, ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void of(Block block, int ticks) {
|
||||
new BlockSignal(block, ticks);
|
||||
if (block == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
of(block.getWorld(), block.getX(), block.getY(), block.getZ(), ticks);
|
||||
}
|
||||
|
||||
public static void of(Block block) {
|
||||
of(block, 100);
|
||||
}
|
||||
|
||||
public static void of(World world, int x, int y, int z, int ticks) {
|
||||
if (world == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location location = new Location(world, x, y, z);
|
||||
Runnable createTask = () -> new BlockSignal(world.getBlockAt(x, y, z), ticks);
|
||||
if (!J.runAt(location, createTask)) {
|
||||
if (!J.isFolia()) {
|
||||
J.s(createTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void of(World world, int x, int y, int z) {
|
||||
of(world, x, y, z, 100);
|
||||
}
|
||||
|
||||
public static Runnable forever(Block block) {
|
||||
Location tg = block.getLocation().clone().add(0.5, 0, 0.5).clone();
|
||||
FallingBlock e = block.getWorld().spawnFallingBlock(tg.clone(), block.getBlockData());
|
||||
@@ -82,26 +108,46 @@ public class BlockSignal {
|
||||
new SR(20) {
|
||||
@Override
|
||||
public void run() {
|
||||
if (e.isDead()) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
if (!J.runEntity(e, () -> {
|
||||
if (e.isDead()) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
e.setTicksLived(1);
|
||||
e.teleport(tg.clone());
|
||||
e.setVelocity(new Vector(0, 0, 0));
|
||||
e.setTicksLived(1);
|
||||
e.teleport(tg.clone());
|
||||
e.setVelocity(new Vector(0, 0, 0));
|
||||
})) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return () -> {
|
||||
e.remove();
|
||||
BlockData type = block.getBlockData();
|
||||
|
||||
MultiBurst.burst.lazy(() -> {
|
||||
for (Player i : block.getWorld().getPlayers()) {
|
||||
i.sendBlockChange(block.getLocation(), block.getBlockData());
|
||||
}
|
||||
});
|
||||
if (!J.runEntity(e, e::remove) && !e.isDead()) {
|
||||
e.remove();
|
||||
}
|
||||
Location blockLocation = block.getLocation();
|
||||
Runnable refreshTask = () -> sendBlockRefresh(block);
|
||||
if (!J.runAt(blockLocation, refreshTask)) {
|
||||
refreshTask.run();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void sendBlockRefresh(Block block) {
|
||||
if (block == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location location = block.getLocation();
|
||||
BlockData blockData = block.getBlockData();
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (!player.getWorld().equals(location.getWorld())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
J.runEntity(player, () -> player.sendBlockChange(location, blockData));
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -16,9 +16,9 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.edit;
|
||||
package art.arcane.iris.core.edit;
|
||||
|
||||
import com.volmit.iris.util.math.M;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -51,10 +51,13 @@ public class BukkitBlockEditor implements BlockEditor {
|
||||
return M.ms();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void setBiome(int x, int z, Biome b) {
|
||||
world.setBiome(x, z, b);
|
||||
int minHeight = world.getMinHeight();
|
||||
int maxHeight = world.getMaxHeight();
|
||||
for (int y = minHeight; y < maxHeight; y++) {
|
||||
world.setBiome(x, y, z, b);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,9 +70,8 @@ public class BukkitBlockEditor implements BlockEditor {
|
||||
return world.getBiome(x, y, z);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public Biome getBiome(int x, int z) {
|
||||
return world.getBiome(x, z);
|
||||
return world.getBiome(x, world.getMinHeight(), z);
|
||||
}
|
||||
}
|
||||
+30
-14
@@ -16,18 +16,20 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.edit;
|
||||
package art.arcane.iris.core.edit;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.math.BlockPosition;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.BlockPosition;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import lombok.Data;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
@@ -48,8 +50,9 @@ public class DustRevealer {
|
||||
this.key = key;
|
||||
this.hits = hits;
|
||||
|
||||
J.s(() -> {
|
||||
new BlockSignal(world.getBlockAt(block.getX(), block.getY(), block.getZ()), 10);
|
||||
Location blockLocation = block.toBlock(world).getLocation();
|
||||
Runnable revealTask = () -> {
|
||||
BlockSignal.of(world, block.getX(), block.getY(), block.getZ(), 10);
|
||||
if (M.r(0.25)) {
|
||||
world.playSound(block.toBlock(world).getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 1f, RNG.r.f(0.2f, 2f));
|
||||
}
|
||||
@@ -90,12 +93,22 @@ public class DustRevealer {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}, RNG.r.i(2, 8));
|
||||
};
|
||||
int delay = RNG.r.i(2, 8);
|
||||
if (!J.runAt(blockLocation, revealTask, delay)) {
|
||||
if (!J.isFolia()) {
|
||||
J.s(revealTask, delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void spawn(Block block, VolmitSender sender) {
|
||||
World world = block.getWorld();
|
||||
Engine access = IrisToolbelt.access(world).getEngine();
|
||||
PlatformChunkGenerator generator = IrisToolbelt.access(world);
|
||||
if (generator == null) {
|
||||
return;
|
||||
}
|
||||
Engine access = generator.getEngine();
|
||||
|
||||
if (access != null) {
|
||||
String a = access.getObjectPlacementKey(block.getX(), block.getY() - block.getWorld().getMinHeight(), block.getZ());
|
||||
@@ -111,6 +124,9 @@ public class DustRevealer {
|
||||
}
|
||||
|
||||
private boolean is(BlockPosition a) {
|
||||
if (a.getY() < world.getMinHeight() || a.getY() >= world.getMaxHeight()) {
|
||||
return false;
|
||||
}
|
||||
int betterY = a.getY() - world.getMinHeight();
|
||||
if (isValidTry(a) && engine.getObjectPlacementKey(a.getX(), betterY, a.getZ()) != null && engine.getObjectPlacementKey(a.getX(), betterY, a.getZ()).equals(key)) {
|
||||
hits.add(a);
|
||||
+2
-2
@@ -16,9 +16,9 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.events;
|
||||
package art.arcane.iris.core.events;
|
||||
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
+2
-2
@@ -16,9 +16,9 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.events;
|
||||
package art.arcane.iris.core.events;
|
||||
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
public class IrisEngineHotloadEvent extends IrisEngineEvent {
|
||||
+38
-10
@@ -1,11 +1,11 @@
|
||||
package com.volmit.iris.core.events;
|
||||
package art.arcane.iris.core.events;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.InventorySlotType;
|
||||
import com.volmit.iris.engine.object.IrisLootTable;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.InventorySlotType;
|
||||
import art.arcane.iris.engine.object.IrisLootTable;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Block;
|
||||
@@ -105,9 +105,37 @@ public class IrisLootEvent extends Event {
|
||||
if (!Bukkit.isPrimaryThread()) {
|
||||
Iris.warn("LootGenerateEvent was not called on the main thread, please report this issue.");
|
||||
Thread.dumpStack();
|
||||
J.sfut(() -> Bukkit.getPluginManager().callEvent(event)).join();
|
||||
} else Bukkit.getPluginManager().callEvent(event);
|
||||
J.sfut(() -> {
|
||||
try {
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("LootGenerateEvent dispatch failed at "
|
||||
+ world.getName() + " [" + x + "," + y + "," + z + "].", e);
|
||||
if (e instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
}
|
||||
if (e instanceof Error error) {
|
||||
throw error;
|
||||
}
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}).join();
|
||||
} else {
|
||||
try {
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("LootGenerateEvent dispatch failed at "
|
||||
+ world.getName() + " [" + x + "," + y + "," + z + "].", e);
|
||||
if (e instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
}
|
||||
if (e instanceof Error error) {
|
||||
throw error;
|
||||
}
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return event.isCancelled();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core.gui;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.events.IrisEngineHotloadEvent;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisGenerator;
|
||||
import art.arcane.iris.engine.object.NoiseStyle;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import art.arcane.volmlib.util.function.Function2;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.math.RollingSequence;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.iris.util.common.parallel.BurstExecutor;
|
||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class NoiseExplorerGUI extends JPanel implements MouseWheelListener, Listener {
|
||||
|
||||
private static final long serialVersionUID = 2094606939770332040L;
|
||||
private static final Color BG = new Color(24, 24, 28);
|
||||
private static final Color SIDEBAR_BG = new Color(20, 20, 24);
|
||||
private static final Color SIDEBAR_SELECTED = new Color(40, 50, 70);
|
||||
private static final Color SIDEBAR_ITEM_COLOR = new Color(170, 170, 185);
|
||||
private static final Color SEARCH_BG = new Color(30, 30, 38);
|
||||
private static final Color SEARCH_FG = new Color(180, 180, 190);
|
||||
private static final Color STATUS_BG = new Color(32, 32, 38, 230);
|
||||
private static final Color STATUS_TEXT = new Color(180, 180, 190);
|
||||
private static final Color ACCENT = new Color(90, 140, 255);
|
||||
private static final Color SEPARATOR = new Color(40, 40, 50);
|
||||
private static final Font STATUS_FONT = new Font(Font.MONOSPACED, Font.PLAIN, 12);
|
||||
private static final Font SIDEBAR_HEADER_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 11);
|
||||
private static final Font SIDEBAR_ITEM_FONT = new Font(Font.SANS_SERIF, Font.PLAIN, 12);
|
||||
private static final Font SEARCH_FONT = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
|
||||
private static final int SIDEBAR_WIDTH = 240;
|
||||
private static final int[] HSB_LUT = new int[256];
|
||||
|
||||
private static final String[] CATEGORY_ORDER = {
|
||||
"Pack Generators", "Simplex", "Perlin", "Cellular", "Iris", "Clover",
|
||||
"Hexagon", "Vascular", "Globe", "Cubic", "Fractal", "Static",
|
||||
"Nowhere", "Sierpinski", "Utility", "Other"
|
||||
};
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
float n = i / 255f;
|
||||
HSB_LUT[i] = Color.HSBtoRGB(0.666f - n * 0.666f, 1f, 1f - n * 0.8f);
|
||||
}
|
||||
}
|
||||
|
||||
private final RollingSequence fpsHistory = new RollingSequence(60);
|
||||
private final boolean colorMode = IrisSettings.get().getGui().colorMode;
|
||||
private final MultiBurst gx = MultiBurst.burst;
|
||||
private double scale = 1;
|
||||
private double animScale = 10;
|
||||
private double ox = 0;
|
||||
private double oz = 0;
|
||||
private double animOx = 0;
|
||||
private double animOz = 0;
|
||||
private double lastMouseX = Double.MAX_VALUE;
|
||||
private double lastMouseZ = Double.MAX_VALUE;
|
||||
private double time = 0;
|
||||
private double animTime = 0;
|
||||
private int imgWidth = 0;
|
||||
private int imgHeight = 0;
|
||||
private BufferedImage img;
|
||||
private CNG cng = NoiseStyle.STATIC.create(new RNG(RNG.r.nextLong()));
|
||||
private Function2<Double, Double, Double> generator;
|
||||
private Supplier<Function2<Double, Double, Double>> loader;
|
||||
private String currentName = "STATIC";
|
||||
|
||||
public NoiseExplorerGUI() {
|
||||
Iris.instance.registerListener(this);
|
||||
setBackground(BG);
|
||||
addMouseWheelListener(this);
|
||||
addMouseMotionListener(new MouseMotionListener() {
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
Point cp = e.getPoint();
|
||||
lastMouseX = cp.getX();
|
||||
lastMouseZ = cp.getY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
Point cp = e.getPoint();
|
||||
ox += (lastMouseX - cp.getX()) * scale;
|
||||
oz += (lastMouseZ - cp.getY()) * scale;
|
||||
lastMouseX = cp.getX();
|
||||
lastMouseZ = cp.getY();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void launch() {
|
||||
Engine engine = findActiveEngine();
|
||||
EventQueue.invokeLater(() -> {
|
||||
NoiseExplorerGUI nv = new NoiseExplorerGUI();
|
||||
buildFrame("Noise Explorer", nv, engine, null, null);
|
||||
});
|
||||
}
|
||||
|
||||
public static void launch(Supplier<Function2<Double, Double, Double>> gen, String genName) {
|
||||
Engine engine = findActiveEngine();
|
||||
EventQueue.invokeLater(() -> {
|
||||
NoiseExplorerGUI nv = new NoiseExplorerGUI();
|
||||
nv.loader = gen;
|
||||
nv.generator = gen.get();
|
||||
nv.currentName = genName;
|
||||
buildFrame("Noise Explorer: " + genName, nv, engine, gen, genName);
|
||||
});
|
||||
}
|
||||
|
||||
private static Engine findActiveEngine() {
|
||||
try {
|
||||
for (World w : new ArrayList<>(Bukkit.getWorlds())) {
|
||||
try {
|
||||
PlatformChunkGenerator access = IrisToolbelt.access(w);
|
||||
if (access != null && access.getEngine() != null && !access.getEngine().isClosed()) {
|
||||
return access.getEngine();
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static JFrame buildFrame(String title, NoiseExplorerGUI nv, Engine engine,
|
||||
Supplier<Function2<Double, Double, Double>> customGen, String customName) {
|
||||
JFrame frame = new JFrame(title);
|
||||
frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
|
||||
frame.getContentPane().setBackground(BG);
|
||||
frame.setLayout(new BorderLayout());
|
||||
|
||||
JPanel sidebar = buildSidebar(nv, engine, customGen, customName);
|
||||
frame.add(sidebar, BorderLayout.WEST);
|
||||
frame.add(nv, BorderLayout.CENTER);
|
||||
|
||||
frame.setSize(1440, 820);
|
||||
frame.setMinimumSize(new Dimension(640, 480));
|
||||
frame.setLocationRelativeTo(null);
|
||||
frame.setVisible(true);
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
Iris.instance.unregisterListener(nv);
|
||||
}
|
||||
});
|
||||
return frame;
|
||||
}
|
||||
|
||||
private static JPanel buildSidebar(NoiseExplorerGUI nv, Engine engine,
|
||||
Supplier<Function2<Double, Double, Double>> customGen, String customName) {
|
||||
JPanel sidebar = new JPanel(new BorderLayout());
|
||||
sidebar.setPreferredSize(new Dimension(SIDEBAR_WIDTH, 0));
|
||||
sidebar.setBackground(SIDEBAR_BG);
|
||||
sidebar.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, SEPARATOR));
|
||||
|
||||
JTextField search = new JTextField();
|
||||
search.setBackground(SEARCH_BG);
|
||||
search.setForeground(SEARCH_FG);
|
||||
search.setCaretColor(SEARCH_FG);
|
||||
search.setFont(SEARCH_FONT);
|
||||
search.setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createMatteBorder(0, 0, 1, 0, SEPARATOR),
|
||||
BorderFactory.createEmptyBorder(8, 10, 8, 10)
|
||||
));
|
||||
search.putClientProperty("JTextField.placeholderText", "Search...");
|
||||
|
||||
LinkedHashMap<String, List<ListItem>> categories = buildCategoryMap(nv, engine, customGen, customName);
|
||||
DefaultListModel<ListItem> model = new DefaultListModel<>();
|
||||
populateModel(model, categories, "");
|
||||
|
||||
JList<ListItem> list = new JList<>(model);
|
||||
list.setBackground(SIDEBAR_BG);
|
||||
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
list.setCellRenderer(new SidebarCellRenderer());
|
||||
list.setFixedCellHeight(-1);
|
||||
|
||||
list.addListSelectionListener(e -> {
|
||||
if (e.getValueIsAdjusting()) return;
|
||||
ListItem selected = list.getSelectedValue();
|
||||
if (selected != null && !selected.header && selected.action != null) {
|
||||
selected.action.run();
|
||||
}
|
||||
});
|
||||
|
||||
search.getDocument().addDocumentListener(new DocumentListener() {
|
||||
private void filter() {
|
||||
String text = search.getText().trim();
|
||||
populateModel(model, categories, text);
|
||||
}
|
||||
|
||||
public void insertUpdate(DocumentEvent e) { filter(); }
|
||||
public void removeUpdate(DocumentEvent e) { filter(); }
|
||||
public void changedUpdate(DocumentEvent e) { filter(); }
|
||||
});
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(list);
|
||||
scrollPane.setBorder(BorderFactory.createEmptyBorder());
|
||||
scrollPane.getVerticalScrollBar().setUnitIncrement(16);
|
||||
scrollPane.getVerticalScrollBar().setBackground(SIDEBAR_BG);
|
||||
|
||||
sidebar.add(search, BorderLayout.NORTH);
|
||||
sidebar.add(scrollPane, BorderLayout.CENTER);
|
||||
return sidebar;
|
||||
}
|
||||
|
||||
private static void populateModel(DefaultListModel<ListItem> model, LinkedHashMap<String, List<ListItem>> categories, String filter) {
|
||||
model.clear();
|
||||
String lower = filter.toLowerCase();
|
||||
for (Map.Entry<String, List<ListItem>> entry : categories.entrySet()) {
|
||||
List<ListItem> matching = new ArrayList<>();
|
||||
for (ListItem item : entry.getValue()) {
|
||||
if (lower.isEmpty() || item.text.toLowerCase().contains(lower) || item.rawName.toLowerCase().contains(lower)) {
|
||||
matching.add(item);
|
||||
}
|
||||
}
|
||||
if (!matching.isEmpty()) {
|
||||
model.addElement(new ListItem(entry.getKey(), entry.getKey(), true, null));
|
||||
for (ListItem item : matching) {
|
||||
model.addElement(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static LinkedHashMap<String, List<ListItem>> buildCategoryMap(NoiseExplorerGUI nv, Engine engine,
|
||||
Supplier<Function2<Double, Double, Double>> customGen, String customName) {
|
||||
LinkedHashMap<String, List<ListItem>> categories = new LinkedHashMap<>();
|
||||
|
||||
if (customGen != null && customName != null) {
|
||||
List<ListItem> custom = new ArrayList<>();
|
||||
custom.add(new ListItem(customName, customName, false, () -> {
|
||||
nv.generator = customGen.get();
|
||||
nv.loader = customGen;
|
||||
nv.currentName = customName;
|
||||
}));
|
||||
categories.put("Custom", custom);
|
||||
}
|
||||
|
||||
Map<String, List<NoiseStyle>> styleGroups = new LinkedHashMap<>();
|
||||
for (NoiseStyle style : NoiseStyle.values()) {
|
||||
String cat = categorize(style);
|
||||
styleGroups.computeIfAbsent(cat, k -> new ArrayList<>()).add(style);
|
||||
}
|
||||
|
||||
if (engine != null && !engine.isClosed()) {
|
||||
List<ListItem> genItems = new ArrayList<>();
|
||||
try {
|
||||
IrisData data = engine.getData();
|
||||
String[] keys = data.getGeneratorLoader().getPossibleKeys();
|
||||
Arrays.sort(keys);
|
||||
for (String key : keys) {
|
||||
IrisGenerator gen = data.getGeneratorLoader().load(key);
|
||||
if (gen != null) {
|
||||
long seed = new RNG(12345).nextParallelRNG(3245).lmax();
|
||||
genItems.add(new ListItem(formatName(key), key, false, () -> {
|
||||
nv.generator = (x, z) -> gen.getHeight(x, z, seed);
|
||||
nv.loader = null;
|
||||
nv.currentName = key;
|
||||
}));
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
if (!genItems.isEmpty()) {
|
||||
categories.put("Pack Generators", genItems);
|
||||
}
|
||||
}
|
||||
|
||||
for (String cat : CATEGORY_ORDER) {
|
||||
if ("Pack Generators".equals(cat)) continue;
|
||||
List<NoiseStyle> styles = styleGroups.get(cat);
|
||||
if (styles != null && !styles.isEmpty()) {
|
||||
List<ListItem> items = new ArrayList<>();
|
||||
for (NoiseStyle style : styles) {
|
||||
items.add(new ListItem(formatName(style.name()), style.name(), false, () -> {
|
||||
nv.cng = style.create(RNG.r.nextParallelRNG(RNG.r.imax()));
|
||||
nv.generator = null;
|
||||
nv.loader = null;
|
||||
nv.currentName = style.name();
|
||||
}));
|
||||
}
|
||||
categories.put(cat, items);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<NoiseStyle>> entry : styleGroups.entrySet()) {
|
||||
if (!categories.containsKey(entry.getKey())) {
|
||||
List<ListItem> items = new ArrayList<>();
|
||||
for (NoiseStyle style : entry.getValue()) {
|
||||
items.add(new ListItem(formatName(style.name()), style.name(), false, () -> {
|
||||
nv.cng = style.create(RNG.r.nextParallelRNG(RNG.r.imax()));
|
||||
nv.generator = null;
|
||||
nv.loader = null;
|
||||
nv.currentName = style.name();
|
||||
}));
|
||||
}
|
||||
categories.put(entry.getKey(), items);
|
||||
}
|
||||
}
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
private static String categorize(NoiseStyle style) {
|
||||
String n = style.name();
|
||||
if (n.startsWith("STATIC")) return "Static";
|
||||
if (n.startsWith("IRIS")) return "Iris";
|
||||
if (n.startsWith("CLOVER")) return "Clover";
|
||||
if (n.startsWith("VASCULAR")) return "Vascular";
|
||||
if (n.equals("FLAT")) return "Utility";
|
||||
if (n.startsWith("CELLULAR")) return "Cellular";
|
||||
if (n.startsWith("HEX") || n.equals("HEXAGON")) return "Hexagon";
|
||||
if (n.startsWith("SIERPINSKI")) return "Sierpinski";
|
||||
if (n.startsWith("NOWHERE")) return "Nowhere";
|
||||
if (n.startsWith("GLOB")) return "Globe";
|
||||
if (n.startsWith("PERLIN")) return "Perlin";
|
||||
if (n.startsWith("CUBIC") || (n.startsWith("FRACTAL") && n.contains("CUBIC"))) return "Cubic";
|
||||
if (n.contains("SIMPLEX") && !n.startsWith("FRACTAL")) return "Simplex";
|
||||
if (n.startsWith("FRACTAL")) return "Fractal";
|
||||
return "Other";
|
||||
}
|
||||
|
||||
private static String formatName(String enumName) {
|
||||
String lower = enumName.toLowerCase().replace('_', ' ');
|
||||
return Character.toUpperCase(lower.charAt(0)) + lower.substring(1);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(IrisEngineHotloadEvent e) {
|
||||
if (generator != null && loader != null) {
|
||||
generator = loader.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseWheelMoved(MouseWheelEvent e) {
|
||||
int notches = e.getWheelRotation();
|
||||
if (e.isControlDown()) {
|
||||
time = time + ((0.0025 * time) * notches);
|
||||
return;
|
||||
}
|
||||
scale = scale + ((0.044 * scale) * notches);
|
||||
scale = Math.max(scale, 0.00001);
|
||||
}
|
||||
|
||||
private double lerp(double current, double target, double speed) {
|
||||
double diff = target - current;
|
||||
if (Math.abs(diff) < 0.001) return target;
|
||||
return current + diff * speed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g) {
|
||||
animScale = lerp(animScale, scale, 0.16);
|
||||
animTime = lerp(animTime, time, 0.29);
|
||||
animOx = lerp(animOx, ox, 0.16);
|
||||
animOz = lerp(animOz, oz, 0.16);
|
||||
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
|
||||
if (g instanceof Graphics2D gg) {
|
||||
gg.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
|
||||
gg.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
|
||||
int pw = getWidth();
|
||||
int ph = getHeight();
|
||||
|
||||
if (pw != imgWidth || ph != imgHeight || img == null) {
|
||||
imgWidth = pw;
|
||||
imgHeight = ph;
|
||||
img = null;
|
||||
}
|
||||
|
||||
int accuracy = M.clip((fpsHistory.getAverage() / 14D), 1D, 64D).intValue();
|
||||
int rw = Math.max(1, pw / accuracy);
|
||||
int rh = Math.max(1, ph / accuracy);
|
||||
|
||||
if (img == null || img.getWidth() != rw || img.getHeight() != rh) {
|
||||
img = new BufferedImage(rw, rh, BufferedImage.TYPE_INT_RGB);
|
||||
}
|
||||
|
||||
int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
|
||||
|
||||
BurstExecutor burst = gx.burst(rw);
|
||||
for (int x = 0; x < rw; x++) {
|
||||
int xx = x;
|
||||
burst.queue(() -> {
|
||||
for (int z = 0; z < rh; z++) {
|
||||
double worldX = (xx * accuracy * animScale) + animOx;
|
||||
double worldZ = (z * accuracy * animScale) + animOz;
|
||||
double n = generator != null
|
||||
? generator.apply(worldX, worldZ)
|
||||
: cng.noise(worldX, worldZ);
|
||||
n = Math.max(0, Math.min(1, n));
|
||||
|
||||
int rgb;
|
||||
if (colorMode) {
|
||||
rgb = HSB_LUT[(int) (n * 255)];
|
||||
} else {
|
||||
int v = (int) (n * 255);
|
||||
rgb = (v << 16) | (v << 8) | v;
|
||||
}
|
||||
pixels[z * rw + xx] = rgb;
|
||||
}
|
||||
});
|
||||
}
|
||||
burst.complete();
|
||||
|
||||
gg.setColor(BG);
|
||||
gg.fillRect(0, 0, pw, ph);
|
||||
gg.drawImage(img, 0, 0, pw, ph, null);
|
||||
|
||||
renderStatusBar(gg, pw, ph, p.getMilliseconds());
|
||||
renderCrosshair(gg, pw, ph);
|
||||
}
|
||||
|
||||
p.end();
|
||||
time += 1D;
|
||||
fpsHistory.put(p.getMilliseconds());
|
||||
|
||||
if (!isVisible() || !getParent().isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
long sleepMs = Math.max(1, 16 - (long) p.getMilliseconds());
|
||||
EventQueue.invokeLater(() -> {
|
||||
J.sleep(sleepMs);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
|
||||
private void renderCrosshair(Graphics2D g, int w, int h) {
|
||||
int cx = w / 2;
|
||||
int cy = h / 2;
|
||||
g.setColor(new Color(255, 255, 255, 40));
|
||||
g.drawLine(cx - 8, cy, cx + 8, cy);
|
||||
g.drawLine(cx, cy - 8, cx, cy + 8);
|
||||
}
|
||||
|
||||
private void renderStatusBar(Graphics2D g, int w, int h, double frameMs) {
|
||||
int barHeight = 28;
|
||||
int y = h - barHeight;
|
||||
|
||||
g.setColor(STATUS_BG);
|
||||
g.fillRect(0, y, w, barHeight);
|
||||
g.setColor(new Color(50, 50, 60));
|
||||
g.drawLine(0, y, w, y);
|
||||
|
||||
g.setFont(STATUS_FONT);
|
||||
g.setColor(STATUS_TEXT);
|
||||
|
||||
double worldX = (w / 2.0 * animScale) + animOx;
|
||||
double worldZ = (h / 2.0 * animScale) + animOz;
|
||||
double noiseVal = generator != null
|
||||
? generator.apply(worldX, worldZ)
|
||||
: cng.noise(worldX, worldZ);
|
||||
noiseVal = Math.max(0, Math.min(1, noiseVal));
|
||||
|
||||
int fps = frameMs > 0 ? (int) (1000.0 / frameMs) : 0;
|
||||
|
||||
String status = String.format(" %s | X: %.1f Z: %.1f | Zoom: %.4f | Value: %.4f | %d FPS",
|
||||
currentName, worldX, worldZ, animScale, noiseVal, fps);
|
||||
g.drawString(status, 8, y + 18);
|
||||
|
||||
int barW = 60;
|
||||
int barX = w - barW - 12;
|
||||
int barY = y + 6;
|
||||
int barH = barHeight - 12;
|
||||
g.setColor(new Color(40, 40, 48));
|
||||
g.fillRoundRect(barX, barY, barW, barH, 4, 4);
|
||||
int fillW = (int) (noiseVal * (barW - 2));
|
||||
g.setColor(ACCENT);
|
||||
g.fillRoundRect(barX + 1, barY + 1, fillW, barH - 2, 3, 3);
|
||||
}
|
||||
|
||||
private static final class ListItem {
|
||||
final String text;
|
||||
final String rawName;
|
||||
final boolean header;
|
||||
final Runnable action;
|
||||
|
||||
ListItem(String text, String rawName, boolean header, Runnable action) {
|
||||
this.text = text;
|
||||
this.rawName = rawName;
|
||||
this.header = header;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SidebarCellRenderer extends DefaultListCellRenderer {
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean selected, boolean focus) {
|
||||
ListItem item = (ListItem) value;
|
||||
super.getListCellRendererComponent(list, item.text, index, !item.header && selected, false);
|
||||
setOpaque(true);
|
||||
if (item.header) {
|
||||
setFont(SIDEBAR_HEADER_FONT);
|
||||
setForeground(ACCENT);
|
||||
setBackground(SIDEBAR_BG);
|
||||
setBorder(BorderFactory.createEmptyBorder(10, 10, 4, 10));
|
||||
} else {
|
||||
setFont(SIDEBAR_ITEM_FONT);
|
||||
setForeground(selected ? Color.WHITE : SIDEBAR_ITEM_COLOR);
|
||||
setBackground(selected ? SIDEBAR_SELECTED : SIDEBAR_BG);
|
||||
setBorder(BorderFactory.createEmptyBorder(3, 20, 3, 10));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
-37
@@ -16,31 +16,36 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.gui;
|
||||
package art.arcane.iris.core.gui;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.pregenerator.IrisPregenerator;
|
||||
import com.volmit.iris.core.pregenerator.PregenListener;
|
||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.format.MemoryMonitor;
|
||||
import com.volmit.iris.util.function.Consumer2;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.pregenerator.IrisPregenerator;
|
||||
import art.arcane.iris.core.pregenerator.PregenListener;
|
||||
import art.arcane.iris.core.pregenerator.PregenTask;
|
||||
import art.arcane.iris.core.pregenerator.PregeneratorMethod;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import art.arcane.volmlib.util.format.MemoryMonitor;
|
||||
import art.arcane.volmlib.util.function.Consumer2;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import art.arcane.volmlib.util.scheduling.ChronoLatch;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import org.bukkit.World;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -53,7 +58,7 @@ public class PregeneratorJob implements PregenListener {
|
||||
private static final Color COLOR_NETWORK_GENERATING = parseColor("#836b8c");
|
||||
private static final Color COLOR_GENERATED = parseColor("#65c295");
|
||||
private static final Color COLOR_CLEANED = parseColor("#34eb93");
|
||||
public static PregeneratorJob instance;
|
||||
private static final AtomicReference<PregeneratorJob> instance = new AtomicReference<>();
|
||||
private final MemoryMonitor monitor;
|
||||
private final PregenTask task;
|
||||
private final boolean saving;
|
||||
@@ -64,14 +69,23 @@ public class PregeneratorJob implements PregenListener {
|
||||
private final Position2 max;
|
||||
private final ChronoLatch cl = new ChronoLatch(TimeUnit.MINUTES.toMillis(1));
|
||||
private final Engine engine;
|
||||
private final ExecutorService service;
|
||||
private JFrame frame;
|
||||
private PregenRenderer renderer;
|
||||
private int rgc = 0;
|
||||
private String[] info;
|
||||
private volatile double lastChunksPerSecond = 0D;
|
||||
private volatile long lastChunksRemaining = 0L;
|
||||
|
||||
public PregeneratorJob(PregenTask task, PregeneratorMethod method, Engine engine) {
|
||||
instance.updateAndGet(old -> {
|
||||
if (old != null) {
|
||||
old.pregenerator.close();
|
||||
old.close();
|
||||
}
|
||||
return this;
|
||||
});
|
||||
this.engine = engine;
|
||||
instance = this;
|
||||
monitor = new MemoryMonitor(50);
|
||||
saving = false;
|
||||
info = new String[]{"Initializing..."};
|
||||
@@ -96,40 +110,63 @@ public class PregeneratorJob implements PregenListener {
|
||||
}, "Iris Pregenerator");
|
||||
t.setPriority(Thread.MIN_PRIORITY);
|
||||
t.start();
|
||||
service = Executors.newVirtualThreadPerTaskExecutor();
|
||||
}
|
||||
|
||||
public static boolean shutdownInstance() {
|
||||
if (instance == null) {
|
||||
PregeneratorJob inst = instance.get();
|
||||
if (inst == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
J.a(() -> instance.pregenerator.close());
|
||||
J.a(inst.pregenerator::close);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static PregeneratorJob getInstance() {
|
||||
return instance;
|
||||
return instance.get();
|
||||
}
|
||||
|
||||
public static boolean pauseResume() {
|
||||
if (instance == null) {
|
||||
PregeneratorJob inst = instance.get();
|
||||
if (inst == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPaused()) {
|
||||
instance.pregenerator.resume();
|
||||
inst.pregenerator.resume();
|
||||
} else {
|
||||
instance.pregenerator.pause();
|
||||
inst.pregenerator.pause();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isPaused() {
|
||||
if (instance == null) {
|
||||
PregeneratorJob inst = instance.get();
|
||||
if (inst == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return instance.paused();
|
||||
return inst.paused();
|
||||
}
|
||||
|
||||
public static double chunksPerSecond() {
|
||||
PregeneratorJob inst = instance.get();
|
||||
return inst == null ? 0D : Math.max(0D, inst.lastChunksPerSecond);
|
||||
}
|
||||
|
||||
public static long chunksRemaining() {
|
||||
PregeneratorJob inst = instance.get();
|
||||
return inst == null ? -1L : Math.max(0L, inst.lastChunksRemaining);
|
||||
}
|
||||
|
||||
public boolean targetsWorld(World world) {
|
||||
if (world == null || engine == null || engine.getWorld() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String targetName = engine.getWorld().name();
|
||||
return targetName != null && targetName.equalsIgnoreCase(world.getName());
|
||||
}
|
||||
|
||||
private static Color parseColor(String c) {
|
||||
@@ -179,7 +216,7 @@ public class PregeneratorJob implements PregenListener {
|
||||
J.a(() -> {
|
||||
pregenerator.close();
|
||||
close();
|
||||
instance = null;
|
||||
instance.compareAndSet(this, null);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -219,10 +256,13 @@ public class PregeneratorJob implements PregenListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method) {
|
||||
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached) {
|
||||
lastChunksPerSecond = chunksPerSecond;
|
||||
lastChunksRemaining = chunksRemaining;
|
||||
|
||||
info = new String[]{
|
||||
(paused() ? "PAUSED" : (saving ? "Saving... " : "Generating")) + " " + Form.f(generated) + " of " + Form.f(totalChunks) + " (" + Form.pc(percent, 0) + " Complete)",
|
||||
"Speed: " + Form.f(chunksPerSecond, 0) + " Chunks/s, " + Form.f(regionsPerMinute, 1) + " Regions/m, " + Form.f(chunksPerMinute, 0) + " Chunks/m",
|
||||
"Speed: " + (cached ? "Cached " : "") + Form.f(chunksPerSecond, 0) + " Chunks/s, " + Form.f(regionsPerMinute, 1) + " Regions/m, " + Form.f(chunksPerMinute, 0) + " Chunks/m",
|
||||
Form.duration(eta, 2) + " Remaining " + " (" + Form.duration(elapsed, 2) + " Elapsed)",
|
||||
"Generation Method: " + method,
|
||||
"Memory: " + Form.memSize(monitor.getUsedBytes(), 2) + " (" + Form.pc(monitor.getUsagePercent(), 0) + ") Pressure: " + Form.memSize(monitor.getPressure(), 0) + "/s",
|
||||
@@ -240,13 +280,16 @@ public class PregeneratorJob implements PregenListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkGenerated(int x, int z) {
|
||||
if (engine != null) {
|
||||
draw(x, z, engine.draw((x << 4) + 8, (z << 4) + 8));
|
||||
return;
|
||||
}
|
||||
public void onChunkGenerated(int x, int z, boolean cached) {
|
||||
if (renderer == null || frame == null || !frame.isVisible()) return;
|
||||
service.submit(() -> {
|
||||
if (engine != null) {
|
||||
draw(x, z, engine.draw((x << 4) + 8, (z << 4) + 8));
|
||||
return;
|
||||
}
|
||||
|
||||
draw(x, z, COLOR_GENERATED);
|
||||
draw(x, z, COLOR_GENERATED);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -304,8 +347,9 @@ public class PregeneratorJob implements PregenListener {
|
||||
@Override
|
||||
public void onClose() {
|
||||
close();
|
||||
instance = null;
|
||||
instance.compareAndSet(this, null);
|
||||
whenDone.forEach(Runnable::run);
|
||||
service.shutdownNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,915 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core.gui;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.gui.components.IrisRenderer;
|
||||
import art.arcane.iris.core.gui.components.RenderType;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.IrisWorld;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import art.arcane.volmlib.util.math.BlockPosition;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.volmlib.util.math.RollingSequence;
|
||||
import art.arcane.volmlib.util.scheduling.ChronoLatch;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.scheduling.O;
|
||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.MouseInputListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static art.arcane.iris.util.common.data.registry.Attributes.MAX_HEALTH;
|
||||
|
||||
public class VisionGUI extends JPanel implements MouseWheelListener, KeyListener, MouseMotionListener, MouseInputListener {
|
||||
private static final long serialVersionUID = 2094606939770332040L;
|
||||
|
||||
private static final Color BG = new Color(18, 18, 22);
|
||||
private static final Color CARD_BG = new Color(28, 28, 36, 220);
|
||||
private static final Color CARD_BORDER = new Color(60, 60, 75, 180);
|
||||
private static final Color TEXT_PRIMARY = new Color(220, 220, 230);
|
||||
private static final Color TEXT_SECONDARY = new Color(140, 140, 155);
|
||||
private static final Color TEXT_DIM = new Color(90, 90, 105);
|
||||
private static final Color ACCENT = new Color(90, 140, 255);
|
||||
private static final Color ACCENT_DIM = new Color(60, 100, 200, 100);
|
||||
private static final Color PLAYER_COLOR = new Color(80, 200, 120);
|
||||
private static final Color MOB_COLOR = new Color(220, 80, 80);
|
||||
private static final Color STATUS_BG = new Color(24, 24, 30, 240);
|
||||
private static final Color GRID_COLOR = new Color(255, 255, 255, 12);
|
||||
private static final Font FONT_STATUS = new Font(Font.MONOSPACED, Font.PLAIN, 12);
|
||||
private static final Font FONT_CARD_TITLE = new Font(Font.SANS_SERIF, Font.BOLD, 13);
|
||||
private static final Font FONT_CARD_BODY = new Font(Font.SANS_SERIF, Font.PLAIN, 12);
|
||||
private static final Font FONT_HELP_KEY = new Font(Font.MONOSPACED, Font.BOLD, 12);
|
||||
private static final Font FONT_NOTIFICATION = new Font(Font.SANS_SERIF, Font.BOLD, 14);
|
||||
private static final int CARD_RADIUS = 8;
|
||||
private static final int CARD_PAD = 12;
|
||||
private static final int STATUS_HEIGHT = 26;
|
||||
|
||||
private final KList<LivingEntity> lastEntities = new KList<>();
|
||||
private final KMap<String, Long> notifications = new KMap<>();
|
||||
private final ChronoLatch centities = new ChronoLatch(1000);
|
||||
private final RollingSequence rs = new RollingSequence(512);
|
||||
private final O<Integer> m = new O<>();
|
||||
private final KMap<BlockPosition, BufferedImage> positions = new KMap<>();
|
||||
private final KMap<BlockPosition, BufferedImage> fastpositions = new KMap<>();
|
||||
private final KSet<BlockPosition> working = new KSet<>();
|
||||
private final KSet<BlockPosition> workingfast = new KSet<>();
|
||||
|
||||
private RenderType currentType = RenderType.BIOME;
|
||||
private boolean help = true;
|
||||
private boolean helpIgnored = false;
|
||||
private boolean shift = false;
|
||||
private Player player = null;
|
||||
private boolean debug = false;
|
||||
private boolean control = false;
|
||||
private boolean eco = false;
|
||||
private boolean lowtile = false;
|
||||
private boolean follow = false;
|
||||
private boolean alt = false;
|
||||
private boolean grid = false;
|
||||
private IrisRenderer renderer;
|
||||
private IrisWorld world;
|
||||
private double velocity = 0;
|
||||
private int lowq = 12;
|
||||
private double scale = 128;
|
||||
private double mscale = 4D;
|
||||
private int w = 0;
|
||||
private int h = 0;
|
||||
private double lx = 0;
|
||||
private double lz = 0;
|
||||
private double ox = 0;
|
||||
private double oz = 0;
|
||||
private double hx = 0;
|
||||
private double hz = 0;
|
||||
private double oxp = 0;
|
||||
private double ozp = 0;
|
||||
private Engine engine;
|
||||
private int tid = 0;
|
||||
private Map<RenderType, JToggleButton> modeButtons;
|
||||
|
||||
private final ExecutorService e = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), r -> {
|
||||
tid++;
|
||||
Thread t = new Thread(r);
|
||||
t.setName("Iris HD Renderer " + tid);
|
||||
t.setPriority(Thread.MIN_PRIORITY);
|
||||
t.setUncaughtExceptionHandler((et, ex) -> {
|
||||
Iris.info("Exception encountered in " + et.getName());
|
||||
ex.printStackTrace();
|
||||
});
|
||||
return t;
|
||||
});
|
||||
|
||||
private final ExecutorService eh = Executors.newFixedThreadPool(3, r -> {
|
||||
tid++;
|
||||
Thread t = new Thread(r);
|
||||
t.setName("Iris Renderer " + tid);
|
||||
t.setPriority(Thread.NORM_PRIORITY);
|
||||
t.setUncaughtExceptionHandler((et, ex) -> {
|
||||
Iris.info("Exception encountered in " + et.getName());
|
||||
ex.printStackTrace();
|
||||
});
|
||||
return t;
|
||||
});
|
||||
|
||||
public VisionGUI(JFrame frame) {
|
||||
m.set(8);
|
||||
rs.put(1);
|
||||
setBackground(BG);
|
||||
addMouseWheelListener(this);
|
||||
addMouseMotionListener(this);
|
||||
addMouseListener(this);
|
||||
frame.addKeyListener(this);
|
||||
J.a(() -> {
|
||||
J.sleep(10000);
|
||||
if (!helpIgnored && help) {
|
||||
help = false;
|
||||
}
|
||||
});
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent windowEvent) {
|
||||
e.shutdown();
|
||||
eh.shutdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void createAndShowGUI(Engine r, int s, IrisWorld world) {
|
||||
JFrame frame = new JFrame("Iris Vision");
|
||||
VisionGUI nv = new VisionGUI(frame);
|
||||
nv.world = world;
|
||||
nv.engine = r;
|
||||
nv.renderer = new IrisRenderer(r);
|
||||
frame.getContentPane().setBackground(BG);
|
||||
frame.setLayout(new BorderLayout());
|
||||
frame.add(buildToolbar(nv), BorderLayout.NORTH);
|
||||
frame.add(nv, BorderLayout.CENTER);
|
||||
frame.setSize(1440, 820);
|
||||
frame.setMinimumSize(new Dimension(640, 480));
|
||||
frame.setLocationRelativeTo(null);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
private static JPanel buildToolbar(VisionGUI nv) {
|
||||
JPanel toolbar = new JPanel(new FlowLayout(FlowLayout.LEFT, 3, 3));
|
||||
toolbar.setBackground(new Color(22, 22, 28));
|
||||
toolbar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(45, 45, 55)));
|
||||
|
||||
JLabel modeLabel = new JLabel("View:");
|
||||
modeLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 11));
|
||||
modeLabel.setForeground(TEXT_SECONDARY);
|
||||
modeLabel.setBorder(BorderFactory.createEmptyBorder(0, 6, 0, 2));
|
||||
toolbar.add(modeLabel);
|
||||
|
||||
ButtonGroup modeGroup = new ButtonGroup();
|
||||
Map<RenderType, JToggleButton> modeButtons = new LinkedHashMap<>();
|
||||
for (RenderType type : RenderType.values()) {
|
||||
JToggleButton btn = createToolbarToggle(modeName(type), type == nv.currentType);
|
||||
btn.addActionListener(e -> {
|
||||
nv.setRenderType(type);
|
||||
for (Map.Entry<RenderType, JToggleButton> entry : modeButtons.entrySet()) {
|
||||
entry.getValue().setSelected(entry.getKey() == type);
|
||||
}
|
||||
});
|
||||
modeGroup.add(btn);
|
||||
modeButtons.put(type, btn);
|
||||
toolbar.add(btn);
|
||||
}
|
||||
nv.modeButtons = modeButtons;
|
||||
|
||||
toolbar.add(createToolbarSeparator());
|
||||
|
||||
JToggleButton gridBtn = createToolbarToggle("Grid", nv.grid);
|
||||
gridBtn.addActionListener(e -> { nv.toggleGrid(); gridBtn.setSelected(nv.grid); });
|
||||
toolbar.add(gridBtn);
|
||||
|
||||
JToggleButton followBtn = createToolbarToggle("Follow", nv.follow);
|
||||
followBtn.addActionListener(e -> { nv.toggleFollow(); followBtn.setSelected(nv.follow); });
|
||||
toolbar.add(followBtn);
|
||||
|
||||
JToggleButton qualityBtn = createToolbarToggle("LQ", nv.lowtile);
|
||||
qualityBtn.addActionListener(e -> { nv.toggleQuality(); qualityBtn.setSelected(nv.lowtile); });
|
||||
toolbar.add(qualityBtn);
|
||||
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
private static JToggleButton createToolbarToggle(String text, boolean selected) {
|
||||
JToggleButton btn = new JToggleButton(text, selected);
|
||||
btn.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 11));
|
||||
btn.setFocusable(false);
|
||||
btn.setForeground(new Color(170, 170, 185));
|
||||
btn.setBackground(new Color(32, 32, 40));
|
||||
btn.setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createLineBorder(new Color(50, 50, 60)),
|
||||
BorderFactory.createEmptyBorder(3, 8, 3, 8)
|
||||
));
|
||||
btn.setOpaque(true);
|
||||
btn.addChangeListener(e -> {
|
||||
if (btn.isSelected()) {
|
||||
btn.setBackground(new Color(50, 60, 85));
|
||||
btn.setForeground(Color.WHITE);
|
||||
} else {
|
||||
btn.setBackground(new Color(32, 32, 40));
|
||||
btn.setForeground(new Color(170, 170, 185));
|
||||
}
|
||||
});
|
||||
return btn;
|
||||
}
|
||||
|
||||
private static JSeparator createToolbarSeparator() {
|
||||
JSeparator sep = new JSeparator(SwingConstants.VERTICAL);
|
||||
sep.setPreferredSize(new Dimension(1, 24));
|
||||
sep.setForeground(new Color(50, 50, 60));
|
||||
sep.setBackground(new Color(22, 22, 28));
|
||||
return sep;
|
||||
}
|
||||
|
||||
public static void launch(Engine g, int i) {
|
||||
J.a(() -> createAndShowGUI(g, i, g.getWorld()));
|
||||
}
|
||||
|
||||
public boolean updateEngine() {
|
||||
if (engine.isClosed()) {
|
||||
if (world.hasRealWorld()) {
|
||||
try {
|
||||
engine = IrisToolbelt.access(world.realWorld()).getEngine();
|
||||
return !engine.isClosed();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
Point cp = e.getPoint();
|
||||
lx = cp.getX();
|
||||
lz = cp.getY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
Point cp = e.getPoint();
|
||||
ox += (lx - cp.getX()) * scale;
|
||||
oz += (lz - cp.getY()) * scale;
|
||||
lx = cp.getX();
|
||||
lz = cp.getY();
|
||||
}
|
||||
|
||||
public void notify(String s) {
|
||||
notifications.put(s, M.ms() + 2500);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_SHIFT) shift = true;
|
||||
if (e.getKeyCode() == KeyEvent.VK_CONTROL) control = true;
|
||||
if (e.getKeyCode() == KeyEvent.VK_SEMICOLON) debug = true;
|
||||
if (e.getKeyCode() == KeyEvent.VK_SLASH) { help = true; helpIgnored = true; }
|
||||
if (e.getKeyCode() == KeyEvent.VK_ALT) alt = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_SEMICOLON) debug = false;
|
||||
if (e.getKeyCode() == KeyEvent.VK_SHIFT) shift = false;
|
||||
if (e.getKeyCode() == KeyEvent.VK_CONTROL) control = false;
|
||||
if (e.getKeyCode() == KeyEvent.VK_SLASH) { help = false; helpIgnored = true; }
|
||||
if (e.getKeyCode() == KeyEvent.VK_ALT) alt = false;
|
||||
|
||||
if (e.getKeyCode() == KeyEvent.VK_F) { toggleFollow(); return; }
|
||||
if (e.getKeyCode() == KeyEvent.VK_R) { dump(); notify("Refreshing"); return; }
|
||||
if (e.getKeyCode() == KeyEvent.VK_P) { toggleQuality(); return; }
|
||||
if (e.getKeyCode() == KeyEvent.VK_E) { eco = !eco; dump(); notify((eco ? "30" : "60") + " FPS"); return; }
|
||||
if (e.getKeyCode() == KeyEvent.VK_G) { toggleGrid(); return; }
|
||||
|
||||
if (e.getKeyCode() == KeyEvent.VK_EQUALS) {
|
||||
mscale = mscale + ((0.044 * mscale) * -3);
|
||||
mscale = Math.max(mscale, 0.00001);
|
||||
dump();
|
||||
return;
|
||||
}
|
||||
if (e.getKeyCode() == KeyEvent.VK_MINUS) {
|
||||
mscale = mscale + ((0.044 * mscale) * 3);
|
||||
mscale = Math.max(mscale, 0.00001);
|
||||
dump();
|
||||
return;
|
||||
}
|
||||
if (e.getKeyCode() == KeyEvent.VK_BACK_SLASH) {
|
||||
mscale = 1D;
|
||||
dump();
|
||||
notify("Zoom Reset");
|
||||
return;
|
||||
}
|
||||
|
||||
int currentMode = currentType.ordinal();
|
||||
for (RenderType i : RenderType.values()) {
|
||||
if (e.getKeyChar() == String.valueOf(i.ordinal() + 1).charAt(0)) {
|
||||
if (i.ordinal() != currentMode) {
|
||||
setRenderType(i);
|
||||
syncModeButtons();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e.getKeyCode() == KeyEvent.VK_M) {
|
||||
setRenderType(RenderType.values()[(currentMode + 1) % RenderType.values().length]);
|
||||
syncModeButtons();
|
||||
}
|
||||
}
|
||||
|
||||
private static String modeName(RenderType type) {
|
||||
return Form.capitalizeWords(type.name().toLowerCase().replaceAll("\\Q_\\E", " "));
|
||||
}
|
||||
|
||||
void setRenderType(RenderType type) {
|
||||
currentType = type;
|
||||
dump();
|
||||
notify(modeName(type));
|
||||
}
|
||||
|
||||
void toggleGrid() {
|
||||
grid = !grid;
|
||||
notify("Grid " + (grid ? "On" : "Off"));
|
||||
}
|
||||
|
||||
void toggleFollow() {
|
||||
follow = !follow;
|
||||
if (player != null && follow) {
|
||||
notify("Following " + player.getName());
|
||||
} else if (follow) {
|
||||
notify("No player in world");
|
||||
follow = false;
|
||||
} else {
|
||||
notify("Follow disabled");
|
||||
}
|
||||
}
|
||||
|
||||
void toggleQuality() {
|
||||
lowtile = !lowtile;
|
||||
dump();
|
||||
notify((lowtile ? "Low" : "High") + " Quality");
|
||||
}
|
||||
|
||||
private void syncModeButtons() {
|
||||
if (modeButtons == null) return;
|
||||
for (Map.Entry<RenderType, JToggleButton> entry : modeButtons.entrySet()) {
|
||||
entry.getValue().setSelected(entry.getKey() == currentType);
|
||||
}
|
||||
}
|
||||
|
||||
private void dump() {
|
||||
positions.clear();
|
||||
fastpositions.clear();
|
||||
}
|
||||
|
||||
public BufferedImage getTile(KSet<BlockPosition> fg, int div, int x, int z, O<Integer> m) {
|
||||
BlockPosition key = new BlockPosition((int) mscale, Math.floorDiv(x, div), Math.floorDiv(z, div));
|
||||
fg.add(key);
|
||||
|
||||
if (positions.containsKey(key)) {
|
||||
return positions.get(key);
|
||||
}
|
||||
|
||||
if (fastpositions.containsKey(key)) {
|
||||
if (!working.contains(key) && working.size() < 9) {
|
||||
m.set(m.get() - 1);
|
||||
if (m.get() >= 0 && velocity < 50) {
|
||||
working.add(key);
|
||||
double mk = mscale;
|
||||
double mkd = scale;
|
||||
e.submit(() -> {
|
||||
PrecisionStopwatch ps = PrecisionStopwatch.start();
|
||||
BufferedImage b = renderer.render(x * mscale, z * mscale, div * mscale, div / (lowtile ? 3 : 1), currentType);
|
||||
rs.put(ps.getMilliseconds());
|
||||
working.remove(key);
|
||||
if (mk == mscale && mkd == scale) {
|
||||
positions.put(key, b);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return fastpositions.get(key);
|
||||
}
|
||||
|
||||
if (workingfast.contains(key) || workingfast.size() > Runtime.getRuntime().availableProcessors()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
workingfast.add(key);
|
||||
double mk = mscale;
|
||||
double mkd = scale;
|
||||
eh.submit(() -> {
|
||||
PrecisionStopwatch ps = PrecisionStopwatch.start();
|
||||
BufferedImage b = renderer.render(x * mscale, z * mscale, div * mscale, div / lowq, currentType);
|
||||
rs.put(ps.getMilliseconds());
|
||||
workingfast.remove(key);
|
||||
if (mk == mscale && mkd == scale) {
|
||||
fastpositions.put(key, b);
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
private double getWorldX(double screenX) {
|
||||
return (mscale * screenX) + ((oxp / scale) * mscale);
|
||||
}
|
||||
|
||||
private double getWorldZ(double screenZ) {
|
||||
return (mscale * screenZ) + ((ozp / scale) * mscale);
|
||||
}
|
||||
|
||||
private double getScreenX(double x) {
|
||||
return (x / mscale) - (oxp / scale);
|
||||
}
|
||||
|
||||
private double getScreenZ(double z) {
|
||||
return (z / mscale) - (ozp / scale);
|
||||
}
|
||||
|
||||
private double lerp(double current, double target, double speed) {
|
||||
double diff = target - current;
|
||||
if (Math.abs(diff) < 0.5) return target;
|
||||
return current + diff * speed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics gx) {
|
||||
if (engine.isClosed()) {
|
||||
EventQueue.invokeLater(() -> {
|
||||
try { setVisible(false); } catch (Throwable ignored) { }
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateEngine()) {
|
||||
dump();
|
||||
}
|
||||
|
||||
velocity = Math.abs(ox - oxp) * 0.36 + Math.abs(oz - ozp) * 0.36;
|
||||
oxp = lerp(oxp, ox, 0.36);
|
||||
ozp = lerp(ozp, oz, 0.36);
|
||||
hx = lerp(hx, lx, 0.36);
|
||||
hz = lerp(hz, lz, 0.36);
|
||||
|
||||
if (centities.flip()) {
|
||||
J.s(() -> {
|
||||
synchronized (lastEntities) {
|
||||
lastEntities.clear();
|
||||
lastEntities.addAll(world.getEntitiesByClass(LivingEntity.class));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
lowq = Math.max(Math.min((int) M.lerp(8, 28, velocity / 1000D), 28), 8);
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
Graphics2D g = (Graphics2D) gx;
|
||||
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
|
||||
w = getWidth();
|
||||
h = getHeight();
|
||||
double vscale = scale;
|
||||
scale = w / 12D;
|
||||
|
||||
if (scale != vscale) {
|
||||
positions.clear();
|
||||
}
|
||||
|
||||
KSet<BlockPosition> gg = new KSet<>();
|
||||
int iscale = (int) scale;
|
||||
g.setColor(BG);
|
||||
g.fillRect(0, 0, w, h);
|
||||
double offsetX = oxp / scale;
|
||||
double offsetZ = ozp / scale;
|
||||
m.set(3);
|
||||
|
||||
for (int r = 0; r < Math.max(w, h); r += iscale) {
|
||||
for (int i = -iscale; i < w + iscale; i += iscale) {
|
||||
for (int j = -iscale; j < h + iscale; j += iscale) {
|
||||
int a = i - (w / 2);
|
||||
int b = j - (h / 2);
|
||||
if (a * a + b * b <= r * r) {
|
||||
int tx = (int) (Math.floor((offsetX + i) / iscale) * iscale);
|
||||
int tz = (int) (Math.floor((offsetZ + j) / iscale) * iscale);
|
||||
BufferedImage t = getTile(gg, iscale, tx, tz, m);
|
||||
|
||||
if (t != null) {
|
||||
int rx = Math.floorMod((int) Math.floor(offsetX), iscale);
|
||||
int rz = Math.floorMod((int) Math.floor(offsetZ), iscale);
|
||||
g.drawImage(t, i - rx, j - rz, iscale, iscale, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (grid) {
|
||||
renderGrid(g, iscale, offsetX, offsetZ);
|
||||
}
|
||||
|
||||
p.end();
|
||||
|
||||
for (BlockPosition i : positions.k()) {
|
||||
if (!gg.contains(i)) {
|
||||
positions.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
handleFollow();
|
||||
renderOverlays(g, p.getMilliseconds());
|
||||
|
||||
if (!isVisible() || !getParent().isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
long targetMs = eco ? 32 : 16;
|
||||
long sleepMs = Math.max(1, targetMs - (long) p.getMilliseconds());
|
||||
J.a(() -> {
|
||||
J.sleep(sleepMs);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
|
||||
private void renderGrid(Graphics2D g, int tileSize, double offsetX, double offsetZ) {
|
||||
g.setColor(GRID_COLOR);
|
||||
int rx = Math.floorMod((int) Math.floor(offsetX), tileSize);
|
||||
int rz = Math.floorMod((int) Math.floor(offsetZ), tileSize);
|
||||
for (int i = -tileSize; i < w + tileSize; i += tileSize) {
|
||||
g.drawLine(i - rx, 0, i - rx, h);
|
||||
}
|
||||
for (int j = -tileSize; j < h + tileSize; j += tileSize) {
|
||||
g.drawLine(0, j - rz, w, j - rz);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFollow() {
|
||||
if (follow && player != null) {
|
||||
animateTo(player.getLocation().getX(), player.getLocation().getZ());
|
||||
}
|
||||
}
|
||||
|
||||
private void renderOverlays(Graphics2D g, double frameMs) {
|
||||
renderEntities(g);
|
||||
|
||||
if (help) {
|
||||
renderOverlayHelp(g);
|
||||
} else if (debug) {
|
||||
renderOverlayDebug(g);
|
||||
}
|
||||
|
||||
renderStatusBar(g, frameMs);
|
||||
renderHoverOverlay(g, shift);
|
||||
|
||||
if (!notifications.isEmpty()) {
|
||||
renderNotification(g);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderStatusBar(Graphics2D g, double frameMs) {
|
||||
int y = h - STATUS_HEIGHT;
|
||||
g.setColor(STATUS_BG);
|
||||
g.fillRect(0, y, w, STATUS_HEIGHT);
|
||||
g.setColor(CARD_BORDER);
|
||||
g.drawLine(0, y, w, y);
|
||||
|
||||
g.setFont(FONT_STATUS);
|
||||
g.setColor(TEXT_SECONDARY);
|
||||
|
||||
double wx = getWorldX(w / 2.0);
|
||||
double wz = getWorldZ(h / 2.0);
|
||||
int fps = frameMs > 0 ? (int) (1000.0 / frameMs) : 0;
|
||||
|
||||
String left = String.format(" %s | %.1f bpp | %s x %s blocks",
|
||||
modeName(currentType), mscale,
|
||||
Form.f((int) (mscale * w)), Form.f((int) (mscale * h)));
|
||||
g.drawString(left, 8, y + 17);
|
||||
|
||||
String right = String.format("X: %s Z: %s | %d FPS ",
|
||||
Form.f((int) wx), Form.f((int) wz), fps);
|
||||
int rw = g.getFontMetrics().stringWidth(right);
|
||||
g.drawString(right, w - rw - 8, y + 17);
|
||||
|
||||
g.setColor(ACCENT);
|
||||
int modeW = g.getFontMetrics().stringWidth(" " + modeName(currentType));
|
||||
g.fillRect(0, y + 1, 3, STATUS_HEIGHT - 1);
|
||||
}
|
||||
|
||||
private void renderEntities(Graphics2D g) {
|
||||
Player b = null;
|
||||
|
||||
for (Player i : world.getPlayers()) {
|
||||
b = i;
|
||||
renderPlayerMarker(g, i.getLocation().getX(), i.getLocation().getZ(), i.getName());
|
||||
}
|
||||
|
||||
synchronized (lastEntities) {
|
||||
double dist = Double.MAX_VALUE;
|
||||
LivingEntity nearest = null;
|
||||
|
||||
for (LivingEntity i : lastEntities) {
|
||||
if (i instanceof Player) continue;
|
||||
renderMobMarker(g, i.getLocation().getX(), i.getLocation().getZ());
|
||||
if (shift) {
|
||||
double d = i.getLocation().distanceSquared(
|
||||
new Location(i.getWorld(), getWorldX(hx), i.getLocation().getY(), getWorldZ(hz)));
|
||||
if (d < dist) {
|
||||
dist = d;
|
||||
nearest = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nearest != null && shift) {
|
||||
double sx = getScreenX(nearest.getLocation().getX());
|
||||
double sz = getScreenZ(nearest.getLocation().getZ());
|
||||
g.setColor(MOB_COLOR);
|
||||
g.fillOval((int) sx - 6, (int) sz - 6, 12, 12);
|
||||
g.setColor(new Color(220, 80, 80, 60));
|
||||
g.fillOval((int) sx - 10, (int) sz - 10, 20, 20);
|
||||
|
||||
KList<String> k = new KList<>();
|
||||
k.add(Form.capitalizeWords(nearest.getType().name().toLowerCase(Locale.ROOT).replaceAll("\\Q_\\E", " ")));
|
||||
k.add("Pos: " + nearest.getLocation().getBlockX() + ", " + nearest.getLocation().getBlockY() + ", " + nearest.getLocation().getBlockZ());
|
||||
k.add("HP: " + Form.f(nearest.getHealth(), 1) + " / " + Form.f(nearest.getAttribute(MAX_HEALTH).getValue(), 1));
|
||||
drawCard(w - CARD_PAD, CARD_PAD, 1, 0, g, k);
|
||||
}
|
||||
}
|
||||
|
||||
player = b;
|
||||
}
|
||||
|
||||
private void renderPlayerMarker(Graphics2D g, double x, double z, String name) {
|
||||
int sx = (int) getScreenX(x);
|
||||
int sz = (int) getScreenZ(z);
|
||||
g.setColor(new Color(80, 200, 120, 40));
|
||||
g.fillOval(sx - 12, sz - 12, 24, 24);
|
||||
g.setColor(PLAYER_COLOR);
|
||||
g.fillOval(sx - 5, sz - 5, 10, 10);
|
||||
g.setColor(new Color(40, 160, 80));
|
||||
g.drawOval(sx - 5, sz - 5, 10, 10);
|
||||
|
||||
g.setFont(FONT_CARD_BODY);
|
||||
g.setColor(TEXT_PRIMARY);
|
||||
int nw = g.getFontMetrics().stringWidth(name);
|
||||
g.drawString(name, sx - nw / 2, sz - 14);
|
||||
}
|
||||
|
||||
private void renderMobMarker(Graphics2D g, double x, double z) {
|
||||
int sx = (int) getScreenX(x);
|
||||
int sz = (int) getScreenZ(z);
|
||||
g.setColor(MOB_COLOR);
|
||||
g.fillRect(sx - 2, sz - 2, 4, 4);
|
||||
}
|
||||
|
||||
private void animateTo(double wx, double wz) {
|
||||
double cx = getWorldX(getWidth() / 2.0);
|
||||
double cz = getWorldZ(getHeight() / 2.0);
|
||||
ox += ((wx - cx) / mscale) * scale;
|
||||
oz += ((wz - cz) / mscale) * scale;
|
||||
}
|
||||
|
||||
private void renderHoverOverlay(Graphics2D g, boolean detailed) {
|
||||
IrisBiome biome = engine.getComplex().getTrueBiomeStream().get(getWorldX(hx), getWorldZ(hz));
|
||||
IrisRegion region = engine.getComplex().getRegionStream().get(getWorldX(hx), getWorldZ(hz));
|
||||
KList<String> l = new KList<>();
|
||||
l.add(biome.getName());
|
||||
l.add(region.getName());
|
||||
l.add("Block " + (int) getWorldX(hx) + ", " + (int) getWorldZ(hz));
|
||||
if (detailed) {
|
||||
l.add("Chunk " + ((int) getWorldX(hx) >> 4) + ", " + ((int) getWorldZ(hz) >> 4));
|
||||
l.add("Region " + (((int) getWorldX(hx) >> 4) >> 5) + ", " + (((int) getWorldZ(hz) >> 4) >> 5));
|
||||
l.add("Key: " + biome.getLoadKey());
|
||||
l.add("File: " + biome.getLoadFile());
|
||||
}
|
||||
drawCard((float) hx + 16, (float) hz, 0, 0, g, l);
|
||||
}
|
||||
|
||||
private void renderOverlayDebug(Graphics2D g) {
|
||||
KList<String> l = new KList<>();
|
||||
l.add("Velocity: " + (int) velocity);
|
||||
l.add("Tiles: " + positions.size() + " HD / " + fastpositions.size() + " LQ");
|
||||
l.add("Workers: " + working.size() + " HD / " + workingfast.size() + " LQ");
|
||||
l.add("Center: " + Form.f((int) getWorldX(getWidth() / 2.0)) + ", " + Form.f((int) getWorldZ(getHeight() / 2.0)));
|
||||
drawCard(CARD_PAD, h - STATUS_HEIGHT - CARD_PAD, 0, 1, g, l);
|
||||
}
|
||||
|
||||
private void renderOverlayHelp(Graphics2D g) {
|
||||
KList<String> keys = new KList<>();
|
||||
KList<String> descs = new KList<>();
|
||||
keys.add("/"); descs.add("Toggle help");
|
||||
keys.add("R"); descs.add("Refresh tiles");
|
||||
keys.add("F"); descs.add("Follow player");
|
||||
keys.add("+/-"); descs.add("Zoom in/out");
|
||||
keys.add("\\"); descs.add("Reset zoom");
|
||||
keys.add("M"); descs.add("Cycle render mode");
|
||||
keys.add("P"); descs.add("Toggle tile quality");
|
||||
keys.add("E"); descs.add("Toggle 30/60 FPS");
|
||||
keys.add("G"); descs.add("Toggle grid");
|
||||
|
||||
int ff = 0;
|
||||
for (RenderType i : RenderType.values()) {
|
||||
ff++;
|
||||
keys.add(String.valueOf(ff));
|
||||
descs.add(modeName(i));
|
||||
}
|
||||
|
||||
keys.add("Shift"); descs.add("Detailed biome info");
|
||||
keys.add("Ctrl+Click"); descs.add("Teleport to cursor");
|
||||
keys.add("Alt+Click"); descs.add("Open biome in editor");
|
||||
|
||||
int maxKeyW = 0;
|
||||
g.setFont(FONT_HELP_KEY);
|
||||
for (String k : keys) {
|
||||
maxKeyW = Math.max(maxKeyW, g.getFontMetrics().stringWidth(k));
|
||||
}
|
||||
|
||||
int lineH = 20;
|
||||
int totalH = keys.size() * lineH + CARD_PAD * 2 + 4;
|
||||
int totalW = maxKeyW + 180 + CARD_PAD * 2;
|
||||
|
||||
drawCardBackground(g, CARD_PAD, CARD_PAD, totalW, totalH);
|
||||
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
int y = CARD_PAD + 16 + i * lineH;
|
||||
|
||||
g.setFont(FONT_HELP_KEY);
|
||||
g.setColor(ACCENT);
|
||||
g.drawString(keys.get(i), CARD_PAD * 2, y);
|
||||
|
||||
g.setFont(FONT_CARD_BODY);
|
||||
g.setColor(TEXT_SECONDARY);
|
||||
g.drawString(descs.get(i), CARD_PAD * 2 + maxKeyW + 16, y);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderNotification(Graphics2D g) {
|
||||
int y = h - STATUS_HEIGHT - 50;
|
||||
g.setFont(FONT_NOTIFICATION);
|
||||
|
||||
KList<String> active = new KList<>();
|
||||
for (String i : notifications.k()) {
|
||||
if (M.ms() > notifications.get(i)) {
|
||||
notifications.remove(i);
|
||||
} else {
|
||||
active.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (active.isEmpty()) return;
|
||||
|
||||
String text = String.join(" | ", active);
|
||||
int tw = g.getFontMetrics().stringWidth(text);
|
||||
int th = g.getFontMetrics().getHeight();
|
||||
int px = (w - tw) / 2 - 16;
|
||||
int py = y - th / 2 - 8;
|
||||
int bw = tw + 32;
|
||||
int bh = th + 16;
|
||||
|
||||
drawCardBackground(g, px, py, bw, bh);
|
||||
g.setColor(TEXT_PRIMARY);
|
||||
g.drawString(text, px + 16, py + th + 4);
|
||||
}
|
||||
|
||||
private void drawCardBackground(Graphics2D g, int x, int y, int w, int h) {
|
||||
RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, w, h, CARD_RADIUS, CARD_RADIUS);
|
||||
g.setColor(CARD_BG);
|
||||
g.fill(rect);
|
||||
g.setColor(CARD_BORDER);
|
||||
g.draw(rect);
|
||||
}
|
||||
|
||||
private void drawCard(float x, float y, double pushX, double pushZ, Graphics2D g, KList<String> text) {
|
||||
g.setFont(FONT_CARD_BODY);
|
||||
int lineH = g.getFontMetrics().getHeight();
|
||||
int cardW = 0;
|
||||
for (String i : text) {
|
||||
cardW = Math.max(cardW, g.getFontMetrics().stringWidth(i));
|
||||
}
|
||||
cardW += CARD_PAD * 2;
|
||||
int cardH = text.size() * lineH + CARD_PAD * 2 - 4;
|
||||
|
||||
int cx = (int) (x - cardW * pushX);
|
||||
int cy = (int) (y - cardH * pushZ);
|
||||
|
||||
drawCardBackground(g, cx, cy, cardW, cardH);
|
||||
|
||||
int ty = cy + CARD_PAD + lineH - 4;
|
||||
for (int i = 0; i < text.size(); i++) {
|
||||
g.setColor(i == 0 ? TEXT_PRIMARY : TEXT_SECONDARY);
|
||||
g.setFont(i == 0 ? FONT_CARD_TITLE : FONT_CARD_BODY);
|
||||
g.drawString(text.get(i), cx + CARD_PAD, ty + i * lineH);
|
||||
}
|
||||
}
|
||||
|
||||
public void mouseWheelMoved(MouseWheelEvent e) {
|
||||
int notches = e.getWheelRotation();
|
||||
if (e.isControlDown()) return;
|
||||
|
||||
double m0 = mscale;
|
||||
double m1 = m0 + ((0.25 * m0) * notches);
|
||||
m1 = Math.max(m1, 0.00001);
|
||||
if (m1 == m0) return;
|
||||
|
||||
positions.clear();
|
||||
fastpositions.clear();
|
||||
|
||||
Point p = e.getPoint();
|
||||
double sx = p.getX();
|
||||
double sz = p.getY();
|
||||
|
||||
double newOxp = scale * ((m0 / m1) * (sx + (oxp / scale)) - sx);
|
||||
double newOzp = scale * ((m0 / m1) * (sz + (ozp / scale)) - sz);
|
||||
|
||||
mscale = m1;
|
||||
oxp = newOxp;
|
||||
ozp = newOzp;
|
||||
ox = oxp;
|
||||
oz = ozp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (control) teleport();
|
||||
else if (alt) open();
|
||||
}
|
||||
|
||||
@Override public void mousePressed(MouseEvent e) { }
|
||||
@Override public void mouseReleased(MouseEvent e) { }
|
||||
@Override public void mouseEntered(MouseEvent e) { }
|
||||
@Override public void mouseExited(MouseEvent e) { }
|
||||
|
||||
private void open() {
|
||||
IrisComplex complex = engine.getComplex();
|
||||
File r = null;
|
||||
switch (currentType) {
|
||||
case BIOME, LAYER_LOAD, DECORATOR_LOAD, OBJECT_LOAD, HEIGHT ->
|
||||
r = complex.getTrueBiomeStream().get(getWorldX(hx), getWorldZ(hz)).openInVSCode();
|
||||
case BIOME_LAND -> r = complex.getLandBiomeStream().get(getWorldX(hx), getWorldZ(hz)).openInVSCode();
|
||||
case BIOME_SEA -> r = complex.getSeaBiomeStream().get(getWorldX(hx), getWorldZ(hz)).openInVSCode();
|
||||
case REGION -> r = complex.getRegionStream().get(getWorldX(hx), getWorldZ(hz)).openInVSCode();
|
||||
case CAVE_LAND -> r = complex.getCaveBiomeStream().get(getWorldX(hx), getWorldZ(hz)).openInVSCode();
|
||||
}
|
||||
if (r != null) {
|
||||
notify("Opened " + r.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private void teleport() {
|
||||
J.s(() -> {
|
||||
if (player != null) {
|
||||
int xx = (int) getWorldX(hx);
|
||||
int zz = (int) getWorldZ(hz);
|
||||
int yy = player.getWorld().getHighestBlockYAt(xx, zz) + 1;
|
||||
player.teleport(new Location(player.getWorld(), xx, yy, zz));
|
||||
notify("Teleported to " + xx + ", " + yy + ", " + zz);
|
||||
} else {
|
||||
notify("No player in world");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+80
-85
@@ -1,85 +1,80 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Double, Double, Integer> 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core.gui.components;
|
||||
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisBiomeGeneratorLink;
|
||||
import art.arcane.iris.util.project.interpolation.IrisInterpolation;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class IrisRenderer {
|
||||
private static final int BLUE = Color.BLUE.getRGB();
|
||||
private static final int YELLOW = Color.YELLOW.getRGB();
|
||||
private static final int GREEN = Color.GREEN.getRGB();
|
||||
|
||||
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);
|
||||
int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
|
||||
BiFunction<Double, Double, Integer> colorFunction = (d, dx) -> 0;
|
||||
|
||||
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(), 1f, 1f).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);
|
||||
if (g.getMax() <= 0) return BLUE;
|
||||
if (g.getMin() < 0) return YELLOW;
|
||||
return GREEN;
|
||||
};
|
||||
}
|
||||
|
||||
double x, z;
|
||||
for (int i = 0; i < resolution; i++) {
|
||||
x = IrisInterpolation.lerp(sx, sx + size, (double) i / (double) resolution);
|
||||
for (int j = 0; j < resolution; j++) {
|
||||
z = IrisInterpolation.lerp(sz, sz + size, (double) j / (double) resolution);
|
||||
pixels[j * resolution + i] = colorFunction.apply(x, z);
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.gui.components;
|
||||
package art.arcane.iris.core.gui.components;
|
||||
|
||||
public enum RenderType {
|
||||
BIOME, BIOME_LAND, BIOME_SEA, REGION, CAVE_LAND, HEIGHT, OBJECT_LOAD, DECORATOR_LOAD, CONTINENT, LAYER_LOAD
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.gui.components;
|
||||
package art.arcane.iris.core.gui.components;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.gui.components;
|
||||
package art.arcane.iris.core.gui.components;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@@ -0,0 +1,58 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldCreator;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
final class BukkitPublicBackend implements WorldLifecycleBackend {
|
||||
private final CapabilitySnapshot capabilities;
|
||||
|
||||
BukkitPublicBackend(CapabilitySnapshot capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(WorldLifecycleRequest request, CapabilitySnapshot capabilities) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<World> create(WorldLifecycleRequest request) {
|
||||
World existing = Bukkit.getWorld(request.worldName());
|
||||
if (existing != null) {
|
||||
return CompletableFuture.completedFuture(existing);
|
||||
}
|
||||
|
||||
WorldCreator creator = request.toWorldCreator();
|
||||
if (request.generator() != null) {
|
||||
WorldLifecycleStaging.stageGenerator(request.worldName(), request.generator(), request.biomeProvider());
|
||||
WorldLifecycleStaging.stageStemGenerator(request.worldName(), request.generator());
|
||||
}
|
||||
|
||||
try {
|
||||
World world = creator.createWorld();
|
||||
return CompletableFuture.completedFuture(world);
|
||||
} catch (Throwable e) {
|
||||
return CompletableFuture.failedFuture(WorldLifecycleSupport.unwrap(e));
|
||||
} finally {
|
||||
WorldLifecycleStaging.clearAll(request.worldName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unload(World world, boolean save) {
|
||||
return WorldLifecycleSupport.unloadWorld(capabilities, world, save);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String backendName() {
|
||||
return "bukkit_public";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describeSelectionReason() {
|
||||
return "public Bukkit world lifecycle path";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
final class CapabilityResolution {
|
||||
private CapabilityResolution() {
|
||||
}
|
||||
|
||||
static Method resolveCreateLevelMethod(Class<?> owner) throws NoSuchMethodException {
|
||||
Method current = resolveMethod(owner, "createLevel", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 3
|
||||
&& "LevelStem".equals(params[0].getSimpleName())
|
||||
&& "WorldLoadingInfoAndData".equals(params[1].getSimpleName())
|
||||
&& "WorldDataAndGenSettings".equals(params[2].getSimpleName());
|
||||
});
|
||||
if (current != null) {
|
||||
return current;
|
||||
}
|
||||
|
||||
Method legacy = resolveMethod(owner, "createLevel", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 4
|
||||
&& "LevelStem".equals(params[0].getSimpleName())
|
||||
&& "WorldLoadingInfo".equals(params[1].getSimpleName())
|
||||
&& "LevelStorageAccess".equals(params[2].getSimpleName())
|
||||
&& "PrimaryLevelData".equals(params[3].getSimpleName());
|
||||
});
|
||||
if (legacy != null) {
|
||||
return legacy;
|
||||
}
|
||||
|
||||
throw new NoSuchMethodException(owner.getName() + "#createLevel");
|
||||
}
|
||||
|
||||
static Method resolveLevelStorageAccessMethod(Class<?> owner) throws NoSuchMethodException {
|
||||
Method exactValidate = resolveMethod(owner, "validateAndCreateAccess", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 2
|
||||
&& String.class.equals(params[0])
|
||||
&& "ResourceKey".equals(params[1].getSimpleName())
|
||||
&& "LevelStorageAccess".equals(method.getReturnType().getSimpleName());
|
||||
});
|
||||
if (exactValidate != null) {
|
||||
return exactValidate;
|
||||
}
|
||||
|
||||
Method oneArgValidate = resolveMethod(owner, "validateAndCreateAccess", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 1
|
||||
&& String.class.equals(params[0])
|
||||
&& "LevelStorageAccess".equals(method.getReturnType().getSimpleName());
|
||||
});
|
||||
if (oneArgValidate != null) {
|
||||
return oneArgValidate;
|
||||
}
|
||||
|
||||
Method exactCreate = resolveMethod(owner, "createAccess", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 2
|
||||
&& String.class.equals(params[0])
|
||||
&& "ResourceKey".equals(params[1].getSimpleName())
|
||||
&& "LevelStorageAccess".equals(method.getReturnType().getSimpleName());
|
||||
});
|
||||
if (exactCreate != null) {
|
||||
return exactCreate;
|
||||
}
|
||||
|
||||
Method oneArgCreate = resolveMethod(owner, "createAccess", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 1
|
||||
&& String.class.equals(params[0])
|
||||
&& "LevelStorageAccess".equals(method.getReturnType().getSimpleName());
|
||||
});
|
||||
if (oneArgCreate != null) {
|
||||
return oneArgCreate;
|
||||
}
|
||||
|
||||
throw new NoSuchMethodException(owner.getName() + "#validateAndCreateAccess/createAccess");
|
||||
}
|
||||
|
||||
static Method resolvePaperWorldDataMethod(Class<?> owner) throws NoSuchMethodException {
|
||||
Method current = resolveMethod(owner, "loadWorldData", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 3
|
||||
&& "MinecraftServer".equals(params[0].getSimpleName())
|
||||
&& "ResourceKey".equals(params[1].getSimpleName())
|
||||
&& String.class.equals(params[2])
|
||||
&& "LoadedWorldData".equals(method.getReturnType().getSimpleName());
|
||||
});
|
||||
if (current != null) {
|
||||
return current;
|
||||
}
|
||||
|
||||
Method legacy = resolveMethod(owner, "getLevelData", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 1 && "LevelStorageAccess".equals(params[0].getSimpleName());
|
||||
});
|
||||
if (legacy != null) {
|
||||
return legacy;
|
||||
}
|
||||
|
||||
throw new NoSuchMethodException(owner.getName() + "#loadWorldData/getLevelData");
|
||||
}
|
||||
|
||||
static Constructor<?> resolveWorldLoadingInfoConstructor(Class<?> owner) throws NoSuchMethodException {
|
||||
Constructor<?> current = resolveConstructor(owner, constructor -> {
|
||||
Class<?>[] params = constructor.getParameterTypes();
|
||||
return params.length == 4
|
||||
&& "Environment".equals(params[0].getSimpleName())
|
||||
&& "ResourceKey".equals(params[1].getSimpleName())
|
||||
&& "ResourceKey".equals(params[2].getSimpleName())
|
||||
&& boolean.class.equals(params[3]);
|
||||
});
|
||||
if (current != null) {
|
||||
return current;
|
||||
}
|
||||
|
||||
Constructor<?> legacy = resolveConstructor(owner, constructor -> {
|
||||
Class<?>[] params = constructor.getParameterTypes();
|
||||
return params.length == 5
|
||||
&& int.class.equals(params[0])
|
||||
&& String.class.equals(params[1])
|
||||
&& String.class.equals(params[2])
|
||||
&& "ResourceKey".equals(params[3].getSimpleName())
|
||||
&& boolean.class.equals(params[4]);
|
||||
});
|
||||
if (legacy != null) {
|
||||
return legacy;
|
||||
}
|
||||
|
||||
throw new NoSuchMethodException(owner.getName() + "#<init>");
|
||||
}
|
||||
|
||||
static Constructor<?> resolveWorldLoadingInfoAndDataConstructor(Class<?> owner) throws NoSuchMethodException {
|
||||
Constructor<?> constructor = resolveConstructor(owner, candidate -> {
|
||||
Class<?>[] params = candidate.getParameterTypes();
|
||||
return params.length == 2
|
||||
&& "WorldLoadingInfo".equals(params[0].getSimpleName())
|
||||
&& "LoadedWorldData".equals(params[1].getSimpleName());
|
||||
});
|
||||
if (constructor == null) {
|
||||
throw new NoSuchMethodException(owner.getName() + "#<init>");
|
||||
}
|
||||
return constructor;
|
||||
}
|
||||
|
||||
static Method resolveCreateNewWorldDataMethod(Class<?> owner) throws NoSuchMethodException {
|
||||
Method method = resolveMethod(owner, "createNewWorldData", candidate -> {
|
||||
Class<?>[] params = candidate.getParameterTypes();
|
||||
return params.length == 5
|
||||
&& "DedicatedServerSettings".equals(params[0].getSimpleName())
|
||||
&& "DataLoadContext".equals(params[1].getSimpleName())
|
||||
&& "Registry".equals(params[2].getSimpleName())
|
||||
&& boolean.class.equals(params[3])
|
||||
&& boolean.class.equals(params[4]);
|
||||
});
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(owner.getName() + "#createNewWorldData");
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
static Method resolveServerRegistryAccessMethod(Class<?> owner) throws NoSuchMethodException {
|
||||
Method method = resolveMethod(owner, "registryAccess", candidate -> candidate.getParameterCount() == 0
|
||||
&& !void.class.equals(candidate.getReturnType()));
|
||||
if (method == null) {
|
||||
throw new NoSuchMethodException(owner.getName() + "#registryAccess");
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
static Method resolveMethod(Class<?> owner, String name, Predicate<Method> predicate) {
|
||||
Method selected = scanMethods(owner.getMethods(), name, predicate);
|
||||
if (selected != null) {
|
||||
return selected;
|
||||
}
|
||||
|
||||
Class<?> current = owner;
|
||||
while (current != null) {
|
||||
selected = scanMethods(current.getDeclaredMethods(), name, predicate);
|
||||
if (selected != null) {
|
||||
selected.setAccessible(true);
|
||||
return selected;
|
||||
}
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static Field resolveField(Class<?> owner, String name) throws NoSuchFieldException {
|
||||
Class<?> current = owner;
|
||||
while (current != null) {
|
||||
try {
|
||||
Field field = current.getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(owner.getName() + "#" + name);
|
||||
}
|
||||
|
||||
private static Method scanMethods(Method[] methods, String name, Predicate<Method> predicate) {
|
||||
for (Method method : methods) {
|
||||
if (!method.getName().equals(name)) {
|
||||
continue;
|
||||
}
|
||||
if (predicate.test(method)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Constructor<?> resolveConstructor(Class<?> owner, Predicate<Constructor<?>> predicate) {
|
||||
for (Constructor<?> constructor : owner.getConstructors()) {
|
||||
if (predicate.test(constructor)) {
|
||||
return constructor;
|
||||
}
|
||||
}
|
||||
for (Constructor<?> constructor : owner.getDeclaredConstructors()) {
|
||||
if (predicate.test(constructor)) {
|
||||
constructor.setAccessible(true);
|
||||
return constructor;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,612 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.scheduling.FoliaScheduler;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class CapabilitySnapshot {
|
||||
public enum PaperLikeFlavor {
|
||||
CURRENT_INFO_AND_DATA,
|
||||
LEGACY_STORAGE_ACCESS,
|
||||
UNSUPPORTED
|
||||
}
|
||||
|
||||
private final ServerFamily serverFamily;
|
||||
private final boolean regionizedRuntime;
|
||||
private final Object worldsProvider;
|
||||
private final Class<?> worldsLevelStemClass;
|
||||
private final Class<?> worldsGeneratorTypeClass;
|
||||
private final String worldsProviderResolution;
|
||||
private final Object bukkitServer;
|
||||
private final Object minecraftServer;
|
||||
private final Method createLevelMethod;
|
||||
private final PaperLikeFlavor paperLikeFlavor;
|
||||
private final Class<?> paperWorldLoaderClass;
|
||||
private final Method paperWorldDataMethod;
|
||||
private final Constructor<?> worldLoadingInfoConstructor;
|
||||
private final Constructor<?> worldLoadingInfoAndDataConstructor;
|
||||
private final Method createNewWorldDataMethod;
|
||||
private final Method levelStorageAccessMethod;
|
||||
private final Field worldLoaderContextField;
|
||||
private final Method serverRegistryAccessMethod;
|
||||
private final Field settingsField;
|
||||
private final Field optionsField;
|
||||
private final Method isDemoMethod;
|
||||
private final Method unloadWorldAsyncMethod;
|
||||
private final Method chunkAtAsyncMethod;
|
||||
private final Method removeLevelMethod;
|
||||
private final String paperLikeResolution;
|
||||
|
||||
private CapabilitySnapshot(
|
||||
ServerFamily serverFamily,
|
||||
boolean regionizedRuntime,
|
||||
Object worldsProvider,
|
||||
Class<?> worldsLevelStemClass,
|
||||
Class<?> worldsGeneratorTypeClass,
|
||||
String worldsProviderResolution,
|
||||
Object bukkitServer,
|
||||
Object minecraftServer,
|
||||
Method createLevelMethod,
|
||||
PaperLikeFlavor paperLikeFlavor,
|
||||
Class<?> paperWorldLoaderClass,
|
||||
Method paperWorldDataMethod,
|
||||
Constructor<?> worldLoadingInfoConstructor,
|
||||
Constructor<?> worldLoadingInfoAndDataConstructor,
|
||||
Method createNewWorldDataMethod,
|
||||
Method levelStorageAccessMethod,
|
||||
Field worldLoaderContextField,
|
||||
Method serverRegistryAccessMethod,
|
||||
Field settingsField,
|
||||
Field optionsField,
|
||||
Method isDemoMethod,
|
||||
Method unloadWorldAsyncMethod,
|
||||
Method chunkAtAsyncMethod,
|
||||
Method removeLevelMethod,
|
||||
String paperLikeResolution
|
||||
) {
|
||||
this.serverFamily = serverFamily;
|
||||
this.regionizedRuntime = regionizedRuntime;
|
||||
this.worldsProvider = worldsProvider;
|
||||
this.worldsLevelStemClass = worldsLevelStemClass;
|
||||
this.worldsGeneratorTypeClass = worldsGeneratorTypeClass;
|
||||
this.worldsProviderResolution = worldsProviderResolution;
|
||||
this.bukkitServer = bukkitServer;
|
||||
this.minecraftServer = minecraftServer;
|
||||
this.createLevelMethod = createLevelMethod;
|
||||
this.paperLikeFlavor = paperLikeFlavor;
|
||||
this.paperWorldLoaderClass = paperWorldLoaderClass;
|
||||
this.paperWorldDataMethod = paperWorldDataMethod;
|
||||
this.worldLoadingInfoConstructor = worldLoadingInfoConstructor;
|
||||
this.worldLoadingInfoAndDataConstructor = worldLoadingInfoAndDataConstructor;
|
||||
this.createNewWorldDataMethod = createNewWorldDataMethod;
|
||||
this.levelStorageAccessMethod = levelStorageAccessMethod;
|
||||
this.worldLoaderContextField = worldLoaderContextField;
|
||||
this.serverRegistryAccessMethod = serverRegistryAccessMethod;
|
||||
this.settingsField = settingsField;
|
||||
this.optionsField = optionsField;
|
||||
this.isDemoMethod = isDemoMethod;
|
||||
this.unloadWorldAsyncMethod = unloadWorldAsyncMethod;
|
||||
this.chunkAtAsyncMethod = chunkAtAsyncMethod;
|
||||
this.removeLevelMethod = removeLevelMethod;
|
||||
this.paperLikeResolution = paperLikeResolution;
|
||||
}
|
||||
|
||||
public static CapabilitySnapshot probe() {
|
||||
Server server = Bukkit.getServer();
|
||||
Object bukkitServer = server;
|
||||
boolean regionizedRuntime = FoliaScheduler.isRegionizedRuntime(server);
|
||||
ServerFamily serverFamily = detectServerFamily(server, regionizedRuntime);
|
||||
|
||||
Object worldsProvider = null;
|
||||
Class<?> worldsLevelStemClass = null;
|
||||
Class<?> worldsGeneratorTypeClass = null;
|
||||
String worldsProviderResolution = "inactive";
|
||||
try {
|
||||
Object[] worldsProviderData = resolveWorldsProvider();
|
||||
worldsProvider = worldsProviderData[0];
|
||||
worldsLevelStemClass = (Class<?>) worldsProviderData[1];
|
||||
worldsGeneratorTypeClass = (Class<?>) worldsProviderData[2];
|
||||
worldsProviderResolution = (String) worldsProviderData[3];
|
||||
} catch (Throwable e) {
|
||||
worldsProviderResolution = e.getClass().getSimpleName() + ": " + String.valueOf(e.getMessage());
|
||||
}
|
||||
|
||||
Object minecraftServer = null;
|
||||
Method createLevelMethod = null;
|
||||
PaperLikeFlavor paperLikeFlavor = PaperLikeFlavor.UNSUPPORTED;
|
||||
Class<?> paperWorldLoaderClass = null;
|
||||
Method paperWorldDataMethod = null;
|
||||
Constructor<?> worldLoadingInfoConstructor = null;
|
||||
Constructor<?> worldLoadingInfoAndDataConstructor = null;
|
||||
Method createNewWorldDataMethod = null;
|
||||
Method levelStorageAccessMethod = null;
|
||||
Field worldLoaderContextField = null;
|
||||
Method serverRegistryAccessMethod = null;
|
||||
Field settingsField = null;
|
||||
Field optionsField = null;
|
||||
Method isDemoMethod = null;
|
||||
Method removeLevelMethod = null;
|
||||
String paperLikeResolution = "inactive";
|
||||
|
||||
try {
|
||||
if (bukkitServer != null) {
|
||||
Method getServerMethod = CapabilityResolution.resolveMethod(bukkitServer.getClass(), "getServer", method -> method.getParameterCount() == 0);
|
||||
if (getServerMethod != null) {
|
||||
minecraftServer = getServerMethod.invoke(bukkitServer);
|
||||
}
|
||||
}
|
||||
|
||||
if (minecraftServer != null) {
|
||||
Class<?> minecraftServerClass = Class.forName("net.minecraft.server.MinecraftServer");
|
||||
if (!minecraftServerClass.isInstance(minecraftServer)) {
|
||||
throw new IllegalStateException("resolved server is not a MinecraftServer: " + minecraftServer.getClass().getName());
|
||||
}
|
||||
|
||||
createLevelMethod = CapabilityResolution.resolveCreateLevelMethod(minecraftServer.getClass());
|
||||
removeLevelMethod = CapabilityResolution.resolveMethod(minecraftServer.getClass(), "removeLevel", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 1 && "ServerLevel".equals(params[0].getSimpleName());
|
||||
});
|
||||
worldLoaderContextField = CapabilityResolution.resolveField(minecraftServer.getClass(), "worldLoaderContext");
|
||||
serverRegistryAccessMethod = CapabilityResolution.resolveServerRegistryAccessMethod(minecraftServer.getClass());
|
||||
settingsField = CapabilityResolution.resolveField(minecraftServer.getClass(), "settings");
|
||||
optionsField = CapabilityResolution.resolveField(minecraftServer.getClass(), "options");
|
||||
isDemoMethod = CapabilityResolution.resolveMethod(minecraftServer.getClass(), "isDemo", method -> method.getParameterCount() == 0 && boolean.class.equals(method.getReturnType()));
|
||||
|
||||
Class<?> mainClass = Class.forName("net.minecraft.server.Main");
|
||||
createNewWorldDataMethod = CapabilityResolution.resolveCreateNewWorldDataMethod(mainClass);
|
||||
|
||||
Class<?> paperLoaderCandidate = Class.forName("io.papermc.paper.world.PaperWorldLoader");
|
||||
paperWorldLoaderClass = paperLoaderCandidate;
|
||||
paperWorldDataMethod = CapabilityResolution.resolvePaperWorldDataMethod(paperLoaderCandidate);
|
||||
Class<?> worldLoadingInfoClass = Class.forName("io.papermc.paper.world.PaperWorldLoader$WorldLoadingInfo");
|
||||
worldLoadingInfoConstructor = CapabilityResolution.resolveWorldLoadingInfoConstructor(worldLoadingInfoClass);
|
||||
|
||||
if (createLevelMethod.getParameterCount() == 3) {
|
||||
Class<?> worldLoadingInfoAndDataClass = Class.forName("io.papermc.paper.world.PaperWorldLoader$WorldLoadingInfoAndData");
|
||||
worldLoadingInfoAndDataConstructor = CapabilityResolution.resolveWorldLoadingInfoAndDataConstructor(worldLoadingInfoAndDataClass);
|
||||
paperLikeFlavor = PaperLikeFlavor.CURRENT_INFO_AND_DATA;
|
||||
} else {
|
||||
Class<?> levelStorageSourceClass = Class.forName("net.minecraft.world.level.storage.LevelStorageSource");
|
||||
levelStorageAccessMethod = CapabilityResolution.resolveLevelStorageAccessMethod(levelStorageSourceClass);
|
||||
paperLikeFlavor = PaperLikeFlavor.LEGACY_STORAGE_ACCESS;
|
||||
}
|
||||
|
||||
paperLikeResolution = "available(flavor=" + paperLikeFlavor.name().toLowerCase(Locale.ROOT)
|
||||
+ ", createLevel=" + createLevelMethod.toGenericString() + ")";
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
paperLikeResolution = e.getClass().getSimpleName() + ": " + String.valueOf(e.getMessage());
|
||||
createLevelMethod = null;
|
||||
paperLikeFlavor = PaperLikeFlavor.UNSUPPORTED;
|
||||
paperWorldLoaderClass = null;
|
||||
paperWorldDataMethod = null;
|
||||
worldLoadingInfoConstructor = null;
|
||||
worldLoadingInfoAndDataConstructor = null;
|
||||
createNewWorldDataMethod = null;
|
||||
levelStorageAccessMethod = null;
|
||||
worldLoaderContextField = null;
|
||||
serverRegistryAccessMethod = null;
|
||||
settingsField = null;
|
||||
optionsField = null;
|
||||
isDemoMethod = null;
|
||||
removeLevelMethod = null;
|
||||
}
|
||||
|
||||
Method unloadWorldAsyncMethod = null;
|
||||
try {
|
||||
if (bukkitServer != null) {
|
||||
unloadWorldAsyncMethod = CapabilityResolution.resolveMethod(bukkitServer.getClass(), "unloadWorldAsync", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 3
|
||||
&& World.class.equals(params[0])
|
||||
&& boolean.class.equals(params[1])
|
||||
&& "Consumer".equals(params[2].getSimpleName());
|
||||
});
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
unloadWorldAsyncMethod = null;
|
||||
}
|
||||
|
||||
Method chunkAtAsyncMethod = null;
|
||||
try {
|
||||
chunkAtAsyncMethod = CapabilityResolution.resolveMethod(World.class, "getChunkAtAsync", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 3
|
||||
&& int.class.equals(params[0])
|
||||
&& int.class.equals(params[1])
|
||||
&& boolean.class.equals(params[2]);
|
||||
});
|
||||
} catch (Throwable ignored) {
|
||||
chunkAtAsyncMethod = null;
|
||||
}
|
||||
|
||||
return new CapabilitySnapshot(
|
||||
serverFamily,
|
||||
regionizedRuntime,
|
||||
worldsProvider,
|
||||
worldsLevelStemClass,
|
||||
worldsGeneratorTypeClass,
|
||||
worldsProviderResolution,
|
||||
bukkitServer,
|
||||
minecraftServer,
|
||||
createLevelMethod,
|
||||
paperLikeFlavor,
|
||||
paperWorldLoaderClass,
|
||||
paperWorldDataMethod,
|
||||
worldLoadingInfoConstructor,
|
||||
worldLoadingInfoAndDataConstructor,
|
||||
createNewWorldDataMethod,
|
||||
levelStorageAccessMethod,
|
||||
worldLoaderContextField,
|
||||
serverRegistryAccessMethod,
|
||||
settingsField,
|
||||
optionsField,
|
||||
isDemoMethod,
|
||||
unloadWorldAsyncMethod,
|
||||
chunkAtAsyncMethod,
|
||||
removeLevelMethod,
|
||||
paperLikeResolution
|
||||
);
|
||||
}
|
||||
|
||||
public static CapabilitySnapshot forTesting(ServerFamily serverFamily, boolean regionizedRuntime, boolean worldsProviderHealthy, boolean paperLikeRuntimeHealthy) {
|
||||
Object minecraftServer = paperLikeRuntimeHealthy ? new TestingPaperLikeServer("datapack-registry", "server-registry") : null;
|
||||
Method createLevelMethod = null;
|
||||
Field worldLoaderContextField = null;
|
||||
Method serverRegistryAccessMethod = null;
|
||||
try {
|
||||
createLevelMethod = paperLikeRuntimeHealthy
|
||||
? TestingPaperLikeServer.class.getDeclaredMethod("createLevel", Object.class, Object.class, Object.class)
|
||||
: null;
|
||||
worldLoaderContextField = paperLikeRuntimeHealthy
|
||||
? CapabilityResolution.resolveField(TestingPaperLikeServer.class, "worldLoaderContext")
|
||||
: null;
|
||||
serverRegistryAccessMethod = paperLikeRuntimeHealthy
|
||||
? CapabilityResolution.resolveServerRegistryAccessMethod(TestingPaperLikeServer.class)
|
||||
: null;
|
||||
} catch (NoSuchMethodException | NoSuchFieldException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return new CapabilitySnapshot(
|
||||
serverFamily,
|
||||
regionizedRuntime,
|
||||
worldsProviderHealthy ? new Object() : null,
|
||||
worldsProviderHealthy ? Object.class : null,
|
||||
worldsProviderHealthy ? Object.class : null,
|
||||
worldsProviderHealthy ? "test-provider" : "inactive",
|
||||
null,
|
||||
minecraftServer,
|
||||
createLevelMethod,
|
||||
paperLikeRuntimeHealthy ? PaperLikeFlavor.CURRENT_INFO_AND_DATA : PaperLikeFlavor.UNSUPPORTED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
worldLoaderContextField,
|
||||
serverRegistryAccessMethod,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
paperLikeRuntimeHealthy ? "available(test)" : "unsupported(test)"
|
||||
);
|
||||
}
|
||||
|
||||
public static CapabilitySnapshot forTestingRuntimeRegistries(ServerFamily serverFamily, boolean regionizedRuntime, Object datapackDimensions, Object serverRegistryAccess) {
|
||||
TestingPaperLikeServer minecraftServer = new TestingPaperLikeServer(datapackDimensions, serverRegistryAccess);
|
||||
Method createLevelMethod;
|
||||
Field worldLoaderContextField;
|
||||
Method registryAccessMethod;
|
||||
try {
|
||||
createLevelMethod = TestingPaperLikeServer.class.getDeclaredMethod("createLevel", Object.class, Object.class, Object.class);
|
||||
worldLoaderContextField = CapabilityResolution.resolveField(TestingPaperLikeServer.class, "worldLoaderContext");
|
||||
registryAccessMethod = CapabilityResolution.resolveServerRegistryAccessMethod(TestingPaperLikeServer.class);
|
||||
} catch (NoSuchMethodException | NoSuchFieldException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return new CapabilitySnapshot(
|
||||
serverFamily,
|
||||
regionizedRuntime,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"inactive",
|
||||
null,
|
||||
minecraftServer,
|
||||
createLevelMethod,
|
||||
PaperLikeFlavor.CURRENT_INFO_AND_DATA,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
worldLoaderContextField,
|
||||
registryAccessMethod,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"available(test-runtime-registries)"
|
||||
);
|
||||
}
|
||||
|
||||
public ServerFamily serverFamily() {
|
||||
return serverFamily;
|
||||
}
|
||||
|
||||
public boolean regionizedRuntime() {
|
||||
return regionizedRuntime;
|
||||
}
|
||||
|
||||
public Object worldsProvider() {
|
||||
return worldsProvider;
|
||||
}
|
||||
|
||||
public Class<?> worldsLevelStemClass() {
|
||||
return worldsLevelStemClass;
|
||||
}
|
||||
|
||||
public Class<?> worldsGeneratorTypeClass() {
|
||||
return worldsGeneratorTypeClass;
|
||||
}
|
||||
|
||||
public Object bukkitServer() {
|
||||
return bukkitServer;
|
||||
}
|
||||
|
||||
public Object minecraftServer() {
|
||||
return minecraftServer;
|
||||
}
|
||||
|
||||
public Method createLevelMethod() {
|
||||
return createLevelMethod;
|
||||
}
|
||||
|
||||
public PaperLikeFlavor paperLikeFlavor() {
|
||||
return paperLikeFlavor;
|
||||
}
|
||||
|
||||
public Class<?> paperWorldLoaderClass() {
|
||||
return paperWorldLoaderClass;
|
||||
}
|
||||
|
||||
public Method paperWorldDataMethod() {
|
||||
return paperWorldDataMethod;
|
||||
}
|
||||
|
||||
public Constructor<?> worldLoadingInfoConstructor() {
|
||||
return worldLoadingInfoConstructor;
|
||||
}
|
||||
|
||||
public Constructor<?> worldLoadingInfoAndDataConstructor() {
|
||||
return worldLoadingInfoAndDataConstructor;
|
||||
}
|
||||
|
||||
public Method createNewWorldDataMethod() {
|
||||
return createNewWorldDataMethod;
|
||||
}
|
||||
|
||||
public Method levelStorageAccessMethod() {
|
||||
return levelStorageAccessMethod;
|
||||
}
|
||||
|
||||
public Field worldLoaderContextField() {
|
||||
return worldLoaderContextField;
|
||||
}
|
||||
|
||||
public Method serverRegistryAccessMethod() {
|
||||
return serverRegistryAccessMethod;
|
||||
}
|
||||
|
||||
public Field settingsField() {
|
||||
return settingsField;
|
||||
}
|
||||
|
||||
public Field optionsField() {
|
||||
return optionsField;
|
||||
}
|
||||
|
||||
public Method isDemoMethod() {
|
||||
return isDemoMethod;
|
||||
}
|
||||
|
||||
public Method unloadWorldAsyncMethod() {
|
||||
return unloadWorldAsyncMethod;
|
||||
}
|
||||
|
||||
public Method chunkAtAsyncMethod() {
|
||||
return chunkAtAsyncMethod;
|
||||
}
|
||||
|
||||
public Method removeLevelMethod() {
|
||||
return removeLevelMethod;
|
||||
}
|
||||
|
||||
public boolean hasWorldsProvider() {
|
||||
return worldsProvider != null && worldsLevelStemClass != null && worldsGeneratorTypeClass != null;
|
||||
}
|
||||
|
||||
public boolean hasPaperLikeRuntime() {
|
||||
return minecraftServer != null
|
||||
&& createLevelMethod != null
|
||||
&& serverRegistryAccessMethod != null
|
||||
&& paperLikeFlavor != PaperLikeFlavor.UNSUPPORTED;
|
||||
}
|
||||
|
||||
public String worldsProviderResolution() {
|
||||
return worldsProviderResolution;
|
||||
}
|
||||
|
||||
public String paperLikeResolution() {
|
||||
return paperLikeResolution;
|
||||
}
|
||||
|
||||
public String describe() {
|
||||
return "family=" + serverFamily.id()
|
||||
+ ", regionizedRuntime=" + regionizedRuntime
|
||||
+ ", worldsProvider=" + worldsProviderResolution
|
||||
+ ", paperLike=" + paperLikeResolution
|
||||
+ ", serverRegistryAccess=" + (serverRegistryAccessMethod != null)
|
||||
+ ", unloadAsync=" + (unloadWorldAsyncMethod != null)
|
||||
+ ", chunkAsync=" + (chunkAtAsyncMethod != null);
|
||||
}
|
||||
|
||||
private static ServerFamily detectServerFamily(Server server, boolean regionizedRuntime) {
|
||||
String bukkitName = server == null ? "" : server.getName();
|
||||
String bukkitVersion = server == null ? "" : server.getVersion();
|
||||
String serverClassName = server == null ? "" : server.getClass().getName();
|
||||
boolean canvasRuntime = hasCanvasRuntime();
|
||||
|
||||
if (containsIgnoreCase(bukkitName, "folia")
|
||||
|| containsIgnoreCase(bukkitVersion, "folia")
|
||||
|| containsIgnoreCase(serverClassName, "folia")) {
|
||||
return ServerFamily.FOLIA;
|
||||
}
|
||||
|
||||
if (canvasRuntime
|
||||
|| containsIgnoreCase(bukkitName, "canvas")
|
||||
|| containsIgnoreCase(bukkitVersion, "canvas")
|
||||
|| containsIgnoreCase(serverClassName, "canvas")) {
|
||||
return regionizedRuntime ? ServerFamily.CANVAS : ServerFamily.CANVAS;
|
||||
}
|
||||
|
||||
if (containsIgnoreCase(bukkitName, "purpur")
|
||||
|| containsIgnoreCase(bukkitVersion, "purpur")
|
||||
|| containsIgnoreCase(serverClassName, "purpur")) {
|
||||
return ServerFamily.PURPUR;
|
||||
}
|
||||
|
||||
if (containsIgnoreCase(bukkitName, "paper")
|
||||
|| containsIgnoreCase(bukkitVersion, "paper")
|
||||
|| containsIgnoreCase(serverClassName, "paper")
|
||||
|| containsIgnoreCase(bukkitName, "pufferfish")
|
||||
|| containsIgnoreCase(bukkitVersion, "pufferfish")
|
||||
|| containsIgnoreCase(serverClassName, "pufferfish")) {
|
||||
return ServerFamily.PAPER;
|
||||
}
|
||||
|
||||
if (containsIgnoreCase(bukkitName, "spigot")
|
||||
|| containsIgnoreCase(bukkitVersion, "spigot")
|
||||
|| containsIgnoreCase(serverClassName, "spigot")) {
|
||||
return ServerFamily.SPIGOT;
|
||||
}
|
||||
|
||||
if (containsIgnoreCase(bukkitName, "craftbukkit")
|
||||
|| containsIgnoreCase(bukkitVersion, "craftbukkit")
|
||||
|| containsIgnoreCase(serverClassName, "craftbukkit")
|
||||
|| containsIgnoreCase(bukkitName, "bukkit")
|
||||
|| containsIgnoreCase(bukkitVersion, "bukkit")) {
|
||||
return ServerFamily.BUKKIT;
|
||||
}
|
||||
|
||||
if (regionizedRuntime || J.isFolia()) {
|
||||
return ServerFamily.FOLIA;
|
||||
}
|
||||
|
||||
return ServerFamily.UNKNOWN;
|
||||
}
|
||||
|
||||
private static boolean hasCanvasRuntime() {
|
||||
try {
|
||||
Class.forName("io.canvasmc.canvas.region.WorldRegionizer");
|
||||
return true;
|
||||
} catch (Throwable ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean containsIgnoreCase(String value, String needle) {
|
||||
if (value == null || needle == null || needle.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return value.toLowerCase(Locale.ROOT).contains(needle.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
private static Object[] resolveWorldsProvider() throws Throwable {
|
||||
try {
|
||||
Class<?> worldsProviderClass = Class.forName("net.thenextlvl.worlds.api.WorldsProvider");
|
||||
Class<?> levelStemClass = Class.forName("net.thenextlvl.worlds.api.generator.LevelStem");
|
||||
Class<?> generatorTypeClass = Class.forName("net.thenextlvl.worlds.api.generator.GeneratorType");
|
||||
Object provider = Bukkit.getServicesManager().load(worldsProviderClass);
|
||||
String resolution = provider == null ? "inactive(service not registered)" : "active(service=" + provider.getClass().getName() + ")";
|
||||
return new Object[]{provider, levelStemClass, generatorTypeClass, resolution};
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
Collection<Class<?>> knownServices = Bukkit.getServicesManager().getKnownServices();
|
||||
for (Class<?> serviceClass : knownServices) {
|
||||
if (!"net.thenextlvl.worlds.api.WorldsProvider".equals(serviceClass.getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RegisteredServiceProvider<?> registration = Bukkit.getServicesManager().getRegistration(serviceClass);
|
||||
if (registration == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object provider = registration.getProvider();
|
||||
ClassLoader loader = serviceClass.getClassLoader();
|
||||
if (loader == null && provider != null) {
|
||||
loader = provider.getClass().getClassLoader();
|
||||
}
|
||||
if (loader == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Class<?> levelStemClass = Class.forName("net.thenextlvl.worlds.api.generator.LevelStem", false, loader);
|
||||
Class<?> generatorTypeClass = Class.forName("net.thenextlvl.worlds.api.generator.GeneratorType", false, loader);
|
||||
return new Object[]{provider, levelStemClass, generatorTypeClass, "active(service-scan=" + provider.getClass().getName() + ")"};
|
||||
}
|
||||
|
||||
return new Object[]{null, null, null, "inactive(service scan found nothing)"};
|
||||
}
|
||||
|
||||
private static final class TestingPaperLikeServer {
|
||||
private final TestingWorldLoaderContext worldLoaderContext;
|
||||
private final Object registryAccess;
|
||||
|
||||
private TestingPaperLikeServer(Object datapackDimensions, Object registryAccess) {
|
||||
this.worldLoaderContext = new TestingWorldLoaderContext(datapackDimensions);
|
||||
this.registryAccess = registryAccess;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void createLevel(Object levelStem, Object worldLoadingInfoAndData, Object worldDataAndGenSettings) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private Object registryAccess() {
|
||||
return registryAccess;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestingWorldLoaderContext {
|
||||
private final Object datapackDimensions;
|
||||
|
||||
private TestingWorldLoaderContext(Object datapackDimensions) {
|
||||
this.datapackDimensions = datapackDimensions;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private Object datapackDimensions() {
|
||||
return datapackDimensions;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
final class PaperLikeRuntimeBackend implements WorldLifecycleBackend {
|
||||
private final CapabilitySnapshot capabilities;
|
||||
|
||||
PaperLikeRuntimeBackend(CapabilitySnapshot capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(WorldLifecycleRequest request, CapabilitySnapshot capabilities) {
|
||||
return request.studio()
|
||||
&& capabilities.serverFamily().isPaperLike()
|
||||
&& capabilities.hasPaperLikeRuntime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<World> create(WorldLifecycleRequest request) {
|
||||
Object legacyStorageAccess = null;
|
||||
try {
|
||||
World existing = Bukkit.getWorld(request.worldName());
|
||||
if (existing != null) {
|
||||
return CompletableFuture.completedFuture(existing);
|
||||
}
|
||||
|
||||
if (request.generator() == null) {
|
||||
return CompletableFuture.failedFuture(new IllegalStateException("Runtime world creation requires a non-null chunk generator."));
|
||||
}
|
||||
|
||||
WorldLifecycleStaging.stageGenerator(request.worldName(), request.generator(), request.biomeProvider());
|
||||
WorldLifecycleSupport.stageRuntimeConfiguration(request.worldName());
|
||||
|
||||
Iris.info("WorldLifecycle runtime LevelStem: world=" + request.worldName()
|
||||
+ ", backend=paper_like_runtime, flavor=" + capabilities.paperLikeFlavor().name().toLowerCase(Locale.ROOT)
|
||||
+ ", registrySource=" + WorldLifecycleSupport.runtimeLevelStemRegistrySource(request));
|
||||
Object levelStem = WorldLifecycleSupport.resolveRuntimeLevelStem(capabilities, request);
|
||||
Object stemKey = WorldLifecycleSupport.createRuntimeLevelStemKey(request.worldName());
|
||||
|
||||
if (capabilities.paperLikeFlavor() == CapabilitySnapshot.PaperLikeFlavor.CURRENT_INFO_AND_DATA) {
|
||||
Object dimensionKey = WorldLifecycleSupport.createDimensionKey(stemKey);
|
||||
Object loadedWorldData = capabilities.paperWorldDataMethod().invoke(null, capabilities.minecraftServer(), dimensionKey, request.worldName());
|
||||
Object worldLoadingInfo = capabilities.worldLoadingInfoConstructor().newInstance(request.environment(), stemKey, dimensionKey, !request.studio());
|
||||
Object worldLoadingInfoAndData = capabilities.worldLoadingInfoAndDataConstructor().newInstance(worldLoadingInfo, loadedWorldData);
|
||||
Object worldDataAndGenSettings = WorldLifecycleSupport.createCurrentWorldDataAndSettings(capabilities, request.worldName());
|
||||
capabilities.createLevelMethod().invoke(capabilities.minecraftServer(), levelStem, worldLoadingInfoAndData, worldDataAndGenSettings);
|
||||
} else {
|
||||
legacyStorageAccess = WorldLifecycleSupport.createLegacyStorageAccess(capabilities, request.worldName());
|
||||
Object primaryLevelData = WorldLifecycleSupport.createLegacyPrimaryLevelData(capabilities, legacyStorageAccess, request.worldName());
|
||||
Object worldLoadingInfo = capabilities.worldLoadingInfoConstructor().newInstance(0, request.worldName(), request.environment().name().toLowerCase(Locale.ROOT), stemKey, !request.studio());
|
||||
capabilities.createLevelMethod().invoke(capabilities.minecraftServer(), levelStem, worldLoadingInfo, legacyStorageAccess, primaryLevelData);
|
||||
}
|
||||
|
||||
World loadedWorld = Bukkit.getWorld(request.worldName());
|
||||
if (loadedWorld == null) {
|
||||
return CompletableFuture.failedFuture(new IllegalStateException("Paper-like runtime backend did not load world \"" + request.worldName() + "\"."));
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(loadedWorld);
|
||||
} catch (Throwable e) {
|
||||
return CompletableFuture.failedFuture(WorldLifecycleSupport.unwrap(e));
|
||||
} finally {
|
||||
WorldLifecycleStaging.clearGenerator(request.worldName());
|
||||
WorldLifecycleSupport.closeLevelStorageAccess(legacyStorageAccess);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unload(World world, boolean save) {
|
||||
return WorldLifecycleSupport.unloadWorld(capabilities, world, save);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String backendName() {
|
||||
return "paper_like_runtime";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describeSelectionReason() {
|
||||
return "server family " + capabilities.serverFamily().id() + " exposes paper-like runtime world lifecycle capabilities";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum ServerFamily {
|
||||
BUKKIT,
|
||||
SPIGOT,
|
||||
PAPER,
|
||||
PURPUR,
|
||||
FOLIA,
|
||||
CANVAS,
|
||||
UNKNOWN;
|
||||
|
||||
public boolean isPaperLike() {
|
||||
return this == PAPER || this == PURPUR || this == FOLIA || this == CANVAS;
|
||||
}
|
||||
|
||||
public String id() {
|
||||
return name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface WorldLifecycleBackend {
|
||||
boolean supports(WorldLifecycleRequest request, CapabilitySnapshot capabilities);
|
||||
|
||||
CompletableFuture<World> create(WorldLifecycleRequest request);
|
||||
|
||||
boolean unload(World world, boolean save);
|
||||
|
||||
String backendName();
|
||||
|
||||
String describeSelectionReason();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
public enum WorldLifecycleCaller {
|
||||
STUDIO,
|
||||
CREATE,
|
||||
BENCHMARK
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldCreator;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
|
||||
public record WorldLifecycleRequest(
|
||||
String worldName,
|
||||
World.Environment environment,
|
||||
ChunkGenerator generator,
|
||||
BiomeProvider biomeProvider,
|
||||
WorldType worldType,
|
||||
boolean generateStructures,
|
||||
boolean hardcore,
|
||||
long seed,
|
||||
boolean studio,
|
||||
boolean benchmark,
|
||||
WorldLifecycleCaller callerKind
|
||||
) {
|
||||
public static WorldLifecycleRequest fromCreator(WorldCreator creator, boolean studio, boolean benchmark, WorldLifecycleCaller callerKind) {
|
||||
return new WorldLifecycleRequest(
|
||||
creator.name(),
|
||||
creator.environment(),
|
||||
creator.generator(),
|
||||
creator.biomeProvider(),
|
||||
creator.type(),
|
||||
creator.generateStructures(),
|
||||
creator.hardcore(),
|
||||
creator.seed(),
|
||||
studio,
|
||||
benchmark,
|
||||
callerKind
|
||||
);
|
||||
}
|
||||
|
||||
public WorldCreator toWorldCreator() {
|
||||
WorldCreator creator = new WorldCreator(worldName)
|
||||
.environment(environment)
|
||||
.generateStructures(generateStructures)
|
||||
.hardcore(hardcore)
|
||||
.type(worldType)
|
||||
.seed(seed)
|
||||
.generator(generator);
|
||||
if (biomeProvider != null) {
|
||||
creator.biomeProvider(biomeProvider);
|
||||
}
|
||||
return creator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class WorldLifecycleService {
|
||||
private static volatile WorldLifecycleService instance;
|
||||
|
||||
private final CapabilitySnapshot capabilities;
|
||||
private final WorldsProviderBackend worldsProviderBackend;
|
||||
private final PaperLikeRuntimeBackend paperLikeRuntimeBackend;
|
||||
private final BukkitPublicBackend bukkitPublicBackend;
|
||||
private final List<WorldLifecycleBackend> backends;
|
||||
private final Map<String, String> worldBackendByName;
|
||||
|
||||
public WorldLifecycleService(CapabilitySnapshot capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
this.worldsProviderBackend = new WorldsProviderBackend(capabilities);
|
||||
this.paperLikeRuntimeBackend = new PaperLikeRuntimeBackend(capabilities);
|
||||
this.bukkitPublicBackend = new BukkitPublicBackend(capabilities);
|
||||
this.backends = List.of(worldsProviderBackend, paperLikeRuntimeBackend, bukkitPublicBackend);
|
||||
this.worldBackendByName = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public static WorldLifecycleService get() {
|
||||
WorldLifecycleService current = instance;
|
||||
if (current != null) {
|
||||
return current;
|
||||
}
|
||||
|
||||
synchronized (WorldLifecycleService.class) {
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
CapabilitySnapshot capabilities = CapabilitySnapshot.probe();
|
||||
instance = new WorldLifecycleService(capabilities);
|
||||
Iris.info("WorldLifecycle capabilities: %s", capabilities.describe());
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public CapabilitySnapshot capabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
public CompletableFuture<World> create(WorldLifecycleRequest request) {
|
||||
WorldLifecycleBackend backend;
|
||||
try {
|
||||
backend = selectCreateBackend(request);
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("WorldLifecycle create backend selection failed for world=\"" + request.worldName()
|
||||
+ "\", caller=" + request.callerKind().name().toLowerCase() + ".", e);
|
||||
return CompletableFuture.failedFuture(e);
|
||||
}
|
||||
Iris.info("WorldLifecycle create: world=%s, caller=%s, backend=%s, reason=%s",
|
||||
request.worldName(),
|
||||
request.callerKind().name().toLowerCase(),
|
||||
backend.backendName(),
|
||||
backend.describeSelectionReason());
|
||||
return backend.create(request).whenComplete((world, throwable) -> {
|
||||
if (throwable != null) {
|
||||
Throwable cause = WorldLifecycleSupport.unwrap(throwable);
|
||||
Iris.reportError("WorldLifecycle create failed: world=\"" + request.worldName()
|
||||
+ "\", caller=" + request.callerKind().name().toLowerCase()
|
||||
+ ", backend=" + backend.backendName()
|
||||
+ ", family=" + capabilities.serverFamily().id() + ".", cause);
|
||||
return;
|
||||
}
|
||||
if (world != null) {
|
||||
worldBackendByName.put(world.getName(), backend.backendName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public World createBlocking(WorldLifecycleRequest request) {
|
||||
try {
|
||||
return create(request).join();
|
||||
} catch (CompletionException e) {
|
||||
throw new IllegalStateException(WorldLifecycleSupport.unwrap(e));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean unload(World world, boolean save) {
|
||||
if (!J.isPrimaryThread()) {
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
J.s(() -> {
|
||||
try {
|
||||
future.complete(unloadDirect(world, save));
|
||||
} catch (Throwable e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
return future.join();
|
||||
}
|
||||
|
||||
return unloadDirect(world, save);
|
||||
}
|
||||
|
||||
private boolean unloadDirect(World world, boolean save) {
|
||||
WorldLifecycleBackend backend = selectUnloadBackend(world.getName());
|
||||
Iris.info("WorldLifecycle unload: world=%s, backend=%s, reason=%s",
|
||||
world.getName(),
|
||||
backend.backendName(),
|
||||
backend.describeSelectionReason());
|
||||
boolean unloaded;
|
||||
try {
|
||||
unloaded = backend.unload(world, save);
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("WorldLifecycle unload failed: world=\"" + world.getName()
|
||||
+ "\", backend=" + backend.backendName()
|
||||
+ ", family=" + capabilities.serverFamily().id() + ".", e);
|
||||
if (e instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
}
|
||||
if (e instanceof Error error) {
|
||||
throw error;
|
||||
}
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
if (unloaded) {
|
||||
worldBackendByName.remove(world.getName());
|
||||
}
|
||||
return unloaded;
|
||||
}
|
||||
|
||||
public String backendNameForWorld(String worldName) {
|
||||
return selectUnloadBackend(worldName).backendName();
|
||||
}
|
||||
|
||||
WorldLifecycleBackend selectCreateBackend(WorldLifecycleRequest request) {
|
||||
if (worldsProviderBackend.supports(request, capabilities)) {
|
||||
return worldsProviderBackend;
|
||||
}
|
||||
|
||||
if (request.studio() && capabilities.serverFamily().isPaperLike()) {
|
||||
if (!paperLikeRuntimeBackend.supports(request, capabilities)) {
|
||||
throw new IllegalStateException("World lifecycle backend paper_like_runtime is unavailable for studio create on "
|
||||
+ capabilities.serverFamily().id() + ": " + capabilities.paperLikeResolution());
|
||||
}
|
||||
return paperLikeRuntimeBackend;
|
||||
}
|
||||
|
||||
for (WorldLifecycleBackend backend : backends) {
|
||||
if (backend.supports(request, capabilities)) {
|
||||
return backend;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException("No world lifecycle backend supports request for \"" + request.worldName() + "\".");
|
||||
}
|
||||
|
||||
WorldLifecycleBackend selectUnloadBackend(String worldName) {
|
||||
String backendName = worldBackendByName.get(worldName);
|
||||
if (backendName == null) {
|
||||
return bukkitPublicBackend;
|
||||
}
|
||||
|
||||
for (WorldLifecycleBackend backend : backends) {
|
||||
if (backend.backendName().equals(backendName)) {
|
||||
return backend;
|
||||
}
|
||||
}
|
||||
|
||||
return bukkitPublicBackend;
|
||||
}
|
||||
|
||||
void rememberBackend(String worldName, String backendName) {
|
||||
worldBackendByName.put(worldName, backendName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class WorldLifecycleStaging {
|
||||
private static final Map<String, ChunkGenerator> stagedGenerators = new ConcurrentHashMap<>();
|
||||
private static final Map<String, BiomeProvider> stagedBiomeProviders = new ConcurrentHashMap<>();
|
||||
private static final Map<String, ChunkGenerator> stagedStemGenerators = new ConcurrentHashMap<>();
|
||||
|
||||
private WorldLifecycleStaging() {
|
||||
}
|
||||
|
||||
public static void stageGenerator(@NotNull String worldName, @NotNull ChunkGenerator generator, @Nullable BiomeProvider biomeProvider) {
|
||||
stagedGenerators.put(worldName, generator);
|
||||
if (biomeProvider != null) {
|
||||
stagedBiomeProviders.put(worldName, biomeProvider);
|
||||
} else {
|
||||
stagedBiomeProviders.remove(worldName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void stageStemGenerator(@NotNull String worldName, @NotNull ChunkGenerator generator) {
|
||||
stagedStemGenerators.put(worldName, generator);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ChunkGenerator consumeGenerator(@NotNull String worldName) {
|
||||
return stagedGenerators.remove(worldName);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static BiomeProvider consumeBiomeProvider(@NotNull String worldName) {
|
||||
return stagedBiomeProviders.remove(worldName);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ChunkGenerator consumeStemGenerator(@NotNull String worldName) {
|
||||
return stagedStemGenerators.remove(worldName);
|
||||
}
|
||||
|
||||
public static void clearGenerator(@NotNull String worldName) {
|
||||
stagedGenerators.remove(worldName);
|
||||
stagedBiomeProviders.remove(worldName);
|
||||
}
|
||||
|
||||
public static void clearStem(@NotNull String worldName) {
|
||||
stagedStemGenerators.remove(worldName);
|
||||
}
|
||||
|
||||
public static void clearAll(@NotNull String worldName) {
|
||||
clearGenerator(worldName);
|
||||
clearStem(worldName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,520 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.iris.core.nms.INMSBinding;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
final class WorldLifecycleSupport {
|
||||
private WorldLifecycleSupport() {
|
||||
}
|
||||
|
||||
static Throwable unwrap(Throwable throwable) {
|
||||
if (throwable instanceof InvocationTargetException invocationTargetException && invocationTargetException.getCause() != null) {
|
||||
return unwrap(invocationTargetException.getCause());
|
||||
}
|
||||
if (throwable instanceof java.util.concurrent.CompletionException completionException && completionException.getCause() != null) {
|
||||
return unwrap(completionException.getCause());
|
||||
}
|
||||
if (throwable instanceof ExecutionException executionException && executionException.getCause() != null) {
|
||||
return unwrap(executionException.getCause());
|
||||
}
|
||||
return throwable;
|
||||
}
|
||||
|
||||
static Object invoke(Method method, Object target, Object... args) throws ReflectiveOperationException {
|
||||
return method.invoke(target, args);
|
||||
}
|
||||
|
||||
static Object invokeNamed(Object target, String methodName, Class<?>[] parameterTypes, Object... args) throws ReflectiveOperationException {
|
||||
Method method = target.getClass().getMethod(methodName, parameterTypes);
|
||||
return method.invoke(target, args);
|
||||
}
|
||||
|
||||
static Object read(Field field, Object target) throws IllegalAccessException {
|
||||
return field.get(target);
|
||||
}
|
||||
|
||||
static void stageRuntimeConfiguration(String worldName) throws ReflectiveOperationException {
|
||||
Object bukkitServer = Bukkit.getServer();
|
||||
if (bukkitServer == null) {
|
||||
throw new IllegalStateException("Bukkit server is unavailable.");
|
||||
}
|
||||
|
||||
Field configurationField = CapabilityResolution.resolveField(bukkitServer.getClass(), "configuration");
|
||||
Object rawConfiguration = configurationField.get(bukkitServer);
|
||||
if (!(rawConfiguration instanceof YamlConfiguration configuration)) {
|
||||
throw new IllegalStateException("CraftServer configuration field is unavailable.");
|
||||
}
|
||||
|
||||
ConfigurationSection worldsSection = configuration.getConfigurationSection("worlds");
|
||||
if (worldsSection == null) {
|
||||
worldsSection = configuration.createSection("worlds");
|
||||
}
|
||||
|
||||
ConfigurationSection worldSection = worldsSection.getConfigurationSection(worldName);
|
||||
if (worldSection == null) {
|
||||
worldSection = worldsSection.createSection(worldName);
|
||||
}
|
||||
|
||||
worldSection.set("generator", "Iris:runtime");
|
||||
}
|
||||
|
||||
static Object getRuntimeDatapackDimensions(CapabilitySnapshot capabilities) throws ReflectiveOperationException {
|
||||
Object worldLoaderContext = read(capabilities.worldLoaderContextField(), capabilities.minecraftServer());
|
||||
Method datapackDimensionsMethod = CapabilityResolution.resolveMethod(worldLoaderContext.getClass(), "datapackDimensions", method -> method.getParameterCount() == 0);
|
||||
if (datapackDimensionsMethod == null) {
|
||||
throw new IllegalStateException("DataLoadContext does not expose datapackDimensions().");
|
||||
}
|
||||
Object datapackDimensions = datapackDimensionsMethod.invoke(worldLoaderContext);
|
||||
if (datapackDimensions == null) {
|
||||
throw new IllegalStateException("DataLoadContext.datapackDimensions() returned null.");
|
||||
}
|
||||
return datapackDimensions;
|
||||
}
|
||||
|
||||
static Object getRuntimeServerRegistryAccess(CapabilitySnapshot capabilities) throws ReflectiveOperationException {
|
||||
Method registryAccessMethod = capabilities.serverRegistryAccessMethod();
|
||||
if (registryAccessMethod == null) {
|
||||
throw new IllegalStateException("MinecraftServer does not expose registryAccess().");
|
||||
}
|
||||
Object registryAccess = registryAccessMethod.invoke(capabilities.minecraftServer());
|
||||
if (registryAccess == null) {
|
||||
throw new IllegalStateException("MinecraftServer.registryAccess() returned null.");
|
||||
}
|
||||
return registryAccess;
|
||||
}
|
||||
|
||||
static Object getRuntimeLevelStemRegistry(CapabilitySnapshot capabilities) throws ReflectiveOperationException {
|
||||
Object datapackDimensions = getRuntimeDatapackDimensions(capabilities);
|
||||
Object levelStemRegistryKey = Class.forName("net.minecraft.core.registries.Registries")
|
||||
.getField("LEVEL_STEM")
|
||||
.get(null);
|
||||
Method lookupMethod = CapabilityResolution.resolveMethod(datapackDimensions.getClass(), "lookupOrThrow", method -> method.getParameterCount() == 1);
|
||||
if (lookupMethod == null) {
|
||||
throw new IllegalStateException("Registry access does not expose lookupOrThrow(...).");
|
||||
}
|
||||
return lookupMethod.invoke(datapackDimensions, levelStemRegistryKey);
|
||||
}
|
||||
|
||||
static Object createRuntimeLevelStemKey(String worldName) throws ReflectiveOperationException {
|
||||
String sanitized = worldName.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9/_-]", "_");
|
||||
String path = "runtime/" + sanitized;
|
||||
Identifier identifier = new Identifier("iris", path);
|
||||
Object rawIdentifier = Class.forName("net.minecraft.resources.Identifier")
|
||||
.getMethod("fromNamespaceAndPath", String.class, String.class)
|
||||
.invoke(null, identifier.namespace(), identifier.key());
|
||||
Object registryKey = Class.forName("net.minecraft.core.registries.Registries")
|
||||
.getField("LEVEL_STEM")
|
||||
.get(null);
|
||||
Method createMethod = Class.forName("net.minecraft.resources.ResourceKey")
|
||||
.getMethod("create", registryKey.getClass(), rawIdentifier.getClass());
|
||||
return createMethod.invoke(null, registryKey, rawIdentifier);
|
||||
}
|
||||
|
||||
static Object createDimensionKey(Object stemKey) throws ReflectiveOperationException {
|
||||
Class<?> resourceKeyClass = Class.forName("net.minecraft.resources.ResourceKey");
|
||||
Method identifierMethod = CapabilityResolution.resolveMethod(resourceKeyClass, "identifier", method -> method.getParameterCount() == 0);
|
||||
Object identifier = identifierMethod.invoke(stemKey);
|
||||
Object dimensionRegistryKey = Class.forName("net.minecraft.core.registries.Registries")
|
||||
.getField("DIMENSION")
|
||||
.get(null);
|
||||
Method createMethod = resourceKeyClass.getMethod("create", dimensionRegistryKey.getClass(), identifier.getClass());
|
||||
return createMethod.invoke(null, dimensionRegistryKey, identifier);
|
||||
}
|
||||
|
||||
static Object resolveRuntimeLevelStem(CapabilitySnapshot capabilities, WorldLifecycleRequest request) throws ReflectiveOperationException {
|
||||
return resolveRuntimeLevelStem(capabilities, request, INMS.get());
|
||||
}
|
||||
|
||||
static Object resolveRuntimeLevelStem(CapabilitySnapshot capabilities, WorldLifecycleRequest request, INMSBinding binding) throws ReflectiveOperationException {
|
||||
ChunkGenerator generator = request.generator();
|
||||
if (generator instanceof PlatformChunkGenerator) {
|
||||
Object registryAccess = getRuntimeServerRegistryAccess(capabilities);
|
||||
try {
|
||||
Object levelStem = binding.createRuntimeLevelStem(registryAccess, generator);
|
||||
if (levelStem == null) {
|
||||
throw new IllegalStateException("Iris NMS binding returned null runtime LevelStem.");
|
||||
}
|
||||
return levelStem;
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("Failed to create runtime LevelStem from full server registry access for world \"" + request.worldName() + "\".", unwrap(e));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Object levelStemRegistry = getRuntimeLevelStemRegistry(capabilities);
|
||||
Object overworldKey = Class.forName("net.minecraft.world.level.dimension.LevelStem")
|
||||
.getField("OVERWORLD")
|
||||
.get(null);
|
||||
Method getValueMethod = CapabilityResolution.resolveMethod(levelStemRegistry.getClass(), "getValue", method -> method.getParameterCount() == 1);
|
||||
if (getValueMethod != null) {
|
||||
Object resolved = getValueMethod.invoke(levelStemRegistry, overworldKey);
|
||||
if (resolved != null) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
Method getMethod = CapabilityResolution.resolveMethod(levelStemRegistry.getClass(), "get", method -> method.getParameterCount() == 1);
|
||||
if (getMethod == null) {
|
||||
throw new IllegalStateException("Unable to resolve OVERWORLD LevelStem from registry.");
|
||||
}
|
||||
Object raw = getMethod.invoke(levelStemRegistry, overworldKey);
|
||||
return extractRegistryValue(raw);
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("Failed to resolve fallback OVERWORLD LevelStem from datapack registry access for world \"" + request.worldName() + "\".", unwrap(e));
|
||||
}
|
||||
}
|
||||
|
||||
static String runtimeLevelStemRegistrySource(WorldLifecycleRequest request) {
|
||||
if (request.generator() instanceof PlatformChunkGenerator) {
|
||||
return "full_server_registry";
|
||||
}
|
||||
return "datapack_level_stem_registry";
|
||||
}
|
||||
|
||||
static Object extractRegistryValue(Object raw) throws ReflectiveOperationException {
|
||||
if (raw == null) {
|
||||
return null;
|
||||
}
|
||||
if (raw instanceof Optional<?> optional) {
|
||||
Object nested = optional.orElse(null);
|
||||
if (nested == null) {
|
||||
return null;
|
||||
}
|
||||
return extractRegistryValue(nested);
|
||||
}
|
||||
Method valueMethod = CapabilityResolution.resolveMethod(raw.getClass(), "value", method -> method.getParameterCount() == 0);
|
||||
if (valueMethod != null) {
|
||||
return valueMethod.invoke(raw);
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
static void applyWorldDataNameAndModInfo(CapabilitySnapshot capabilities, Object worldDataAndGenSettings, String worldName) throws ReflectiveOperationException {
|
||||
Method dataMethod = CapabilityResolution.resolveMethod(worldDataAndGenSettings.getClass(), "data", method -> method.getParameterCount() == 0);
|
||||
if (dataMethod == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object worldData = dataMethod.invoke(worldDataAndGenSettings);
|
||||
if (worldData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Method checkNameMethod = CapabilityResolution.resolveMethod(worldData.getClass(), "checkName", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 1 && String.class.equals(params[0]);
|
||||
});
|
||||
if (checkNameMethod != null) {
|
||||
checkNameMethod.invoke(worldData, worldName);
|
||||
}
|
||||
|
||||
Method getModdedStatusMethod = CapabilityResolution.resolveMethod(capabilities.minecraftServer().getClass(), "getModdedStatus", method -> method.getParameterCount() == 0);
|
||||
Method getServerModNameMethod = CapabilityResolution.resolveMethod(capabilities.minecraftServer().getClass(), "getServerModName", method -> method.getParameterCount() == 0);
|
||||
if (getModdedStatusMethod == null || getServerModNameMethod == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object modCheck = getModdedStatusMethod.invoke(capabilities.minecraftServer());
|
||||
Method shouldReportAsModifiedMethod = CapabilityResolution.resolveMethod(modCheck.getClass(), "shouldReportAsModified", method -> method.getParameterCount() == 0);
|
||||
Method setModdedInfoMethod = CapabilityResolution.resolveMethod(worldData.getClass(), "setModdedInfo", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 2 && String.class.equals(params[0]) && boolean.class.equals(params[1]);
|
||||
});
|
||||
if (shouldReportAsModifiedMethod == null || setModdedInfoMethod == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean modified = Boolean.TRUE.equals(shouldReportAsModifiedMethod.invoke(modCheck));
|
||||
String modName = (String) getServerModNameMethod.invoke(capabilities.minecraftServer());
|
||||
setModdedInfoMethod.invoke(worldData, modName, modified);
|
||||
}
|
||||
|
||||
static Object createCurrentWorldDataAndSettings(CapabilitySnapshot capabilities, String worldName) throws ReflectiveOperationException {
|
||||
Object settings = read(capabilities.settingsField(), capabilities.minecraftServer());
|
||||
Object worldLoaderContext = read(capabilities.worldLoaderContextField(), capabilities.minecraftServer());
|
||||
Object levelStemRegistry = getRuntimeLevelStemRegistry(capabilities);
|
||||
boolean demo = Boolean.TRUE.equals(capabilities.isDemoMethod().invoke(capabilities.minecraftServer()));
|
||||
Object options = read(capabilities.optionsField(), capabilities.minecraftServer());
|
||||
Method hasMethod = CapabilityResolution.resolveMethod(options.getClass(), "has", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 1 && String.class.equals(params[0]);
|
||||
});
|
||||
boolean bonusChest = hasMethod != null && Boolean.TRUE.equals(hasMethod.invoke(options, "bonusChest"));
|
||||
Object dataLoadOutput = capabilities.createNewWorldDataMethod().invoke(null, settings, worldLoaderContext, levelStemRegistry, demo, bonusChest);
|
||||
Method cookieMethod = CapabilityResolution.resolveMethod(dataLoadOutput.getClass(), "cookie", method -> method.getParameterCount() == 0);
|
||||
if (cookieMethod == null) {
|
||||
throw new IllegalStateException("WorldLoader.DataLoadOutput does not expose cookie().");
|
||||
}
|
||||
Object worldDataAndGenSettings = cookieMethod.invoke(dataLoadOutput);
|
||||
applyWorldDataNameAndModInfo(capabilities, worldDataAndGenSettings, worldName);
|
||||
return worldDataAndGenSettings;
|
||||
}
|
||||
|
||||
static Object createLegacyPrimaryLevelData(CapabilitySnapshot capabilities, Object levelStorageAccess, String worldName) throws ReflectiveOperationException {
|
||||
Object levelDataResult = capabilities.paperWorldDataMethod().invoke(null, levelStorageAccess);
|
||||
Method fatalErrorMethod = CapabilityResolution.resolveMethod(levelDataResult.getClass(), "fatalError", method -> method.getParameterCount() == 0);
|
||||
Method dataTagMethod = CapabilityResolution.resolveMethod(levelDataResult.getClass(), "dataTag", method -> method.getParameterCount() == 0);
|
||||
if (fatalErrorMethod != null && Boolean.TRUE.equals(fatalErrorMethod.invoke(levelDataResult))) {
|
||||
throw new IllegalStateException("Paper runtime world-data helper reported a fatal error for \"" + worldName + "\".");
|
||||
}
|
||||
if (dataTagMethod != null && dataTagMethod.invoke(levelDataResult) != null) {
|
||||
throw new IllegalStateException("Runtime world \"" + worldName + "\" already contains level data.");
|
||||
}
|
||||
|
||||
Object settings = read(capabilities.settingsField(), capabilities.minecraftServer());
|
||||
Object worldLoaderContext = read(capabilities.worldLoaderContextField(), capabilities.minecraftServer());
|
||||
Object levelStemRegistry = getRuntimeLevelStemRegistry(capabilities);
|
||||
boolean demo = Boolean.TRUE.equals(capabilities.isDemoMethod().invoke(capabilities.minecraftServer()));
|
||||
Object options = read(capabilities.optionsField(), capabilities.minecraftServer());
|
||||
Method hasMethod = CapabilityResolution.resolveMethod(options.getClass(), "has", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 1 && String.class.equals(params[0]);
|
||||
});
|
||||
boolean bonusChest = hasMethod != null && Boolean.TRUE.equals(hasMethod.invoke(options, "bonusChest"));
|
||||
Object dataLoadOutput = capabilities.createNewWorldDataMethod().invoke(null, settings, worldLoaderContext, levelStemRegistry, demo, bonusChest);
|
||||
Method cookieMethod = CapabilityResolution.resolveMethod(dataLoadOutput.getClass(), "cookie", method -> method.getParameterCount() == 0);
|
||||
if (cookieMethod == null) {
|
||||
throw new IllegalStateException("WorldLoader.DataLoadOutput does not expose cookie().");
|
||||
}
|
||||
Object primaryLevelData = cookieMethod.invoke(dataLoadOutput);
|
||||
|
||||
Method checkNameMethod = CapabilityResolution.resolveMethod(primaryLevelData.getClass(), "checkName", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 1 && String.class.equals(params[0]);
|
||||
});
|
||||
if (checkNameMethod != null) {
|
||||
checkNameMethod.invoke(primaryLevelData, worldName);
|
||||
}
|
||||
|
||||
Method getModdedStatusMethod = CapabilityResolution.resolveMethod(capabilities.minecraftServer().getClass(), "getModdedStatus", method -> method.getParameterCount() == 0);
|
||||
Method getServerModNameMethod = CapabilityResolution.resolveMethod(capabilities.minecraftServer().getClass(), "getServerModName", method -> method.getParameterCount() == 0);
|
||||
if (getModdedStatusMethod != null && getServerModNameMethod != null) {
|
||||
Object modCheck = getModdedStatusMethod.invoke(capabilities.minecraftServer());
|
||||
Method shouldReportAsModifiedMethod = CapabilityResolution.resolveMethod(modCheck.getClass(), "shouldReportAsModified", method -> method.getParameterCount() == 0);
|
||||
Method setModdedInfoMethod = CapabilityResolution.resolveMethod(primaryLevelData.getClass(), "setModdedInfo", method -> {
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
return params.length == 2 && String.class.equals(params[0]) && boolean.class.equals(params[1]);
|
||||
});
|
||||
if (shouldReportAsModifiedMethod != null && setModdedInfoMethod != null) {
|
||||
boolean modified = Boolean.TRUE.equals(shouldReportAsModifiedMethod.invoke(modCheck));
|
||||
String modName = (String) getServerModNameMethod.invoke(capabilities.minecraftServer());
|
||||
setModdedInfoMethod.invoke(primaryLevelData, modName, modified);
|
||||
}
|
||||
}
|
||||
|
||||
return primaryLevelData;
|
||||
}
|
||||
|
||||
static Object createLegacyStorageAccess(CapabilitySnapshot capabilities, String worldName) throws ReflectiveOperationException {
|
||||
Class<?> levelStorageSourceClass = Class.forName("net.minecraft.world.level.storage.LevelStorageSource");
|
||||
Method createDefaultMethod = levelStorageSourceClass.getMethod("createDefault", Path.class);
|
||||
Object levelStorageSource = createDefaultMethod.invoke(null, Bukkit.getWorldContainer().toPath());
|
||||
Method storageAccessMethod = capabilities.levelStorageAccessMethod();
|
||||
if (storageAccessMethod.getParameterCount() == 1) {
|
||||
return storageAccessMethod.invoke(levelStorageSource, worldName);
|
||||
}
|
||||
Object overworldStemKey = Class.forName("net.minecraft.world.level.dimension.LevelStem")
|
||||
.getField("OVERWORLD")
|
||||
.get(null);
|
||||
return storageAccessMethod.invoke(levelStorageSource, worldName, overworldStemKey);
|
||||
}
|
||||
|
||||
static void closeLevelStorageAccess(Object levelStorageAccess) {
|
||||
if (levelStorageAccess == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Method closeMethod = levelStorageAccess.getClass().getMethod("close");
|
||||
closeMethod.invoke(levelStorageAccess);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
static boolean unloadWorld(CapabilitySnapshot capabilities, World world, boolean save) {
|
||||
if (world == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CompletableFuture<Boolean> asyncUnload = unloadWorldViaAsyncApi(capabilities, world, save);
|
||||
if (asyncUnload != null) {
|
||||
return resolveAsyncUnload(asyncUnload);
|
||||
}
|
||||
|
||||
try {
|
||||
return Bukkit.unloadWorld(world, save);
|
||||
} catch (UnsupportedOperationException unsupported) {
|
||||
if (capabilities.minecraftServer() == null || capabilities.removeLevelMethod() == null) {
|
||||
throw unsupported;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (save) {
|
||||
world.save();
|
||||
}
|
||||
|
||||
Method getHandleMethod = world.getClass().getMethod("getHandle");
|
||||
Object serverLevel = getHandleMethod.invoke(world);
|
||||
closeServerLevel(world, serverLevel);
|
||||
detachServerLevel(capabilities, serverLevel, world.getName());
|
||||
return Bukkit.getWorld(world.getName()) == null;
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("Failed to unload world \"" + world.getName() + "\" through the selected world lifecycle backend.", unwrap(e));
|
||||
}
|
||||
}
|
||||
|
||||
private static CompletableFuture<Boolean> unloadWorldViaAsyncApi(CapabilitySnapshot capabilities, World world, boolean save) {
|
||||
if (capabilities.unloadWorldAsyncMethod() == null || capabilities.bukkitServer() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CompletableFuture<Boolean> callbackFuture = new CompletableFuture<>();
|
||||
Runnable invokeTask = () -> {
|
||||
Consumer<Boolean> callback = result -> callbackFuture.complete(Boolean.TRUE.equals(result));
|
||||
try {
|
||||
capabilities.unloadWorldAsyncMethod().invoke(capabilities.bukkitServer(), world, save, callback);
|
||||
} catch (Throwable e) {
|
||||
callbackFuture.completeExceptionally(unwrap(e));
|
||||
}
|
||||
};
|
||||
|
||||
if (J.isFolia() && !isGlobalTickThread()) {
|
||||
CompletableFuture<Void> scheduled = J.sfut(invokeTask);
|
||||
if (scheduled == null) {
|
||||
callbackFuture.completeExceptionally(new IllegalStateException("Failed to schedule global unload task."));
|
||||
return callbackFuture;
|
||||
}
|
||||
scheduled.whenComplete((unused, throwable) -> {
|
||||
if (throwable != null) {
|
||||
callbackFuture.completeExceptionally(unwrap(throwable));
|
||||
}
|
||||
});
|
||||
return callbackFuture;
|
||||
}
|
||||
|
||||
invokeTask.run();
|
||||
return callbackFuture;
|
||||
}
|
||||
|
||||
private static boolean resolveAsyncUnload(CompletableFuture<Boolean> asyncUnload) {
|
||||
if (J.isPrimaryThread()) {
|
||||
if (!asyncUnload.isDone()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
return Boolean.TRUE.equals(asyncUnload.join());
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("Failed to consume async world unload result.", unwrap(e));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return Boolean.TRUE.equals(asyncUnload.get(120, TimeUnit.SECONDS));
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("Failed while waiting for async world unload result.", unwrap(e));
|
||||
}
|
||||
}
|
||||
|
||||
private static void closeServerLevel(World world, Object serverLevel) throws Throwable {
|
||||
Method closeMethod = CapabilityResolution.resolveMethod(serverLevel.getClass(), "close", method -> method.getParameterCount() == 0);
|
||||
if (closeMethod == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!J.isFolia()) {
|
||||
closeMethod.invoke(serverLevel);
|
||||
return;
|
||||
}
|
||||
|
||||
Location spawn = world.getSpawnLocation();
|
||||
int chunkX = spawn == null ? 0 : spawn.getBlockX() >> 4;
|
||||
int chunkZ = spawn == null ? 0 : spawn.getBlockZ() >> 4;
|
||||
CompletableFuture<Void> closeFuture = new CompletableFuture<>();
|
||||
boolean scheduled = J.runRegion(world, chunkX, chunkZ, () -> {
|
||||
try {
|
||||
closeMethod.invoke(serverLevel);
|
||||
closeFuture.complete(null);
|
||||
} catch (Throwable e) {
|
||||
closeFuture.completeExceptionally(unwrap(e));
|
||||
}
|
||||
});
|
||||
if (!scheduled) {
|
||||
throw new IllegalStateException("Failed to schedule region close task for world \"" + world.getName() + "\".");
|
||||
}
|
||||
closeFuture.get(90, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
private static void removeWorldFromCraftServerMap(String worldName) throws ReflectiveOperationException {
|
||||
Object bukkitServer = Bukkit.getServer();
|
||||
if (bukkitServer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Field worldsField = CapabilityResolution.resolveField(bukkitServer.getClass(), "worlds");
|
||||
Object rawWorlds = worldsField.get(bukkitServer);
|
||||
if (rawWorlds instanceof Map map) {
|
||||
map.remove(worldName);
|
||||
map.remove(worldName.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
|
||||
private static void detachServerLevel(CapabilitySnapshot capabilities, Object serverLevel, String worldName) throws Throwable {
|
||||
Runnable detachTask = () -> {
|
||||
try {
|
||||
capabilities.removeLevelMethod().invoke(capabilities.minecraftServer(), serverLevel);
|
||||
removeWorldFromCraftServerMap(worldName);
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
|
||||
if (!J.isFolia() || isGlobalTickThread()) {
|
||||
detachTask.run();
|
||||
return;
|
||||
}
|
||||
|
||||
CompletableFuture<Void> detachFuture = J.sfut(detachTask);
|
||||
if (detachFuture == null) {
|
||||
throw new IllegalStateException("Failed to schedule global detach task for world \"" + worldName + "\".");
|
||||
}
|
||||
detachFuture.get(15, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
static boolean isGlobalTickThread() {
|
||||
Object server = Bukkit.getServer();
|
||||
if (server == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Method method = server.getClass().getMethod("isGlobalTickThread");
|
||||
return Boolean.TRUE.equals(method.invoke(server));
|
||||
} catch (Throwable ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
final class WorldsProviderBackend implements WorldLifecycleBackend {
|
||||
private final CapabilitySnapshot capabilities;
|
||||
|
||||
WorldsProviderBackend(CapabilitySnapshot capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(WorldLifecycleRequest request, CapabilitySnapshot capabilities) {
|
||||
return request.studio() && capabilities.hasWorldsProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public CompletableFuture<World> create(WorldLifecycleRequest request) {
|
||||
try {
|
||||
Path worldPath = new File(Bukkit.getWorldContainer(), request.worldName()).toPath();
|
||||
Object builder = WorldLifecycleSupport.invokeNamed(capabilities.worldsProvider(), "levelBuilder", new Class[]{Path.class}, worldPath);
|
||||
builder = WorldLifecycleSupport.invokeNamed(builder, "name", new Class[]{String.class}, request.worldName());
|
||||
builder = WorldLifecycleSupport.invokeNamed(builder, "seed", new Class[]{long.class}, request.seed());
|
||||
builder = WorldLifecycleSupport.invokeNamed(builder, "levelStem", new Class[]{capabilities.worldsLevelStemClass()}, resolveLevelStem(request.environment()));
|
||||
builder = WorldLifecycleSupport.invokeNamed(builder, "chunkGenerator", new Class[]{org.bukkit.generator.ChunkGenerator.class}, request.generator());
|
||||
builder = WorldLifecycleSupport.invokeNamed(builder, "biomeProvider", new Class[]{org.bukkit.generator.BiomeProvider.class}, request.biomeProvider());
|
||||
builder = WorldLifecycleSupport.invokeNamed(builder, "generatorType", new Class[]{capabilities.worldsGeneratorTypeClass()}, resolveGeneratorType(request.worldType()));
|
||||
builder = WorldLifecycleSupport.invokeNamed(builder, "structures", new Class[]{boolean.class}, request.generateStructures());
|
||||
builder = WorldLifecycleSupport.invokeNamed(builder, "hardcore", new Class[]{boolean.class}, request.hardcore());
|
||||
Object levelBuilder = WorldLifecycleSupport.invokeNamed(builder, "build", new Class[0]);
|
||||
Object async = WorldLifecycleSupport.invokeNamed(levelBuilder, "createAsync", new Class[0]);
|
||||
if (async instanceof CompletableFuture<?> future) {
|
||||
return future.thenApply(world -> (World) world);
|
||||
}
|
||||
return CompletableFuture.failedFuture(new IllegalStateException("Worlds provider createAsync did not return CompletableFuture."));
|
||||
} catch (Throwable e) {
|
||||
return CompletableFuture.failedFuture(WorldLifecycleSupport.unwrap(e));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unload(World world, boolean save) {
|
||||
return WorldLifecycleSupport.unloadWorld(capabilities, world, save);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String backendName() {
|
||||
return "worlds_provider";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describeSelectionReason() {
|
||||
return "external Worlds provider is registered and healthy";
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
private Object resolveLevelStem(World.Environment environment) {
|
||||
String key;
|
||||
if (environment == World.Environment.NETHER) {
|
||||
key = "NETHER";
|
||||
} else if (environment == World.Environment.THE_END) {
|
||||
key = "END";
|
||||
} else {
|
||||
key = "OVERWORLD";
|
||||
}
|
||||
Class<? extends Enum> enumClass = capabilities.worldsLevelStemClass().asSubclass(Enum.class);
|
||||
return Enum.valueOf(enumClass, key);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
private Object resolveGeneratorType(WorldType worldType) {
|
||||
String typeName = worldType == null ? "NORMAL" : worldType.getName();
|
||||
String key;
|
||||
if ("FLAT".equalsIgnoreCase(typeName)) {
|
||||
key = "FLAT";
|
||||
} else if ("AMPLIFIED".equalsIgnoreCase(typeName)) {
|
||||
key = "AMPLIFIED";
|
||||
} else if ("LARGE_BIOMES".equalsIgnoreCase(typeName) || "LARGEBIOMES".equalsIgnoreCase(typeName)) {
|
||||
key = "LARGE_BIOMES";
|
||||
} else {
|
||||
key = "NORMAL";
|
||||
}
|
||||
Class<? extends Enum> enumClass = capabilities.worldsGeneratorTypeClass().asSubclass(Enum.class);
|
||||
return Enum.valueOf(enumClass, key.toUpperCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
//package com.volmit.iris.core.link;
|
||||
//package art.arcane.iris.core.link;
|
||||
//
|
||||
//import com.jojodmo.customitems.api.CustomItemsAPI;
|
||||
//import com.jojodmo.customitems.item.custom.CustomItem;
|
||||
//import com.jojodmo.customitems.item.custom.block.CustomMushroomBlock;
|
||||
//import com.jojodmo.customitems.version.SafeMaterial;
|
||||
//import com.volmit.iris.util.collection.KList;
|
||||
//import com.volmit.iris.util.reflect.WrappedField;
|
||||
//import com.volmit.iris.util.reflect.WrappedReturningMethod;
|
||||
//import art.arcane.volmlib.util.collection.KList;
|
||||
//import art.arcane.iris.util.common.reflect.WrappedField;
|
||||
//import art.arcane.iris.util.common.reflect.WrappedReturningMethod;
|
||||
//import org.bukkit.block.BlockFace;
|
||||
//import org.bukkit.block.data.BlockData;
|
||||
//import org.bukkit.block.data.MultipleFacing;
|
||||
@@ -0,0 +1,170 @@
|
||||
package art.arcane.iris.core.link;
|
||||
|
||||
import art.arcane.iris.core.link.data.DataType;
|
||||
import art.arcane.iris.core.nms.container.BiomeColor;
|
||||
import art.arcane.iris.core.nms.container.BlockProperty;
|
||||
import art.arcane.iris.core.nms.container.Pair;
|
||||
import art.arcane.iris.engine.data.cache.Cache;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.MissingResourceException;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public abstract class ExternalDataProvider implements Listener {
|
||||
|
||||
@NonNull
|
||||
private final String pluginId;
|
||||
|
||||
@Nullable
|
||||
public Plugin getPlugin() {
|
||||
return Bukkit.getPluginManager().getPlugin(pluginId);
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return getPlugin() != null && getPlugin().isEnabled();
|
||||
}
|
||||
|
||||
public abstract void init();
|
||||
|
||||
/**
|
||||
* @see ExternalDataProvider#getBlockData(Identifier, KMap)
|
||||
*/
|
||||
@NotNull
|
||||
public BlockData getBlockData(@NotNull Identifier blockId) throws MissingResourceException {
|
||||
return getBlockData(blockId, new KMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a {@link BlockData} corresponding to the blockID
|
||||
* it is used in any place Iris accepts {@link BlockData}
|
||||
*
|
||||
* @param blockId The id of the block to get
|
||||
* @param state The state of the block to get
|
||||
* @return Corresponding {@link BlockData} to the blockId
|
||||
* may return {@link IrisCustomData} for blocks that need a world for placement
|
||||
* @throws MissingResourceException when the blockId is invalid
|
||||
*/
|
||||
@NotNull
|
||||
public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap<String, String> state) throws MissingResourceException {
|
||||
throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of all {@link BlockProperty} objects associated with the specified block identifier.
|
||||
*
|
||||
* @param blockId The identifier of the block whose properties are to be retrieved. Must not be null.
|
||||
* @return A list of {@link BlockProperty} objects representing the properties of the block.
|
||||
* @throws MissingResourceException If the specified block identifier is invalid or cannot be found.
|
||||
*/
|
||||
@NotNull
|
||||
public List<BlockProperty> getBlockProperties(@NotNull Identifier blockId) throws MissingResourceException {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ExternalDataProvider#getItemStack(Identifier)
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getItemStack(@NotNull Identifier itemId) throws MissingResourceException {
|
||||
return getItemStack(itemId, new KMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a {@link ItemStack} corresponding to the itemID
|
||||
* it is used in loot tables
|
||||
*
|
||||
* @param itemId The id of the item to get
|
||||
* @param customNbt Custom nbt to apply to the item
|
||||
* @return Corresponding {@link ItemStack} to the itemId
|
||||
* @throws MissingResourceException when the itemId is invalid
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap<String, Object> customNbt) throws MissingResourceException {
|
||||
throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used for placing blocks that need to use the plugins api
|
||||
* it will only be called when the {@link ExternalDataProvider#getBlockData(Identifier, KMap)} returned a {@link IrisCustomData}
|
||||
*
|
||||
* @param engine The engine of the world the block is being placed in
|
||||
* @param block The block where the block should be placed
|
||||
* @param blockId The blockId to place
|
||||
*/
|
||||
public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) {}
|
||||
|
||||
/**
|
||||
* Spawns a mob in the specified location using the given engine and entity identifier.
|
||||
*
|
||||
* @param location The location in the world where the mob should spawn. Must not be null.
|
||||
* @param entityId The identifier of the mob entity to spawn. Must not be null.
|
||||
* @return The spawned {@link Entity} if successful, or null if the mob could not be spawned.
|
||||
*/
|
||||
@Nullable
|
||||
public Entity spawnMob(@NotNull Location location, @NotNull Identifier entityId) throws MissingResourceException {
|
||||
throw new MissingResourceException("Failed to find Entity!", entityId.namespace(), entityId.key());
|
||||
}
|
||||
|
||||
public abstract @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType);
|
||||
|
||||
public abstract boolean isValidProvider(@NotNull Identifier id, DataType dataType);
|
||||
|
||||
protected static Pair<Float, BlockFace> parseYawAndFace(@NotNull Engine engine, @NotNull Block block, @NotNull KMap<@NotNull String, @NotNull String> state) {
|
||||
float yaw = 0;
|
||||
BlockFace face = BlockFace.NORTH;
|
||||
|
||||
long seed = engine.getSeedManager().getSeed() + Cache.key(block.getX(), block.getZ()) + block.getY();
|
||||
RNG rng = new RNG(seed);
|
||||
if ("true".equals(state.get("randomYaw"))) {
|
||||
yaw = rng.f(0, 360);
|
||||
} else if (state.containsKey("yaw")) {
|
||||
yaw = Float.parseFloat(state.get("yaw"));
|
||||
}
|
||||
if ("true".equals(state.get("randomFace"))) {
|
||||
BlockFace[] faces = BlockFace.values();
|
||||
face = faces[rng.i(0, faces.length - 1)];
|
||||
} else if (state.containsKey("face")) {
|
||||
face = BlockFace.valueOf(state.get("face").toUpperCase());
|
||||
}
|
||||
if (face == BlockFace.SELF) {
|
||||
face = BlockFace.NORTH;
|
||||
}
|
||||
|
||||
return new Pair<>(yaw, face);
|
||||
}
|
||||
|
||||
protected static List<BlockProperty> YAW_FACE_BIOME_PROPERTIES = List.of(
|
||||
BlockProperty.ofEnum(BiomeColor.class, "matchBiome", null),
|
||||
BlockProperty.ofBoolean("randomYaw", false),
|
||||
BlockProperty.ofDouble("yaw", 0, 0, 360f, false, true),
|
||||
BlockProperty.ofBoolean("randomFace", true),
|
||||
new BlockProperty(
|
||||
"face",
|
||||
BlockFace.class,
|
||||
BlockFace.NORTH,
|
||||
Arrays.asList(BlockFace.values()).subList(0, BlockFace.values().length - 1),
|
||||
BlockFace::name
|
||||
)
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.volmit.iris.core.link;
|
||||
package art.arcane.iris.core.link;
|
||||
|
||||
import org.bukkit.NamespacedKey;
|
||||
|
||||
+5
-5
@@ -16,12 +16,12 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.link;
|
||||
package art.arcane.iris.core.link;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core.link;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.mvplugins.multiverse.core.MultiverseCoreApi;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.options.ImportWorldOptions;
|
||||
|
||||
public class MultiverseCoreLink {
|
||||
private final boolean active;
|
||||
|
||||
public MultiverseCoreLink() {
|
||||
active = Bukkit.getPluginManager().getPlugin("Multiverse-Core") != null;
|
||||
}
|
||||
|
||||
public void removeFromConfig(World world) {
|
||||
removeFromConfig(world.getName());
|
||||
}
|
||||
|
||||
public void removeFromConfig(String world) {
|
||||
if (!active) return;
|
||||
var manager = worldManager();
|
||||
manager.removeWorld(world).onSuccess(manager::saveWorldsConfig);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void updateWorld(World bukkitWorld, String pack) {
|
||||
if (!active) return;
|
||||
var generator = "Iris:" + pack;
|
||||
var manager = worldManager();
|
||||
var world = manager.getWorld(bukkitWorld).getOrElse(() -> {
|
||||
var options = ImportWorldOptions.worldName(bukkitWorld.getName())
|
||||
.generator(generator)
|
||||
.environment(bukkitWorld.getEnvironment())
|
||||
.useSpawnAdjust(false);
|
||||
return manager.importWorld(options).get();
|
||||
});
|
||||
|
||||
world.setAutoLoad(false);
|
||||
if (!generator.equals(world.getGenerator())) {
|
||||
var field = MultiverseWorld.class.getDeclaredField("worldConfig");
|
||||
field.setAccessible(true);
|
||||
|
||||
var config = field.get(world);
|
||||
config.getClass()
|
||||
.getDeclaredMethod("setGenerator", String.class)
|
||||
.invoke(config, generator);
|
||||
}
|
||||
|
||||
manager.saveWorldsConfig();
|
||||
}
|
||||
|
||||
private WorldManager worldManager() {
|
||||
var api = MultiverseCoreApi.get();
|
||||
return api.getWorldManager();
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -1,9 +1,9 @@
|
||||
package com.volmit.iris.core.link;
|
||||
package art.arcane.iris.core.link;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.util.data.Cuboid;
|
||||
import com.volmit.iris.util.data.KCache;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
import art.arcane.volmlib.util.data.Cuboid;
|
||||
import art.arcane.volmlib.util.data.KCache;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -47,6 +47,7 @@ public class WorldEditLink {
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Could not get selection");
|
||||
e.printStackTrace();
|
||||
Iris.reportError(e);
|
||||
active.reset();
|
||||
active.aquire(() -> false);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package art.arcane.iris.core.link.data;
|
||||
|
||||
import art.arcane.iris.core.link.ExternalDataProvider;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.core.nms.container.BlockProperty;
|
||||
import art.arcane.iris.core.service.ExternalDataSVC;
|
||||
import art.arcane.iris.engine.data.cache.Cache;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
|
||||
import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture;
|
||||
import net.momirealms.craftengine.bukkit.api.CraftEngineItems;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.properties.BooleanProperty;
|
||||
import net.momirealms.craftengine.core.block.properties.IntegerProperty;
|
||||
import net.momirealms.craftengine.core.block.properties.Property;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class CraftEngineDataProvider extends ExternalDataProvider {
|
||||
private static final BlockProperty[] FURNITURE_PROPERTIES = new BlockProperty[]{
|
||||
BlockProperty.ofBoolean("randomYaw", false),
|
||||
BlockProperty.ofDouble("yaw", 0, 0, 360f, false, true),
|
||||
BlockProperty.ofBoolean("randomPitch", false),
|
||||
BlockProperty.ofDouble("pitch", 0, 0, 360f, false, true),
|
||||
};
|
||||
|
||||
public CraftEngineDataProvider() {
|
||||
super("CraftEngine");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<BlockProperty> getBlockProperties(@NotNull Identifier blockId) throws MissingResourceException {
|
||||
Key key = Key.of(blockId.namespace(), blockId.key());
|
||||
net.momirealms.craftengine.core.block.CustomBlock block = CraftEngineBlocks.byId(key);
|
||||
if (block != null) {
|
||||
return block.properties().stream().map(CraftEngineDataProvider::convert).toList();
|
||||
}
|
||||
|
||||
net.momirealms.craftengine.core.entity.furniture.CustomFurniture furniture = CraftEngineFurniture.byId(key);
|
||||
if (furniture != null) {
|
||||
BlockProperty[] properties = Arrays.copyOf(FURNITURE_PROPERTIES, 5);
|
||||
properties[4] = new BlockProperty(
|
||||
"variant",
|
||||
String.class,
|
||||
furniture.anyVariantName(),
|
||||
furniture.variants().keySet(),
|
||||
Function.identity()
|
||||
);
|
||||
return List.of(properties);
|
||||
}
|
||||
|
||||
throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap<String, Object> customNbt) throws MissingResourceException {
|
||||
net.momirealms.craftengine.core.item.CustomItem<ItemStack> item = CraftEngineItems.byId(Key.of(itemId.namespace(), itemId.key()));
|
||||
if (item == null) {
|
||||
throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key());
|
||||
}
|
||||
|
||||
return item.buildItemStack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap<String, String> state) throws MissingResourceException {
|
||||
Key key = Key.of(blockId.namespace(), blockId.key());
|
||||
if (CraftEngineBlocks.byId(key) == null && CraftEngineFurniture.byId(key) == null) {
|
||||
throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key());
|
||||
}
|
||||
|
||||
return IrisCustomData.of(B.getAir(), ExternalDataSVC.buildState(blockId, state));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) {
|
||||
art.arcane.iris.core.nms.container.Pair<Identifier, KMap<String, String>> statePair = ExternalDataSVC.parseState(blockId);
|
||||
Identifier baseBlockId = statePair.getA();
|
||||
KMap<String, String> state = statePair.getB();
|
||||
Key key = Key.of(baseBlockId.namespace(), baseBlockId.key());
|
||||
|
||||
net.momirealms.craftengine.core.block.CustomBlock customBlock = CraftEngineBlocks.byId(key);
|
||||
if (customBlock != null) {
|
||||
ImmutableBlockState blockState = customBlock.defaultState();
|
||||
|
||||
for (Map.Entry<String, String> entry : state.entrySet()) {
|
||||
Property<?> property = customBlock.getProperty(entry.getKey());
|
||||
if (property == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Comparable<?> tag = property.optional(entry.getValue()).orElse(null);
|
||||
if (tag == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
blockState = ImmutableBlockState.with(blockState, property, tag);
|
||||
}
|
||||
|
||||
CraftEngineBlocks.place(block.getLocation(), blockState, false);
|
||||
return;
|
||||
}
|
||||
|
||||
net.momirealms.craftengine.core.entity.furniture.CustomFurniture furniture = CraftEngineFurniture.byId(key);
|
||||
if (furniture == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location location = parseYawAndPitch(engine, block, state);
|
||||
String variant = state.getOrDefault("variant", furniture.anyVariantName());
|
||||
CraftEngineFurniture.place(location, furniture, variant, false);
|
||||
}
|
||||
|
||||
private static Location parseYawAndPitch(@NotNull Engine engine, @NotNull Block block, @NotNull Map<String, String> state) {
|
||||
Location location = block.getLocation();
|
||||
long seed = engine.getSeedManager().getSeed() + Cache.key(block.getX(), block.getZ()) + block.getY();
|
||||
RNG rng = new RNG(seed);
|
||||
|
||||
if ("true".equals(state.get("randomYaw"))) {
|
||||
location.setYaw(rng.f(0, 360));
|
||||
} else if (state.containsKey("yaw")) {
|
||||
location.setYaw(Float.parseFloat(state.get("yaw")));
|
||||
}
|
||||
|
||||
if ("true".equals(state.get("randomPitch"))) {
|
||||
location.setPitch(rng.f(0, 360));
|
||||
} else if (state.containsKey("pitch")) {
|
||||
location.setPitch(Float.parseFloat(state.get("pitch")));
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) {
|
||||
Stream<Key> keys = switch (dataType) {
|
||||
case ENTITY -> Stream.<Key>empty();
|
||||
case ITEM -> CraftEngineItems.loadedItems().keySet().stream();
|
||||
case BLOCK -> Stream.concat(CraftEngineBlocks.loadedBlocks().keySet().stream(),
|
||||
CraftEngineFurniture.loadedFurniture().keySet().stream());
|
||||
};
|
||||
return keys.map(key -> new Identifier(key.namespace(), key.value())).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidProvider(@NotNull Identifier id, DataType dataType) {
|
||||
Key key = Key.of(id.namespace(), id.key());
|
||||
return switch (dataType) {
|
||||
case ENTITY -> false;
|
||||
case ITEM -> CraftEngineItems.byId(key) != null;
|
||||
case BLOCK -> CraftEngineBlocks.byId(key) != null || CraftEngineFurniture.byId(key) != null;
|
||||
};
|
||||
}
|
||||
|
||||
private static <T extends Comparable<T>> BlockProperty convert(Property<T> raw) {
|
||||
return switch (raw) {
|
||||
case BooleanProperty property -> BlockProperty.ofBoolean(property.name(), property.defaultValue());
|
||||
case IntegerProperty property -> BlockProperty.ofLong(property.name(), property.defaultValue(), property.min, property.max, false, false);
|
||||
default -> new BlockProperty(raw.name(), raw.valueClass(), raw.defaultValue(), raw.possibleValues(), raw::valueName);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package art.arcane.iris.core.link.data;
|
||||
|
||||
import art.arcane.iris.core.link.ExternalDataProvider;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public enum DataType implements BiPredicate<ExternalDataProvider, Identifier> {
|
||||
ITEM,
|
||||
BLOCK,
|
||||
ENTITY;
|
||||
|
||||
@Override
|
||||
public boolean test(ExternalDataProvider dataProvider, Identifier identifier) {
|
||||
if (!dataProvider.isValidProvider(identifier, this)) return false;
|
||||
try {
|
||||
switch (this) {
|
||||
case ITEM -> dataProvider.getItemStack(identifier);
|
||||
case BLOCK -> dataProvider.getBlockData(identifier);
|
||||
case ENTITY -> {}
|
||||
}
|
||||
return true;
|
||||
} catch (MissingResourceException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Predicate<Identifier> asPredicate(ExternalDataProvider dataProvider) {
|
||||
return i -> test(dataProvider, i);
|
||||
}
|
||||
}
|
||||
+17
-33
@@ -1,16 +1,18 @@
|
||||
package com.volmit.iris.core.link;
|
||||
package art.arcane.iris.core.link.data;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.reflect.WrappedField;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.link.ExternalDataProvider;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.iris.util.common.reflect.WrappedField;
|
||||
import com.willfp.ecoitems.items.EcoItem;
|
||||
import com.willfp.ecoitems.items.EcoItems;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.MissingResourceException;
|
||||
|
||||
public class EcoItemsDataProvider extends ExternalDataProvider {
|
||||
@@ -34,12 +36,6 @@ public class EcoItemsDataProvider extends ExternalDataProvider {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap<String, String> state) throws MissingResourceException {
|
||||
throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap<String, Object> customNbt) throws MissingResourceException {
|
||||
@@ -48,30 +44,18 @@ public class EcoItemsDataProvider extends ExternalDataProvider {
|
||||
return itemStack.get(item).clone();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Identifier[] getBlockTypes() {
|
||||
return new Identifier[0];
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Identifier[] getItemTypes() {
|
||||
KList<Identifier> names = new KList<>();
|
||||
for (EcoItem item : EcoItems.INSTANCE.values()) {
|
||||
try {
|
||||
Identifier key = Identifier.fromNamespacedKey(id.get(item));
|
||||
if (getItemStack(key) != null)
|
||||
names.add(key);
|
||||
} catch (MissingResourceException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return names.toArray(new Identifier[0]);
|
||||
public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) {
|
||||
if (dataType != DataType.ITEM) return List.of();
|
||||
return EcoItems.INSTANCE.values()
|
||||
.stream()
|
||||
.map(x -> Identifier.fromNamespacedKey(id.get(x)))
|
||||
.filter(dataType.asPredicate(this))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidProvider(@NotNull Identifier id, boolean isItem) {
|
||||
return id.namespace().equalsIgnoreCase("ecoitems") && isItem;
|
||||
public boolean isValidProvider(@NotNull Identifier id, DataType dataType) {
|
||||
return id.namespace().equalsIgnoreCase("ecoitems") && dataType == DataType.ITEM;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package art.arcane.iris.core.link.data;
|
||||
|
||||
import com.ssomar.score.api.executableitems.ExecutableItemsAPI;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.link.ExternalDataProvider;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ExecutableItemsDataProvider extends ExternalDataProvider {
|
||||
public ExecutableItemsDataProvider() {
|
||||
super("ExecutableItems");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
Iris.info("Setting up ExecutableItems Link...");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap<String, Object> customNbt) throws MissingResourceException {
|
||||
return ExecutableItemsAPI.getExecutableItemsManager().getExecutableItem(itemId.key())
|
||||
.map(item -> item.buildItem(1, Optional.empty()))
|
||||
.orElseThrow(() -> new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) {
|
||||
if (dataType != DataType.ITEM) return List.of();
|
||||
return ExecutableItemsAPI.getExecutableItemsManager()
|
||||
.getExecutableItemIdsList()
|
||||
.stream()
|
||||
.map(name -> new Identifier("executable_items", name))
|
||||
.filter(dataType.asPredicate(this))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidProvider(@NotNull Identifier key, DataType dataType) {
|
||||
return key.namespace().equalsIgnoreCase("executable_items") && dataType == DataType.ITEM;
|
||||
}
|
||||
}
|
||||
+24
-42
@@ -1,14 +1,15 @@
|
||||
package com.volmit.iris.core.link;
|
||||
package art.arcane.iris.core.link.data;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.service.ExternalDataSVC;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.data.IrisCustomData;
|
||||
import com.volmit.iris.util.reflect.WrappedField;
|
||||
import com.volmit.iris.util.reflect.WrappedReturningMethod;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.link.ExternalDataProvider;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.core.service.ExternalDataSVC;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||
import art.arcane.iris.util.common.reflect.WrappedField;
|
||||
import art.arcane.iris.util.common.reflect.WrappedReturningMethod;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
@@ -18,6 +19,8 @@ import org.bukkit.block.data.type.Leaves;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.function.Supplier;
|
||||
@@ -64,7 +67,7 @@ public class HMCLeavesDataProvider extends ExternalDataProvider {
|
||||
BlockData blockData = Bukkit.createBlockData(material);
|
||||
if (IrisSettings.get().getGenerator().preventLeafDecay && blockData instanceof Leaves leaves)
|
||||
leaves.setPersistent(true);
|
||||
return new IrisCustomData(blockData, ExternalDataSVC.buildState(blockId, state));
|
||||
return IrisCustomData.of(blockData, ExternalDataSVC.buildState(blockId, state));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -89,41 +92,20 @@ public class HMCLeavesDataProvider extends ExternalDataProvider {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Identifier[] getBlockTypes() {
|
||||
KList<Identifier> names = new KList<>();
|
||||
for (String name : blockDataMap.keySet()) {
|
||||
try {
|
||||
Identifier key = new Identifier("hmcleaves", name);
|
||||
if (getBlockData(key) != null)
|
||||
names.add(key);
|
||||
} catch (MissingResourceException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return names.toArray(new Identifier[0]);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Identifier[] getItemTypes() {
|
||||
KList<Identifier> names = new KList<>();
|
||||
for (String name : itemDataField.keySet()) {
|
||||
try {
|
||||
Identifier key = new Identifier("hmcleaves", name);
|
||||
if (getItemStack(key) != null)
|
||||
names.add(key);
|
||||
} catch (MissingResourceException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return names.toArray(new Identifier[0]);
|
||||
public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) {
|
||||
if (dataType == DataType.ENTITY) return List.of();
|
||||
return (dataType == DataType.BLOCK ? blockDataMap.keySet() : itemDataField.keySet())
|
||||
.stream()
|
||||
.map(x -> new Identifier("hmcleaves", x))
|
||||
.filter(dataType.asPredicate(this))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidProvider(@NotNull Identifier id, boolean isItem) {
|
||||
return (isItem ? itemDataField.keySet() : blockDataMap.keySet()).contains(id.key());
|
||||
public boolean isValidProvider(@NotNull Identifier id, DataType dataType) {
|
||||
if (dataType == DataType.ENTITY) return false;
|
||||
return (dataType == DataType.ITEM ? itemDataField.keySet() : blockDataMap.keySet()).contains(id.key());
|
||||
}
|
||||
|
||||
private <C, T> Map<String, T> getMap(C config, String name) {
|
||||
@@ -0,0 +1,107 @@
|
||||
package art.arcane.iris.core.link.data;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.link.ExternalDataProvider;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||
import dev.lone.itemsadder.api.CustomBlock;
|
||||
import dev.lone.itemsadder.api.CustomStack;
|
||||
import dev.lone.itemsadder.api.Events.ItemsAdderLoadDataEvent;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ItemAdderDataProvider extends ExternalDataProvider {
|
||||
|
||||
private volatile Set<String> itemNamespaces = Set.of();
|
||||
private volatile Set<String> blockNamespaces = Set.of();
|
||||
|
||||
public ItemAdderDataProvider() {
|
||||
super("ItemsAdder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
updateNamespaces();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onLoadData(ItemsAdderLoadDataEvent event) {
|
||||
updateNamespaces();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap<String, String> state) throws MissingResourceException {
|
||||
CustomBlock block = CustomBlock.getInstance(blockId.toString());
|
||||
if (block == null) {
|
||||
throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key());
|
||||
}
|
||||
return IrisCustomData.of(block.getBaseBlockData(), blockId);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap<String, Object> customNbt) throws MissingResourceException {
|
||||
CustomStack stack = CustomStack.getInstance(itemId.toString());
|
||||
if (stack == null) {
|
||||
throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key());
|
||||
}
|
||||
return stack.getItemStack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) {
|
||||
CustomBlock custom;
|
||||
if ((custom = CustomBlock.place(blockId.toString(), block.getLocation())) == null)
|
||||
return;
|
||||
block.setBlockData(custom.getBaseBlockData(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) {
|
||||
return switch (dataType) {
|
||||
case ENTITY -> List.of();
|
||||
case ITEM -> CustomStack.getNamespacedIdsInRegistry()
|
||||
.stream()
|
||||
.map(Identifier::fromString)
|
||||
.toList();
|
||||
case BLOCK -> CustomBlock.getNamespacedIdsInRegistry()
|
||||
.stream()
|
||||
.map(Identifier::fromString)
|
||||
.toList();
|
||||
};
|
||||
}
|
||||
|
||||
private void updateNamespaces() {
|
||||
try {
|
||||
updateNamespaces(DataType.ITEM);
|
||||
updateNamespaces(DataType.BLOCK);
|
||||
} catch (Throwable e) {
|
||||
Iris.warn("Failed to update ItemAdder namespaces: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNamespaces(DataType dataType) {
|
||||
var namespaces = getTypes(dataType).stream().map(Identifier::namespace).collect(Collectors.toSet());
|
||||
if (dataType == DataType.ITEM) itemNamespaces = namespaces;
|
||||
else blockNamespaces = namespaces;
|
||||
Iris.debug("Updated ItemAdder namespaces: " + dataType + " - " + namespaces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidProvider(@NotNull Identifier id, DataType dataType) {
|
||||
if (dataType == DataType.ENTITY) return false;
|
||||
return dataType == DataType.ITEM ? itemNamespaces.contains(id.namespace()) : blockNamespaces.contains(id.namespace());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package art.arcane.iris.core.link.data;
|
||||
|
||||
import art.arcane.iris.core.link.ExternalDataProvider;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.core.service.ExternalDataSVC;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||
import me.kryniowesegryderiusz.kgenerators.Main;
|
||||
import me.kryniowesegryderiusz.kgenerators.api.KGeneratorsAPI;
|
||||
import me.kryniowesegryderiusz.kgenerators.generators.locations.objects.GeneratorLocation;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.MissingResourceException;
|
||||
|
||||
public class KGeneratorsDataProvider extends ExternalDataProvider {
|
||||
public KGeneratorsDataProvider() {
|
||||
super("KGenerators");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap<String, String> state) throws MissingResourceException {
|
||||
if (Main.getGenerators().get(blockId.key()) == null) throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key());
|
||||
return IrisCustomData.of(Material.STRUCTURE_VOID.createBlockData(), ExternalDataSVC.buildState(blockId, state));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap<String, Object> customNbt) throws MissingResourceException {
|
||||
var gen = Main.getGenerators().get(itemId.key());
|
||||
if (gen == null) throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key());
|
||||
return gen.getGeneratorItem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) {
|
||||
if (block.getType() != Material.STRUCTURE_VOID) return;
|
||||
var existing = KGeneratorsAPI.getLoadedGeneratorLocation(block.getLocation());
|
||||
if (existing != null) return;
|
||||
block.setBlockData(B.getAir(), false);
|
||||
var gen = Main.getGenerators().get(blockId.key());
|
||||
if (gen == null) return;
|
||||
var loc = new GeneratorLocation(-1, gen, block.getLocation(), Main.getPlacedGenerators().getChunkInfo(block.getChunk()), null, null);
|
||||
Main.getDatabases().getDb().saveGenerator(loc);
|
||||
Main.getPlacedGenerators().addLoaded(loc);
|
||||
Main.getSchedules().schedule(loc, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) {
|
||||
if (dataType == DataType.ENTITY) return List.of();
|
||||
return Main.getGenerators().getAll().stream()
|
||||
.map(gen -> new Identifier("kgenerators", gen.getId()))
|
||||
.filter(dataType.asPredicate(this))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidProvider(@NotNull Identifier id, DataType dataType) {
|
||||
if (dataType == DataType.ENTITY) return false;
|
||||
return "kgenerators".equalsIgnoreCase(id.namespace());
|
||||
}
|
||||
}
|
||||
+31
-45
@@ -1,21 +1,24 @@
|
||||
package com.volmit.iris.core.link;
|
||||
package art.arcane.iris.core.link.data;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.link.ExternalDataProvider;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import net.Indyuce.mmoitems.MMOItems;
|
||||
import net.Indyuce.mmoitems.api.ItemTier;
|
||||
import net.Indyuce.mmoitems.api.Type;
|
||||
import net.Indyuce.mmoitems.api.block.CustomBlock;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class MMOItemsDataProvider extends ExternalDataProvider {
|
||||
|
||||
@@ -85,52 +88,35 @@ public class MMOItemsDataProvider extends ExternalDataProvider {
|
||||
return item;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Identifier[] getBlockTypes() {
|
||||
KList<Identifier> names = new KList<>();
|
||||
for (Integer id : api().getCustomBlocks().getBlockIds()) {
|
||||
try {
|
||||
Identifier key = new Identifier("mmoitems", String.valueOf(id));
|
||||
if (getBlockData(key) != null)
|
||||
names.add(key);
|
||||
} catch (MissingResourceException ignored) {
|
||||
}
|
||||
}
|
||||
return names.toArray(new Identifier[0]);
|
||||
}
|
||||
public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) {
|
||||
return switch (dataType) {
|
||||
case ENTITY -> List.of();
|
||||
case BLOCK -> api().getCustomBlocks().getBlockIds().stream().map(id -> new Identifier("mmoitems", String.valueOf(id)))
|
||||
.filter(dataType.asPredicate(this))
|
||||
.toList();
|
||||
case ITEM -> {
|
||||
Supplier<Collection<Identifier>> supplier = () -> api().getTypes()
|
||||
.getAll()
|
||||
.stream()
|
||||
.flatMap(type -> api()
|
||||
.getTemplates()
|
||||
.getTemplateNames(type)
|
||||
.stream()
|
||||
.map(name -> new Identifier("mmoitems_" + type.getId(), name)))
|
||||
.filter(dataType.asPredicate(this))
|
||||
.toList();
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Identifier[] getItemTypes() {
|
||||
KList<Identifier> names = new KList<>();
|
||||
Runnable run = () -> {
|
||||
for (Type type : api().getTypes().getAll()) {
|
||||
for (String name : api().getTemplates().getTemplateNames(type)) {
|
||||
try {
|
||||
Identifier key = new Identifier("mmoitems_" + type.getId(), name);
|
||||
if (getItemStack(key) != null)
|
||||
names.add(key);
|
||||
} catch (MissingResourceException ignored) {
|
||||
}
|
||||
}
|
||||
if (Bukkit.isPrimaryThread()) yield supplier.get();
|
||||
else yield J.sfut(supplier).join();
|
||||
}
|
||||
};
|
||||
if (Bukkit.isPrimaryThread()) run.run();
|
||||
else {
|
||||
try {
|
||||
J.sfut(run).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
Iris.error("Failed getting MMOItems item types!");
|
||||
Iris.reportError(e);
|
||||
}
|
||||
}
|
||||
return names.toArray(new Identifier[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidProvider(@NotNull Identifier id, boolean isItem) {
|
||||
return isItem ? id.namespace().split("_", 2).length == 2 : id.namespace().equals("mmoitems");
|
||||
public boolean isValidProvider(@NotNull Identifier id, DataType dataType) {
|
||||
if (dataType == DataType.ENTITY) return false;
|
||||
return dataType == DataType.ITEM ? id.namespace().split("_", 2).length == 2 : id.namespace().equals("mmoitems");
|
||||
}
|
||||
|
||||
private MMOItems api() {
|
||||
+41
-68
@@ -16,19 +16,19 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.link;
|
||||
package art.arcane.iris.core.link.data;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.service.ExternalDataSVC;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.data.B;
|
||||
import com.volmit.iris.util.data.IrisCustomData;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.link.ExternalDataProvider;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.iris.core.nms.container.BiomeColor;
|
||||
import art.arcane.iris.core.nms.container.BlockProperty;
|
||||
import art.arcane.iris.core.service.ExternalDataSVC;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||
import io.lumine.mythic.bukkit.BukkitAdapter;
|
||||
import io.lumine.mythic.bukkit.utils.serialize.Chroma;
|
||||
import io.lumine.mythiccrucible.MythicCrucible;
|
||||
@@ -37,11 +37,12 @@ import io.lumine.mythiccrucible.items.ItemManager;
|
||||
import io.lumine.mythiccrucible.items.blocks.CustomBlockItemContext;
|
||||
import io.lumine.mythiccrucible.items.furniture.FurnitureItemContext;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -71,13 +72,26 @@ public class MythicCrucibleDataProvider extends ExternalDataProvider {
|
||||
CustomBlockItemContext blockItemContext = crucibleItem.getBlockData();
|
||||
FurnitureItemContext furnitureItemContext = crucibleItem.getFurnitureData();
|
||||
if (furnitureItemContext != null) {
|
||||
return new IrisCustomData(B.getAir(), ExternalDataSVC.buildState(blockId, state));
|
||||
return IrisCustomData.of(B.getAir(), ExternalDataSVC.buildState(blockId, state));
|
||||
} else if (blockItemContext != null) {
|
||||
return blockItemContext.getBlockData();
|
||||
}
|
||||
throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<BlockProperty> getBlockProperties(@NotNull Identifier blockId) throws MissingResourceException {
|
||||
CrucibleItem crucibleItem = this.itemManager.getItem(blockId.key())
|
||||
.orElseThrow(() -> new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()));
|
||||
|
||||
if (crucibleItem.getFurnitureData() != null) {
|
||||
return YAW_FACE_BIOME_PROPERTIES;
|
||||
} else if (crucibleItem.getBlockData() != null) {
|
||||
return List.of();
|
||||
}
|
||||
throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap<String, Object> customNbt) throws MissingResourceException {
|
||||
@@ -88,69 +102,27 @@ public class MythicCrucibleDataProvider extends ExternalDataProvider {
|
||||
.generateItemStack(1));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Identifier[] getBlockTypes() {
|
||||
KList<Identifier> names = new KList<>();
|
||||
for (CrucibleItem item : this.itemManager.getItems()) {
|
||||
if (item.getBlockData() == null) continue;
|
||||
try {
|
||||
Identifier key = new Identifier("crucible", item.getInternalName());
|
||||
if (getBlockData(key) != null) {
|
||||
Iris.info("getBlockTypes: Block loaded '" + item.getInternalName() + "'");
|
||||
names.add(key);
|
||||
}
|
||||
} catch (MissingResourceException ignored) {}
|
||||
}
|
||||
return names.toArray(new Identifier[0]);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Identifier[] getItemTypes() {
|
||||
KList<Identifier> names = new KList<>();
|
||||
for (CrucibleItem item : this.itemManager.getItems()) {
|
||||
try {
|
||||
Identifier key = new Identifier("crucible", item.getInternalName());
|
||||
if (getItemStack(key) != null) {
|
||||
Iris.info("getItemTypes: Item loaded '" + item.getInternalName() + "'");
|
||||
names.add(key);
|
||||
}
|
||||
} catch (MissingResourceException ignored) {}
|
||||
}
|
||||
return names.toArray(new Identifier[0]);
|
||||
public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) {
|
||||
return itemManager.getItems()
|
||||
.stream()
|
||||
.map(i -> new Identifier("crucible", i.getInternalName()))
|
||||
.filter(dataType.asPredicate(this))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) {
|
||||
var pair = ExternalDataSVC.parseState(blockId);
|
||||
var state = pair.getB();
|
||||
blockId = pair.getA();
|
||||
var parsedState = ExternalDataSVC.parseState(blockId);
|
||||
var state = parsedState.getB();
|
||||
blockId = parsedState.getA();
|
||||
|
||||
Optional<CrucibleItem> item = itemManager.getItem(blockId.key());
|
||||
if (item.isEmpty()) return;
|
||||
FurnitureItemContext furniture = item.get().getFurnitureData();
|
||||
if (furniture == null) return;
|
||||
|
||||
float yaw = 0;
|
||||
BlockFace face = BlockFace.NORTH;
|
||||
long seed = engine.getSeedManager().getSeed() + Cache.key(block.getX(), block.getZ()) + block.getY();
|
||||
RNG rng = new RNG(seed);
|
||||
if ("true".equals(state.get("randomYaw"))) {
|
||||
yaw = rng.f(0, 360);
|
||||
} else if (state.containsKey("yaw")) {
|
||||
yaw = Float.parseFloat(state.get("yaw"));
|
||||
}
|
||||
if ("true".equals(state.get("randomFace"))) {
|
||||
BlockFace[] faces = BlockFace.values();
|
||||
face = faces[rng.i(0, faces.length - 1)];
|
||||
} else if (state.containsKey("face")) {
|
||||
face = BlockFace.valueOf(state.get("face").toUpperCase());
|
||||
}
|
||||
if (face == BlockFace.SELF) {
|
||||
face = BlockFace.NORTH;
|
||||
}
|
||||
|
||||
var pair = parseYawAndFace(engine, block, state);
|
||||
BiomeColor type = null;
|
||||
Chroma color = null;
|
||||
try {
|
||||
@@ -161,11 +133,12 @@ public class MythicCrucibleDataProvider extends ExternalDataProvider {
|
||||
if (biomeColor == null) return;
|
||||
color = Chroma.of(biomeColor.getRGB());
|
||||
}
|
||||
furniture.place(block, face, yaw, color);
|
||||
furniture.place(block, pair.getB(), pair.getA(), color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidProvider(@NotNull Identifier key, boolean isItem) {
|
||||
public boolean isValidProvider(@NotNull Identifier key, DataType dataType) {
|
||||
if (dataType == DataType.ENTITY) return false;
|
||||
return key.namespace().equalsIgnoreCase("crucible");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package art.arcane.iris.core.link.data;
|
||||
|
||||
import art.arcane.iris.core.link.ExternalDataProvider;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import io.lumine.mythic.api.adapters.AbstractLocation;
|
||||
import io.lumine.mythic.api.config.MythicLineConfig;
|
||||
import io.lumine.mythic.api.mobs.entities.SpawnReason;
|
||||
import io.lumine.mythic.api.skills.conditions.ILocationCondition;
|
||||
import io.lumine.mythic.bukkit.BukkitAdapter;
|
||||
import io.lumine.mythic.bukkit.MythicBukkit;
|
||||
import io.lumine.mythic.bukkit.adapters.BukkitWorld;
|
||||
import io.lumine.mythic.bukkit.events.MythicConditionLoadEvent;
|
||||
import io.lumine.mythic.core.mobs.ActiveMob;
|
||||
import io.lumine.mythic.core.mobs.MobStack;
|
||||
import io.lumine.mythic.core.skills.SkillCondition;
|
||||
import io.lumine.mythic.core.utils.annotations.MythicCondition;
|
||||
import io.lumine.mythic.core.utils.annotations.MythicField;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MythicMobsDataProvider extends ExternalDataProvider {
|
||||
public MythicMobsDataProvider() {
|
||||
super("MythicMobs");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Entity spawnMob(@NotNull Location location, @NotNull Identifier entityId) throws MissingResourceException {
|
||||
var mm = spawnMob(BukkitAdapter.adapt(location), entityId);
|
||||
return mm == null ? null : mm.getEntity().getBukkitEntity();
|
||||
}
|
||||
|
||||
private ActiveMob spawnMob(AbstractLocation location, Identifier entityId) throws MissingResourceException {
|
||||
var manager = MythicBukkit.inst().getMobManager();
|
||||
var mm = manager.getMythicMob(entityId.key()).orElse(null);
|
||||
if (mm == null) {
|
||||
var stack = manager.getMythicMobStack(entityId.key());
|
||||
if (stack == null) throw new MissingResourceException("Failed to find Mob!", entityId.namespace(), entityId.key());
|
||||
return stack.spawn(location, 1d, SpawnReason.OTHER, null);
|
||||
}
|
||||
return mm.spawn(location, 1d, SpawnReason.OTHER, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) {
|
||||
if (dataType != DataType.ENTITY) return List.of();
|
||||
var manager = MythicBukkit.inst().getMobManager();
|
||||
return Stream.concat(manager.getMobNames().stream(),
|
||||
manager.getMobStacks()
|
||||
.stream()
|
||||
.map(MobStack::getName)
|
||||
)
|
||||
.distinct()
|
||||
.map(name -> new Identifier("mythicmobs", name))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidProvider(@NotNull Identifier id, DataType dataType) {
|
||||
return id.namespace().equalsIgnoreCase("mythicmobs") && dataType == DataType.ENTITY;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(MythicConditionLoadEvent event) {
|
||||
switch (event.getConditionName()) {
|
||||
case "irisbiome" -> event.register(new IrisBiomeCondition(event.getConditionName(), event.getConfig()));
|
||||
case "irisregion" -> event.register(new IrisRegionCondition(event.getConditionName(), event.getConfig()));
|
||||
}
|
||||
}
|
||||
|
||||
@MythicCondition(author = "CrazyDev22", name = "irisbiome", description = "Tests if the target is within the given list of biomes")
|
||||
public static class IrisBiomeCondition extends SkillCondition implements ILocationCondition {
|
||||
@MythicField(name = "biome", aliases = {"b"}, description = "A list of biomes to check")
|
||||
private Set<String> biomes = ConcurrentHashMap.newKeySet();
|
||||
@MythicField(name = "surface", aliases = {"s"}, description = "If the biome check should only be performed on the surface")
|
||||
private boolean surface;
|
||||
|
||||
public IrisBiomeCondition(String line, MythicLineConfig mlc) {
|
||||
super(line);
|
||||
String b = mlc.getString(new String[]{"biome", "b"}, "");
|
||||
biomes.addAll(Arrays.asList(b.split(",")));
|
||||
surface = mlc.getBoolean(new String[]{"surface", "s"}, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(AbstractLocation target) {
|
||||
var access = IrisToolbelt.access(((BukkitWorld) target.getWorld()).getBukkitWorld());
|
||||
if (access == null) return false;
|
||||
var engine = access.getEngine();
|
||||
if (engine == null) return false;
|
||||
var biome = surface ?
|
||||
engine.getSurfaceBiome(target.getBlockX(), target.getBlockZ()) :
|
||||
engine.getBiomeOrMantle(target.getBlockX(), target.getBlockY() - engine.getMinHeight(), target.getBlockZ());
|
||||
return biomes.contains(biome.getLoadKey());
|
||||
}
|
||||
}
|
||||
|
||||
@MythicCondition(author = "CrazyDev22", name = "irisregion", description = "Tests if the target is within the given list of biomes")
|
||||
public static class IrisRegionCondition extends SkillCondition implements ILocationCondition {
|
||||
@MythicField(name = "region", aliases = {"r"}, description = "A list of regions to check")
|
||||
private Set<String> regions = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public IrisRegionCondition(String line, MythicLineConfig mlc) {
|
||||
super(line);
|
||||
String b = mlc.getString(new String[]{"region", "r"}, "");
|
||||
regions.addAll(Arrays.asList(b.split(",")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(AbstractLocation target) {
|
||||
var access = IrisToolbelt.access(((BukkitWorld) target.getWorld()).getBukkitWorld());
|
||||
if (access == null) return false;
|
||||
var engine = access.getEngine();
|
||||
if (engine == null) return false;
|
||||
var region = engine.getRegion(target.getBlockX(), target.getBlockZ());
|
||||
return regions.contains(region.getLoadKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
-76
@@ -1,34 +1,34 @@
|
||||
package com.volmit.iris.core.link;
|
||||
package art.arcane.iris.core.link.data;
|
||||
|
||||
import com.nexomc.nexo.api.NexoBlocks;
|
||||
import com.nexomc.nexo.api.NexoFurniture;
|
||||
import com.nexomc.nexo.api.NexoItems;
|
||||
import com.nexomc.nexo.items.ItemBuilder;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.service.ExternalDataSVC;
|
||||
import com.volmit.iris.engine.data.cache.Cache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.data.B;
|
||||
import com.volmit.iris.util.data.IrisCustomData;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import art.arcane.iris.core.link.ExternalDataProvider;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.iris.core.nms.container.BiomeColor;
|
||||
import art.arcane.iris.core.nms.container.BlockProperty;
|
||||
import art.arcane.iris.core.service.ExternalDataSVC;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.ItemDisplay;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.LeatherArmorMeta;
|
||||
import org.bukkit.inventory.meta.MapMeta;
|
||||
import org.bukkit.inventory.meta.PotionMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class NexoDataProvider extends ExternalDataProvider {
|
||||
private final AtomicBoolean failed = new AtomicBoolean(false);
|
||||
|
||||
public NexoDataProvider() {
|
||||
super("Nexo");
|
||||
}
|
||||
@@ -49,14 +49,23 @@ public class NexoDataProvider extends ExternalDataProvider {
|
||||
BlockData data = NexoBlocks.blockData(blockId.key());
|
||||
if (data == null)
|
||||
throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key());
|
||||
return new IrisCustomData(data, blockState);
|
||||
return IrisCustomData.of(data, blockState);
|
||||
} else if (NexoFurniture.isFurniture(blockId.key())) {
|
||||
return new IrisCustomData(B.getAir(), blockState);
|
||||
return IrisCustomData.of(B.getAir(), blockState);
|
||||
}
|
||||
|
||||
throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<BlockProperty> getBlockProperties(@NotNull Identifier blockId) throws MissingResourceException {
|
||||
if (!NexoItems.exists(blockId.key())) {
|
||||
throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key());
|
||||
}
|
||||
|
||||
return NexoFurniture.isFurniture(blockId.key()) ? YAW_FACE_BIOME_PROPERTIES : List.of();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap<String, Object> customNbt) throws MissingResourceException {
|
||||
@@ -64,14 +73,19 @@ public class NexoDataProvider extends ExternalDataProvider {
|
||||
if (builder == null) {
|
||||
throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key());
|
||||
}
|
||||
return builder.build();
|
||||
try {
|
||||
return builder.build();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) {
|
||||
var pair = ExternalDataSVC.parseState(blockId);
|
||||
var state = pair.getB();
|
||||
blockId = pair.getA();
|
||||
var statePair = ExternalDataSVC.parseState(blockId);
|
||||
var state = statePair.getB();
|
||||
blockId = statePair.getA();
|
||||
|
||||
if (NexoBlocks.isCustomBlock(blockId.key())) {
|
||||
NexoBlocks.place(blockId.key(), block.getLocation());
|
||||
@@ -81,26 +95,8 @@ public class NexoDataProvider extends ExternalDataProvider {
|
||||
if (!NexoFurniture.isFurniture(blockId.key()))
|
||||
return;
|
||||
|
||||
float yaw = 0;
|
||||
BlockFace face = BlockFace.NORTH;
|
||||
|
||||
long seed = engine.getSeedManager().getSeed() + Cache.key(block.getX(), block.getZ()) + block.getY();
|
||||
RNG rng = new RNG(seed);
|
||||
if ("true".equals(state.get("randomYaw"))) {
|
||||
yaw = rng.f(0, 360);
|
||||
} else if (state.containsKey("yaw")) {
|
||||
yaw = Float.parseFloat(state.get("yaw"));
|
||||
}
|
||||
if ("true".equals(state.get("randomFace"))) {
|
||||
BlockFace[] faces = BlockFace.values();
|
||||
face = faces[rng.i(0, faces.length - 1)];
|
||||
} else if (state.containsKey("face")) {
|
||||
face = BlockFace.valueOf(state.get("face").toUpperCase());
|
||||
}
|
||||
if (face == BlockFace.SELF) {
|
||||
face = BlockFace.NORTH;
|
||||
}
|
||||
ItemDisplay display = NexoFurniture.place(blockId.key(), block.getLocation(), yaw, face);
|
||||
var pair = parseYawAndFace(engine, block, state);
|
||||
ItemDisplay display = NexoFurniture.place(blockId.key(), block.getLocation(), pair.getA(), pair.getB());
|
||||
if (display == null) return;
|
||||
ItemStack itemStack = display.getItemStack();
|
||||
if (itemStack == null) return;
|
||||
@@ -114,51 +110,31 @@ public class NexoDataProvider extends ExternalDataProvider {
|
||||
var biomeColor = INMS.get().getBiomeColor(block.getLocation(), type);
|
||||
if (biomeColor == null) return;
|
||||
var potionColor = Color.fromARGB(biomeColor.getAlpha(), biomeColor.getRed(), biomeColor.getGreen(), biomeColor.getBlue());
|
||||
if (itemStack.getItemMeta() instanceof PotionMeta meta) {
|
||||
meta.setColor(potionColor);
|
||||
itemStack.setItemMeta(meta);
|
||||
var meta = itemStack.getItemMeta();
|
||||
switch (meta) {
|
||||
case LeatherArmorMeta armor -> armor.setColor(potionColor);
|
||||
case PotionMeta potion -> potion.setColor(potionColor);
|
||||
case MapMeta map -> map.setColor(potionColor);
|
||||
case null, default -> {}
|
||||
}
|
||||
itemStack.setItemMeta(meta);
|
||||
}
|
||||
display.setItemStack(itemStack);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Identifier[] getBlockTypes() {
|
||||
return NexoItems.itemNames().stream()
|
||||
public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) {
|
||||
if (dataType == DataType.ENTITY) return List.of();
|
||||
return NexoItems.itemNames()
|
||||
.stream()
|
||||
.map(i -> new Identifier("nexo", i))
|
||||
.filter(i -> {
|
||||
try {
|
||||
return getBlockData(i) != null;
|
||||
} catch (MissingResourceException e) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.toArray(Identifier[]::new);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Identifier[] getItemTypes() {
|
||||
return NexoItems.itemNames().stream()
|
||||
.map(i -> new Identifier("nexo", i))
|
||||
.filter(i -> {
|
||||
try {
|
||||
return getItemStack(i) != null;
|
||||
} catch (MissingResourceException e) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.toArray(Identifier[]::new);
|
||||
.filter(dataType.asPredicate(this))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidProvider(@NotNull Identifier id, boolean isItem) {
|
||||
public boolean isValidProvider(@NotNull Identifier id, DataType dataType) {
|
||||
if (dataType == DataType.ENTITY) return false;
|
||||
return "nexo".equalsIgnoreCase(id.namespace());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return super.isReady() && !failed.get();
|
||||
}
|
||||
}
|
||||
+56
-14
@@ -16,19 +16,21 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.loader;
|
||||
package art.arcane.iris.core.loader;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.engine.object.IrisImage;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KSet;
|
||||
import com.volmit.iris.util.data.KCache;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.engine.object.IrisImage;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import art.arcane.volmlib.util.data.KCache;
|
||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class ImageResourceLoader extends ResourceLoader<IrisImage> {
|
||||
@@ -67,12 +69,28 @@ public class ImageResourceLoader extends ResourceLoader<IrisImage> {
|
||||
}
|
||||
}
|
||||
|
||||
void getPNGFiles(File directory, Set<String> m) {
|
||||
for (File file : directory.listFiles()) {
|
||||
void getPNGFiles(File directory, Set<String> m, HashSet<String> visitedDirectories) {
|
||||
if (directory == null || !directory.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (directory.isDirectory()) {
|
||||
String canonicalDirectory = toCanonicalPath(directory);
|
||||
if (canonicalDirectory != null && !visitedDirectories.add(canonicalDirectory)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
File[] listedFiles = directory.listFiles();
|
||||
if (listedFiles == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (File file : listedFiles) {
|
||||
if (file.isFile() && file.getName().endsWith(".png")) {
|
||||
m.add(file.getName().replaceAll("\\Q.png\\E", ""));
|
||||
} else if (file.isDirectory()) {
|
||||
getPNGFiles(file, m);
|
||||
getPNGFiles(file, m, visitedDirectories);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,10 +103,11 @@ public class ImageResourceLoader extends ResourceLoader<IrisImage> {
|
||||
|
||||
Iris.debug("Building " + resourceTypeName + " Possibility Lists");
|
||||
KSet<String> m = new KSet<>();
|
||||
HashSet<String> visitedDirectories = new HashSet<>();
|
||||
|
||||
|
||||
for (File i : getFolders()) {
|
||||
getPNGFiles(i, m);
|
||||
getPNGFiles(i, m, visitedDirectories);
|
||||
}
|
||||
|
||||
// for (File i : getFolders()) {
|
||||
@@ -116,7 +135,23 @@ public class ImageResourceLoader extends ResourceLoader<IrisImage> {
|
||||
return possibleKeys;
|
||||
}
|
||||
|
||||
private String toCanonicalPath(File file) {
|
||||
try {
|
||||
return file.getCanonicalPath();
|
||||
} catch (IOException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public File findFile(String name) {
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (name.equals("null")) {
|
||||
Iris.warn("Refusing " + resourceTypeName + " lookup for literal string \"null\" (called by " + callerHint() + ")");
|
||||
return null;
|
||||
}
|
||||
|
||||
for (File i : getFolders(name)) {
|
||||
for (File j : i.listFiles()) {
|
||||
if (j.isFile() && j.getName().endsWith(".png") && j.getName().split("\\Q.\\E")[0].equals(name)) {
|
||||
@@ -131,7 +166,7 @@ public class ImageResourceLoader extends ResourceLoader<IrisImage> {
|
||||
}
|
||||
}
|
||||
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name);
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name + " (called by " + callerHint() + ")");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -155,12 +190,19 @@ public class ImageResourceLoader extends ResourceLoader<IrisImage> {
|
||||
}
|
||||
}
|
||||
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name);
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name + " (called by " + callerHint() + ")");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IrisImage load(String name, boolean warn) {
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (name.equals("null") && warn) {
|
||||
Iris.warn("Refusing " + resourceTypeName + " load for literal string \"null\" (called by " + callerHint() + ")");
|
||||
return null;
|
||||
}
|
||||
return loadCache.get(name);
|
||||
}
|
||||
}
|
||||
+141
-148
@@ -16,36 +16,38 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.loader;
|
||||
package art.arcane.iris.core.loader;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
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.*;
|
||||
import com.volmit.iris.engine.object.annotations.Snippet;
|
||||
import com.volmit.iris.engine.object.matter.IrisMatterObject;
|
||||
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.format.C;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.parallel.BurstExecutor;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.reflect.OldEnum;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.*;
|
||||
import art.arcane.iris.engine.object.annotations.Snippet;
|
||||
import art.arcane.iris.engine.object.matter.IrisMatterObject;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.mantle.flag.MantleFlagAdapter;
|
||||
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.iris.util.common.parallel.BurstExecutor;
|
||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
||||
import art.arcane.iris.util.common.reflect.KeyedType;
|
||||
import art.arcane.volmlib.util.scheduling.ChronoLatch;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import lombok.Data;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
@Data
|
||||
public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
@@ -58,9 +60,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
private ResourceLoader<IrisRegion> regionLoader;
|
||||
private ResourceLoader<IrisDimension> dimensionLoader;
|
||||
private ResourceLoader<IrisGenerator> generatorLoader;
|
||||
private ResourceLoader<IrisJigsawPiece> jigsawPieceLoader;
|
||||
private ResourceLoader<IrisJigsawPool> jigsawPoolLoader;
|
||||
private ResourceLoader<IrisJigsawStructure> jigsawStructureLoader;
|
||||
private ResourceLoader<IrisEntity> entityLoader;
|
||||
private ResourceLoader<IrisMarker> markerLoader;
|
||||
private ResourceLoader<IrisSpawner> spawnerLoader;
|
||||
@@ -70,9 +69,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
private ResourceLoader<IrisObject> objectLoader;
|
||||
private ResourceLoader<IrisMatterObject> matterLoader;
|
||||
private ResourceLoader<IrisImage> imageLoader;
|
||||
private ResourceLoader<IrisScript> scriptLoader;
|
||||
private ResourceLoader<IrisCave> caveLoader;
|
||||
private ResourceLoader<IrisRavine> ravineLoader;
|
||||
private ResourceLoader<IrisMatterObject> matterObjectLoader;
|
||||
private KMap<String, KList<String>> possibleSnippets;
|
||||
private Gson gson;
|
||||
@@ -92,8 +88,12 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
return dataLoaders.computeIfAbsent(dataFolder, IrisData::new);
|
||||
}
|
||||
|
||||
public static Optional<IrisData> getLoaded(File dataFolder) {
|
||||
return Optional.ofNullable(dataLoaders.get(dataFolder));
|
||||
}
|
||||
|
||||
public static void dereference() {
|
||||
dataLoaders.v().forEach(IrisData::cleanupEngine);
|
||||
dataLoaders.values().forEach(IrisData::cleanupEngine);
|
||||
}
|
||||
|
||||
public static int cacheSize() {
|
||||
@@ -111,92 +111,76 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
Iris.warn(" " + rl.getResourceTypeName() + " @ /" + rl.getFolderName() + ": Cache=" + rl.getLoadCache().getSize() + " Folders=" + rl.getFolders().size());
|
||||
}
|
||||
|
||||
public static IrisObject loadAnyObject(String key) {
|
||||
return loadAny(key, (dm) -> dm.getObjectLoader().load(key, false));
|
||||
public static IrisObject loadAnyObject(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisObject.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisMatterObject loadAnyMatter(String key) {
|
||||
return loadAny(key, (dm) -> dm.getMatterLoader().load(key, false));
|
||||
public static IrisMatterObject loadAnyMatter(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisMatterObject.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisBiome loadAnyBiome(String key) {
|
||||
return loadAny(key, (dm) -> dm.getBiomeLoader().load(key, false));
|
||||
public static IrisBiome loadAnyBiome(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisBiome.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisExpression loadAnyExpression(String key) {
|
||||
return loadAny(key, (dm) -> dm.getExpressionLoader().load(key, false));
|
||||
public static IrisExpression loadAnyExpression(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisExpression.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisMod loadAnyMod(String key) {
|
||||
return loadAny(key, (dm) -> dm.getModLoader().load(key, false));
|
||||
public static IrisMod loadAnyMod(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisMod.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisJigsawPiece loadAnyJigsawPiece(String key) {
|
||||
return loadAny(key, (dm) -> dm.getJigsawPieceLoader().load(key, false));
|
||||
public static IrisEntity loadAnyEntity(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisEntity.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisJigsawPool loadAnyJigsawPool(String key) {
|
||||
return loadAny(key, (dm) -> dm.getJigsawPoolLoader().load(key, false));
|
||||
public static IrisLootTable loadAnyLootTable(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisLootTable.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisEntity loadAnyEntity(String key) {
|
||||
return loadAny(key, (dm) -> dm.getEntityLoader().load(key, false));
|
||||
public static IrisBlockData loadAnyBlock(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisBlockData.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisLootTable loadAnyLootTable(String key) {
|
||||
return loadAny(key, (dm) -> dm.getLootLoader().load(key, false));
|
||||
public static IrisSpawner loadAnySpaner(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisSpawner.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisBlockData loadAnyBlock(String key) {
|
||||
return loadAny(key, (dm) -> dm.getBlockLoader().load(key, false));
|
||||
public static IrisRegion loadAnyRegion(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisRegion.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisSpawner loadAnySpaner(String key) {
|
||||
return loadAny(key, (dm) -> dm.getSpawnerLoader().load(key, false));
|
||||
public static IrisMarker loadAnyMarker(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisMarker.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisScript loadAnyScript(String key) {
|
||||
return loadAny(key, (dm) -> dm.getScriptLoader().load(key, false));
|
||||
public static IrisImage loadAnyImage(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisImage.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisRavine loadAnyRavine(String key) {
|
||||
return loadAny(key, (dm) -> dm.getRavineLoader().load(key, false));
|
||||
public static IrisDimension loadAnyDimension(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisDimension.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisRegion loadAnyRegion(String key) {
|
||||
return loadAny(key, (dm) -> dm.getRegionLoader().load(key, false));
|
||||
public static IrisGenerator loadAnyGenerator(String key, @Nullable IrisData nearest) {
|
||||
return loadAny(IrisGenerator.class, key, nearest);
|
||||
}
|
||||
|
||||
public static IrisMarker loadAnyMarker(String key) {
|
||||
return loadAny(key, (dm) -> dm.getMarkerLoader().load(key, false));
|
||||
}
|
||||
|
||||
public static IrisCave loadAnyCave(String key) {
|
||||
return loadAny(key, (dm) -> dm.getCaveLoader().load(key, false));
|
||||
}
|
||||
|
||||
public static IrisImage loadAnyImage(String key) {
|
||||
return loadAny(key, (dm) -> dm.getImageLoader().load(key, false));
|
||||
}
|
||||
|
||||
public static IrisDimension loadAnyDimension(String key) {
|
||||
return loadAny(key, (dm) -> dm.getDimensionLoader().load(key, false));
|
||||
}
|
||||
|
||||
public static IrisJigsawStructure loadAnyJigsawStructure(String key) {
|
||||
return loadAny(key, (dm) -> dm.getJigsawStructureLoader().load(key, false));
|
||||
}
|
||||
|
||||
public static IrisGenerator loadAnyGenerator(String key) {
|
||||
return loadAny(key, (dm) -> dm.getGeneratorLoader().load(key, false));
|
||||
}
|
||||
|
||||
public static <T extends IrisRegistrant> T loadAny(String key, Function<IrisData, T> v) {
|
||||
public static <T extends IrisRegistrant> T loadAny(Class<T> type, String key, @Nullable IrisData nearest) {
|
||||
try {
|
||||
if (nearest != null) {
|
||||
T t = nearest.load(type, key, false);
|
||||
if (t != null) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
for (File i : Objects.requireNonNull(Iris.instance.getDataFolder("packs").listFiles())) {
|
||||
if (i.isDirectory()) {
|
||||
IrisData dm = get(i);
|
||||
T t = v.apply(dm);
|
||||
if (dm == nearest) continue;
|
||||
T t = dm.load(type, key, false);
|
||||
|
||||
if (t != null) {
|
||||
return t;
|
||||
@@ -211,6 +195,17 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T extends IrisRegistrant> T load(Class<T> type, String key, boolean warn) {
|
||||
var loader = getLoader(type);
|
||||
if (loader == null) return null;
|
||||
return loader.load(key, warn);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends IrisRegistrant> ResourceLoader<T> getLoader(Class<T> type) {
|
||||
return (ResourceLoader<T>) loaders.get(type);
|
||||
}
|
||||
|
||||
public ResourceLoader<?> getTypedLoaderFor(File f) {
|
||||
String[] k = f.getPath().split("\\Q" + File.separator + "\\E");
|
||||
|
||||
@@ -233,42 +228,12 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
}
|
||||
|
||||
public void preprocessObject(IrisRegistrant t) {
|
||||
try {
|
||||
IrisContext ctx = IrisContext.get();
|
||||
Engine engine = this.engine;
|
||||
|
||||
if (engine == null && ctx != null && ctx.getEngine() != null) {
|
||||
engine = ctx.getEngine();
|
||||
}
|
||||
|
||||
if (engine == null && t.getPreprocessors().isNotEmpty()) {
|
||||
Iris.error("Failed to preprocess object " + t.getLoadKey() + " because there is no engine context here. (See stack below)");
|
||||
try {
|
||||
throw new RuntimeException();
|
||||
} catch (Throwable ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (engine != null && t.getPreprocessors().isNotEmpty()) {
|
||||
synchronized (this) {
|
||||
engine.getExecution().getAPI().setPreprocessorObject(t);
|
||||
|
||||
for (String i : t.getPreprocessors()) {
|
||||
engine.getExecution().execute(i);
|
||||
Iris.debug("Loader<" + C.GREEN + t.getTypeName() + C.LIGHT_PURPLE + "> iprocess " + C.YELLOW + t.getLoadKey() + C.LIGHT_PURPLE + " in <rainbow>" + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Failed to preprocess object!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
closed = true;
|
||||
dump();
|
||||
dataLoaders.remove(dataFolder);
|
||||
}
|
||||
|
||||
public IrisData copy() {
|
||||
@@ -285,9 +250,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
} else if (registrant.equals(IrisMatterObject.class)) {
|
||||
r = (ResourceLoader<T>) new MatterObjectResourceLoader(dataFolder, this, rr.getFolderName(),
|
||||
rr.getTypeName());
|
||||
} else if (registrant.equals(IrisScript.class)) {
|
||||
r = (ResourceLoader<T>) new ScriptResourceLoader(dataFolder, this, rr.getFolderName(),
|
||||
rr.getTypeName());
|
||||
} else if (registrant.equals(IrisImage.class)) {
|
||||
r = (ResourceLoader<T>) new ImageResourceLoader(dataFolder, this, rr.getFolderName(),
|
||||
rr.getTypeName());
|
||||
@@ -300,6 +262,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
|
||||
return r;
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
Iris.error("Failed to create loader! " + registrant.getCanonicalName());
|
||||
}
|
||||
@@ -308,12 +271,14 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
}
|
||||
|
||||
public synchronized void hotloaded() {
|
||||
closed = false;
|
||||
possibleSnippets = new KMap<>();
|
||||
builder = new GsonBuilder()
|
||||
.addDeserializationExclusionStrategy(this)
|
||||
.addSerializationExclusionStrategy(this)
|
||||
.setLenient()
|
||||
.setStrictness(Strictness.LENIENT)
|
||||
.registerTypeAdapterFactory(this)
|
||||
.registerTypeAdapter(MantleFlag.class, new MantleFlagAdapter())
|
||||
.setPrettyPrinting();
|
||||
loaders.clear();
|
||||
File packs = dataFolder;
|
||||
@@ -325,29 +290,20 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
this.biomeLoader = registerLoader(IrisBiome.class);
|
||||
this.modLoader = registerLoader(IrisMod.class);
|
||||
this.dimensionLoader = registerLoader(IrisDimension.class);
|
||||
this.jigsawPoolLoader = registerLoader(IrisJigsawPool.class);
|
||||
this.jigsawStructureLoader = registerLoader(IrisJigsawStructure.class);
|
||||
this.jigsawPieceLoader = registerLoader(IrisJigsawPiece.class);
|
||||
this.generatorLoader = registerLoader(IrisGenerator.class);
|
||||
this.caveLoader = registerLoader(IrisCave.class);
|
||||
this.markerLoader = registerLoader(IrisMarker.class);
|
||||
this.ravineLoader = registerLoader(IrisRavine.class);
|
||||
this.blockLoader = registerLoader(IrisBlockData.class);
|
||||
this.expressionLoader = registerLoader(IrisExpression.class);
|
||||
this.objectLoader = registerLoader(IrisObject.class);
|
||||
this.imageLoader = registerLoader(IrisImage.class);
|
||||
this.scriptLoader = registerLoader(IrisScript.class);
|
||||
this.matterObjectLoader = registerLoader(IrisMatterObject.class);
|
||||
if (OldEnum.exists()) {
|
||||
builder.registerTypeAdapterFactory(new TypeAdapterFactory() {
|
||||
@Override
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||
return (TypeAdapter<T>) OldEnum.create(type.getRawType());
|
||||
}
|
||||
});
|
||||
}
|
||||
builder.registerTypeAdapterFactory(KeyedType::createTypeAdapter);
|
||||
|
||||
gson = builder.create();
|
||||
|
||||
if (engine != null) {
|
||||
engine.hotload();
|
||||
}
|
||||
}
|
||||
|
||||
public void dump() {
|
||||
@@ -360,6 +316,33 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
for (ResourceLoader<?> i : loaders.values()) {
|
||||
i.clearList();
|
||||
}
|
||||
possibleSnippets.clear();
|
||||
}
|
||||
|
||||
public Set<Class<?>> resolveSnippets() {
|
||||
var result = new HashSet<Class<?>>();
|
||||
var processed = new HashSet<Class<?>>();
|
||||
|
||||
var queue = new LinkedList<Class<?>>(loaders.keySet());
|
||||
while (!queue.isEmpty()) {
|
||||
var type = queue.poll();
|
||||
if (shouldSkipClass(type) || !processed.add(type))
|
||||
continue;
|
||||
if (type.isAnnotationPresent(Snippet.class))
|
||||
result.add(type);
|
||||
|
||||
try {
|
||||
for (var field : type.getDeclaredFields()) {
|
||||
if (shouldSkipField(new FieldAttributes(field)))
|
||||
continue;
|
||||
|
||||
queue.add(field.getType());
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toLoadKey(File f) {
|
||||
@@ -408,6 +391,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
}
|
||||
|
||||
String snippetType = typeToken.getRawType().getDeclaredAnnotation(Snippet.class).value();
|
||||
String snippedBase = "snippet/" + snippetType + "/";
|
||||
|
||||
return new TypeAdapter<>() {
|
||||
@Override
|
||||
@@ -421,20 +405,20 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
|
||||
if (reader.peek().equals(JsonToken.STRING)) {
|
||||
String r = reader.nextString();
|
||||
if (!r.startsWith("snippet/"))
|
||||
return null;
|
||||
if (!r.startsWith(snippedBase))
|
||||
r = snippedBase + r.substring(8);
|
||||
|
||||
if (r.startsWith("snippet/" + snippetType + "/")) {
|
||||
File f = new File(getDataFolder(), r + ".json");
|
||||
|
||||
if (f.exists()) {
|
||||
try {
|
||||
JsonReader snippetReader = new JsonReader(new FileReader(f));
|
||||
return adapter.read(snippetReader);
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Couldn't read snippet " + r + " in " + reader.getPath() + " (" + e.getMessage() + ")");
|
||||
}
|
||||
} else {
|
||||
Iris.error("Couldn't find snippet " + r + " in " + reader.getPath());
|
||||
File f = new File(getDataFolder(), r + ".json");
|
||||
if (f.exists()) {
|
||||
try (JsonReader snippetReader = new JsonReader(new FileReader(f))){
|
||||
return adapter.read(snippetReader);
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Couldn't read snippet " + r + " in " + reader.getPath() + " (" + e.getMessage() + ")");
|
||||
}
|
||||
} else {
|
||||
Iris.error("Couldn't find snippet " + r + " in " + reader.getPath());
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -461,11 +445,20 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
KList<String> l = new KList<>();
|
||||
|
||||
File snippetFolder = new File(getDataFolder(), "snippet/" + f);
|
||||
if (!snippetFolder.exists()) return l;
|
||||
|
||||
if (snippetFolder.exists() && snippetFolder.isDirectory()) {
|
||||
for (File i : snippetFolder.listFiles()) {
|
||||
l.add("snippet/" + f + "/" + i.getName().split("\\Q.\\E")[0]);
|
||||
}
|
||||
String absPath = snippetFolder.getAbsolutePath();
|
||||
try (var stream = Files.walk(snippetFolder.toPath())) {
|
||||
stream.filter(Files::isRegularFile)
|
||||
.map(Path::toAbsolutePath)
|
||||
.map(Path::toString)
|
||||
.filter(s -> s.endsWith(".json"))
|
||||
.map(s -> s.substring(absPath.length() + 1))
|
||||
.map(s -> s.replace("\\", "/"))
|
||||
.map(s -> s.split("\\Q.\\E")[0])
|
||||
.forEach(s -> l.add("snippet/" + s));
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return l;
|
||||
@@ -477,7 +470,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
}
|
||||
|
||||
public void savePrefetch(Engine engine) {
|
||||
BurstExecutor b = MultiBurst.burst.burst(loaders.size());
|
||||
BurstExecutor b = MultiBurst.ioBurst.burst(loaders.size());
|
||||
|
||||
for (ResourceLoader<?> i : loaders.values()) {
|
||||
b.queue(() -> {
|
||||
@@ -494,7 +487,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
}
|
||||
|
||||
public void loadPrefetch(Engine engine) {
|
||||
BurstExecutor b = MultiBurst.burst.burst(loaders.size());
|
||||
BurstExecutor b = MultiBurst.ioBurst.burst(loaders.size());
|
||||
|
||||
for (ResourceLoader<?> i : loaders.values()) {
|
||||
b.queue(() -> {
|
||||
@@ -509,4 +502,4 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
b.complete();
|
||||
Iris.info("Loaded Prefetch Cache to reduce generation disk use.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-14
@@ -16,29 +16,21 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.loader;
|
||||
package art.arcane.iris.core.loader;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.engine.object.IrisScript;
|
||||
import com.volmit.iris.engine.object.annotations.ArrayType;
|
||||
import com.volmit.iris.engine.object.annotations.Desc;
|
||||
import com.volmit.iris.engine.object.annotations.RegistryListResource;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
|
||||
@Data
|
||||
public abstract class IrisRegistrant {
|
||||
@Desc("Preprocess this object in-memory when it's loaded, run scripts using the variable 'Iris.getPreprocessorObject()' and modify properties about this object before it's used.")
|
||||
@RegistryListResource(IrisScript.class)
|
||||
@ArrayType(min = 1, type = String.class)
|
||||
private KList<String> preprocessors = new KList<>();
|
||||
|
||||
@EqualsAndHashCode.Exclude
|
||||
private transient IrisData loader;
|
||||
|
||||
private transient String loadKey;
|
||||
@@ -0,0 +1,203 @@
|
||||
package art.arcane.iris.core.loader;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class JsonSchemaValidator {
|
||||
private static final ConcurrentHashMap<Class<?>, Set<String>> FIELD_CACHE = new ConcurrentHashMap<>();
|
||||
private static final int SUGGESTION_MAX_DISTANCE = 4;
|
||||
|
||||
private JsonSchemaValidator() {
|
||||
}
|
||||
|
||||
static void validateTopLevelKeys(JSONObject parsed, String rawText, File file, String resourceTypeName, Class<?> objectClass) {
|
||||
if (parsed == null || objectClass == null) {
|
||||
return;
|
||||
}
|
||||
Set<String> known = FIELD_CACHE.computeIfAbsent(objectClass, JsonSchemaValidator::collectFieldNames);
|
||||
for (String key : parsed.keySet()) {
|
||||
if (known.contains(key)) {
|
||||
continue;
|
||||
}
|
||||
reportUnknownKey(key, rawText, file, resourceTypeName, known);
|
||||
}
|
||||
}
|
||||
|
||||
static void reportLoadFailure(File file, String rawText, String resourceTypeName, Throwable error) {
|
||||
String message = error.getMessage();
|
||||
if (message == null || message.isBlank()) {
|
||||
message = error.getClass().getSimpleName();
|
||||
}
|
||||
int line = extractLineFromMessage(message);
|
||||
String location = file.getPath();
|
||||
if (line > 0) {
|
||||
location = location + ":" + line;
|
||||
}
|
||||
StringBuilder out = new StringBuilder();
|
||||
out.append("Couldn't load ").append(resourceTypeName)
|
||||
.append(C.RED).append(" in ").append(C.WHITE).append(location).append(C.RED)
|
||||
.append(" -> ").append(message);
|
||||
String snippet = buildSnippet(rawText, line);
|
||||
if (snippet != null) {
|
||||
out.append('\n').append(snippet);
|
||||
}
|
||||
Iris.warn(out.toString());
|
||||
}
|
||||
|
||||
private static void reportUnknownKey(String key, String rawText, File file, String resourceTypeName, Set<String> known) {
|
||||
int line = findLineForKey(rawText, key);
|
||||
String suggestion = closestMatch(key, known);
|
||||
StringBuilder out = new StringBuilder();
|
||||
out.append("Unknown ").append(resourceTypeName).append(" field ")
|
||||
.append(C.WHITE).append('"').append(key).append('"').append(C.YELLOW)
|
||||
.append(" in ").append(C.WHITE).append(file.getPath());
|
||||
if (line > 0) {
|
||||
out.append(":").append(line);
|
||||
}
|
||||
out.append(C.YELLOW).append(" (Gson will silently ignore this)");
|
||||
if (suggestion != null) {
|
||||
out.append(". Did you mean ").append(C.WHITE).append('"').append(suggestion).append('"').append(C.YELLOW).append("?");
|
||||
}
|
||||
String snippet = buildSnippet(rawText, line);
|
||||
if (snippet != null) {
|
||||
out.append('\n').append(snippet);
|
||||
}
|
||||
Iris.warn(out.toString());
|
||||
}
|
||||
|
||||
private static Set<String> collectFieldNames(Class<?> cls) {
|
||||
Set<String> names = new LinkedHashSet<>();
|
||||
Class<?> c = cls;
|
||||
while (c != null && c != Object.class) {
|
||||
for (Field field : c.getDeclaredFields()) {
|
||||
int mods = field.getModifiers();
|
||||
if (Modifier.isStatic(mods) || Modifier.isTransient(mods)) {
|
||||
continue;
|
||||
}
|
||||
if (field.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
SerializedName serialized = field.getAnnotation(SerializedName.class);
|
||||
if (serialized != null) {
|
||||
names.add(serialized.value());
|
||||
Collections.addAll(names, serialized.alternate());
|
||||
} else {
|
||||
names.add(field.getName());
|
||||
}
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
return Collections.unmodifiableSet(names);
|
||||
}
|
||||
|
||||
private static int findLineForKey(String rawText, String key) {
|
||||
if (rawText == null || key == null) {
|
||||
return -1;
|
||||
}
|
||||
Pattern pattern = Pattern.compile("\"" + Pattern.quote(key) + "\"\\s*:");
|
||||
Matcher matcher = pattern.matcher(rawText);
|
||||
if (!matcher.find()) {
|
||||
return -1;
|
||||
}
|
||||
int index = matcher.start();
|
||||
int line = 1;
|
||||
for (int i = 0; i < index; i++) {
|
||||
if (rawText.charAt(i) == '\n') {
|
||||
line++;
|
||||
}
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
private static int extractLineFromMessage(String message) {
|
||||
if (message == null) {
|
||||
return -1;
|
||||
}
|
||||
Matcher m = Pattern.compile("line\\s+(\\d+)").matcher(message);
|
||||
if (m.find()) {
|
||||
try {
|
||||
return Integer.parseInt(m.group(1));
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static String buildSnippet(String rawText, int line) {
|
||||
if (rawText == null || line <= 0) {
|
||||
return null;
|
||||
}
|
||||
String[] lines = rawText.split("\n", -1);
|
||||
if (line > lines.length) {
|
||||
return null;
|
||||
}
|
||||
int from = Math.max(0, line - 2);
|
||||
int to = Math.min(lines.length, line + 1);
|
||||
StringBuilder out = new StringBuilder();
|
||||
int width = String.valueOf(to).length();
|
||||
for (int i = from; i < to; i++) {
|
||||
int n = i + 1;
|
||||
boolean focus = n == line;
|
||||
out.append(focus ? C.RED + "> " : C.GRAY + " ");
|
||||
out.append(String.format("%" + width + "d", n)).append(" | ");
|
||||
String content = lines[i];
|
||||
if (content.length() > 200) {
|
||||
content = content.substring(0, 200) + "...";
|
||||
}
|
||||
out.append(content);
|
||||
if (i < to - 1) {
|
||||
out.append('\n');
|
||||
}
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static String closestMatch(String key, Set<String> known) {
|
||||
String lowerKey = key.toLowerCase();
|
||||
String best = null;
|
||||
int bestDistance = Integer.MAX_VALUE;
|
||||
for (String candidate : known) {
|
||||
int d = levenshtein(lowerKey, candidate.toLowerCase());
|
||||
if (d < bestDistance) {
|
||||
bestDistance = d;
|
||||
best = candidate;
|
||||
}
|
||||
}
|
||||
if (best == null) {
|
||||
return null;
|
||||
}
|
||||
int threshold = Math.min(SUGGESTION_MAX_DISTANCE, Math.max(1, key.length() / 2));
|
||||
return bestDistance <= threshold ? best : null;
|
||||
}
|
||||
|
||||
private static int levenshtein(String a, String b) {
|
||||
int[] prev = new int[b.length() + 1];
|
||||
int[] curr = new int[b.length() + 1];
|
||||
for (int j = 0; j <= b.length(); j++) {
|
||||
prev[j] = j;
|
||||
}
|
||||
for (int i = 1; i <= a.length(); i++) {
|
||||
curr[0] = i;
|
||||
for (int j = 1; j <= b.length(); j++) {
|
||||
int cost = a.charAt(i - 1) == b.charAt(j - 1) ? 0 : 1;
|
||||
curr[j] = Math.min(Math.min(curr[j - 1] + 1, prev[j] + 1), prev[j - 1] + cost);
|
||||
}
|
||||
int[] tmp = prev;
|
||||
prev = curr;
|
||||
curr = tmp;
|
||||
}
|
||||
return prev[b.length()];
|
||||
}
|
||||
}
|
||||
+56
-14
@@ -16,17 +16,19 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.loader;
|
||||
package art.arcane.iris.core.loader;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.engine.object.matter.IrisMatterObject;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KSet;
|
||||
import com.volmit.iris.util.data.KCache;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.engine.object.matter.IrisMatterObject;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import art.arcane.volmlib.util.data.KCache;
|
||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class MatterObjectResourceLoader extends ResourceLoader<IrisMatterObject> {
|
||||
private String[] possibleKeys;
|
||||
@@ -65,12 +67,28 @@ public class MatterObjectResourceLoader extends ResourceLoader<IrisMatterObject>
|
||||
}
|
||||
}
|
||||
|
||||
private void findMatFiles(File dir, KSet<String> m) {
|
||||
for (File file : dir.listFiles()) {
|
||||
private void findMatFiles(File dir, KSet<String> m, HashSet<String> visitedDirectories) {
|
||||
if (dir == null || !dir.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dir.isDirectory()) {
|
||||
String canonicalDirectory = toCanonicalPath(dir);
|
||||
if (canonicalDirectory != null && !visitedDirectories.add(canonicalDirectory)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
File[] listedFiles = dir.listFiles();
|
||||
if (listedFiles == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (File file : listedFiles) {
|
||||
if (file.isFile() && file.getName().endsWith(".mat")) {
|
||||
m.add(file.getName().replaceAll("\\Q.mat\\E", ""));
|
||||
} else if (file.isDirectory()) {
|
||||
findMatFiles(file, m);
|
||||
findMatFiles(file, m, visitedDirectories);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,9 +100,10 @@ public class MatterObjectResourceLoader extends ResourceLoader<IrisMatterObject>
|
||||
|
||||
Iris.debug("Building " + resourceTypeName + " Possibility Lists");
|
||||
KSet<String> m = new KSet<>();
|
||||
HashSet<String> visitedDirectories = new HashSet<>();
|
||||
|
||||
for (File folder : getFolders()) {
|
||||
findMatFiles(folder, m);
|
||||
findMatFiles(folder, m, visitedDirectories);
|
||||
}
|
||||
|
||||
KList<String> v = new KList<>(m);
|
||||
@@ -92,6 +111,14 @@ public class MatterObjectResourceLoader extends ResourceLoader<IrisMatterObject>
|
||||
return possibleKeys;
|
||||
}
|
||||
|
||||
private String toCanonicalPath(File file) {
|
||||
try {
|
||||
return file.getCanonicalPath();
|
||||
} catch (IOException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// public String[] getPossibleKeys() {
|
||||
// if (possibleKeys != null) {
|
||||
@@ -127,6 +154,14 @@ public class MatterObjectResourceLoader extends ResourceLoader<IrisMatterObject>
|
||||
// }
|
||||
|
||||
public File findFile(String name) {
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (name.equals("null")) {
|
||||
Iris.warn("Refusing " + resourceTypeName + " lookup for literal string \"null\" (called by " + callerHint() + ")");
|
||||
return null;
|
||||
}
|
||||
|
||||
for (File i : getFolders(name)) {
|
||||
for (File j : i.listFiles()) {
|
||||
if (j.isFile() && j.getName().endsWith(".mat") && j.getName().split("\\Q.\\E")[0].equals(name)) {
|
||||
@@ -141,7 +176,7 @@ public class MatterObjectResourceLoader extends ResourceLoader<IrisMatterObject>
|
||||
}
|
||||
}
|
||||
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name);
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name + " (called by " + callerHint() + ")");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -165,12 +200,19 @@ public class MatterObjectResourceLoader extends ResourceLoader<IrisMatterObject>
|
||||
}
|
||||
}
|
||||
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name);
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name + " (called by " + callerHint() + ")");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IrisMatterObject load(String name, boolean warn) {
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (name.equals("null") && warn) {
|
||||
Iris.warn("Refusing " + resourceTypeName + " load for literal string \"null\" (called by " + callerHint() + ")");
|
||||
return null;
|
||||
}
|
||||
return loadCache.get(name);
|
||||
}
|
||||
}
|
||||
+66
-17
@@ -16,17 +16,19 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.loader;
|
||||
package art.arcane.iris.core.loader;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.engine.object.IrisObject;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KSet;
|
||||
import com.volmit.iris.util.data.KCache;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.engine.object.IrisObject;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import art.arcane.volmlib.util.data.KCache;
|
||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class ObjectResourceLoader extends ResourceLoader<IrisObject> {
|
||||
public ObjectResourceLoader(File root, IrisData idm, String folderName, String resourceTypeName) {
|
||||
@@ -59,7 +61,12 @@ public class ObjectResourceLoader extends ResourceLoader<IrisObject> {
|
||||
return t;
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
Iris.warn("Couldn't read " + resourceTypeName + " file: " + j.getPath() + ": " + e.getMessage());
|
||||
String message = e.getMessage();
|
||||
String reason = e.getClass().getSimpleName();
|
||||
if (message != null && !message.isBlank()) {
|
||||
reason = reason + ": " + message;
|
||||
}
|
||||
Iris.warn("Couldn't read " + resourceTypeName + " file: " + j.getPath() + " (" + reason + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -70,27 +77,60 @@ public class ObjectResourceLoader extends ResourceLoader<IrisObject> {
|
||||
}
|
||||
Iris.debug("Building " + resourceTypeName + " Possibility Lists");
|
||||
KSet<String> m = new KSet<>();
|
||||
HashSet<String> visitedDirectories = new HashSet<>();
|
||||
for (File i : getFolders()) {
|
||||
m.addAll(getFiles(i, ".iob", true));
|
||||
m.addAll(getFiles(i, ".iob", true, visitedDirectories));
|
||||
}
|
||||
possibleKeys = m.toArray(new String[0]);
|
||||
return possibleKeys;
|
||||
}
|
||||
|
||||
private KList<String> getFiles(File dir, String ext, boolean skipDirName) {
|
||||
private KList<String> getFiles(File dir, String ext, boolean skipDirName, HashSet<String> visitedDirectories) {
|
||||
KList<String> paths = new KList<>();
|
||||
if (dir == null || !dir.exists()) {
|
||||
return paths;
|
||||
}
|
||||
|
||||
if (dir.isDirectory()) {
|
||||
String canonicalDirectory = toCanonicalPath(dir);
|
||||
if (canonicalDirectory != null && !visitedDirectories.add(canonicalDirectory)) {
|
||||
return paths;
|
||||
}
|
||||
}
|
||||
|
||||
File[] listedFiles = dir.listFiles();
|
||||
if (listedFiles == null) {
|
||||
return paths;
|
||||
}
|
||||
|
||||
String name = skipDirName ? "" : dir.getName() + "/";
|
||||
for (File f : dir.listFiles()) {
|
||||
for (File f : listedFiles) {
|
||||
if (f.isFile() && f.getName().endsWith(ext)) {
|
||||
paths.add(name + f.getName().replaceAll("\\Q" + ext + "\\E", ""));
|
||||
} else if (f.isDirectory()) {
|
||||
getFiles(f, ext, false).forEach(e -> paths.add(name + e));
|
||||
getFiles(f, ext, false, visitedDirectories).forEach(e -> paths.add(name + e));
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
private String toCanonicalPath(File file) {
|
||||
try {
|
||||
return file.getCanonicalPath();
|
||||
} catch (IOException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public File findFile(String name) {
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (name.equals("null")) {
|
||||
Iris.warn("Refusing " + resourceTypeName + " lookup for literal string \"null\" (called by " + callerHint() + ")");
|
||||
return null;
|
||||
}
|
||||
|
||||
for (File i : getFolders(name)) {
|
||||
for (File j : i.listFiles()) {
|
||||
if (j.isFile() && j.getName().endsWith(".iob") && j.getName().split("\\Q.\\E")[0].equals(name)) {
|
||||
@@ -105,7 +145,7 @@ public class ObjectResourceLoader extends ResourceLoader<IrisObject> {
|
||||
}
|
||||
}
|
||||
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name);
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name + " (called by " + callerHint() + ")");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -129,12 +169,21 @@ public class ObjectResourceLoader extends ResourceLoader<IrisObject> {
|
||||
}
|
||||
}
|
||||
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IrisObject load(String name, boolean warn) {
|
||||
return loadCache.get(name);
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (name.equals("null") && warn) {
|
||||
Iris.warn("Refusing " + resourceTypeName + " load for literal string \"null\" (called by " + callerHint() + ")");
|
||||
return null;
|
||||
}
|
||||
IrisObject result = loadCache.get(name);
|
||||
if (result == null && warn) {
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name + " (called by " + callerHint() + ")");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+162
-70
@@ -16,37 +16,43 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.loader;
|
||||
package art.arcane.iris.core.loader;
|
||||
|
||||
import com.google.common.util.concurrent.AtomicDouble;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.project.SchemaBuilder;
|
||||
import com.volmit.iris.core.service.PreservationSVC;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.framework.MeteredCache;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KSet;
|
||||
import com.volmit.iris.util.data.KCache;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.io.CustomOutputStream;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.json.JSONArray;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.parallel.BurstExecutor;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.project.SchemaBuilder;
|
||||
import art.arcane.iris.core.service.PreservationSVC;
|
||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.MeteredCache;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import art.arcane.volmlib.util.data.KCache;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import art.arcane.volmlib.util.io.CustomOutputStream;
|
||||
import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import art.arcane.iris.util.common.parallel.BurstExecutor;
|
||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
||||
import art.arcane.volmlib.util.scheduling.ChronoLatch;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
@@ -59,7 +65,14 @@ import java.util.zip.GZIPOutputStream;
|
||||
public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
public static final AtomicDouble tlt = new AtomicDouble(0);
|
||||
private static final int CACHE_SIZE = 100000;
|
||||
protected final AtomicReference<KList<File>> folderCache;
|
||||
private static final ExecutorService schemaBuildExecutor = Executors.newSingleThreadExecutor(runnable -> {
|
||||
Thread thread = new Thread(runnable, "Iris-Schema-Builder");
|
||||
thread.setDaemon(true);
|
||||
thread.setPriority(Thread.MIN_PRIORITY);
|
||||
return thread;
|
||||
});
|
||||
private static final Set<String> schemaBuildQueue = ConcurrentHashMap.newKeySet();
|
||||
protected final AtomicCache<KList<File>> folderCache;
|
||||
protected KSet<String> firstAccess;
|
||||
protected File root;
|
||||
protected String folderName;
|
||||
@@ -75,7 +88,7 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
public ResourceLoader(File root, IrisData manager, String folderName, String resourceTypeName, Class<? extends T> objectClass) {
|
||||
this.manager = manager;
|
||||
firstAccess = new KSet<>();
|
||||
folderCache = new AtomicReference<>();
|
||||
folderCache = new AtomicCache<>();
|
||||
sec = new ChronoLatch(5000);
|
||||
loads = new AtomicInteger();
|
||||
this.objectClass = objectClass;
|
||||
@@ -100,12 +113,31 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
o.put("fileMatch", new JSONArray(fm.toArray()));
|
||||
o.put("url", "./.iris/schema/" + getFolderName() + "-schema.json");
|
||||
File a = new File(getManager().getDataFolder(), ".iris/schema/" + getFolderName() + "-schema.json");
|
||||
J.attemptAsync(() -> IO.writeAll(a, new SchemaBuilder(objectClass, manager).construct().toString(4)));
|
||||
String schemaPath = a.getAbsolutePath();
|
||||
if (!a.exists() && schemaBuildQueue.add(schemaPath)) {
|
||||
schemaBuildExecutor.execute(() -> {
|
||||
try {
|
||||
IO.writeAll(a, new SchemaBuilder(objectClass, manager).construct().toString(4));
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
} finally {
|
||||
schemaBuildQueue.remove(schemaPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
public File findFile(String name) {
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (name.equals("null")) {
|
||||
Iris.warn("Refusing " + resourceTypeName + " lookup for literal string \"null\" (called by " + callerHint() + ")");
|
||||
return null;
|
||||
}
|
||||
|
||||
for (File i : getFolders(name)) {
|
||||
for (File j : i.listFiles()) {
|
||||
if (j.isFile() && j.getName().endsWith(".json") && j.getName().split("\\Q.\\E")[0].equals(name)) {
|
||||
@@ -120,11 +152,34 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
}
|
||||
}
|
||||
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name);
|
||||
Iris.warn("Couldn't find " + resourceTypeName + ": " + name + " (called by " + callerHint() + ")");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static String describeName(String name) {
|
||||
if (name == null) return "<java null>";
|
||||
if (name.isEmpty()) return "<empty string>";
|
||||
if (name.equals("null")) return "\"null\" (literal string)";
|
||||
return "\"" + name + "\"";
|
||||
}
|
||||
|
||||
protected static String callerHint() {
|
||||
StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
|
||||
return walker.walk(frames -> frames
|
||||
.filter(f -> {
|
||||
String cn = f.getClassName();
|
||||
return !cn.startsWith("art.arcane.iris.core.loader.")
|
||||
&& !cn.startsWith("art.arcane.volmlib.util.data.")
|
||||
&& !cn.startsWith("com.github.benmanes.caffeine.");
|
||||
})
|
||||
.limit(3)
|
||||
.map(f -> f.getClassName().substring(f.getClassName().lastIndexOf('.') + 1)
|
||||
+ "." + f.getMethodName() + ":" + f.getLineNumber())
|
||||
.reduce((a, b) -> a + " <- " + b)
|
||||
.orElse("<unknown>"));
|
||||
}
|
||||
|
||||
public void logLoad(File path, T t) {
|
||||
loads.getAndIncrement();
|
||||
|
||||
@@ -143,24 +198,52 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
}
|
||||
|
||||
public void failLoad(File path, Throwable e) {
|
||||
J.a(() -> Iris.warn("Couldn't Load " + resourceTypeName + " file: " + path.getPath() + ": " + e.getMessage()));
|
||||
failLoad(path, null, e);
|
||||
}
|
||||
|
||||
public void failLoad(File path, String rawText, Throwable e) {
|
||||
J.a(() -> JsonSchemaValidator.reportLoadFailure(path, rawText, resourceTypeName, e));
|
||||
}
|
||||
|
||||
private KList<File> matchAllFiles(File root, Predicate<File> f) {
|
||||
KList<File> fx = new KList<>();
|
||||
matchFiles(root, fx, f);
|
||||
return fx;
|
||||
KList<File> files = new KList<>();
|
||||
HashSet<String> visitedDirectories = new HashSet<>();
|
||||
matchFiles(root, files, f, visitedDirectories);
|
||||
return files;
|
||||
}
|
||||
|
||||
private void matchFiles(File at, KList<File> files, Predicate<File> f) {
|
||||
private void matchFiles(File at, KList<File> files, Predicate<File> f, HashSet<String> visitedDirectories) {
|
||||
if (at == null || !at.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (at.isDirectory()) {
|
||||
for (File i : at.listFiles()) {
|
||||
matchFiles(i, files, f);
|
||||
String canonicalPath = toCanonicalPath(at);
|
||||
if (canonicalPath != null && !visitedDirectories.add(canonicalPath)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (f.test(at)) {
|
||||
files.add(at);
|
||||
|
||||
File[] listedFiles = at.listFiles();
|
||||
if (listedFiles == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (File listedFile : listedFiles) {
|
||||
matchFiles(listedFile, files, f, visitedDirectories);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (f.test(at)) {
|
||||
files.add(at);
|
||||
}
|
||||
}
|
||||
|
||||
private String toCanonicalPath(File file) {
|
||||
try {
|
||||
return file.getCanonicalPath();
|
||||
} catch (IOException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +252,6 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
return possibleKeys;
|
||||
}
|
||||
|
||||
KSet<String> m = new KSet<>();
|
||||
KList<File> files = getFolders();
|
||||
|
||||
if (files == null) {
|
||||
@@ -177,6 +259,7 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
return possibleKeys;
|
||||
}
|
||||
|
||||
HashSet<String> m = new HashSet<>();
|
||||
for (File i : files) {
|
||||
for (File j : matchAllFiles(i, (f) -> f.getName().endsWith(".json"))) {
|
||||
m.add(i.toURI().relativize(j.toURI()).getPath().replaceAll("\\Q.json\\E", ""));
|
||||
@@ -193,10 +276,14 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
}
|
||||
|
||||
protected T loadFile(File j, String name) {
|
||||
String rawText = null;
|
||||
try {
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
rawText = IO.readAll(j);
|
||||
JSONObject parsed = new JSONObject(rawText);
|
||||
JsonSchemaValidator.validateTopLevelKeys(parsed, rawText, j, resourceTypeName, objectClass);
|
||||
T t = getManager().getGson()
|
||||
.fromJson(preprocess(new JSONObject(IO.readAll(j))).toString(0), objectClass);
|
||||
.fromJson(preprocess(parsed).toString(0), objectClass);
|
||||
t.setLoadKey(name);
|
||||
t.setLoadFile(j);
|
||||
t.setLoader(manager);
|
||||
@@ -206,7 +293,7 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
return t;
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
failLoad(j, e);
|
||||
failLoad(j, rawText, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -215,6 +302,10 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
return j;
|
||||
}
|
||||
|
||||
public Stream<T> streamAll() {
|
||||
return streamAll(Arrays.stream(getPossibleKeys()));
|
||||
}
|
||||
|
||||
public Stream<T> streamAll(Stream<String> s) {
|
||||
return s.map(this::load);
|
||||
}
|
||||
@@ -235,13 +326,15 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
|
||||
public KList<T> loadAllParallel(KList<String> s) {
|
||||
KList<T> m = new KList<>();
|
||||
BurstExecutor burst = MultiBurst.burst.burst(s.size());
|
||||
BurstExecutor burst = MultiBurst.ioBurst.burst(s.size());
|
||||
|
||||
for (String i : s) {
|
||||
burst.queue(() -> {
|
||||
T t = load(i);
|
||||
if (t == null)
|
||||
return;
|
||||
|
||||
if (t != null) {
|
||||
synchronized (m) {
|
||||
m.add(t);
|
||||
}
|
||||
});
|
||||
@@ -304,15 +397,16 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
}
|
||||
|
||||
public T load(String name, boolean warn) {
|
||||
if (name == null) {
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (name.equals("null") && warn) {
|
||||
Iris.warn("Refusing " + resourceTypeName + " load for literal string \"null\" (called by " + callerHint() + ")");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
firstAccess.add(name);
|
||||
var set = firstAccess;
|
||||
if (set != null) firstAccess.add(name);
|
||||
return loadCache.get(name);
|
||||
}
|
||||
|
||||
@@ -335,21 +429,24 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
}
|
||||
|
||||
din.close();
|
||||
file.deleteOnExit();
|
||||
Iris.info("Loading " + s.size() + " prefetch " + getFolderName());
|
||||
firstAccess = null;
|
||||
loadAllParallel(s);
|
||||
}
|
||||
|
||||
public void saveFirstAccess(Engine engine) throws IOException {
|
||||
if (firstAccess == null) return;
|
||||
String id = "DIM" + Math.abs(engine.getSeedManager().getSeed() + engine.getDimension().getVersion() + engine.getDimension().getLoadKey().hashCode());
|
||||
File file = Iris.instance.getDataFile("prefetch/" + id + "/" + Math.abs(getFolderName().hashCode()) + ".ipfch");
|
||||
file.getParentFile().mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
GZIPOutputStream gzo = new CustomOutputStream(fos, 9);
|
||||
DataOutputStream dos = new DataOutputStream(gzo);
|
||||
dos.writeInt(firstAccess.size());
|
||||
var set = firstAccess;
|
||||
firstAccess = null;
|
||||
dos.writeInt(set.size());
|
||||
|
||||
for (String i : firstAccess) {
|
||||
for (String i : set) {
|
||||
dos.writeUTF(i);
|
||||
}
|
||||
|
||||
@@ -358,29 +455,24 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
}
|
||||
|
||||
public KList<File> getFolders() {
|
||||
synchronized (folderCache) {
|
||||
if (folderCache.get() == null) {
|
||||
KList<File> fc = new KList<>();
|
||||
return folderCache.aquire(() -> {
|
||||
KList<File> fc = new KList<>();
|
||||
|
||||
File[] files = root.listFiles();
|
||||
if (files == null) {
|
||||
throw new IllegalStateException("Failed to list files in " + root);
|
||||
}
|
||||
File[] files = root.listFiles();
|
||||
if (files == null) {
|
||||
throw new IllegalStateException("Failed to list files in " + root);
|
||||
}
|
||||
|
||||
for (File i : files) {
|
||||
if (i.isDirectory()) {
|
||||
if (i.getName().equals(folderName)) {
|
||||
fc.add(i);
|
||||
break;
|
||||
}
|
||||
for (File i : files) {
|
||||
if (i.isDirectory()) {
|
||||
if (i.getName().equals(folderName)) {
|
||||
fc.add(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
folderCache.set(fc);
|
||||
}
|
||||
}
|
||||
|
||||
return folderCache.get();
|
||||
return fc;
|
||||
});
|
||||
}
|
||||
|
||||
public KList<File> getFolders(String rc) {
|
||||
@@ -400,7 +492,7 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
public void clearCache() {
|
||||
possibleKeys = null;
|
||||
loadCache.invalidate();
|
||||
folderCache.set(null);
|
||||
folderCache.reset();
|
||||
}
|
||||
|
||||
public File fileFor(T b) {
|
||||
@@ -426,7 +518,7 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
||||
}
|
||||
|
||||
public void clearList() {
|
||||
folderCache.set(null);
|
||||
folderCache.reset();
|
||||
possibleKeys = null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core.nms;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.nms.v1X.NMSBinding1X;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class INMS {
|
||||
private static final Version CURRENT = Boolean.getBoolean("iris.no-version-limit") ?
|
||||
new Version(Integer.MAX_VALUE, Integer.MAX_VALUE, null) :
|
||||
new Version(21, 11, null);
|
||||
|
||||
private static final List<Version> REVISION = List.of(
|
||||
new Version(21, 11, "v1_21_R7")
|
||||
);
|
||||
|
||||
private static final List<Version> PACKS = List.of(
|
||||
new Version(21, 11, "31100")
|
||||
);
|
||||
|
||||
//@done
|
||||
private static final INMSBinding binding = bind();
|
||||
public static final String OVERWORLD_TAG = getTag(PACKS, "31100");
|
||||
|
||||
public static INMSBinding get() {
|
||||
return binding;
|
||||
}
|
||||
|
||||
public static String getNMSTag() {
|
||||
if (IrisSettings.get().getGeneral().isDisableNMS()) {
|
||||
return "BUKKIT";
|
||||
}
|
||||
|
||||
try {
|
||||
String name = Bukkit.getServer().getClass().getCanonicalName();
|
||||
if (name.equals("org.bukkit.craftbukkit.CraftServer")) {
|
||||
return getTag(REVISION, "BUKKIT");
|
||||
} else {
|
||||
return name.split("\\Q.\\E")[3];
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
Iris.error("Failed to determine server nms version!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return "BUKKIT";
|
||||
}
|
||||
|
||||
private static INMSBinding bind() {
|
||||
String code = getNMSTag();
|
||||
boolean disableNms = IrisSettings.get().getGeneral().isDisableNMS();
|
||||
List<String> probeCodes = NmsBindingProbeSupport.getBindingProbeCodes(code, disableNms, getFallbackBindingCodes());
|
||||
if ("BUKKIT".equals(code) && !disableNms) {
|
||||
Iris.info("NMS tag resolution fell back to Bukkit; probing supported revision bindings.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < probeCodes.size(); i++) {
|
||||
INMSBinding resolvedBinding = tryBind(probeCodes.get(i), i == 0);
|
||||
if (resolvedBinding != null) {
|
||||
return resolvedBinding;
|
||||
}
|
||||
}
|
||||
|
||||
if (disableNms) {
|
||||
Iris.info("Craftbukkit " + code + " <-> " + NMSBinding1X.class.getSimpleName() + " Successfully Bound");
|
||||
Iris.warn("Note: NMS support is disabled. Iris is running in limited Bukkit fallback mode.");
|
||||
return new NMSBinding1X();
|
||||
}
|
||||
|
||||
MinecraftVersion detectedVersion = getMinecraftVersion();
|
||||
String serverVersion = detectedVersion == null ? Bukkit.getServer().getVersion() : detectedVersion.value();
|
||||
throw new IllegalStateException("Iris requires Minecraft 1.21.11 or newer. Detected server version: " + serverVersion);
|
||||
}
|
||||
|
||||
private static String getTag(List<Version> versions, String def) {
|
||||
MinecraftVersion detectedVersion = getMinecraftVersion();
|
||||
if (detectedVersion == null) {
|
||||
return def;
|
||||
}
|
||||
|
||||
if (detectedVersion.isNewerThan(CURRENT.major, CURRENT.minor)) {
|
||||
return versions.getFirst().tag;
|
||||
}
|
||||
|
||||
for (Version p : versions) {
|
||||
if (!detectedVersion.isAtLeast(p.major, p.minor)) {
|
||||
continue;
|
||||
}
|
||||
return p.tag;
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
private static MinecraftVersion getMinecraftVersion() {
|
||||
try {
|
||||
return MinecraftVersion.detect(Bukkit.getServer());
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
Iris.error("Failed to determine server minecraft version!");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static INMSBinding tryBind(String code, boolean announce) {
|
||||
if (announce) {
|
||||
Iris.info("Locating NMS Binding for " + code);
|
||||
} else {
|
||||
Iris.info("Probing NMS Binding for " + code);
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> clazz = Class.forName("art.arcane.iris.core.nms." + code + ".NMSBinding");
|
||||
Object candidate = clazz.getConstructor().newInstance();
|
||||
if (candidate instanceof INMSBinding binding) {
|
||||
Iris.info("Craftbukkit " + code + " <-> " + candidate.getClass().getSimpleName() + " Successfully Bound");
|
||||
return binding;
|
||||
}
|
||||
} catch (ClassNotFoundException | NoClassDefFoundError classNotFoundException) {
|
||||
Iris.warn("Failed to load NMS binding class for " + code + ": " + classNotFoundException.getMessage());
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Set<String> getFallbackBindingCodes() {
|
||||
Set<String> codes = new LinkedHashSet<>();
|
||||
for (Version version : REVISION) {
|
||||
if (version.tag != null && !version.tag.isBlank()) {
|
||||
codes.add(version.tag);
|
||||
}
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
|
||||
private record Version(int major, int minor, String tag) {}
|
||||
}
|
||||
+57
-25
@@ -16,20 +16,26 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.nms;
|
||||
package art.arcane.iris.core.nms;
|
||||
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.math.Vector3d;
|
||||
import com.volmit.iris.util.nbt.mca.palette.MCABiomeContainer;
|
||||
import com.volmit.iris.util.nbt.mca.palette.MCAPaletteAccess;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.core.lifecycle.WorldLifecycleCaller;
|
||||
import art.arcane.iris.core.lifecycle.WorldLifecycleRequest;
|
||||
import art.arcane.iris.core.lifecycle.WorldLifecycleService;
|
||||
import art.arcane.iris.core.nms.container.BiomeColor;
|
||||
import art.arcane.iris.core.nms.container.BlockProperty;
|
||||
import art.arcane.iris.core.nms.datapack.DataVersion;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.math.Vector3d;
|
||||
import art.arcane.volmlib.util.nbt.mca.palette.MCABiomeContainer;
|
||||
import art.arcane.volmlib.util.nbt.mca.palette.MCAPaletteAccess;
|
||||
import art.arcane.volmlib.util.nbt.tag.CompoundTag;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.entity.Entity;
|
||||
@@ -39,6 +45,8 @@ import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface INMSBinding {
|
||||
boolean hasTile(Material material);
|
||||
@@ -90,17 +98,34 @@ public interface INMSBinding {
|
||||
MCABiomeContainer newBiomeContainer(int min, int max);
|
||||
|
||||
default World createWorld(WorldCreator c) {
|
||||
if (missingDimensionTypes(true, true, true))
|
||||
throw new IllegalStateException("Missing dimenstion types to create world");
|
||||
WorldLifecycleRequest request = WorldLifecycleRequest.fromCreator(c, false, false, WorldLifecycleCaller.CREATE);
|
||||
return createWorld(c, request);
|
||||
}
|
||||
|
||||
try (var ignored = injectLevelStems()) {
|
||||
return c.createWorld();
|
||||
default CompletableFuture<World> createWorldAsync(WorldCreator c) {
|
||||
WorldLifecycleRequest request = WorldLifecycleRequest.fromCreator(c, false, false, WorldLifecycleCaller.CREATE);
|
||||
return createWorldAsync(c, request);
|
||||
}
|
||||
|
||||
default World createWorld(WorldCreator c, WorldLifecycleRequest request) {
|
||||
validateDimensionTypes(c);
|
||||
return WorldLifecycleService.get().createBlocking(request);
|
||||
}
|
||||
|
||||
default CompletableFuture<World> createWorldAsync(WorldCreator c, WorldLifecycleRequest request) {
|
||||
try {
|
||||
validateDimensionTypes(c);
|
||||
return WorldLifecycleService.get().create(request);
|
||||
} catch (Throwable e) {
|
||||
return CompletableFuture.failedFuture(e);
|
||||
}
|
||||
}
|
||||
|
||||
int countCustomBiomes();
|
||||
default Object createRuntimeLevelStem(Object registryAccess, ChunkGenerator raw) {
|
||||
throw new UnsupportedOperationException("Active NMS binding does not support runtime LevelStem creation.");
|
||||
}
|
||||
|
||||
void forceBiomeInto(int x, int y, int z, Object somethingVeryDirty, ChunkGenerator.BiomeGrid chunk);
|
||||
int countCustomBiomes();
|
||||
|
||||
default boolean supportsDataPacks() {
|
||||
return false;
|
||||
@@ -108,7 +133,7 @@ public interface INMSBinding {
|
||||
|
||||
MCAPaletteAccess createPalette();
|
||||
|
||||
void injectBiomesFromMantle(Chunk e, Mantle mantle);
|
||||
void injectBiomesFromMantle(Chunk e, Mantle<Matter> mantle);
|
||||
|
||||
ItemStack applyCustomNbt(ItemStack itemStack, KMap<String, Object> customNbt) throws IllegalArgumentException;
|
||||
|
||||
@@ -121,18 +146,25 @@ public interface INMSBinding {
|
||||
Color getBiomeColor(Location location, BiomeColor type);
|
||||
|
||||
default DataVersion getDataVersion() {
|
||||
return DataVersion.V1192;
|
||||
return DataVersion.V1_19_2;
|
||||
}
|
||||
|
||||
default int getSpawnChunkCount(World world) {
|
||||
return 441;
|
||||
}
|
||||
|
||||
KList<String> getStructureKeys();
|
||||
boolean missingDimensionTypes(String... keys);
|
||||
|
||||
AutoClosing injectLevelStems();
|
||||
default boolean injectBukkit() {
|
||||
return true;
|
||||
}
|
||||
|
||||
Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end);
|
||||
KMap<Material, List<BlockProperty>> getBlockProperties();
|
||||
|
||||
boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end);
|
||||
private void validateDimensionTypes(WorldCreator c) {
|
||||
if (c.generator() instanceof PlatformChunkGenerator gen
|
||||
&& missingDimensionTypes(gen.getTarget().getDimension().getDimensionTypeKey())) {
|
||||
throw new IllegalStateException("Missing dimension types to create world");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package art.arcane.iris.core.nms;
|
||||
|
||||
import org.bukkit.Server;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class MinecraftVersion {
|
||||
private static final Pattern DECORATED_VERSION_PATTERN = Pattern.compile("\\(MC: ([0-9]+(?:\\.[0-9]+){0,2})\\)");
|
||||
|
||||
private final String value;
|
||||
private final int major;
|
||||
private final int minor;
|
||||
|
||||
private MinecraftVersion(String value, int major, int minor) {
|
||||
this.value = value;
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
}
|
||||
|
||||
public static MinecraftVersion detect(Server server) {
|
||||
if (server == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MinecraftVersion runtimeVersion = fromRuntimeMinecraftVersion(server);
|
||||
if (runtimeVersion != null) {
|
||||
return runtimeVersion;
|
||||
}
|
||||
|
||||
MinecraftVersion decoratedVersion = fromDecoratedVersion(server.getVersion());
|
||||
if (decoratedVersion != null) {
|
||||
return decoratedVersion;
|
||||
}
|
||||
|
||||
return fromBukkitVersion(server.getBukkitVersion());
|
||||
}
|
||||
|
||||
static MinecraftVersion fromRuntimeMinecraftVersion(Server server) {
|
||||
try {
|
||||
Method method = server.getClass().getMethod("getMinecraftVersion");
|
||||
Object value = method.invoke(server);
|
||||
if (value instanceof String version) {
|
||||
return fromVersionToken(version);
|
||||
}
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static MinecraftVersion fromDecoratedVersion(String input) {
|
||||
if (input == null || input.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Matcher matcher = DECORATED_VERSION_PATTERN.matcher(input);
|
||||
if (!matcher.find()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fromVersionToken(matcher.group(1));
|
||||
}
|
||||
|
||||
static MinecraftVersion fromBukkitVersion(String input) {
|
||||
if (input == null || input.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String versionToken = input.split("-", 2)[0].trim();
|
||||
return fromVersionToken(versionToken);
|
||||
}
|
||||
|
||||
private static MinecraftVersion fromVersionToken(String input) {
|
||||
if (input == null || input.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] parts = input.split("\\.");
|
||||
if (parts.length < 2 || !"1".equals(parts[0])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
int major = Integer.parseInt(parts[1]);
|
||||
int minor = parts.length > 2 ? Integer.parseInt(parts[2]) : 0;
|
||||
return new MinecraftVersion(input, major, minor);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public int major() {
|
||||
return major;
|
||||
}
|
||||
|
||||
public int minor() {
|
||||
return minor;
|
||||
}
|
||||
|
||||
public boolean isAtLeast(int major, int minor) {
|
||||
return this.major > major || (this.major == major && this.minor >= minor);
|
||||
}
|
||||
|
||||
public boolean isNewerThan(int major, int minor) {
|
||||
return this.major > major || (this.major == major && this.minor > minor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package art.arcane.iris.core.nms;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
final class NmsBindingProbeSupport {
|
||||
private NmsBindingProbeSupport() {
|
||||
}
|
||||
|
||||
static List<String> getBindingProbeCodes(String code, boolean disableNms, Collection<String> fallbackCodes) {
|
||||
List<String> probeCodes = new ArrayList<>();
|
||||
if (code == null || code.isBlank()) {
|
||||
return probeCodes;
|
||||
}
|
||||
|
||||
if (!"BUKKIT".equals(code)) {
|
||||
probeCodes.add(code);
|
||||
return probeCodes;
|
||||
}
|
||||
|
||||
if (disableNms || fallbackCodes == null) {
|
||||
return probeCodes;
|
||||
}
|
||||
|
||||
probeCodes.addAll(fallbackCodes);
|
||||
return probeCodes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package art.arcane.iris.core.nms.container;
|
||||
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.function.NastyRunnable;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class AutoClosing implements AutoCloseable {
|
||||
private static final KMap<Thread, AutoClosing> CONTEXTS = new KMap<>();
|
||||
private final AtomicBoolean closed = new AtomicBoolean();
|
||||
private final NastyRunnable action;
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (closed.getAndSet(true)) return;
|
||||
try {
|
||||
removeContext();
|
||||
action.run();
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void storeContext() {
|
||||
CONTEXTS.put(Thread.currentThread(), this);
|
||||
}
|
||||
|
||||
public void removeContext() {
|
||||
CONTEXTS.values().removeIf(c -> c == this);
|
||||
}
|
||||
|
||||
public static void closeContext() {
|
||||
AutoClosing closing = CONTEXTS.remove(Thread.currentThread());
|
||||
if (closing == null) return;
|
||||
closing.close();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.volmit.iris.core.nms.container;
|
||||
package art.arcane.iris.core.nms.container;
|
||||
|
||||
public enum BiomeColor {
|
||||
FOG,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.volmit.iris.core.nms.container;
|
||||
package art.arcane.iris.core.nms.container;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -0,0 +1,198 @@
|
||||
package art.arcane.iris.core.nms.container;
|
||||
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class BlockProperty {
|
||||
private static final Set<Class<?>> NATIVES = Set.of(Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Boolean.class, String.class);
|
||||
private final String name;
|
||||
private final Class<?> type;
|
||||
|
||||
private final Object defaultValue;
|
||||
private final Set<Object> values;
|
||||
private final Function<Object, String> nameFunction;
|
||||
private final Function<Object, Object> jsonFunction;
|
||||
|
||||
public <T extends Comparable<T>> BlockProperty(
|
||||
String name,
|
||||
Class<T> type,
|
||||
T defaultValue,
|
||||
Collection<T> values,
|
||||
Function<T, String> nameFunction
|
||||
) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.defaultValue = defaultValue;
|
||||
this.values = Collections.unmodifiableSet(new TreeSet<>(values));
|
||||
this.nameFunction = (Function<Object, String>) (Object) nameFunction;
|
||||
jsonFunction = NATIVES.contains(type) ? Function.identity() : this.nameFunction::apply;
|
||||
}
|
||||
|
||||
public static <T extends Enum<T>> BlockProperty ofEnum(Class<T> type, String name, T defaultValue) {
|
||||
return new BlockProperty(
|
||||
name,
|
||||
type,
|
||||
defaultValue,
|
||||
Arrays.asList(type.getEnumConstants()),
|
||||
val -> val == null ? "null" : val.name()
|
||||
);
|
||||
}
|
||||
|
||||
public static BlockProperty ofDouble(String name, float defaultValue, float min, float max, boolean exclusiveMin, boolean exclusiveMax) {
|
||||
return new BoundedDouble(
|
||||
name,
|
||||
defaultValue,
|
||||
min,
|
||||
max,
|
||||
exclusiveMin,
|
||||
exclusiveMax,
|
||||
(f) -> String.format("%.2f", f)
|
||||
);
|
||||
}
|
||||
|
||||
public static BlockProperty ofLong(String name, long defaultValue, long min, long max, boolean exclusiveMin, boolean exclusiveMax) {
|
||||
return new BoundedLong(
|
||||
name,
|
||||
defaultValue,
|
||||
min,
|
||||
max,
|
||||
exclusiveMin,
|
||||
exclusiveMax,
|
||||
value -> Long.toString(value)
|
||||
);
|
||||
}
|
||||
|
||||
public static BlockProperty ofBoolean(String name, boolean defaultValue) {
|
||||
return new BlockProperty(
|
||||
name,
|
||||
Boolean.class,
|
||||
defaultValue,
|
||||
List.of(true, false),
|
||||
(b) -> b ? "true" : "false"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return name + "=" + nameFunction.apply(defaultValue) + " [" + String.join(",", names()) + "]";
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String defaultValue() {
|
||||
return nameFunction.apply(defaultValue);
|
||||
}
|
||||
|
||||
public List<String> names() {
|
||||
return values.stream().map(nameFunction).toList();
|
||||
}
|
||||
|
||||
public Object defaultValueAsJson() {
|
||||
return jsonFunction.apply(defaultValue);
|
||||
}
|
||||
|
||||
public JSONArray valuesAsJson() {
|
||||
return new JSONArray(values.stream().map(jsonFunction).toList());
|
||||
}
|
||||
|
||||
public JSONObject buildJson() {
|
||||
var json = new JSONObject();
|
||||
json.put("type", jsonType());
|
||||
json.put("default", defaultValueAsJson());
|
||||
if (!values.isEmpty()) json.put("enum", valuesAsJson());
|
||||
return json;
|
||||
}
|
||||
|
||||
public String jsonType() {
|
||||
if (type == Boolean.class)
|
||||
return "boolean";
|
||||
if (type == Byte.class || type == Short.class || type == Integer.class || type == Long.class)
|
||||
return "integer";
|
||||
if (type == Float.class || type == Double.class)
|
||||
return "number";
|
||||
return "string";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||
var that = (BlockProperty) obj;
|
||||
return Objects.equals(this.name, that.name) &&
|
||||
Objects.equals(this.values, that.values) &&
|
||||
Objects.equals(this.type, that.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, values, type);
|
||||
}
|
||||
|
||||
private static class BoundedLong extends BlockProperty {
|
||||
private final long min;
|
||||
private final long max;
|
||||
private final boolean exclusiveMin;
|
||||
private final boolean exclusiveMax;
|
||||
|
||||
public BoundedLong(
|
||||
String name,
|
||||
long defaultValue,
|
||||
long min,
|
||||
long max,
|
||||
boolean exclusiveMin,
|
||||
boolean exclusiveMax,
|
||||
Function<Long, String> nameFunction
|
||||
) {
|
||||
super(name, Long.class, defaultValue, List.of(), nameFunction);
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.exclusiveMin = exclusiveMin;
|
||||
this.exclusiveMax = exclusiveMax;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject buildJson() {
|
||||
return super.buildJson()
|
||||
.put("minimum", min)
|
||||
.put("maximum", max)
|
||||
.put("exclusiveMinimum", exclusiveMin)
|
||||
.put("exclusiveMaximum", exclusiveMax);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BoundedDouble extends BlockProperty {
|
||||
private final double min, max;
|
||||
private final boolean exclusiveMin, exclusiveMax;
|
||||
|
||||
public BoundedDouble(
|
||||
String name,
|
||||
double defaultValue,
|
||||
double min,
|
||||
double max,
|
||||
boolean exclusiveMin,
|
||||
boolean exclusiveMax,
|
||||
Function<Double, String> nameFunction
|
||||
) {
|
||||
super(name, Double.class, defaultValue, List.of(), nameFunction);
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.exclusiveMin = exclusiveMin;
|
||||
this.exclusiveMax = exclusiveMax;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject buildJson() {
|
||||
return super.buildJson()
|
||||
.put("minimum", min)
|
||||
.put("maximum", max)
|
||||
.put("exclusiveMinimum", exclusiveMin)
|
||||
.put("exclusiveMaximum", exclusiveMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.volmit.iris.core.nms.container;
|
||||
package art.arcane.iris.core.nms.container;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -0,0 +1,60 @@
|
||||
package art.arcane.iris.core.nms.datapack;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.iris.core.nms.datapack.v1192.DataFixerV1192;
|
||||
import art.arcane.iris.core.nms.datapack.v1206.DataFixerV1206;
|
||||
import art.arcane.iris.core.nms.datapack.v1213.DataFixerV1213;
|
||||
import art.arcane.iris.core.nms.datapack.v1217.DataFixerV1217;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
//https://minecraft.wiki/w/Pack_format
|
||||
@Getter
|
||||
public enum DataVersion {
|
||||
UNSUPPORTED("0.0.0", 0, () -> null),
|
||||
V1_19_2("1.19.2", 10, DataFixerV1192::new),
|
||||
V1_20_5("1.20.6", 41, DataFixerV1206::new),
|
||||
V1_21_3("1.21.3", 57, DataFixerV1213::new),
|
||||
V1_21_11("1.21.11", 75, DataFixerV1217::new);
|
||||
private static final KMap<DataVersion, IDataFixer> cache = new KMap<>();
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final Supplier<IDataFixer> constructor;
|
||||
private final String version;
|
||||
private final int packFormat;
|
||||
|
||||
DataVersion(String version, int packFormat, Supplier<IDataFixer> constructor) {
|
||||
this.constructor = constructor;
|
||||
this.packFormat = packFormat;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public IDataFixer get() {
|
||||
return cache.computeIfAbsent(this, k -> constructor.get());
|
||||
}
|
||||
|
||||
public static IDataFixer getDefault() {
|
||||
DataVersion version = INMS.get().getDataVersion();
|
||||
if (version == null || version == UNSUPPORTED) {
|
||||
DataVersion fallback = getLatest();
|
||||
Iris.warn("Unsupported datapack version mapping detected, falling back to latest fixer: " + fallback.getVersion());
|
||||
return fallback.get();
|
||||
}
|
||||
|
||||
IDataFixer fixer = version.get();
|
||||
if (fixer == null) {
|
||||
DataVersion fallback = getLatest();
|
||||
Iris.warn("Null datapack fixer for " + version.getVersion() + ", falling back to latest fixer: " + fallback.getVersion());
|
||||
return fallback.get();
|
||||
}
|
||||
|
||||
return fixer;
|
||||
}
|
||||
|
||||
public static DataVersion getLatest() {
|
||||
return values()[values().length - 1];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package art.arcane.iris.core.nms.datapack;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||
import art.arcane.iris.engine.object.IrisDimensionTypeOptions;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface IDataFixer {
|
||||
default JSONObject fixCustomBiome(IrisBiomeCustom biome, JSONObject json) {
|
||||
return json;
|
||||
}
|
||||
|
||||
JSONObject resolve(Dimension dimension, @Nullable IrisDimensionTypeOptions options);
|
||||
|
||||
void fixDimension(Dimension dimension, JSONObject json);
|
||||
|
||||
default JSONObject createDimension(Dimension base, int minY, int height, int logicalHeight, @Nullable IrisDimensionTypeOptions options) {
|
||||
JSONObject obj = resolve(base, options);
|
||||
obj.put("min_y", minY);
|
||||
obj.put("height", height);
|
||||
obj.put("logical_height", logicalHeight);
|
||||
fixDimension(base, obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
enum Dimension {
|
||||
OVERWORLD,
|
||||
NETHER,
|
||||
END
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package art.arcane.iris.core.nms.datapack.v1192;
|
||||
|
||||
import art.arcane.iris.core.nms.datapack.IDataFixer;
|
||||
import art.arcane.iris.engine.object.IrisDimensionTypeOptions;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static art.arcane.iris.engine.object.IrisDimensionTypeOptions.TriState.*;
|
||||
|
||||
public class DataFixerV1192 implements IDataFixer {
|
||||
private static final Map<Dimension, IrisDimensionTypeOptions> OPTIONS = Map.of(
|
||||
Dimension.OVERWORLD, new IrisDimensionTypeOptions(
|
||||
FALSE,
|
||||
TRUE,
|
||||
FALSE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
TRUE,
|
||||
TRUE,
|
||||
FALSE,
|
||||
1d,
|
||||
0f,
|
||||
null,
|
||||
192,
|
||||
0),
|
||||
Dimension.NETHER, new IrisDimensionTypeOptions(
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
TRUE,
|
||||
FALSE,
|
||||
FALSE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
8d,
|
||||
0.1f,
|
||||
18000L,
|
||||
null,
|
||||
15),
|
||||
Dimension.END, new IrisDimensionTypeOptions(
|
||||
FALSE,
|
||||
FALSE,
|
||||
FALSE,
|
||||
FALSE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
FALSE,
|
||||
FALSE,
|
||||
1d,
|
||||
0f,
|
||||
6000L,
|
||||
null,
|
||||
0)
|
||||
);
|
||||
|
||||
private static final Map<Dimension, String> DIMENSIONS = Map.of(
|
||||
Dimension.OVERWORLD, """
|
||||
{
|
||||
"effects": "minecraft:overworld",
|
||||
"infiniburn": "#minecraft:infiniburn_overworld",
|
||||
"monster_spawn_light_level": {
|
||||
"type": "minecraft:uniform",
|
||||
"value": {
|
||||
"max_inclusive": 7,
|
||||
"min_inclusive": 0
|
||||
}
|
||||
}
|
||||
}""",
|
||||
Dimension.NETHER, """
|
||||
{
|
||||
"effects": "minecraft:the_nether",
|
||||
"infiniburn": "#minecraft:infiniburn_nether",
|
||||
"monster_spawn_light_level": 7,
|
||||
}""",
|
||||
Dimension.END, """
|
||||
{
|
||||
"effects": "minecraft:the_end",
|
||||
"infiniburn": "#minecraft:infiniburn_end",
|
||||
"monster_spawn_light_level": {
|
||||
"type": "minecraft:uniform",
|
||||
"value": {
|
||||
"max_inclusive": 7,
|
||||
"min_inclusive": 0
|
||||
}
|
||||
}
|
||||
}"""
|
||||
);
|
||||
|
||||
@Override
|
||||
public JSONObject resolve(Dimension dimension, @Nullable IrisDimensionTypeOptions options) {
|
||||
return options == null ? OPTIONS.get(dimension).toJson() : options.resolve(OPTIONS.get(dimension)).toJson();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fixDimension(Dimension dimension, JSONObject json) {
|
||||
var missing = new JSONObject(DIMENSIONS.get(dimension));
|
||||
for (String key : missing.keySet()) {
|
||||
if (json.has(key)) continue;
|
||||
json.put(key, missing.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
-14
@@ -1,12 +1,15 @@
|
||||
package com.volmit.iris.core.nms.datapack.v1206;
|
||||
package art.arcane.iris.core.nms.datapack.v1206;
|
||||
|
||||
import com.volmit.iris.core.nms.datapack.v1192.DataFixerV1192;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustomSpawn;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustomSpawnType;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.json.JSONArray;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.nms.datapack.v1192.DataFixerV1192;
|
||||
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||
import art.arcane.iris.engine.object.IrisBiomeCustomSpawn;
|
||||
import art.arcane.iris.engine.object.IrisBiomeCustomSpawnType;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -26,9 +29,23 @@ public class DataFixerV1206 extends DataFixerV1192 {
|
||||
KMap<IrisBiomeCustomSpawnType, JSONArray> groups = new KMap<>();
|
||||
|
||||
for (IrisBiomeCustomSpawn i : spawns) {
|
||||
JSONArray g = groups.computeIfAbsent(i.getGroup(), (k) -> new JSONArray());
|
||||
if (i == null) {
|
||||
continue;
|
||||
}
|
||||
EntityType type = i.getType();
|
||||
if (type == null) {
|
||||
Iris.warn("Skipping custom biome spawn with null entity type in biome " + biome.getId());
|
||||
continue;
|
||||
}
|
||||
IrisBiomeCustomSpawnType group = i.getGroup() == null ? IrisBiomeCustomSpawnType.MISC : i.getGroup();
|
||||
JSONArray g = groups.computeIfAbsent(group, (k) -> new JSONArray());
|
||||
JSONObject o = new JSONObject();
|
||||
o.put("type", i.getType().getKey());
|
||||
NamespacedKey key = type.getKey();
|
||||
if (key == null) {
|
||||
Iris.warn("Skipping custom biome spawn with unresolved entity key in biome " + biome.getId());
|
||||
continue;
|
||||
}
|
||||
o.put("type", key.toString());
|
||||
o.put("weight", i.getWeight());
|
||||
o.put("minCount", i.getMinCount());
|
||||
o.put("maxCount", i.getMaxCount());
|
||||
@@ -45,13 +62,12 @@ public class DataFixerV1206 extends DataFixerV1192 {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject rawDimension(Dimension dimension) {
|
||||
JSONObject json = super.rawDimension(dimension);
|
||||
public void fixDimension(Dimension dimension, JSONObject json) {
|
||||
super.fixDimension(dimension, json);
|
||||
if (!(json.get("monster_spawn_light_level") instanceof JSONObject lightLevel))
|
||||
return json;
|
||||
return;
|
||||
var value = (JSONObject) lightLevel.remove("value");
|
||||
lightLevel.put("max_inclusive", value.get("max_inclusive"));
|
||||
lightLevel.put("min_inclusive", value.get("min_inclusive"));
|
||||
return json;
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
package com.volmit.iris.core.nms.datapack.v1213;
|
||||
package art.arcane.iris.core.nms.datapack.v1213;
|
||||
|
||||
import com.volmit.iris.core.nms.datapack.v1206.DataFixerV1206;
|
||||
import com.volmit.iris.engine.object.IrisBiomeCustom;
|
||||
import com.volmit.iris.util.json.JSONArray;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import art.arcane.iris.core.nms.datapack.v1206.DataFixerV1206;
|
||||
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
|
||||
public class DataFixerV1213 extends DataFixerV1206 {
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package art.arcane.iris.core.nms.datapack.v1217;
|
||||
|
||||
import art.arcane.iris.core.nms.datapack.v1213.DataFixerV1213;
|
||||
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class DataFixerV1217 extends DataFixerV1213 {
|
||||
private static final Map<Dimension, String> DIMENSIONS = Map.of(
|
||||
Dimension.OVERWORLD, """
|
||||
{
|
||||
"ambient_light": 0.0,
|
||||
"has_ender_dragon_fight": false,
|
||||
"attributes": {
|
||||
"minecraft:audio/ambient_sounds": {
|
||||
"mood": {
|
||||
"block_search_extent": 8,
|
||||
"offset": 2.0,
|
||||
"sound": "minecraft:ambient.cave",
|
||||
"tick_delay": 6000
|
||||
}
|
||||
},
|
||||
"minecraft:audio/background_music": {
|
||||
"creative": {
|
||||
"max_delay": 24000,
|
||||
"min_delay": 12000,
|
||||
"sound": "minecraft:music.creative"
|
||||
},
|
||||
"default": {
|
||||
"max_delay": 24000,
|
||||
"min_delay": 12000,
|
||||
"sound": "minecraft:music.game"
|
||||
}
|
||||
},
|
||||
"minecraft:visual/cloud_color": "#ccffffff",
|
||||
"minecraft:visual/fog_color": "#c0d8ff",
|
||||
"minecraft:visual/sky_color": "#78a7ff"
|
||||
},
|
||||
"timelines": "#minecraft:in_overworld"
|
||||
}""",
|
||||
Dimension.NETHER, """
|
||||
{
|
||||
"ambient_light": 0.1,
|
||||
"has_ender_dragon_fight": false,
|
||||
"attributes": {
|
||||
"minecraft:gameplay/sky_light_level": 4.0,
|
||||
"minecraft:gameplay/snow_golem_melts": true,
|
||||
"minecraft:visual/fog_end_distance": 96.0,
|
||||
"minecraft:visual/fog_start_distance": 10.0,
|
||||
"minecraft:visual/sky_light_color": "#7a7aff",
|
||||
"minecraft:visual/sky_light_factor": 0.0
|
||||
},
|
||||
"cardinal_light": "nether",
|
||||
"skybox": "none",
|
||||
"timelines": "#minecraft:in_nether"
|
||||
}""",
|
||||
Dimension.END, """
|
||||
{
|
||||
"ambient_light": 0.25,
|
||||
"has_ender_dragon_fight": true,
|
||||
"attributes": {
|
||||
"minecraft:audio/ambient_sounds": {
|
||||
"mood": {
|
||||
"block_search_extent": 8,
|
||||
"offset": 2.0,
|
||||
"sound": "minecraft:ambient.cave",
|
||||
"tick_delay": 6000
|
||||
}
|
||||
},
|
||||
"minecraft:audio/background_music": {
|
||||
"default": {
|
||||
"max_delay": 24000,
|
||||
"min_delay": 6000,
|
||||
"replace_current_music": true,
|
||||
"sound": "minecraft:music.end"
|
||||
}
|
||||
},
|
||||
"minecraft:visual/fog_color": "#181318",
|
||||
"minecraft:visual/sky_color": "#000000",
|
||||
"minecraft:visual/sky_light_color": "#e580ff",
|
||||
"minecraft:visual/sky_light_factor": 0.0
|
||||
},
|
||||
"skybox": "end",
|
||||
"timelines": "#minecraft:in_end"
|
||||
}"""
|
||||
);
|
||||
|
||||
@Override
|
||||
public JSONObject fixCustomBiome(IrisBiomeCustom biome, JSONObject json) {
|
||||
json = super.fixCustomBiome(biome, json);
|
||||
var effects = json.getJSONObject("effects");
|
||||
var attributes = new JSONObject();
|
||||
|
||||
attributes.put("minecraft:visual/fog_color", effects.remove("fog_color"));
|
||||
attributes.put("minecraft:visual/sky_color", effects.remove("sky_color"));
|
||||
attributes.put("minecraft:visual/water_fog_color", effects.remove("water_fog_color"));
|
||||
|
||||
JSONObject particle = (JSONObject) effects.remove("particle");
|
||||
if (particle != null) {
|
||||
particle.put("particle", particle.remove("options"));
|
||||
attributes.put("minecraft:visual/ambient_particles", new JSONArray()
|
||||
.put(particle));
|
||||
}
|
||||
json.put("attributes", attributes);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fixDimension(Dimension dimension, JSONObject json) {
|
||||
super.fixDimension(dimension, json);
|
||||
|
||||
var attributes = new JSONObject();
|
||||
if ((Boolean) json.remove("ultrawarm")) {
|
||||
attributes.put("minecraft:gameplay/water_evaporates", true);
|
||||
attributes.put("minecraft:gameplay/fast_lava", true);
|
||||
attributes.put("minecraft:gameplay/snow_golem_melts", true);
|
||||
attributes.put("minecraft:visual/default_dripstone_particle", new JSONObject()
|
||||
.put("type", "minecraft:dripping_dripstone_lava"));
|
||||
}
|
||||
|
||||
if ((Boolean) json.remove("bed_works")) {
|
||||
attributes.put("minecraft:gameplay/bed_rule", new JSONObject()
|
||||
.put("can_set_spawn", "always")
|
||||
.put("can_sleep", "when_dark")
|
||||
.put("error_message", new JSONObject()
|
||||
.put("translate", "block.minecraft.bed.no_sleep")));
|
||||
} else {
|
||||
attributes.put("minecraft:gameplay/bed_rule", new JSONObject()
|
||||
.put("can_set_spawn", "never")
|
||||
.put("can_sleep", "never")
|
||||
.put("explodes", true));
|
||||
}
|
||||
|
||||
attributes.put("minecraft:gameplay/respawn_anchor_works", json.remove("respawn_anchor_works"));
|
||||
attributes.put("minecraft:gameplay/piglins_zombify", !(Boolean) json.remove("piglin_safe"));
|
||||
attributes.put("minecraft:gameplay/can_start_raid", json.remove("has_raids"));
|
||||
|
||||
var cloud_height = json.remove("cloud_height");
|
||||
if (cloud_height != null) attributes.put("minecraft:visual/cloud_height", cloud_height);
|
||||
|
||||
boolean natural = (Boolean) json.remove("natural");
|
||||
attributes.put("minecraft:gameplay/nether_portal_spawns_piglin", natural);
|
||||
if (natural != (dimension == Dimension.OVERWORLD)) {
|
||||
attributes.put("minecraft:gameplay/eyeblossom_open", natural);
|
||||
attributes.put("minecraft:gameplay/creaking_active", natural);
|
||||
}
|
||||
|
||||
//json.put("has_fixed_time", json.remove("fixed_time") != null); //TODO investigate
|
||||
json.put("attributes", attributes);
|
||||
|
||||
json.remove("effects");
|
||||
var defaults = new JSONObject(DIMENSIONS.get(dimension));
|
||||
merge(json, defaults);
|
||||
}
|
||||
|
||||
private void merge(JSONObject base, JSONObject override) {
|
||||
for (String key : override.keySet()) {
|
||||
switch (base.opt(key)) {
|
||||
case null -> base.put(key, override.opt(key));
|
||||
case JSONObject base1 when override.opt(key) instanceof JSONObject override1 -> merge(base1, override1);
|
||||
case JSONArray base1 when override.opt(key) instanceof JSONArray override1 -> {
|
||||
for (Object o : override1) {
|
||||
base1.put(o);
|
||||
}
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
-44
@@ -16,31 +16,32 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.nms.v1X;
|
||||
package art.arcane.iris.core.nms.v1X;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMSBinding;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.math.Vector3d;
|
||||
import com.volmit.iris.util.nbt.mca.palette.MCABiomeContainer;
|
||||
import com.volmit.iris.util.nbt.mca.palette.MCAPaletteAccess;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.nms.INMSBinding;
|
||||
import art.arcane.iris.core.nms.container.BiomeColor;
|
||||
import art.arcane.iris.core.nms.container.BlockProperty;
|
||||
import art.arcane.iris.core.nms.datapack.DataVersion;
|
||||
import art.arcane.iris.core.nms.container.Pair;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.math.Vector3d;
|
||||
import art.arcane.volmlib.util.nbt.mca.palette.MCABiomeContainer;
|
||||
import art.arcane.volmlib.util.nbt.mca.palette.MCAPaletteAccess;
|
||||
import art.arcane.volmlib.util.nbt.tag.CompoundTag;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.generator.structure.Structure;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public class NMSBinding1X implements INMSBinding {
|
||||
@@ -83,7 +84,7 @@ public class NMSBinding1X implements INMSBinding {
|
||||
|
||||
|
||||
@Override
|
||||
public void injectBiomesFromMantle(Chunk e, Mantle mantle) {
|
||||
public void injectBiomesFromMantle(Chunk e, Mantle<Matter> mantle) {
|
||||
|
||||
}
|
||||
|
||||
@@ -112,29 +113,19 @@ public class NMSBinding1X implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
public KList<String> getStructureKeys() {
|
||||
var list = StreamSupport.stream(Registry.STRUCTURE.spliterator(), false)
|
||||
.map(Structure::getKey)
|
||||
.map(NamespacedKey::toString)
|
||||
.toList();
|
||||
return new KList<>(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoClosing injectLevelStems() {
|
||||
return new AutoClosing(() -> {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
return new Pair<>(0, new AutoClosing(() -> {}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
|
||||
public boolean missingDimensionTypes(String... keys) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KMap<Material, List<BlockProperty>> getBlockProperties() {
|
||||
KMap<Material, List<BlockProperty>> map = new KMap<>();
|
||||
for (Material m : Material.values()) {
|
||||
if (m.isBlock()) map.put(m, List.of());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag serializeEntity(Entity location) {
|
||||
return null;
|
||||
@@ -211,7 +202,11 @@ public class NMSBinding1X implements INMSBinding {
|
||||
|
||||
@Override
|
||||
public KList<Biome> getBiomes() {
|
||||
return new KList<>(Biome.values()).qdel(Biome.CUSTOM);
|
||||
KList<Biome> biomes = new KList<>();
|
||||
for (Biome biome : Registry.BIOME) {
|
||||
biomes.add(biome);
|
||||
}
|
||||
return biomes;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -219,9 +214,16 @@ public class NMSBinding1X implements INMSBinding {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataVersion getDataVersion() {
|
||||
return DataVersion.UNSUPPORTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBiomeId(Biome biome) {
|
||||
return biome.ordinal();
|
||||
List<Biome> biomes = StreamSupport.stream(Registry.BIOME.spliterator(), false).toList();
|
||||
int index = biomes.indexOf(biome);
|
||||
return Math.max(index, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -243,11 +245,6 @@ public class NMSBinding1X implements INMSBinding {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceBiomeInto(int x, int y, int z, Object somethingVeryDirty, ChunkGenerator.BiomeGrid chunk) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3d getBoundingbox(org.bukkit.entity.EntityType entity) {
|
||||
return null;
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class BrokenPackException extends RuntimeException {
|
||||
private final String packName;
|
||||
private final List<String> reasons;
|
||||
|
||||
public BrokenPackException(String packName, List<String> reasons) {
|
||||
super(buildMessage(packName, reasons));
|
||||
this.packName = packName;
|
||||
this.reasons = reasons == null ? new ArrayList<>() : new ArrayList<>(reasons);
|
||||
}
|
||||
|
||||
public String getPackName() {
|
||||
return packName;
|
||||
}
|
||||
|
||||
public List<String> getReasons() {
|
||||
return Collections.unmodifiableList(reasons);
|
||||
}
|
||||
|
||||
private static String buildMessage(String packName, List<String> reasons) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Iris pack '").append(packName).append("' is broken and cannot be used for world or studio creation.");
|
||||
if (reasons != null) {
|
||||
for (String reason : reasons) {
|
||||
if (reason == null || reason.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
sb.append(System.lineSeparator()).append(" - ").append(reason);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
+15
-15
@@ -16,22 +16,22 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.pack;
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.loader.ResourceLoader;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.engine.object.IrisWorld;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.exceptions.IrisException;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.json.JSONArray;
|
||||
import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.loader.ResourceLoader;
|
||||
import art.arcane.iris.core.service.StudioSVC;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisWorld;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.exceptions.IrisException;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
import lombok.Data;
|
||||
import org.bukkit.World;
|
||||
import org.zeroturnaround.zip.commons.FileUtils;
|
||||
+10
-10
@@ -16,15 +16,15 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.pack;
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.plugin.VolmitSender;
|
||||
import com.volmit.iris.util.scheduling.jobs.DownloadJob;
|
||||
import com.volmit.iris.util.scheduling.jobs.JobCollection;
|
||||
import com.volmit.iris.util.scheduling.jobs.SingleJob;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.service.StudioSVC;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.iris.util.common.scheduling.jobs.DownloadJob;
|
||||
import art.arcane.iris.util.common.scheduling.jobs.JobCollection;
|
||||
import art.arcane.iris.util.common.scheduling.jobs.SingleJob;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import org.zeroturnaround.zip.ZipUtil;
|
||||
@@ -45,7 +45,7 @@ public class IrisPackRepository {
|
||||
private String repo = "overworld";
|
||||
|
||||
@Builder.Default
|
||||
private String branch = "master";
|
||||
private String branch = "stable";
|
||||
|
||||
@Builder.Default
|
||||
private String tag = "";
|
||||
@@ -94,7 +94,7 @@ public class IrisPackRepository {
|
||||
return IrisPackRepository.builder()
|
||||
.user("IrisDimensions")
|
||||
.repo(g)
|
||||
.branch(g.equals("overworld") ? "stable" : "master")
|
||||
.branch("stable")
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class PackValidationRegistry {
|
||||
private static final Map<String, PackValidationResult> RESULTS = new ConcurrentHashMap<>();
|
||||
|
||||
private PackValidationRegistry() {
|
||||
}
|
||||
|
||||
public static void publish(PackValidationResult result) {
|
||||
if (result == null || result.getPackName() == null || result.getPackName().isBlank()) {
|
||||
return;
|
||||
}
|
||||
RESULTS.put(result.getPackName(), result);
|
||||
}
|
||||
|
||||
public static PackValidationResult get(String packName) {
|
||||
if (packName == null || packName.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return RESULTS.get(packName);
|
||||
}
|
||||
|
||||
public static boolean isBroken(String packName) {
|
||||
PackValidationResult result = get(packName);
|
||||
return result != null && !result.isLoadable();
|
||||
}
|
||||
|
||||
public static Map<String, PackValidationResult> snapshot() {
|
||||
return Collections.unmodifiableMap(RESULTS);
|
||||
}
|
||||
|
||||
public static void remove(String packName) {
|
||||
if (packName == null || packName.isBlank()) {
|
||||
return;
|
||||
}
|
||||
RESULTS.remove(packName);
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
RESULTS.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class PackValidationResult {
|
||||
private final String packName;
|
||||
private final List<String> blockingErrors;
|
||||
private final List<String> warnings;
|
||||
private final List<String> removedUnusedFiles;
|
||||
private final long validatedAtMillis;
|
||||
|
||||
public PackValidationResult(String packName,
|
||||
List<String> blockingErrors,
|
||||
List<String> warnings,
|
||||
List<String> removedUnusedFiles,
|
||||
long validatedAtMillis) {
|
||||
this.packName = packName;
|
||||
this.blockingErrors = blockingErrors == null ? new ArrayList<>() : new ArrayList<>(blockingErrors);
|
||||
this.warnings = warnings == null ? new ArrayList<>() : new ArrayList<>(warnings);
|
||||
this.removedUnusedFiles = removedUnusedFiles == null ? new ArrayList<>() : new ArrayList<>(removedUnusedFiles);
|
||||
this.validatedAtMillis = validatedAtMillis;
|
||||
}
|
||||
|
||||
public String getPackName() {
|
||||
return packName;
|
||||
}
|
||||
|
||||
public boolean isLoadable() {
|
||||
return blockingErrors.isEmpty();
|
||||
}
|
||||
|
||||
public List<String> getBlockingErrors() {
|
||||
return Collections.unmodifiableList(blockingErrors);
|
||||
}
|
||||
|
||||
public List<String> getWarnings() {
|
||||
return Collections.unmodifiableList(warnings);
|
||||
}
|
||||
|
||||
public List<String> getRemovedUnusedFiles() {
|
||||
return Collections.unmodifiableList(removedUnusedFiles);
|
||||
}
|
||||
|
||||
public long getValidatedAtMillis() {
|
||||
return validatedAtMillis;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class PackValidator {
|
||||
private static final String TRASH_ROOT = ".iris-trash";
|
||||
private static final String DATAPACK_IMPORTS = "datapack-imports";
|
||||
private static final String EXTERNAL_DATAPACKS = "externaldatapacks";
|
||||
private static final String INTERNAL_DATAPACKS = "internaldatapacks";
|
||||
private static final String DATAPACKS_FOLDER = "datapacks";
|
||||
private static final String CACHE_FOLDER = "cache";
|
||||
private static final String OBJECTS_FOLDER = "objects";
|
||||
private static final String DIMENSIONS_FOLDER = "dimensions";
|
||||
private static final List<String> MANAGED_RESOURCE_FOLDERS = List.of(
|
||||
"biomes",
|
||||
"regions",
|
||||
"entities",
|
||||
"spawners",
|
||||
"loot",
|
||||
"generators",
|
||||
"expressions",
|
||||
"markers",
|
||||
"blocks",
|
||||
"mods"
|
||||
);
|
||||
private static final DateTimeFormatter TRASH_STAMP = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
|
||||
|
||||
private PackValidator() {
|
||||
}
|
||||
|
||||
public static PackValidationResult validate(File packFolder) {
|
||||
String packName = packFolder == null ? "<unknown>" : packFolder.getName();
|
||||
List<String> blockingErrors = new ArrayList<>();
|
||||
List<String> warnings = new ArrayList<>();
|
||||
List<String> removedUnusedFiles = new ArrayList<>();
|
||||
long validatedAt = System.currentTimeMillis();
|
||||
|
||||
if (packFolder == null || !packFolder.isDirectory()) {
|
||||
blockingErrors.add("Pack folder does not exist or is not a directory.");
|
||||
return new PackValidationResult(packName, blockingErrors, warnings, removedUnusedFiles, validatedAt);
|
||||
}
|
||||
|
||||
File dimensionsFolder = new File(packFolder, DIMENSIONS_FOLDER);
|
||||
if (!dimensionsFolder.isDirectory()) {
|
||||
blockingErrors.add("Missing dimensions/ folder.");
|
||||
return new PackValidationResult(packName, blockingErrors, warnings, removedUnusedFiles, validatedAt);
|
||||
}
|
||||
|
||||
File[] dimensionFiles = dimensionsFolder.listFiles(f -> f.isFile() && f.getName().endsWith(".json"));
|
||||
if (dimensionFiles == null || dimensionFiles.length == 0) {
|
||||
blockingErrors.add("No dimension JSON files under dimensions/.");
|
||||
return new PackValidationResult(packName, blockingErrors, warnings, removedUnusedFiles, validatedAt);
|
||||
}
|
||||
|
||||
validateDimensions(packFolder, dimensionFiles, blockingErrors, warnings);
|
||||
|
||||
try {
|
||||
String packTextCorpus = buildPackTextCorpus(packFolder);
|
||||
runUnusedResourceGc(packFolder, packTextCorpus, removedUnusedFiles, warnings);
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("PackValidator GC pass failed for pack '" + packName + "'", e);
|
||||
warnings.add("Unused-resource GC pass failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
return new PackValidationResult(packName, blockingErrors, warnings, removedUnusedFiles, validatedAt);
|
||||
}
|
||||
|
||||
private static void validateDimensions(File packFolder, File[] dimensionFiles, List<String> blockingErrors, List<String> warnings) {
|
||||
File regionsFolder = new File(packFolder, "regions");
|
||||
File biomesFolder = new File(packFolder, "biomes");
|
||||
|
||||
for (File dimFile : dimensionFiles) {
|
||||
String dimensionKey = stripExtension(dimFile.getName());
|
||||
JSONObject dimJson;
|
||||
try {
|
||||
dimJson = new JSONObject(Files.readString(dimFile.toPath(), StandardCharsets.UTF_8));
|
||||
} catch (Throwable e) {
|
||||
blockingErrors.add("Dimension '" + dimensionKey + "' has invalid JSON: " + e.getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
JSONArray regionsArray = dimJson.optJSONArray("regions");
|
||||
if (regionsArray == null || regionsArray.length() == 0) {
|
||||
blockingErrors.add("Dimension '" + dimensionKey + "' declares no regions.");
|
||||
continue;
|
||||
}
|
||||
|
||||
int resolvedRegions = 0;
|
||||
for (int i = 0; i < regionsArray.length(); i++) {
|
||||
String regionKey = regionsArray.optString(i, null);
|
||||
if (regionKey == null || regionKey.isBlank()) {
|
||||
warnings.add("Dimension '" + dimensionKey + "' has a blank region entry at index " + i + ".");
|
||||
continue;
|
||||
}
|
||||
File regionFile = new File(regionsFolder, regionKey + ".json");
|
||||
if (!regionFile.isFile()) {
|
||||
blockingErrors.add("Dimension '" + dimensionKey + "' references missing region '" + regionKey + "'.");
|
||||
continue;
|
||||
}
|
||||
|
||||
JSONObject regionJson;
|
||||
try {
|
||||
regionJson = new JSONObject(Files.readString(regionFile.toPath(), StandardCharsets.UTF_8));
|
||||
} catch (Throwable e) {
|
||||
blockingErrors.add("Region '" + regionKey + "' has invalid JSON: " + e.getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
int anyBiome = countBiomeRefs(regionJson, "landBiomes", biomesFolder, regionKey, warnings)
|
||||
+ countBiomeRefs(regionJson, "seaBiomes", biomesFolder, regionKey, warnings)
|
||||
+ countBiomeRefs(regionJson, "shoreBiomes", biomesFolder, regionKey, warnings)
|
||||
+ countBiomeRefs(regionJson, "caveBiomes", biomesFolder, regionKey, warnings);
|
||||
if (anyBiome == 0) {
|
||||
blockingErrors.add("Region '" + regionKey + "' has no resolvable biomes.");
|
||||
}
|
||||
resolvedRegions++;
|
||||
}
|
||||
|
||||
if (resolvedRegions == 0) {
|
||||
blockingErrors.add("Dimension '" + dimensionKey + "' has no resolvable regions.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int countBiomeRefs(JSONObject regionJson, String field, File biomesFolder, String regionKey, List<String> warnings) {
|
||||
JSONArray arr = regionJson.optJSONArray(field);
|
||||
if (arr == null) {
|
||||
return 0;
|
||||
}
|
||||
int resolved = 0;
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
String biomeKey = arr.optString(i, null);
|
||||
if (biomeKey == null || biomeKey.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
File biomeFile = new File(biomesFolder, biomeKey + ".json");
|
||||
if (!biomeFile.isFile()) {
|
||||
warnings.add("Region '" + regionKey + "' references missing biome '" + biomeKey + "' in " + field + ".");
|
||||
continue;
|
||||
}
|
||||
resolved++;
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private static String buildPackTextCorpus(File packFolder) {
|
||||
StringBuilder sb = new StringBuilder(1 << 16);
|
||||
try (Stream<Path> stream = Files.walk(packFolder.toPath())) {
|
||||
stream.filter(Files::isRegularFile)
|
||||
.filter(PackValidator::isScannableJsonPath)
|
||||
.forEach(p -> {
|
||||
try {
|
||||
sb.append(Files.readString(p, StandardCharsets.UTF_8));
|
||||
sb.append('\n');
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("PackValidator failed to walk pack folder for corpus scan", e);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static boolean isScannableJsonPath(Path path) {
|
||||
String name = path.getFileName().toString();
|
||||
if (!name.endsWith(".json")) {
|
||||
return false;
|
||||
}
|
||||
String str = path.toString().replace(File.separatorChar, '/');
|
||||
if (str.contains("/" + TRASH_ROOT + "/")) {
|
||||
return false;
|
||||
}
|
||||
if (str.contains("/" + DATAPACK_IMPORTS + "/")) {
|
||||
return false;
|
||||
}
|
||||
if (str.contains("/" + EXTERNAL_DATAPACKS + "/")) {
|
||||
return false;
|
||||
}
|
||||
if (str.contains("/" + INTERNAL_DATAPACKS + "/")) {
|
||||
return false;
|
||||
}
|
||||
if (str.contains("/" + DATAPACKS_FOLDER + "/")) {
|
||||
return false;
|
||||
}
|
||||
if (str.contains("/" + CACHE_FOLDER + "/")) {
|
||||
return false;
|
||||
}
|
||||
if (str.contains("/" + OBJECTS_FOLDER + "/")) {
|
||||
return false;
|
||||
}
|
||||
if (str.contains("/.iris/")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void runUnusedResourceGc(File packFolder, String corpus, List<String> removedUnusedFiles, List<String> warnings) {
|
||||
if (corpus == null || corpus.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
File trashRoot = new File(packFolder, TRASH_ROOT + File.separator + LocalDateTime.now().format(TRASH_STAMP));
|
||||
Set<File> scheduledForTrash = new LinkedHashSet<>();
|
||||
|
||||
for (String folderName : MANAGED_RESOURCE_FOLDERS) {
|
||||
File resourceFolder = new File(packFolder, folderName);
|
||||
if (!resourceFolder.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<File> files = listJsonRecursive(resourceFolder);
|
||||
for (File resourceFile : files) {
|
||||
String key = deriveKey(resourceFolder, resourceFile);
|
||||
if (key == null || key.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
if (isReferenced(corpus, key)) {
|
||||
continue;
|
||||
}
|
||||
scheduledForTrash.add(resourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (scheduledForTrash.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (File file : scheduledForTrash) {
|
||||
try {
|
||||
Path src = file.toPath();
|
||||
Path relative = packFolder.toPath().relativize(src);
|
||||
Path dest = trashRoot.toPath().resolve(relative);
|
||||
Files.createDirectories(dest.getParent());
|
||||
Files.move(src, dest, StandardCopyOption.REPLACE_EXISTING);
|
||||
removedUnusedFiles.add(relative.toString().replace(File.separatorChar, '/'));
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("PackValidator failed to move unused file " + file.getPath() + " to trash", e);
|
||||
warnings.add("Failed to quarantine unused file " + file.getName() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isReferenced(String corpus, String key) {
|
||||
String needleQuoted = "\"" + key + "\"";
|
||||
if (corpus.contains(needleQuoted)) {
|
||||
return true;
|
||||
}
|
||||
int slash = key.indexOf('/');
|
||||
if (slash > 0) {
|
||||
String tail = key.substring(slash + 1);
|
||||
if (!tail.isBlank() && corpus.contains("\"" + tail + "\"")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<File> listJsonRecursive(File root) {
|
||||
List<File> out = new ArrayList<>();
|
||||
try (Stream<Path> stream = Files.walk(root.toPath())) {
|
||||
stream.filter(Files::isRegularFile)
|
||||
.filter(p -> p.getFileName().toString().endsWith(".json"))
|
||||
.forEach(p -> out.add(p.toFile()));
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static String deriveKey(File resourceFolder, File resourceFile) {
|
||||
Path relative = resourceFolder.toPath().relativize(resourceFile.toPath());
|
||||
String str = relative.toString().replace(File.separatorChar, '/');
|
||||
if (!str.endsWith(".json")) {
|
||||
return null;
|
||||
}
|
||||
return str.substring(0, str.length() - ".json".length());
|
||||
}
|
||||
|
||||
private static String stripExtension(String name) {
|
||||
int dot = name.lastIndexOf('.');
|
||||
return dot <= 0 ? name : name.substring(0, dot);
|
||||
}
|
||||
|
||||
public static int restoreTrash(File packFolder) {
|
||||
if (packFolder == null || !packFolder.isDirectory()) {
|
||||
return 0;
|
||||
}
|
||||
File trashRoot = new File(packFolder, TRASH_ROOT);
|
||||
if (!trashRoot.isDirectory()) {
|
||||
return 0;
|
||||
}
|
||||
File[] dumps = trashRoot.listFiles(File::isDirectory);
|
||||
if (dumps == null || dumps.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
Arrays.sort(dumps, Comparator.comparing(File::getName));
|
||||
File latestDump = dumps[dumps.length - 1];
|
||||
int restored = 0;
|
||||
try (Stream<Path> stream = Files.walk(latestDump.toPath())) {
|
||||
List<Path> files = stream.filter(Files::isRegularFile).toList();
|
||||
for (Path src : files) {
|
||||
Path relative = latestDump.toPath().relativize(src);
|
||||
Path dest = packFolder.toPath().resolve(relative);
|
||||
Files.createDirectories(dest.getParent());
|
||||
Files.move(src, dest, StandardCopyOption.REPLACE_EXISTING);
|
||||
restored++;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("PackValidator failed to restore trash for pack " + packFolder.getName(), e);
|
||||
}
|
||||
deleteFolderQuiet(latestDump);
|
||||
return restored;
|
||||
}
|
||||
|
||||
private static void deleteFolderQuiet(File folder) {
|
||||
if (folder == null || !folder.exists()) {
|
||||
return;
|
||||
}
|
||||
try (Stream<Path> stream = Files.walk(folder.toPath())) {
|
||||
stream.sorted(Comparator.reverseOrder())
|
||||
.map(Path::toFile)
|
||||
.forEach(File::delete);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<String> listReferencedKeysFromCorpus(String corpus) {
|
||||
Set<String> keys = new HashSet<>();
|
||||
if (corpus == null) {
|
||||
return keys;
|
||||
}
|
||||
int i = 0;
|
||||
while (i < corpus.length()) {
|
||||
int start = corpus.indexOf('"', i);
|
||||
if (start < 0) {
|
||||
break;
|
||||
}
|
||||
int end = corpus.indexOf('"', start + 1);
|
||||
if (end < 0) {
|
||||
break;
|
||||
}
|
||||
keys.add(corpus.substring(start + 1, end));
|
||||
i = end + 1;
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
+28
-34
@@ -1,20 +1,20 @@
|
||||
package com.volmit.iris.core.pregenerator;
|
||||
package art.arcane.iris.core.pregenerator;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisBiome;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.format.Form;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.math.M;
|
||||
import com.volmit.iris.util.math.Position2;
|
||||
import com.volmit.iris.util.math.RollingSequence;
|
||||
import com.volmit.iris.util.math.Spiraler;
|
||||
import com.volmit.iris.util.scheduling.ChronoLatch;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import art.arcane.volmlib.util.math.RollingSequence;
|
||||
import art.arcane.volmlib.util.math.Spiraler;
|
||||
import art.arcane.volmlib.util.scheduling.ChronoLatch;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -22,7 +22,6 @@ import org.bukkit.World;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
@@ -158,16 +157,15 @@ public class DeepSearchPregenerator extends Thread implements Listener {
|
||||
for (int j = 0; j < 16; j++) {
|
||||
int height = engine.getHeight(xx + i, zz + j);
|
||||
if (height > 300) {
|
||||
File found = new File("plugins" + "iris" + "found.txt");
|
||||
FileWriter writer = new FileWriter(found);
|
||||
if (!found.exists()) {
|
||||
found.createNewFile();
|
||||
}
|
||||
File found = new File("plugins", "iris" + File.separator + "found.txt");
|
||||
found.getParentFile().mkdirs();
|
||||
IrisBiome biome = engine.getBiome(xx, engine.getHeight(), zz);
|
||||
Iris.info("Found at! " + xx + ", " + zz + "Biome ID: " + biome.getName() + ", ");
|
||||
writer.write("Biome at: X: " + xx + " Z: " + zz + "Biome ID: " + biome.getName() + ", ");
|
||||
Iris.info("Found at! " + xx + ", " + zz + " Biome ID: " + biome.getName());
|
||||
try (FileWriter writer = new FileWriter(found, true)) {
|
||||
writer.write("Biome at: X: " + xx + " Z: " + zz + " Biome ID: " + biome.getName() + "\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,16 +233,13 @@ public class DeepSearchPregenerator extends Thread implements Listener {
|
||||
}
|
||||
save();
|
||||
jobs.remove(world.getName());
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (deepFile.exists()){
|
||||
deepFile.delete();
|
||||
J.sleep(1000);
|
||||
}
|
||||
Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
|
||||
J.a(() -> {
|
||||
while (deepFile.exists()) {
|
||||
deepFile.delete();
|
||||
J.sleep(1000);
|
||||
}
|
||||
}.runTaskLater(Iris.instance, 20L);
|
||||
Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed.");
|
||||
}, 20);
|
||||
} catch (Exception e) {
|
||||
Iris.error("Failed to shutdown DeepSearch for " + world.getName());
|
||||
e.printStackTrace();
|
||||
@@ -271,4 +266,3 @@ public class DeepSearchPregenerator extends Thread implements Listener {
|
||||
boolean paused = false;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user