Compare commits

..

447 Commits

Author SHA1 Message Date
Brian Neumann-Fopiano 821cc027db fix 2026-04-23 22:44:52 -04:00
Brian Neumann-Fopiano 1968ef2f2c Wip floaters 2026-04-23 21:29:23 -04:00
Brian Neumann-Fopiano ce29b70618 Content 2026-04-23 16:38:32 -04:00
Brian Neumann-Fopiano 3d128b70a7 RedoDeco 2026-04-22 19:23:50 -04:00
Brian Neumann-Fopiano 23fad24fb7 Whoops 2026-04-22 16:33:25 -04:00
Brian Neumann-Fopiano 6df718e6ca floatt 2026-04-22 09:42:45 -04:00
Brian Neumann-Fopiano 787c728060 Amending / removing all structures will redesign later im over this 2026-04-20 18:20:53 -04:00
Brian Neumann-Fopiano 88bbce82fe Object Studio and Plausibilizer 1.0 2026-04-18 14:50:36 -04:00
Brian Neumann-Fopiano e6a8351e57 Bulk changes and fixes
namely the chunk issue, and objects not wantingto place on cave tops.
2026-04-17 22:28:35 -04:00
Brian Neumann-Fopiano b82472d521 Loads 2026-04-17 16:14:49 -04:00
Brian Neumann-Fopiano 8c05f1bf1d polish 2026-04-15 16:35:49 -04:00
Brian Neumann-Fopiano 6a5616abae d 2026-04-15 15:52:18 -04:00
Brian Neumann-Fopiano 6096c39192 OptiPass3 2026-04-15 15:13:35 -04:00
Brian Neumann-Fopiano b42d0e4710 OptiPass 2
Smart Sampling and stuff
2026-04-15 13:26:52 -04:00
Brian Neumann-Fopiano dcb3306197 Opti pass 1 2026-04-15 12:50:12 -04:00
Brian Neumann-Fopiano 9a231b2bcf Studio FIxes 2026-04-15 09:45:02 -04:00
Brian Neumann-Fopiano aa706d027b WIP Building for latest.
Still Quantizing, WOrking on that next
also removed prebake
2026-04-15 09:05:41 -04:00
Brian Neumann-Fopiano 568fb07f66 gr 2026-04-13 17:06:29 -04:00
Brian Neumann-Fopiano bf207b7062 speed pass 2026-02-24 03:57:07 -05:00
Brian Neumann-Fopiano 18d4dce1db dwa 2026-02-22 23:25:01 -05:00
Brian Neumann-Fopiano 651dfa247e perfpass 2026-02-22 09:58:33 -05:00
Brian Neumann-Fopiano 5b1ff6d2f7 ugh 2026-02-22 09:08:31 -05:00
Brian Neumann-Fopiano 589baafc28 kts gone 2026-02-22 08:59:50 -05:00
Brian Neumann-Fopiano 130073989d Fix Shafts 2026-02-22 07:09:59 -05:00
Brian Neumann-Fopiano 2ca8cc7ad3 f 2026-02-21 18:56:10 -05:00
Brian Neumann-Fopiano 9f35855599 dwa 2026-02-21 04:14:18 -05:00
Brian Neumann-Fopiano 5110a33857 forgor 2026-02-21 03:50:43 -05:00
Brian Neumann-Fopiano a38e87cce3 f 2026-02-21 03:49:10 -05:00
Brian Neumann-Fopiano 1d09cd5f0f dwa 2026-02-21 03:37:44 -05:00
Brian Neumann-Fopiano 72c891ce5b dwa 2026-02-20 23:17:17 -05:00
Brian Neumann-Fopiano 3964185a81 Working 2026-02-20 17:53:11 -05:00
Brian Neumann-Fopiano afc5f67bc2 f 2026-02-19 22:35:29 -05:00
Brian Neumann-Fopiano 1c72d6410f f 2026-02-19 19:50:29 -05:00
Brian Neumann-Fopiano c2c612eb35 d 2026-02-19 16:49:54 -05:00
Brian Neumann-Fopiano 415c2f5837 JIGGY! 2026-02-19 04:09:06 -05:00
Brian Neumann-Fopiano b491d9efd0 im not getting Jiggy with it 2026-02-19 02:02:31 -05:00
Brian Neumann-Fopiano d643461e2e f 2026-02-19 01:23:08 -05:00
Brian Neumann-Fopiano 1f41e195cf p1 2026-02-18 23:44:23 -05:00
Brian Neumann-Fopiano 7b9280083f f 2026-02-18 23:43:02 -05:00
Brian Neumann-Fopiano 73525c170d Noise Pass 2026-02-18 23:10:49 -05:00
Brian Neumann-Fopiano 16c861a781 f 2026-02-18 20:56:30 -05:00
Brian Neumann-Fopiano d6bd5ec82f d 2026-02-18 20:24:25 -05:00
Brian Neumann-Fopiano 1829fa3eb1 d
d
2026-02-18 15:18:21 -05:00
Brian Neumann-Fopiano 8d215e3911 V 2026-02-18 15:08:55 -05:00
Brian Neumann-Fopiano ba819e4899 canvas checks 2026-02-17 22:44:01 -05:00
Brian Neumann-Fopiano 05d79b6d40 Create std 2026-02-17 06:18:55 -05:00
Brian Neumann-Fopiano cf64fbb730 Waow 2026-02-17 02:54:34 -05:00
Brian Neumann-Fopiano 43a65135b1 WIP - Dont compile 2026-02-17 00:34:34 -05:00
Brian Neumann-Fopiano 440c1bec6c "Folia" 2026-02-16 06:07:25 -05:00
Brian Neumann-Fopiano 0fd8bcc488 f 2026-02-15 17:43:28 -05:00
Brian Neumann-Fopiano bb1c0b8f68 FIXES 2026-02-15 04:49:23 -05:00
Brian Neumann-Fopiano fc25acdea2 CMD 2026-02-14 23:55:01 -05:00
Brian Neumann-Fopiano 0aa6f6d523 d 2026-02-14 19:58:45 -05:00
Brian Neumann-Fopiano a79c1a7954 ignore, just tracking info 2026-02-14 18:11:43 -05:00
Brian Neumann-Fopiano 7885762cd7 shakey shakey 2026-02-14 05:44:10 -05:00
Brian Neumann-Fopiano 86ed3f0095 OOPS
kinda need this
2026-02-14 04:52:30 -05:00
Brian Neumann-Fopiano 99fcc8fd03 WIP 2026-02-14 01:56:05 -05:00
Brian Neumann-Fopiano 41888e33f6 Fixing fallbacks for macos Folk
Compiles fine, just stricter defaults
2026-02-11 00:01:23 -05:00
Aidan Aeternum 25fa2553e5 v+ 2026-02-02 03:28:49 -05:00
Aidan Aeternum 86f78baecf Merge pull request #1236 from VolmitSoftware/dev
3.9.1
2026-02-02 03:28:30 -05:00
Julian Krings c31158578f fix datapack generation on 1.21.11 2026-01-29 16:40:33 +01:00
Aidan Aeternum 4e86d7d634 v+ 2026-01-27 20:09:25 -05:00
Aidan Aeternum 62fe29cf34 Merge pull request #1235 from VolmitSoftware/dev
3.9.0
2026-01-27 20:08:36 -05:00
Julian Krings a3bcea4f3e increase memory assumption per region 2026-01-26 21:37:29 +01:00
Julian Krings 7befce1084 initial 1.21.11 support 2026-01-23 11:46:15 +01:00
Aidan Aeternum 351a1aa495 v+ 2026-01-15 02:50:30 -05:00
Aidan Aeternum d2cb8a9032 Merge pull request #1234 from VolmitSoftware/dev
3.8.2
2026-01-15 02:50:03 -05:00
Julian Krings 509715087c fix typo 2026-01-13 15:11:21 +01:00
Julian Krings 12527ecdd8 improve cachedChunks lookup in the mantle writer 2026-01-05 14:58:58 +01:00
Julian Krings 01a2999e03 decrease locking for mantle generation 2026-01-05 14:58:09 +01:00
Julian Krings dbafe84fa5 make context dereference method fail-safe 2026-01-02 14:12:44 +01:00
Julian Krings 40b020fc5d improve matter generator once again 2026-01-02 14:10:35 +01:00
Julian Krings d15f7d62d1 cleanup async world ticker 2026-01-02 14:10:35 +01:00
Julian Krings f9c062c794 improve mantle cleanup to prevent thread starvation 2026-01-02 14:10:34 +01:00
Aidan Aeternum 6a89b8bd06 v+ 2026-01-02 06:17:00 -05:00
Aidan Aeternum 194fcb2ea7 Merge pull request #1232 from VolmitSoftware/dev
3.8.1
2026-01-02 06:16:28 -05:00
Julian Krings 3b68f855b2 get back some speed by loading four mantle regions into cache at once 2025-12-31 13:38:42 +01:00
Julian Krings cfbf68d37a add check for headless environments to the edit command 2025-12-31 13:01:18 +01:00
Julian Krings 9999f3e429 fix chunk artifacts 2025-12-31 13:00:45 +01:00
Julian Krings 97fa0562a4 fix custom block data 2025-12-23 22:51:41 +01:00
Julian Krings 3abe671851 suppress unchecked cast warning for the ChunkedDataCache 2025-12-18 18:53:59 +01:00
Julian Krings e164a3bb5c Merge branch 'feat/safeguard' into dev
# Conflicts:
#	build.gradle.kts
#	core/src/main/java/com/volmit/iris/Iris.java
2025-12-18 18:44:36 +01:00
Julian Krings 4dfb4441e4 fix buildscript 2025-12-18 14:23:51 +01:00
Julian Krings e686f67453 add command to fix offsets of all objects 2025-12-18 14:23:35 +01:00
Julian Krings e2eed4812a ignore empty objects for shrinkwrap 2025-12-18 14:22:40 +01:00
Julian Krings 1737833bfe fix legacy object parsing 2025-12-18 14:22:21 +01:00
piexel a4c5f46c37 Fix /iris object convert for .schem files with 128+ block types 2025-12-18 11:25:38 +01:00
piexel 025fa833c4 Fix Grass won't place on moss blocks 2025-12-18 11:21:16 +01:00
Julian Krings 98cc82cc3d Fix jigsaw editor bricking when opening a piece a missing object 2025-12-18 11:13:43 +01:00
Julian Krings 298365f588 fix studio open not working when not in spectator 2025-12-18 11:06:09 +01:00
Julian Krings 90e5720e2e improve buildscript 2025-12-18 11:05:28 +01:00
Julian Krings 7cd43791f4 fix offset with shrinkwrap 2025-12-18 11:05:14 +01:00
Julian Krings a251d192ad fix loot for objects underground 2025-12-18 11:00:04 +01:00
Julian Krings abfff28f43 fix redstone not rotating 2025-12-18 10:59:57 +01:00
Julian Krings fbdae4c928 improve overwriting of the regenerated chunks 2025-12-18 10:59:32 +01:00
Aidan Aeternum 93ca26e368 v+ 2025-12-17 12:12:25 -05:00
Aidan Aeternum 8c1db1c223 Merge pull request #1231 from VolmitSoftware/dev
3.8.0
2025-12-17 12:11:28 -05:00
Julian Krings 123708601f improve pregen cache 2025-12-01 16:01:54 +01:00
Julian Krings b5ab4968ba fix compile 2025-11-30 15:36:36 +01:00
Julian Krings 93ae6037bd whoops forgot to disable craftbukkit relocations on <=1.20.4 2025-11-29 11:49:15 +01:00
Julian Krings dd8e487a3b fix tile entity serialization on paper 1.21.6+ servers 2025-11-29 11:13:30 +01:00
Julian Krings d3c8377a12 allow switching between buildtools and userdev 2025-11-29 11:12:16 +01:00
Julian Krings 84f3687c0c update compat for iron bars 2025-11-27 16:09:31 +01:00
Julian Krings ed1e1f1181 whoops forgot those providers 2025-11-26 21:33:03 +01:00
Julian Krings 528c97f367 replace IrisCustomData class with a proxy to keep full access to the base BlockData 2025-11-26 21:32:13 +01:00
Julian Krings f7a459f3bc initial 1.21.9/10 bindings 2025-11-26 21:25:05 +01:00
Julian Krings cd179b4321 reintroduce native threads for the updater as a setting and cleanup 2025-11-26 20:55:48 +01:00
Julian Krings 6373dbb1b8 potential optimization for the noise cache 2025-11-21 16:21:54 +01:00
Julian Krings 0b0797f876 minor optimization for chunk updater 2025-11-21 16:21:31 +01:00
Julian Krings 446acefc91 minor optimization for the mantle writer 2025-11-21 16:21:02 +01:00
Julian Krings 7a44e555b2 rename noise cache size setting and decrease default value 2025-11-21 16:20:32 +01:00
Julian Krings 57d4c2935c add cache sizes to engine status 2025-11-21 16:16:15 +01:00
Julian Krings 234fb1b0c4 also include parent class for schema generation 2025-11-08 00:58:12 +01:00
Julian Krings 0882b5acc4 use parent shared classloader to reflect intelij behavior 2025-11-06 22:32:28 +01:00
Julian Krings 18da26e1fa minor performance improvement 2025-11-06 22:27:14 +01:00
Julian Krings 65aa95f2a5 fix wrong radius when marking mantle chunks as completed 2025-11-06 22:25:37 +01:00
Julian Krings 5330ddc4ec fix deleting object ids with mantle cleanup 2025-11-06 22:20:30 +01:00
Julian Krings a7fdd37569 fix studio loot command 2025-11-06 22:16:27 +01:00
Julian Krings a226fea9e2 fix regen command 2025-11-06 16:22:02 +01:00
Julian Krings 8cea165a29 even more performance improvements 2025-11-06 15:29:03 +01:00
Julian Krings 1d7cba184c fix linear palette not growing correctly 2025-11-06 14:41:39 +01:00
Julian Krings 4ca7ea3911 minor speed improvements 2025-11-01 22:36:25 +01:00
Julian Krings ea5919def2 stop writing first access each time the engine is opened 2025-10-30 16:40:35 +01:00
Julian Krings be35e49302 use coroutines for mantle generation 2025-10-30 16:40:08 +01:00
Julian Krings aadd03990a optimize data palette for mantle slices 2025-10-27 19:53:47 +01:00
Julian Krings 38a579453d optimize noise cache 2025-10-26 13:49:03 +01:00
Julian Krings 0bf5da2ca1 optimize object maps 2025-10-26 13:48:11 +01:00
Julian Krings 317848692e improve regen speed 2025-10-06 14:12:01 +02:00
Julian Krings 22118de9e9 fix object smart bore 2025-10-06 14:10:37 +02:00
Julian Krings d7039d120b fix place commands causing unwanted block updates 2025-10-06 13:00:00 +02:00
Julian Krings 979ee4e7d8 switch hashing algorithm for objects once more 2025-10-05 21:46:12 +02:00
Julian Krings f68d45bd30 dynamically resolve snippet classes 2025-10-05 00:21:38 +02:00
Julian Krings b86d7f303e restructure the shared kts classloader to be more consistent 2025-10-05 00:20:55 +02:00
Julian Krings c573843314 fix kts dependency resolver 2025-10-05 00:17:57 +02:00
Julian Krings 51a7bef18e whoops forgot escaping the path 2025-10-04 17:19:11 +02:00
Julian Krings 0e237aa1ad fix generated build.gradle.kts on external dives on windows 2025-10-04 13:41:48 +02:00
Julian Krings b46c413f6b make gradle setup print to console on failure 2025-10-04 13:40:47 +02:00
Julian Krings f3ef1ca2ae fix typo in preprocessors description 2025-10-04 13:40:07 +02:00
Julian Krings 703e61dd54 fix preprocessors not applying reliably 2025-10-04 13:39:33 +02:00
Aidan Aeternum e1ec6b7827 v+ 2025-10-03 16:08:05 -04:00
Aidan Aeternum f94292fdac Merge pull request #1228 from VolmitSoftware/dev
3.7.11
2025-10-03 16:07:47 -04:00
Julian Krings 7d153bf985 optimize objects to avoid hash collision 2025-10-02 15:25:37 +02:00
Julian Krings f85f15ed02 fix converter for complex schematic 2025-10-02 11:43:16 +02:00
Julian Krings 867686eced fix deserialisation of unversioned mantle plates 2025-10-01 12:57:38 +02:00
Julian Krings 526efd3ae1 restructure safeguard 2025-09-29 15:45:56 +02:00
Aidan Aeternum 9d796bd2a0 v+ 2025-09-27 07:34:58 -04:00
Aidan Aeternum 1a9a5d80ad Merge pull request #1223 from VolmitSoftware/dev
3.7.10
2025-09-27 07:34:31 -04:00
Julian Krings c5c7f9bdc5 fix minor issue with nms tools 2025-09-22 18:10:34 +02:00
Julian Krings 01a421b732 add file extensions to the script property descriptions 2025-09-21 22:35:34 +02:00
Julian Krings ae92bcf194 add kts hook for chunk updates 2025-09-21 22:30:48 +02:00
Julian Krings 7e7933858b suppress json syntax exceptions from being reported to sentry 2025-09-21 15:47:29 +02:00
Julian Krings 9c073ecbcb fix infinite loop due to writing the gradle.kts 2025-09-21 14:01:59 +02:00
Julian Krings f4617c1996 add tabcompletion for mythic mobs mob stacks 2025-09-21 12:51:59 +02:00
Julian Krings 21a2e4feef fix hotloading when changing kts 2025-09-21 12:13:57 +02:00
Julian Krings 258d0d3aaa make sure that the pack is installed correctly 2025-09-21 00:15:34 +02:00
Julian Krings 27b2fd0823 show other packs data again 2025-09-20 23:31:33 +02:00
Julian Krings 0524adb0df fix compile 2025-09-20 23:05:24 +02:00
Julian Krings 3981b0976d cleanup command framework and fix random locator fails 2025-09-20 23:05:16 +02:00
Julian Krings b5811cae08 cleanup IrisData usage 2025-09-20 23:04:11 +02:00
Julian Krings c998fd1fd9 move additional build data in its own constants class 2025-09-20 17:25:20 +02:00
Julian Krings a7d874d37f use scheduled thread pool for scoreboard svc to prevent freezes 2025-09-20 15:27:49 +02:00
Julian Krings 7c41f86fb3 add slope condition for slabs 2025-09-20 14:28:33 +02:00
Julian Krings e5e0561d5a cleanup world creator 2025-09-17 18:02:45 +02:00
Julian Krings d50cdfec3e cleanup pack installation 2025-09-17 18:02:20 +02:00
Julian Krings 00997c1902 return if missing datapack entries were found 2025-09-17 17:36:18 +02:00
Julian Krings 3095a92522 add experimental setting to force place custom blocks as early as possible 2025-09-10 23:06:46 +02:00
Julian Krings fca309dec7 move the loading block data message into debug 2025-09-10 17:02:51 +02:00
Julian Krings 2793ed1035 send block update after placing itemsadder blocks 2025-09-10 17:01:29 +02:00
Julian Krings e5908285af remove old worlds from cache before retrieving 2025-09-10 16:15:01 +02:00
Julian Krings a8ee321eb8 Allow placing datapack structures in Iris worlds (#1225)
* Allow placing datapack structures in Iris worlds

* command for generating configs for datapack structures

* remove the sub dir from the snippet key
2025-09-07 18:36:08 +02:00
Julian Krings aa14242b54 another mantle fix 2025-09-07 16:50:23 +02:00
Julian Krings f6968269b4 add toggle to offset noise types fixing seeds 2025-09-06 14:35:25 +02:00
Julian Krings 0d0251e2f1 add toggle to change rarity algorithm 2025-09-06 14:21:52 +02:00
Julian Krings 81b8fb02ae replace rarity calculation due to it breaking at more than two values 2025-09-06 14:21:52 +02:00
Julian Krings 77842489e5 add command for checking the effective rarity of regions 2025-09-06 14:21:52 +02:00
Julian Krings eda1f59d3a fix broken cave floors 2025-09-05 17:02:36 +02:00
Julian Krings 5418868559 fix hotloading mantle components 2025-09-05 16:56:56 +02:00
Julian Krings 1d81daafbb add option to control the noise threshold for cave shapes 2025-09-05 16:06:53 +02:00
Julian Krings 609a3585c1 use flat maven repository for classpath context 2025-09-05 16:04:49 +02:00
Julian Krings 571dde608c add shared class loader support for script dependencies 2025-09-05 12:21:28 +02:00
Julian Krings 3a13f5a7c1 fix dependency resolve failing on new intelij versions 2025-09-04 23:07:37 +02:00
Julian Krings e5654b74d4 Auto Completion for block properties (#1222) 2025-09-04 18:06:00 +02:00
Julian Krings a75738dd7a minor noise optimizations 2025-09-04 16:13:41 +02:00
Julian Krings 003be1f88b use mantle chunk for deposit modifier 2025-09-04 13:56:05 +02:00
Julian Krings 1eaafae20d move caffeine cache executors into a virtual thread executor 2025-09-04 13:55:34 +02:00
Julian Krings 79088c0305 fix hyperlock not releasing on exceptions 2025-09-04 13:55:03 +02:00
Julian Krings 9e147774bd revert mantle change to hopefully fix deadlock 2025-09-04 13:54:28 +02:00
Julian Krings e51b632c8f Merge pull request #1221 from VolmitSoftware/feat/kts
Kotlin script engine
2025-09-04 13:43:08 +02:00
Julian Krings 1aa64c9a02 add hook for custom engine modes 2025-09-03 17:02:39 +02:00
Julian Krings 1e148d8fcd minor code cleanup 2025-09-03 13:32:08 +02:00
Julian Krings f63cabd8b8 fix mantle flag serialization 2025-09-03 12:56:02 +02:00
Julian Krings 176d3a5f9f remove old system property 2025-09-02 22:14:59 +02:00
Julian Krings eb184983de fix json property description on vscode 2025-09-02 22:09:32 +02:00
Julian Krings d1c307865d properly generate gradle wrapper for kts tab completion 2025-09-02 22:05:11 +02:00
Julian Krings 4a26b8b34f fix compile 2025-09-02 21:57:34 +02:00
Julian Krings 33fd01c3ac Merge branch 'dev' into feat/kts
# Conflicts:
#	core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java
#	core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java
#	core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java
#	core/src/main/java/com/volmit/iris/util/mantle/Mantle.java
#	core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java
2025-09-02 12:47:23 +02:00
Julian Krings 34874080e7 cleanup some array types for json schema generation 2025-09-02 12:28:54 +02:00
Julian Krings 43131ed8f6 make jar scanner also load child classes 2025-09-02 12:27:26 +02:00
Julian Krings 9ea425aee4 isolate io stuff in its own MultiBurst 2025-09-02 12:27:01 +02:00
Aidan Aeternum 709f05c1a5 v+ 2025-08-29 18:23:28 -04:00
Aidan Aeternum ec74add5de Merge pull request #1219 from VolmitSoftware/dev
3.7.2
2025-08-29 18:23:02 -04:00
Julian Krings d5b706764a isolate jigsaw component from objects 2025-08-27 16:12:12 +02:00
Julian Krings ca4c205a4a decrease wait times in mantle components 2025-08-27 16:08:49 +02:00
Julian Krings 7b9c2ae6ad minor optimizations 2025-08-27 12:34:52 +02:00
Julian Krings d0e9d44152 fix random null pointer in the resource loader 2025-08-27 00:33:59 +02:00
Julian Krings 2cdffaae33 include stronghold in mantle radius calc 2025-08-27 00:33:30 +02:00
Julian Krings d4a8beac95 fix more structures not being placed properly 2025-08-27 00:32:58 +02:00
Julian Krings 0e0e4075d8 fix more unsafe mantle operations 2025-08-27 00:32:34 +02:00
Julian Krings 9c492a2e66 save dev command for mantle panics 2025-08-26 17:46:27 +02:00
Julian Krings 96bf83684c add the shutdown hook on plugin enable instead of disable to prevent issues 2025-08-26 17:45:20 +02:00
Julian Krings 25ea9ae62d make regen respect multicore setting 2025-08-26 17:23:49 +02:00
Julian Krings 7938c150dd fix the DataContainer for the last time 2025-08-26 17:23:33 +02:00
Julian Krings a7b4bf3ff2 hopefully fix the DataContainer this time 2025-08-25 20:53:52 +02:00
Julian Krings 693a05f2cb move the updater command out of dev 2025-08-25 19:44:03 +02:00
Julian Krings e48cbe1f69 performance optimizations and add two experimental options for further optimizations 2025-08-24 22:33:35 +02:00
Julian Krings 558f6fa8dd Merge pull request #1218 from VolmitSoftware/fix/mantle
Mantle Fixes
2025-08-24 22:24:09 +02:00
Julian Krings 67b29cc363 cleanup palette and fix incorrect bit resizing 2025-08-24 18:54:44 +02:00
Julian Krings 4702534f9c add size check to prevent further corruption in case of read offset 2025-08-24 17:21:21 +02:00
Julian Krings 29390c5e0a fix potential issues in the CountingDataInputStream 2025-08-24 17:20:01 +02:00
Julian Krings e8bfce469d change the trimming cycle to make sure one step happens per second 2025-08-24 17:19:50 +02:00
Julian Krings 770e2f47a2 centralize file channels to prevent concurrent access 2025-08-24 17:19:39 +02:00
Julian Krings 747e7b3330 optimize the chunk updater 2025-08-21 17:27:07 +02:00
Julian Krings 8662f3b47a update ItemsAdder namespaces on load data 2025-08-21 13:50:21 +02:00
Aidan Aeternum 6e738e69e7 v+ 2025-08-21 02:40:05 -04:00
Aidan Aeternum 4bf14c83e1 Merge pull request #1215 from VolmitSoftware/dev
3.7.1
2025-08-21 02:39:19 -04:00
Julian Krings 837674c295 update sentry link 2025-08-19 18:00:22 +02:00
Julian Krings e5f3bbd69e make ItemsAdderDataProvider more reliable when adding items & blocks 2025-08-12 22:18:56 +02:00
Julian Krings 2885a39299 cleanup script engine 2025-08-09 00:00:31 +02:00
Julian Krings bd1b06c761 make studio map zoom in on the cursor target 2025-08-08 21:40:36 +02:00
Julian Krings 3e1143112a fix offset in studio map and some other issues 2025-08-08 21:38:26 +02:00
Julian Krings 834b214fbf add setting to control deposits replacing bedrock 2025-08-07 17:11:38 +02:00
Julian Krings 768e569400 Merge branch 'dev' into feat/kts
# Conflicts:
#	core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java
#	gradle/libs.versions.toml
2025-08-02 23:32:08 +02:00
Julian Krings a5d04333dd whoops messed up the structure slice 2025-08-02 23:19:11 +02:00
Julian Krings ebe8da0fd5 update slimjar for built-in spigot helper 2025-08-02 21:22:44 +02:00
Julian Krings 5eb25f3977 add property to use registry keys for where applicable
disabled by default for now
2025-08-02 00:01:33 +02:00
Julian Krings a5bca0a9bb fix not reading enum values correctly 2025-08-01 13:06:25 +02:00
Julian Krings a82882c1c1 move local maven repo into iris directory 2025-08-01 12:35:08 +02:00
Julian Krings 05193bd0d9 make depencency resolvers more predictable 2025-08-01 04:51:58 +02:00
Julian Krings 96efc15c36 fix local dependencies not resolving properly 2025-07-31 17:08:50 +02:00
Julian Krings 67f456cf53 cleanup script engine 2025-07-31 00:16:23 +02:00
Julian Krings 58b1bd115f update adventure api 2025-07-30 19:18:57 +02:00
Julian Krings 8ddc8abdb9 fix resolving dependencies in scripts 2025-07-30 16:54:19 +02:00
Julian Krings 12c2c71739 Merge branch 'refs/heads/dev' into feat/kts
# Conflicts:
#	core/build.gradle.kts
#	core/src/main/java/com/volmit/iris/Iris.java
#	core/src/main/java/com/volmit/iris/core/link/data/MythicMobsDataProvider.java
#	core/src/main/java/com/volmit/iris/util/misc/SlimJar.java
#	gradle/libs.versions.toml
2025-07-29 13:34:25 +02:00
Julian Krings 8705ca6e47 save structure placed at block 2025-07-28 21:44:27 +02:00
Julian Krings a610d0a7a9 remove unnecessary classpath injections 2025-07-28 20:53:01 +02:00
Julian Krings 0648cfd3fa add message for promotion of world to main world 2025-07-28 15:16:21 +02:00
Julian Krings fba9c17e3f fix not using relocated bukkit.yml correctly and remove duplicate code 2025-07-28 15:08:07 +02:00
Julian Krings 74128d58cf disable pack trimming for now 2025-07-28 15:06:34 +02:00
Julian Krings 70130e976d add getter method to IrisWorlds 2025-07-28 13:53:11 +02:00
Julian Krings 501c426302 fix slimjar error for new projects 2025-07-28 12:31:07 +02:00
Julian Krings 8471f15bc8 make world creation more failsafe 2025-07-27 23:23:04 +02:00
Julian Krings c4539441a0 fix datapack generation ignoring worlds when updating from <3.7.0 2025-07-27 23:22:40 +02:00
Julian Krings 472a98da16 update slimjar 2025-07-27 18:43:33 +02:00
Julian Krings 76a6f1465a update slimjar 2025-07-26 14:31:45 +02:00
Julian Krings b0ca0e3617 whoops messed up the extension retrieval 2025-07-25 13:21:16 +02:00
Julian Krings 08b9058c8f make git commit retrieval fail safe 2025-07-25 13:07:13 +02:00
Julian Krings 556bef6e43 fix more unsafe mantle chunk accesses 2025-07-25 12:44:01 +02:00
Julian Krings 16a4d20c90 use grgit to resolve current commit 2025-07-23 18:06:52 +02:00
Julian Krings e5f2fee926 add info about used commit to sentry reports 2025-07-23 15:07:06 +02:00
Julian Krings 3949468a60 inject into the library loader to make sure libraries can always be loaded 2025-07-23 14:45:54 +02:00
Julian Krings fc54fcb7eb make itemsadder data provider standalone 2025-07-22 12:12:13 +02:00
Julian Krings 9d6e4e87d8 fix custom blocks not placing 2025-07-22 12:11:41 +02:00
Julian Krings f50e964d4f hopefully fix unsafe mantle chunk operations 2025-07-21 17:14:45 +02:00
Julian Krings 8262e52893 cleanup regen 2025-07-19 18:10:57 +02:00
Julian Krings 1c41dc69ce Merge pull request #1213 from VolmitSoftware/feat/caves
Feat/caves
2025-07-19 17:59:32 +02:00
Julian Krings dee46a4284 cleanup object mask rotation 2025-07-19 17:48:58 +02:00
Julian Krings a8892b04ef bump nms for 1.21.8 support 2025-07-19 12:59:50 +02:00
Julian Krings d49f7d7821 add parameter to the create command to make it your main world 2025-07-13 17:43:11 +02:00
Julian Krings 106a3834ab fix iris create command failing 2025-07-13 17:42:10 +02:00
Julian Krings 387e8adfe2 ignore errors when scanning for external data providers 2025-07-12 18:31:39 +02:00
Julian Krings 9ade90d9ca make the mythic mobs link a data provider and make adding custom mob plugins easier 2025-07-12 15:51:49 +02:00
Julian Krings 3d2392843a update maven core to fix dependency resolution issues 2025-07-12 14:18:35 +02:00
Julian Krings a9891e819a v+ 2025-07-12 12:51:04 +02:00
Julian Krings 02c13a9391 Merge pull request #1211 from VolmitSoftware/dev
3.7.0
2025-07-12 12:42:38 +02:00
Julian Krings f6590c26e7 fix download failing for dimensions with snippets 2025-07-11 13:07:20 +02:00
Julian Krings 64bb81626c update overworld pack 2025-07-11 12:54:16 +02:00
Julian Krings 343dc429d5 fix null pointer when failing to load a dimension 2025-07-10 19:20:46 +02:00
Julian Krings 8f5f44bc96 open vscode after studio world was created 2025-07-10 18:24:50 +02:00
Julian Krings 6964b99744 fix reading custom mantle flags from json 2025-07-10 16:41:46 +02:00
Julian Krings 11cfd85f6a allow creation of custom mantle flags for custom mantle components 2025-07-10 16:11:02 +02:00
Julian Krings c5416f54fa add script hook for custom noise 2025-07-09 23:40:04 +02:00
Julian Krings 94c5782490 move loading message to debug 2025-07-09 18:49:55 +02:00
Julian Krings cc49b0f540 hopefully fix eta 2025-07-09 17:37:58 +02:00
Julian Krings 1bc6192c8b merge kts engine into this codebase 2025-07-09 15:16:11 +02:00
Julian Krings 4b0766c097 remove bfs 2025-07-09 14:24:24 +02:00
Julian Krings ec5cb2d646 Merge branch 'dev' into feat/kts
# Conflicts:
#	core/build.gradle.kts
#	core/src/main/java/com/volmit/iris/Iris.java
#	core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java
2025-07-09 14:23:53 +02:00
Julian Krings 0edaeeec99 massive cleanup of maven repos and update dependencies 2025-07-08 23:40:10 +02:00
Julian Krings a0543bbbf2 relocate slimjar 2025-07-08 19:59:45 +02:00
Julian Krings 0b1af51227 add option to disable specific mantle components for testing 2025-07-08 19:52:09 +02:00
Julian Krings 03c5998c02 generate a dummy region for standalone focus biomes 2025-07-08 19:08:12 +02:00
Julian Krings 6b193f695a switch to slimjar 2025-07-08 18:46:59 +02:00
Julian Krings 0a30881f87 add jaxen to runtime dependencies 2025-07-08 16:08:30 +02:00
Julian Krings c01a7def5d add iris data setup hook 2025-07-08 15:37:14 +02:00
Julian Krings 50db1d11a7 move script engine libraries into the iris plugin folder 2025-07-08 12:30:21 +02:00
Julian Krings 2e2ea8f1e4 use paper plugin loader to avoid maven central warning 2025-07-08 09:19:57 +02:00
Julian Krings badf108d56 Merge branch 'dev' into feat/kts
# Conflicts:
#	core/build.gradle.kts
#	core/src/main/java/com/volmit/iris/Iris.java
#	core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java
#	core/src/main/java/com/volmit/iris/util/io/IO.java
#	core/src/main/resources/plugin.yml
2025-07-08 00:36:30 +02:00
Julian Krings f9888d19a5 whoops forgot to update publish task 2025-07-08 00:24:03 +02:00
Julian Krings fbc5cee300 if it still doesn't work, idk what will 2025-07-08 00:22:31 +02:00
Julian Krings 12777bc3f0 does this shit work now?? 2025-07-07 23:59:42 +02:00
Julian Krings 9324b1b5c0 fix api file generation 2025-07-07 23:42:30 +02:00
Julian Krings 2ecb555619 add publishing for the api artifact 2025-07-07 23:37:37 +02:00
Julian Krings 20c7891c2f add api generation task 2025-07-07 22:54:35 +02:00
Julian Krings c35c858eee add scripts for engine setup 2025-07-07 21:06:36 +02:00
Julian Krings cb93e78242 fix safeguard error 2025-07-07 13:18:31 +02:00
Julian Krings c9ed4519a8 remove multiverse core incompatibility warning (#1212) 2025-07-06 20:00:06 +02:00
Julian Krings 86d986dfbc fix deposits spawning in columns 2025-07-06 19:40:14 +02:00
Julian Krings 54402faea8 fix automatic deepslate variant replacement 2025-07-04 13:18:28 +02:00
Julian Krings 77b4253624 fix sentry safeguard info 2025-07-04 12:34:20 +02:00
Julian Krings 44af23ba2e add check for new dimension types 2025-07-04 00:06:55 +02:00
Julian Krings 4e8079e431 use ticket queue for pregen by default 2025-07-04 00:01:16 +02:00
Julian Krings 39f65d02bf 1.21.6/7 Support (#1210) 2025-07-03 23:58:31 +02:00
Julian Krings 8c025a9ba2 clear snippet lists on hotload 2025-07-03 23:45:27 +02:00
Julian Krings 7119fa8ef7 Merge pull request #1207 from VolmitSoftware/feat/byte_buddy
Feat/byte buddy
2025-07-03 23:24:30 +02:00
Julian Krings 6f0b2b6bba initialize generators for isolated focus biomes / regions 2025-06-27 12:21:59 +02:00
Julian Krings 0ae1334a57 fix snippet tab completion 2025-06-27 12:20:25 +02:00
Julian Krings 5d28563b7c implement generate surface for ores 2025-06-24 19:43:46 +02:00
Julian Krings cf8243a000 make caves more deterministic 2025-06-24 19:37:37 +02:00
Julian Krings cca0bed482 fix cave max size calculation 2025-06-24 16:53:26 +02:00
Julian Krings a802edc375 add some options for customizing caves 2025-06-23 17:16:45 +02:00
Julian Krings 3677931114 improve snippet finder 2025-06-23 15:51:53 +02:00
Julian Krings e38dae0a32 fix required properties for json schemas 2025-06-23 15:51:29 +02:00
Julian Krings d537459c5a Merge branch 'dev' into feat/byte_buddy
# Conflicts:
#	build.gradle.kts
#	core/build.gradle.kts
#	core/src/main/java/com/volmit/iris/Iris.java
2025-06-23 14:24:43 +02:00
Julian Krings 67398174bb cache iris worlds in a map for datapack generation 2025-06-23 12:25:39 +02:00
Julian Krings 0a0f77ff58 fix splash being shown twice 2025-06-23 12:25:38 +02:00
Julian Krings e5cb4d82a3 Add KGenerators support (#1209) 2025-06-21 12:05:56 +02:00
Julian Krings 9d44ac0b47 fix caves being non-deterministic 2025-06-21 12:04:39 +02:00
Julian Krings 7f6d65a13e fix worm ignoring the breakSurface option 2025-06-21 12:01:06 +02:00
Julian Krings ad720f4aa2 fix worm using xStyle for y and z 2025-06-21 11:51:49 +02:00
Julian Krings 80548f753c isolate java agent to prevent class loader issues 2025-06-21 11:48:58 +02:00
Julian Krings c9c8a9e412 fix null pointers in pack trim method 2025-06-16 22:53:56 +02:00
Julian Krings d048c073ac disable trim for fallback pack download 2025-06-16 22:53:11 +02:00
Julian Krings 2929a1f0a7 update Script Engine to e08b6f893e 2025-06-16 22:44:15 +02:00
Julian Krings b6f9f68b9f add global preprocessor setting 2025-06-16 22:43:26 +02:00
Julian Krings da2dd42e28 invert suppressed error reporting for dev enviroments 2025-06-16 16:59:24 +02:00
Julian Krings 88360ef772 Merge branch 'dev' into feat/byte_buddy
# Conflicts:
#	build.gradle.kts
#	core/src/main/java/com/volmit/iris/Iris.java
2025-06-15 23:00:39 +02:00
Julian Krings 73787e21d2 Merge branch 'dev' into feat/kts
# Conflicts:
#	build.gradle
#	core/src/main/java/com/volmit/iris/Iris.java
#	core/src/main/java/com/volmit/iris/util/io/IO.java
2025-06-15 22:54:56 +02:00
Aidan Aeternum 840608a40f v+ 2025-06-14 16:49:22 -04:00
Aidan Aeternum fcedc35635 Merge pull request #1205 from VolmitSoftware/dev
3.6.11
2025-06-14 16:47:20 -04:00
Julian Krings 32d9a5e40a make sentry engine context hotload safe 2025-06-14 11:46:51 +02:00
Julian Krings 8df6253604 add safeguard info to sentry reports 2025-06-14 11:44:30 +02:00
Julian Krings 01b62c13b6 fix deleting mantle temp files before they are fully written 2025-06-14 11:18:14 +02:00
Julian Krings f32f73e65a disable error reporting in dev environment 2025-06-13 12:27:03 +02:00
Julian Krings 7b7118fe0d add id for servers hash of jvm name & version, processor info, memory size and plugins 2025-06-13 12:26:56 +02:00
Julian Krings 851ac18f0d implement custom conditions for mythic mobs (#1204) 2025-06-11 13:23:09 +02:00
Julian Krings 37be7ca847 don't send json and zip file closed exceptions to sentry 2025-06-11 13:14:28 +02:00
Julian Krings ed67b4d3c2 fix spawners not having entities due to using old format 2025-06-11 13:03:31 +02:00
Julian Krings b62ac875d5 cleanup gradle 2025-06-10 16:48:00 +02:00
Julian Krings 4c3f95b0e1 Merge branch 'dev' into feat/byte_buddy
# Conflicts:
#	build.gradle
#	core/src/main/java/com/volmit/iris/Iris.java
2025-06-10 13:33:56 +02:00
Julian Krings 8712c8874c disable global pregen cache by default for now 2025-06-09 23:27:12 +02:00
Julian Krings ccc3bab8e0 allow carving recursion 2025-06-09 23:25:33 +02:00
Julian Krings 113f25dab8 more sentry context 2025-06-09 23:17:47 +02:00
Julian Krings cd80acdc7d fix IndexOutOfBoundsException when getting the selection from the wand 2025-06-09 19:50:50 +02:00
Julian Krings 9316ea9e5b fix npe when creating cuboids 2025-06-09 19:49:06 +02:00
Julian Krings e2a3f25dcb fix load in parallel method 2025-06-09 19:42:03 +02:00
Julian Krings 944cc19ebc use proper shuffling algorithm for the loot 2025-06-09 19:35:43 +02:00
Julian Krings 172e234514 fix divide by zero in the engine svc 2025-06-09 19:19:00 +02:00
Julian Krings f9dac8a3a1 fix compile while missing the sentry auth token 2025-06-09 18:40:43 +02:00
Julian Krings eefbbab7ee remove legacy chars before minimessage deserialization 2025-06-09 18:39:11 +02:00
Julian Krings 9cf567e1ff remove unused util to fix class not found exception 2025-06-09 13:47:24 +02:00
Julian Krings b8ee7561dd fix failing to create temp file when user deleted the temp directory 2025-06-09 13:44:57 +02:00
Julian Krings c0d17742e8 fix release task for sentry 2025-06-09 13:43:00 +02:00
Julian Krings a88d389e0f bump gradle wrapper to 8.14.2 and switch to kotlin dsl (#1203) 2025-06-09 12:45:27 +02:00
Julian Krings b341089996 v+ 2025-06-09 02:12:14 +02:00
Julian Krings 9cbfd5a10b Merge pull request #1202 from VolmitSoftware/dev
3.6.10
2025-06-09 02:11:22 +02:00
Julian Krings fdaf8ff9d3 relocate sentry to fix incompatibility with some plugins using a very old sentry version 2025-06-09 00:17:38 +02:00
Julian Krings e63d84c052 remove server name from reports 2025-06-09 00:16:58 +02:00
Julian Krings 0d103a934a fix ticking engine players in the wrong dimension 2025-06-08 12:43:56 +02:00
Julian Krings 8119207254 sentry changes
- info message on enabling
- add release tag
- disable uncaught exception handling to prevent reporting other plugins issues
2025-06-08 12:29:24 +02:00
Julian Krings b66e6d8335 correct sentry dsn 2025-06-07 14:00:22 +02:00
Julian Krings ce2b62f5ae add sentry release task 2025-06-06 18:37:11 +02:00
Julian Krings c767b6c8e8 check sentry env token as fallback 2025-06-06 18:36:41 +02:00
Julian Krings 67328d7d10 remove test exception for sentry 2025-06-06 17:35:13 +02:00
Julian Krings 329e136a66 implement opt-out auto reporting with sentry (#1201) 2025-06-06 17:33:16 +02:00
Julian Krings 52f87befa2 return noop pregen cache when the service is disabled and write pregen cache to temp file first then replace real one 2025-06-06 17:24:23 +02:00
Julian Krings 2ee22db072 fix mantle write failing on windows 2025-06-06 16:49:42 +02:00
Julian Krings cc27e87376 fix world height decreases deleting the whole chunk 2025-06-05 20:34:25 +02:00
Julian Krings abb1d9cd62 add bytebuddy binding for 1.21.5 2025-06-05 20:26:16 +02:00
Julian Krings e0ad029c3d Merge branch 'dev' into feat/byte_buddy
# Conflicts:
#	build.gradle
#	core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java
2025-06-05 19:27:01 +02:00
Julian Krings 5705caa1ba fix benchmarking not disabling properly 2025-06-04 20:20:48 +02:00
Julian Krings a56cd4c268 use block data use for slice 2025-06-04 16:32:29 +02:00
Julian Krings 48cc6bb49c improve mantle writer speeds 2025-06-04 15:03:00 +02:00
Aidan Aeternum 635ee02459 v+ 2025-06-02 18:48:01 -04:00
Aidan Aeternum 2c7b7c8c91 Merge pull request #1199 from VolmitSoftware/dev
3.8.9
2025-06-02 18:47:38 -04:00
Julian Krings ad0662be54 initial 1.21.5 bindings 2025-06-02 18:18:17 +02:00
Julian Krings 835c8422f1 calculate spawn location async 2025-06-02 17:42:36 +02:00
Julian Krings 2c60192de3 use scheduled thread pool instead of loopers for the EngineSVC 2025-06-02 17:29:38 +02:00
Julian Krings adb7188eb9 refactor mantle cleanup 2025-06-02 17:29:30 +02:00
Julian Krings 2436ebb857 Merge pull request #1198 from VolmitSoftware/feat/versioned_mantle 2025-06-02 17:28:58 +02:00
Julian Krings ad85a0bbd1 implement marker exhaustionChance 2025-05-29 23:17:23 +02:00
Julian Krings 22ac9ebf47 update nexo api to release 1.6.0 2025-05-29 14:18:30 +02:00
Julian Krings 3cb9585dd8 fix pregenerator not closing when a new one is started 2025-05-28 21:48:41 +02:00
Julian Krings f4d1177c51 fix cave fluid ignoring fluid palette 2025-05-28 12:57:11 +02:00
Julian Krings b132862a60 update last use for in use tectonic plates 2025-05-27 22:50:30 +02:00
Julian Krings 2452a2f633 cleanup of the mantle trimmer / EngineSVC 2025-05-27 16:23:02 +02:00
Julian Krings f0476fea9b implement version header for tectonic plates 2025-05-26 20:54:12 +02:00
Julian Krings 90c6457d37 write to plates to a temp file first and then move it into the mantle dir 2025-05-26 17:29:15 +02:00
Julian Krings 61301ffd4d close engines using a shutdown hook 2025-05-26 17:29:15 +02:00
Julian Krings e42317139d fix engines not closing on server stop 2025-05-26 17:29:15 +02:00
Julian Krings f68600464b remove redundant method from NMSBinding1X 2025-05-25 18:12:48 +02:00
Julian Krings 97ddfd309b fix updater not working 2025-05-21 13:05:07 +02:00
Aidan Aeternum 5d42c5cae0 v+ 2025-05-20 12:32:04 -04:00
Aidan Aeternum 52fecb48d8 Merge pull request #1196 from VolmitSoftware/dev
3.6.8
2025-05-20 12:31:35 -04:00
Julian Krings ce29dc98c3 fix spawn markers not being removed 2025-05-16 12:41:31 +02:00
Julian Krings 463e3d9658 Merge pull request #1193 from VolmitSoftware/feat/faster_pregen
Feat/faster pregen
2025-05-16 12:29:41 +02:00
Julian Krings 9152b25d51 fix pack hash calculation 2025-05-16 12:22:35 +02:00
Julian Krings bb9c72e414 Partially Merge pull request #1192 from dan28000/Iris 2025-05-16 12:11:46 +02:00
Julian Krings 976648340e add pregen method that doesn't use waiting threads 2025-05-16 12:02:38 +02:00
Julian Krings 5958bcb22e update Script Engine to 41396a77d5 2025-04-28 21:40:44 +02:00
Julian Krings 8eb35aa8be update Script Engine to b5e2cc6e 2025-04-28 21:24:54 +02:00
Julian Krings 49ce84a9e9 Merge branch 'dev' into feat/faster_pregen
# Conflicts:
#	core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java
2025-04-27 13:56:28 +02:00
Julian Krings 6724b0f4c5 minor cleanup and improved safeguard for missing dimension types 2025-04-25 21:18:33 +02:00
Julian Krings e72abc8c39 generate json schemas for intelij 2025-04-24 16:00:23 +02:00
Julian Krings 0a2f35dd8d update the scripting engine and fix cached file extension 2025-04-23 21:24:14 +02:00
Julian Krings c597c55c2c replace rhino scripting engine with kotlin scripting 2025-04-23 14:21:09 +02:00
Aidan Aeternum f93995e152 v+ 2025-04-21 20:27:43 -04:00
Aidan Aeternum 7a5a2e5909 Merge pull request #1191 from VolmitSoftware/dev
3.6.7
2025-04-21 20:27:18 -04:00
dan28000 e15d31a0ed would be nice to display the right number 2025-04-21 22:59:29 +02:00
dan28000 09cdd61a68 fix method
when having relocated worlds folder this method would fail making warning appear
2025-04-21 20:45:20 +02:00
dan28000 0575cd85c8 delete old stuff
delete legacy command
2025-04-21 20:44:38 +02:00
Julian Krings 2008975a8a expose most of the dimension type options 2025-04-17 17:30:29 +02:00
Julian Krings aad1d3815a Merge branch 'dev' into feat/byte_buddy 2025-04-16 23:18:49 +02:00
Julian Krings 86c64f99e9 filter out non iris pack directories in allPacks stream 2025-04-16 23:01:57 +02:00
Julian Krings 6577f4a5de rough safeguard for missing dimension type 2025-04-16 22:02:42 +02:00
Julian Krings 3415e7c7af implement complete world height isolation 2025-04-16 21:52:30 +02:00
Pixel f1c72974fd Cleanup (#1186)
* Remove server benchmark command

* Moved lazy pregen to dev
2025-04-15 15:53:50 +02:00
Julian Krings 26e2e20840 Feat/pregen cache (#1190)
Add cache to pregen to skip already generated chunks faster
2025-04-14 21:26:39 +02:00
Julian Krings c00dcf205b fix deadlock when closing pregen method while using modified concurrency 2025-04-14 21:25:31 +02:00
Julian Krings a10a784c3b add max concurrency setting for pregen 2025-04-14 21:25:31 +02:00
Julian Krings f99cc61042 add setting to use a virtual thread executor instead of MultiBurst for async pregen 2025-04-14 21:25:31 +02:00
Julian Krings d2a1e5cc1e fix duplicate & redundant purpur recommendations 2025-04-14 21:23:52 +02:00
Julian Krings 3e2c0fa025 Decrease MSPT impact of throwing ender eyes in Iris worlds 2025-04-14 21:23:37 +02:00
Julian Krings 9c151abac7 replace world load context injection with bytecode injections 2025-04-10 16:26:58 +02:00
Aidan Aeternum ab4770400e v+ 2025-04-10 04:49:32 -04:00
Aidan Aeternum 1a810d5d62 Merge pull request #1184 from VolmitSoftware/dev
3.6.6
2025-04-10 04:49:02 -04:00
Julian Krings 536d20bca7 remove context injection on world init 2025-04-09 12:16:03 +02:00
Julian Krings 9a691ac5b4 setup runServer for all supported versions 2025-04-09 10:57:35 +02:00
Julian Krings 71078a20a9 Don't inject world load context for main worlds to prevent incompatibilities 2025-04-08 21:15:19 +02:00
Julian Krings 566fca2b52 Add info message for failed increase of worker threads 2025-04-07 19:00:06 +02:00
Julian Krings 74e2576ca2 Prevent saving the iris level stems 2025-04-06 16:31:35 +02:00
Julian Krings fb0bc112e3 increase worker threads on paper servers during pregen 2025-03-30 14:31:11 +02:00
Julian Krings 407e51378c fix applying x offset to z coords in Spiraler 2025-03-27 15:02:47 +01:00
Julian Krings c468eb1ab1 make pregen use block radius as input 2025-03-27 15:02:47 +01:00
Aidan Aeternum bdb7cc61e5 v+ 2025-03-26 15:41:35 -04:00
Aidan Aeternum e8f9e841c4 Merge pull request #1181 from VolmitSoftware/dev
3.6.5
2025-03-26 15:41:06 -04:00
Julian Krings 1b1b9d97b7 update overworld pack to 31020 2025-03-25 19:15:47 +01:00
Julian Krings 24355064ff add safeguard for missing dimension types to prevent world corruption 2025-03-25 19:14:20 +01:00
Julian Krings 06a45056d9 use flat level source instead of trying to get the levelstems 2025-03-22 12:38:25 +01:00
1212 changed files with 57740 additions and 88827 deletions
+3 -1
View File
@@ -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/
+5 -5
View File
@@ -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
View File
@@ -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.4-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' }
}
}
+25
View File
@@ -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')
}
+162
View File
@@ -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();
}
}
+5
View File
@@ -0,0 +1,5 @@
public class Config {
public int jvm = 25;
public NMSBinding.Type type = NMSBinding.Type.DIRECT;
public String version;
}
+282
View File
@@ -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
}
}
+14
View File
@@ -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
View File
@@ -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 })
}
}
}
+1
View File
@@ -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));
}
}
@@ -16,18 +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 com.volmit.iris.util.scheduling.ChronoLatch;
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;
@@ -44,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() {
@@ -130,52 +128,92 @@ 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() {
return getThreadCount(worldGenParallelism);
}
}
@Data
public static class IrisSettingsPregen {
public boolean useCacheByDefault = true;
public boolean useHighPriority = false;
public boolean useVirtualThreads = false;
public boolean useTicketQueue = true;
public 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;
@@ -195,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;
@@ -207,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
@@ -214,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");
}
}
}
@@ -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;
}
}
}
@@ -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).");
}
}
}
@@ -16,35 +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.IrisSettings;
import com.volmit.iris.core.gui.PregeneratorJob;
import com.volmit.iris.core.pregenerator.LazyPregenerator;
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 org.bukkit.Bukkit;
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;
import java.io.File;
@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());
}
@Decree(name = "pregen", aliases = "pregenerate", description = "Pregenerate your Iris worlds!")
public class CommandPregen implements DecreeExecutor {
@Decree(description = "Pregenerate a world")
@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) {
@@ -52,13 +55,12 @@ public class CommandPregen implements DecreeExecutor {
sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example.");
}
radius = Math.max(radius, 1024);
int w = (radius >> 9 + 1) * 2;
IrisToolbelt.pregenerate(PregenTask
.builder()
.center(new Position2(center.getBlockX() >> 9, center.getBlockZ() >> 9))
.gui(true)
.width(w)
.height(w)
.center(new Position2(center.getBlockX(), center.getBlockZ()))
.gui(gui)
.radiusX(radius)
.radiusZ(radius)
.build(), world);
String msg = C.GREEN + "Pregen started in " + C.GOLD + world.getName() + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ();
sender().sendMessage(msg);
@@ -70,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...");
@@ -79,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") + ".");
@@ -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 {
}
@@ -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");
@@ -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;
}
}
}
@@ -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;
@@ -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));
}
}
}
@@ -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);
}
}
@@ -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);
@@ -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;
@@ -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 {
@@ -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;
}
}
}
@@ -16,37 +16,39 @@
* 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.core.tools.IrisPackBenchmarking;
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;
import static com.volmit.iris.core.tools.IrisPackBenchmarking.benchmarkInProgress;
public class PregeneratorJob implements PregenListener {
private static final Color COLOR_EXISTS = parseColor("#4d7d5b");
private static final Color COLOR_BLACK = parseColor("#4d7d5b");
@@ -56,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;
@@ -67,26 +69,35 @@ 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..."};
this.task = task;
this.pregenerator = new IrisPregenerator(task, method, this);
max = new Position2(0, 0);
min = new Position2(0, 0);
task.iterateRegions((xx, zz) -> {
min.setX(Math.min(xx << 5, min.getX()));
min.setZ(Math.min(zz << 5, min.getZ()));
max.setX(Math.max((xx << 5) + 31, max.getX()));
max.setZ(Math.max((zz << 5) + 31, max.getZ()));
min = new Position2(Integer.MAX_VALUE, Integer.MAX_VALUE);
task.iterateAllChunks((xx, zz) -> {
min.setX(Math.min(xx, min.getX()));
min.setZ(Math.min(zz, min.getZ()));
max.setX(Math.max(xx, max.getX()));
max.setZ(Math.max(zz, max.getZ()));
});
if (IrisSettings.get().getGui().isUseServerLaunchedGuis() && task.isGui()) {
@@ -99,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) {
@@ -162,7 +196,7 @@ public class PregeneratorJob implements PregenListener {
}
public void drawRegion(int x, int z, Color color) {
J.a(() -> PregenTask.iterateRegion(x, z, (xx, zz) -> {
J.a(() -> task.iterateChunks(x, z, (xx, zz) -> {
draw(xx, zz, color);
J.sleep(3);
}));
@@ -182,7 +216,7 @@ public class PregeneratorJob implements PregenListener {
J.a(() -> {
pregenerator.close();
close();
instance = null;
instance.compareAndSet(this, null);
});
}
@@ -222,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",
@@ -243,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
@@ -307,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");
}
});
}
}
@@ -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;
}
}
@@ -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
@@ -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.*;
@@ -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));
}
}
@@ -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,4 +1,4 @@
package com.volmit.iris.core.link;
package art.arcane.iris.core.link;
import org.bukkit.NamespacedKey;
@@ -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();
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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());
}
}
@@ -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() {
@@ -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());
}
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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.");
}
}
}
@@ -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()];
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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) {}
}
@@ -16,32 +16,37 @@
* 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.Dolphin;
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.*;
import java.awt.Color;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface INMSBinding {
boolean hasTile(Material material);
@@ -93,14 +98,34 @@ public interface INMSBinding {
MCABiomeContainer newBiomeContainer(int min, int max);
default World createWorld(WorldCreator c) {
try (var ignored = injectLevelStems()) {
return c.createWorld();
WorldLifecycleRequest request = WorldLifecycleRequest.fromCreator(c, false, false, WorldLifecycleCaller.CREATE);
return createWorld(c, request);
}
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,20 +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);
default AutoClosing injectLevelStems() {
return new AutoClosing(() -> {});
default boolean injectBukkit() {
return true;
}
default Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
return new Pair<>(0, injectLevelStems());
KMap<Material, List<BlockProperty>> getBlockProperties();
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,4 +1,4 @@
package com.volmit.iris.core.nms.container;
package art.arcane.iris.core.nms.container;
public enum BiomeColor {
FOG,
@@ -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,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));
}
}
}
@@ -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;
}
}
@@ -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 -> {}
}
}
}
}
@@ -16,29 +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.BiomeColor;
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 {
@@ -81,7 +84,7 @@ public class NMSBinding1X implements INMSBinding {
@Override
public void injectBiomesFromMantle(Chunk e, Mantle mantle) {
public void injectBiomesFromMantle(Chunk e, Mantle<Matter> mantle) {
}
@@ -110,12 +113,17 @@ 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);
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
@@ -194,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
@@ -202,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
@@ -226,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();
}
}
@@ -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;
@@ -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;
}
}
@@ -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