mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-07-14 10:53:57 +00:00
Compare commits
4557 Commits
1.0
..
unification
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dea984d77 | |||
| cd05fd1bca | |||
| e42ab6fae6 | |||
| 402bcb04fe | |||
| 6111b5670a | |||
| 0a7a531646 | |||
| d1fec88769 | |||
| 848e056bb8 | |||
| 6b64624233 | |||
| d1cc83a40b | |||
| 927c48f8c7 | |||
| 29ff3f01a7 | |||
| a8e46e3590 | |||
| 75e8c22268 | |||
| f777ea6b18 | |||
| 8fb627f9c1 | |||
| 3cfbcbf8ed | |||
| 79e80bef33 | |||
| 796d81e58b | |||
| b0a5d97e9c | |||
| ad6732812e | |||
| f482b8ef7e | |||
| e2f6061f98 | |||
| b98a341dc7 | |||
| 261a850af4 | |||
| 516537db72 | |||
| 921a98f210 | |||
| 2bac948edb | |||
| 26d9115904 | |||
| 763ff90a61 | |||
| f32094a404 | |||
| 6b24105640 | |||
| a3d7269a99 | |||
| e1196b0364 | |||
| 4c462ba78a | |||
| 65d4abe5c0 | |||
| 5142c048c7 | |||
| 88c87175b3 | |||
| 56a0c75a39 | |||
| 704c94aab4 | |||
| b5d08b6203 | |||
| b584cb4193 | |||
| af3035088a | |||
| df888e5516 | |||
| ca10edef0c | |||
| 986c7f12cd | |||
| 6c5e991051 | |||
| 1a834ed6f6 | |||
| c9929e0880 | |||
| 3063801a3a | |||
| 13002b04df | |||
| 1c9f9d92bb | |||
| 4568f0b4b6 | |||
| 1c08eb23b2 | |||
| 7e224325f3 | |||
| a1ff3b2c55 | |||
| abfc122171 | |||
| 6a68884ce9 | |||
| c5a2a57822 | |||
| 58d24ca55e | |||
| c6c02a9040 | |||
| a530a03459 | |||
| a82b8f4a9c | |||
| cf2e7df298 | |||
| d20b3af9b9 | |||
| 01d98d2141 | |||
| febe930db0 | |||
| 5c832ba128 | |||
| 03f9e233da | |||
| 11efae428f | |||
| 1a6fff35e8 | |||
| 540a416bc9 | |||
| 7eb6e87a81 | |||
| 9ff1c502ef | |||
| 08c6f7ddf9 | |||
| 4bc1913f03 | |||
| 821cc027db | |||
| 1968ef2f2c | |||
| ce29b70618 | |||
| 3d128b70a7 | |||
| 23fad24fb7 | |||
| 6df718e6ca | |||
| 787c728060 | |||
| 88bbce82fe | |||
| e6a8351e57 | |||
| b82472d521 | |||
| 8c05f1bf1d | |||
| 6a5616abae | |||
| 6096c39192 | |||
| b42d0e4710 | |||
| dcb3306197 | |||
| 9a231b2bcf | |||
| aa706d027b | |||
| 568fb07f66 | |||
| bf207b7062 | |||
| 18d4dce1db | |||
| 651dfa247e | |||
| 5b1ff6d2f7 | |||
| 589baafc28 | |||
| 130073989d | |||
| 2ca8cc7ad3 | |||
| 9f35855599 | |||
| 5110a33857 | |||
| a38e87cce3 | |||
| 1d09cd5f0f | |||
| 72c891ce5b | |||
| 3964185a81 | |||
| afc5f67bc2 | |||
| 1c72d6410f | |||
| c2c612eb35 | |||
| 415c2f5837 | |||
| b491d9efd0 | |||
| d643461e2e | |||
| 1f41e195cf | |||
| 7b9280083f | |||
| 73525c170d | |||
| 16c861a781 | |||
| d6bd5ec82f | |||
| 1829fa3eb1 | |||
| 8d215e3911 | |||
| ba819e4899 | |||
| 05d79b6d40 | |||
| cf64fbb730 | |||
| 43a65135b1 | |||
| 440c1bec6c | |||
| 0fd8bcc488 | |||
| bb1c0b8f68 | |||
| fc25acdea2 | |||
| 0aa6f6d523 | |||
| a79c1a7954 | |||
| 7885762cd7 | |||
| 86ed3f0095 | |||
| 99fcc8fd03 | |||
| 41888e33f6 | |||
| 25fa2553e5 | |||
| 86f78baecf | |||
| c31158578f | |||
| 4e86d7d634 | |||
| 62fe29cf34 | |||
| a3bcea4f3e | |||
| 7befce1084 | |||
| 351a1aa495 | |||
| d2cb8a9032 | |||
| 509715087c | |||
| 12527ecdd8 | |||
| 01a2999e03 | |||
| dbafe84fa5 | |||
| 40b020fc5d | |||
| d15f7d62d1 | |||
| f9c062c794 | |||
| 6a89b8bd06 | |||
| 194fcb2ea7 | |||
| 3b68f855b2 | |||
| cfbf68d37a | |||
| 9999f3e429 | |||
| 97fa0562a4 | |||
| 3abe671851 | |||
| e164a3bb5c | |||
| 4dfb4441e4 | |||
| e686f67453 | |||
| e2eed4812a | |||
| 1737833bfe | |||
| a4c5f46c37 | |||
| 025fa833c4 | |||
| 98cc82cc3d | |||
| 298365f588 | |||
| 90e5720e2e | |||
| 7cd43791f4 | |||
| a251d192ad | |||
| abfff28f43 | |||
| fbdae4c928 | |||
| 93ca26e368 | |||
| 8c1db1c223 | |||
| 123708601f | |||
| b5ab4968ba | |||
| 93ae6037bd | |||
| dd8e487a3b | |||
| d3c8377a12 | |||
| 84f3687c0c | |||
| ed1e1f1181 | |||
| 528c97f367 | |||
| f7a459f3bc | |||
| cd179b4321 | |||
| 6373dbb1b8 | |||
| 0b0797f876 | |||
| 446acefc91 | |||
| 7a44e555b2 | |||
| 57d4c2935c | |||
| 234fb1b0c4 | |||
| 0882b5acc4 | |||
| 18da26e1fa | |||
| 65aa95f2a5 | |||
| 5330ddc4ec | |||
| a7fdd37569 | |||
| a226fea9e2 | |||
| 8cea165a29 | |||
| 1d7cba184c | |||
| 4ca7ea3911 | |||
| ea5919def2 | |||
| be35e49302 | |||
| aadd03990a | |||
| 38a579453d | |||
| 0bf5da2ca1 | |||
| 317848692e | |||
| 22118de9e9 | |||
| d7039d120b | |||
| 979ee4e7d8 | |||
| f68d45bd30 | |||
| b86d7f303e | |||
| c573843314 | |||
| 51a7bef18e | |||
| 0e237aa1ad | |||
| b46c413f6b | |||
| f3ef1ca2ae | |||
| 703e61dd54 | |||
| e1ec6b7827 | |||
| f94292fdac | |||
| 7d153bf985 | |||
| f85f15ed02 | |||
| 867686eced | |||
| 526efd3ae1 | |||
| 9d796bd2a0 | |||
| 1a9a5d80ad | |||
| c5c7f9bdc5 | |||
| 01a421b732 | |||
| ae92bcf194 | |||
| 7e7933858b | |||
| 9c073ecbcb | |||
| f4617c1996 | |||
| 21a2e4feef | |||
| 258d0d3aaa | |||
| 27b2fd0823 | |||
| 0524adb0df | |||
| 3981b0976d | |||
| b5811cae08 | |||
| c998fd1fd9 | |||
| a7d874d37f | |||
| 7c41f86fb3 | |||
| e5e0561d5a | |||
| d50cdfec3e | |||
| 00997c1902 | |||
| 3095a92522 | |||
| fca309dec7 | |||
| 2793ed1035 | |||
| e5908285af | |||
| a8ee321eb8 | |||
| aa14242b54 | |||
| f6968269b4 | |||
| 0d0251e2f1 | |||
| 81b8fb02ae | |||
| 77842489e5 | |||
| eda1f59d3a | |||
| 5418868559 | |||
| 1d81daafbb | |||
| 609a3585c1 | |||
| 571dde608c | |||
| 3a13f5a7c1 | |||
| e5654b74d4 | |||
| a75738dd7a | |||
| 003be1f88b | |||
| 1eaafae20d | |||
| 79088c0305 | |||
| 9e147774bd | |||
| e51b632c8f | |||
| 1aa64c9a02 | |||
| 1e148d8fcd | |||
| f63cabd8b8 | |||
| 176d3a5f9f | |||
| eb184983de | |||
| d1c307865d | |||
| 4a26b8b34f | |||
| 33fd01c3ac | |||
| 34874080e7 | |||
| 43131ed8f6 | |||
| 9ea425aee4 | |||
| 709f05c1a5 | |||
| ec74add5de | |||
| d5b706764a | |||
| ca4c205a4a | |||
| 7b9c2ae6ad | |||
| d0e9d44152 | |||
| 2cdffaae33 | |||
| d4a8beac95 | |||
| 0e0e4075d8 | |||
| 9c492a2e66 | |||
| 96bf83684c | |||
| 25ea9ae62d | |||
| 7938c150dd | |||
| a7b4bf3ff2 | |||
| 693a05f2cb | |||
| e48cbe1f69 | |||
| 558f6fa8dd | |||
| 67b29cc363 | |||
| 4702534f9c | |||
| 29390c5e0a | |||
| e8bfce469d | |||
| 770e2f47a2 | |||
| 747e7b3330 | |||
| 8662f3b47a | |||
| 6e738e69e7 | |||
| 4bf14c83e1 | |||
| 837674c295 | |||
| e5f3bbd69e | |||
| 2885a39299 | |||
| bd1b06c761 | |||
| 3e1143112a | |||
| 834b214fbf | |||
| 768e569400 | |||
| a5d04333dd | |||
| ebe8da0fd5 | |||
| 5eb25f3977 | |||
| a5bca0a9bb | |||
| a82882c1c1 | |||
| 05193bd0d9 | |||
| 96efc15c36 | |||
| 67f456cf53 | |||
| 58b1bd115f | |||
| 8ddc8abdb9 | |||
| 12c2c71739 | |||
| 8705ca6e47 | |||
| a610d0a7a9 | |||
| 0648cfd3fa | |||
| fba9c17e3f | |||
| 74128d58cf | |||
| 70130e976d | |||
| 501c426302 | |||
| 8471f15bc8 | |||
| c4539441a0 | |||
| 472a98da16 | |||
| 76a6f1465a | |||
| b0ca0e3617 | |||
| 08b9058c8f | |||
| 556bef6e43 | |||
| 16a4d20c90 | |||
| e5f2fee926 | |||
| 3949468a60 | |||
| fc54fcb7eb | |||
| 9d6e4e87d8 | |||
| f50e964d4f | |||
| 8262e52893 | |||
| 1c41dc69ce | |||
| dee46a4284 | |||
| a8892b04ef | |||
| d49f7d7821 | |||
| 106a3834ab | |||
| 387e8adfe2 | |||
| 9ade90d9ca | |||
| 3d2392843a | |||
| a9891e819a | |||
| 02c13a9391 | |||
| f6590c26e7 | |||
| 64bb81626c | |||
| 343dc429d5 | |||
| 8f5f44bc96 | |||
| 6964b99744 | |||
| 11cfd85f6a | |||
| c5416f54fa | |||
| 94c5782490 | |||
| cc49b0f540 | |||
| 1bc6192c8b | |||
| 4b0766c097 | |||
| ec5cb2d646 | |||
| 0edaeeec99 | |||
| a0543bbbf2 | |||
| 0b1af51227 | |||
| 03c5998c02 | |||
| 6b193f695a | |||
| 0a30881f87 | |||
| c01a7def5d | |||
| 50db1d11a7 | |||
| 2e2ea8f1e4 | |||
| badf108d56 | |||
| f9888d19a5 | |||
| fbc5cee300 | |||
| 12777bc3f0 | |||
| 9324b1b5c0 | |||
| 2ecb555619 | |||
| 20c7891c2f | |||
| c35c858eee | |||
| cb93e78242 | |||
| c9ed4519a8 | |||
| 86d986dfbc | |||
| 54402faea8 | |||
| 77b4253624 | |||
| 44af23ba2e | |||
| 4e8079e431 | |||
| 39f65d02bf | |||
| 8c025a9ba2 | |||
| 7119fa8ef7 | |||
| 6f0b2b6bba | |||
| 0ae1334a57 | |||
| 5d28563b7c | |||
| cf8243a000 | |||
| cca0bed482 | |||
| a802edc375 | |||
| 3677931114 | |||
| e38dae0a32 | |||
| d537459c5a | |||
| 67398174bb | |||
| 0a0f77ff58 | |||
| e5cb4d82a3 | |||
| 9d44ac0b47 | |||
| 7f6d65a13e | |||
| ad720f4aa2 | |||
| 80548f753c | |||
| c9c8a9e412 | |||
| d048c073ac | |||
| 2929a1f0a7 | |||
| b6f9f68b9f | |||
| da2dd42e28 | |||
| 88360ef772 | |||
| 73787e21d2 | |||
| 840608a40f | |||
| fcedc35635 | |||
| 32d9a5e40a | |||
| 8df6253604 | |||
| 01b62c13b6 | |||
| f32f73e65a | |||
| 7b7118fe0d | |||
| 851ac18f0d | |||
| 37be7ca847 | |||
| ed67b4d3c2 | |||
| b62ac875d5 | |||
| 4c3f95b0e1 | |||
| 8712c8874c | |||
| ccc3bab8e0 | |||
| 113f25dab8 | |||
| cd80acdc7d | |||
| 9316ea9e5b | |||
| e2a3f25dcb | |||
| 944cc19ebc | |||
| 172e234514 | |||
| f9dac8a3a1 | |||
| eefbbab7ee | |||
| 9cf567e1ff | |||
| b8ee7561dd | |||
| c0d17742e8 | |||
| a88d389e0f | |||
| b341089996 | |||
| 9cbfd5a10b | |||
| fdaf8ff9d3 | |||
| e63d84c052 | |||
| 0d103a934a | |||
| 8119207254 | |||
| b66e6d8335 | |||
| ce2b62f5ae | |||
| c767b6c8e8 | |||
| 67328d7d10 | |||
| 329e136a66 | |||
| 52f87befa2 | |||
| 2ee22db072 | |||
| cc27e87376 | |||
| abb1d9cd62 | |||
| e0ad029c3d | |||
| 5705caa1ba | |||
| a56cd4c268 | |||
| 48cc6bb49c | |||
| 635ee02459 | |||
| 2c7b7c8c91 | |||
| ad0662be54 | |||
| 835c8422f1 | |||
| 2c60192de3 | |||
| adb7188eb9 | |||
| 2436ebb857 | |||
| ad85a0bbd1 | |||
| 22ac9ebf47 | |||
| 3cb9585dd8 | |||
| f4d1177c51 | |||
| b132862a60 | |||
| 2452a2f633 | |||
| f0476fea9b | |||
| 90c6457d37 | |||
| 61301ffd4d | |||
| e42317139d | |||
| f68600464b | |||
| 97ddfd309b | |||
| 5d42c5cae0 | |||
| 52fecb48d8 | |||
| ce29dc98c3 | |||
| 463e3d9658 | |||
| 9152b25d51 | |||
| bb9c72e414 | |||
| 976648340e | |||
| 5958bcb22e | |||
| 8eb35aa8be | |||
| 49ce84a9e9 | |||
| 6724b0f4c5 | |||
| e72abc8c39 | |||
| 0a2f35dd8d | |||
| c597c55c2c | |||
| f93995e152 | |||
| 7a5a2e5909 | |||
| e15d31a0ed | |||
| 09cdd61a68 | |||
| 0575cd85c8 | |||
| 2008975a8a | |||
| aad1d3815a | |||
| 86c64f99e9 | |||
| 6577f4a5de | |||
| 3415e7c7af | |||
| f1c72974fd | |||
| 26e2e20840 | |||
| c00dcf205b | |||
| a10a784c3b | |||
| f99cc61042 | |||
| d2a1e5cc1e | |||
| 3e2c0fa025 | |||
| 9c151abac7 | |||
| ab4770400e | |||
| 1a810d5d62 | |||
| 536d20bca7 | |||
| 9a691ac5b4 | |||
| 71078a20a9 | |||
| 566fca2b52 | |||
| 74e2576ca2 | |||
| fb0bc112e3 | |||
| 407e51378c | |||
| c468eb1ab1 | |||
| bdb7cc61e5 | |||
| e8f9e841c4 | |||
| 1b1b9d97b7 | |||
| 24355064ff | |||
| 06a45056d9 | |||
| dfe4894be7 | |||
| 8eb2287ec0 | |||
| c4f0722614 | |||
| 7fa1484b21 | |||
| 1c5eb8b910 | |||
| 94bf530d93 | |||
| 686ae57b5b | |||
| a911685aaf | |||
| 6899761ca9 | |||
| a58958fd62 | |||
| 307f3c9158 | |||
| 4f275c2e06 | |||
| 7e4e3f3cd8 | |||
| 84e5add564 | |||
| 4d4adbb76f | |||
| ff2f285784 | |||
| 8b1636e78a | |||
| 3bdad10562 | |||
| ac03a977aa | |||
| d7270f66e1 | |||
| b220b1bffa | |||
| 4796fe98cb | |||
| ece905ec6e | |||
| 53c9e7c04c | |||
| 29f6f52443 | |||
| a778cc51a6 | |||
| c6963d0cd3 | |||
| 489844f61b | |||
| 4d1b0246ca | |||
| 13f3511fa8 | |||
| f6f2766315 | |||
| 56530a1245 | |||
| 210a1f29a7 | |||
| 847bf972ae | |||
| e5d21fdf7e | |||
| 0b2fd3b358 | |||
| 10484d1226 | |||
| ce0092c52a | |||
| 474e033c2b | |||
| 62aad1f497 | |||
| 32b5157682 | |||
| 70717ea282 | |||
| 15975f108c | |||
| 66c66e82c1 | |||
| 4f6da95d8e | |||
| b37ccbdf01 | |||
| 30dbe0881a | |||
| 20ad4657a9 | |||
| d4986f42a6 | |||
| 8df15c0c2d | |||
| 24e1c578c8 | |||
| 1c3bff7559 | |||
| a09657b4d0 | |||
| 910220d3ca | |||
| fc05c24e3a | |||
| e1a7e772cf | |||
| 3c6411c322 | |||
| 628761affa | |||
| fa7b0f68ff | |||
| 487d0ac237 | |||
| e79e3fbe45 | |||
| 23a0ab23aa | |||
| c78ffab948 | |||
| d58f497b71 | |||
| 3284ab84c5 | |||
| 5cf0ac9315 | |||
| 4e4e820826 | |||
| cb6e4570f4 | |||
| fb59c7370d | |||
| c16e65f0a8 | |||
| d9681edf62 | |||
| 520c156d5f | |||
| 5c26ec2521 | |||
| d192e2b6d1 | |||
| 5b60b655ed | |||
| 97c7ac0528 | |||
| 090ff730a7 | |||
| c1f797e7c9 | |||
| 4a1a6b80a6 | |||
| e189b2389c | |||
| 3265447536 | |||
| 8c7c9f89e1 | |||
| d7a991b9b3 | |||
| abf6c93f2e | |||
| 1b76a66760 | |||
| c83ac67b47 | |||
| 1dca502a90 | |||
| cacef8c8fc | |||
| 623fd45ef4 | |||
| 007b4b0b53 | |||
| b6bacee095 | |||
| d5251350a1 | |||
| 1f9c72d093 | |||
| a1495a10e5 | |||
| 11ae48bea1 | |||
| b0f4b29a2d | |||
| c38bb1cd01 | |||
| 7faa727bd2 | |||
| 9e40259ca2 | |||
| 94a7692735 | |||
| 8b803a87f0 | |||
| 3ae6e92eaf | |||
| 0f3c52a5aa | |||
| 747be7aa09 | |||
| d651f204f8 | |||
| 2bd3ac7a9b | |||
| 5e437b34e3 | |||
| a5ef89a128 | |||
| 558b8e4eca | |||
| 0a001b8a63 | |||
| 6c6c9654c1 | |||
| e21fdf46e0 | |||
| a2ff5f58ed | |||
| b0eedee519 | |||
| e101155a4c | |||
| 3c9bcc9bb0 | |||
| 6763844030 | |||
| 08fa436885 | |||
| df8fa79209 | |||
| 8041db4f40 | |||
| 18e57e4097 | |||
| cc584ba377 | |||
| 0c92c20c65 | |||
| ec8af56f0d | |||
| 79341bf562 | |||
| 3f24d5c8e1 | |||
| 66a1739666 | |||
| 29007fdbfa | |||
| be3e8ebd51 | |||
| 38ad345f85 | |||
| 414f46a08d | |||
| 9144606688 | |||
| effb0f5b91 | |||
| bb7bbb6379 | |||
| dafca8e9db | |||
| 41b7aec084 | |||
| dfe1cce6de | |||
| fc793592f7 | |||
| c3ac41f894 | |||
| d0688782b1 | |||
| 25b41fe62c | |||
| b468478fcb | |||
| b6dc934198 | |||
| f58078e8a0 | |||
| b6457e47e6 | |||
| d275466e1e | |||
| f9cb107728 | |||
| 68ad206252 | |||
| 288bead792 | |||
| de670ddfd5 | |||
| 79d6f34879 | |||
| ceb6c15c97 | |||
| 7cf43ad7ab | |||
| ab3397a373 | |||
| 8e9f78e982 | |||
| 61bbfee640 | |||
| cf4796bd12 | |||
| bd89d8a308 | |||
| df2186d70f | |||
| f9638e830f | |||
| cd55a7fed4 | |||
| fc890a5ba1 | |||
| 2ea54b7f2f | |||
| e90e3901f5 | |||
| 36d58f29ab | |||
| 864c7ae27c | |||
| 6d104a2d1c | |||
| 89e754245d | |||
| 4c68d99c6f | |||
| f8f0a65f0a | |||
| 6ff74639b5 | |||
| af80f882da | |||
| 84ce7372c3 | |||
| e31d39c5f7 | |||
| 2296c1368b | |||
| 21d7d96dbc | |||
| 956e511fe0 | |||
| e427887f8c | |||
| 88ea43fbbe | |||
| 29526a80a9 | |||
| 48f901fc8c | |||
| 6c3f3dc889 | |||
| 743410d3ee | |||
| acccbb028f | |||
| 8b31fdc780 | |||
| 55017b9ac2 | |||
| 0288d35c85 | |||
| 8b3577dd0b | |||
| ced77ec70d | |||
| e1e90f5c8a | |||
| dc3487583d | |||
| e1d73ebcd5 | |||
| d7cf3b9500 | |||
| 8ce4e3c6b2 | |||
| 76f7d46b22 | |||
| cfc52ed909 | |||
| cee94d40e1 | |||
| cad7166cbe | |||
| c05fdbd0ec | |||
| c9b26ebaff | |||
| bee1973390 | |||
| 57efcab0b5 | |||
| b1ab2b84f8 | |||
| 02601e5ee4 | |||
| 070479acf3 | |||
| 6b2ba74237 | |||
| 619de29710 | |||
| 12556fa98f | |||
| bef4c0497f | |||
| 9193497d36 | |||
| ca6affbde9 | |||
| 81c40ce228 | |||
| 6ba1d571f2 | |||
| b0663f9b6c | |||
| 3ced6f3e9e | |||
| 3146d61efe | |||
| a967b6af85 | |||
| db3bc74364 | |||
| eb19d9a846 | |||
| 96dd299de7 | |||
| aee0806aa8 | |||
| e39f13bbb4 | |||
| 52ec80d384 | |||
| d9f8909bdc | |||
| 65aa9dc343 | |||
| 9bf7fdf174 | |||
| dafb59e5a8 | |||
| 79f86ca87d | |||
| 1749fcf6b5 | |||
| e1a0481cdf | |||
| 4576b95814 | |||
| e113fa6a61 | |||
| 07228afbff | |||
| afdffc2453 | |||
| 6348442962 | |||
| fd10c005b0 | |||
| 44500d6af9 | |||
| 6ddb0b5304 | |||
| 8ce0976f88 | |||
| 8345a58f2b | |||
| f92234297e | |||
| 6a5bcd5990 | |||
| 1fe6d9a636 | |||
| 97f6ab66c3 | |||
| b78c182d94 | |||
| 09a0f83013 | |||
| f68d7420e3 | |||
| 55c0fa5f32 | |||
| 8b5d1d0298 | |||
| b70e94dc65 | |||
| 49a6552168 | |||
| 0dd6892b28 | |||
| fec587edad | |||
| 712937d661 | |||
| ace434e7b0 | |||
| 1e5aa6e8b0 | |||
| 441b27f8f4 | |||
| 3f3bf70212 | |||
| a12eddbedf | |||
| 12abc1709e | |||
| 9e6035e7b4 | |||
| 20b41d65d3 | |||
| 1593bb2088 | |||
| d4f9a20379 | |||
| 6e247597a4 | |||
| 8a8be4545c | |||
| 05f4955989 | |||
| 93469fb3b4 | |||
| a70258d69f | |||
| c653d852e4 | |||
| 40163d25b8 | |||
| 5b4265783e | |||
| 720417b6c8 | |||
| bb78f412e0 | |||
| a919b91efb | |||
| a3dcf031c9 | |||
| 56eb4b6b84 | |||
| 393cb362db | |||
| 64e422036c | |||
| 468e7ef018 | |||
| e3e4ecbc5c | |||
| 545ffc0e9d | |||
| 8125c8d5f4 | |||
| 304b01d0cf | |||
| a5f687fd76 | |||
| fc1761a55b | |||
| e7c9cad7f6 | |||
| 979af82122 | |||
| 71a62b9c73 | |||
| bb020cab25 | |||
| 1ad35c1310 | |||
| 6470b2f4a9 | |||
| c1d5ba55cd | |||
| 100e450514 | |||
| 0f1d1d9860 | |||
| 178a462a4e | |||
| 8f4ae613f0 | |||
| 9e6963b6ce | |||
| cb9a73c60e | |||
| 0e666a4c35 | |||
| c80138a354 | |||
| 79a4ebcf65 | |||
| a7118aa785 | |||
| 2555cd23a0 | |||
| 484fbeca7b | |||
| 9a45e0df10 | |||
| 9175296fc6 | |||
| 5a4a86aeba | |||
| 768fa7beb5 | |||
| 6645eb9806 | |||
| d7a283c99f | |||
| 8f019cd794 | |||
| c1d9cc62cb | |||
| 010a1e9e91 | |||
| 856c926cde | |||
| 72ed312654 | |||
| c7fe4723f7 | |||
| bfbea83a4a | |||
| 55c58fe896 | |||
| 72949e0950 | |||
| 61c0ddb15b | |||
| 9ac4024e4e | |||
| 7fb2a51a33 | |||
| 5b7c0b2bc3 | |||
| fc15175ff9 | |||
| 1eec3a09c1 | |||
| 60d349dd8b | |||
| 0c8d144ffb | |||
| b898f73a05 | |||
| c2b9b0ba0e | |||
| 206a4e9057 | |||
| 83a0a7dd54 | |||
| 63b8a935ae | |||
| 16affd11cc | |||
| 33b0dec9da | |||
| 121a463788 | |||
| 3625e70948 | |||
| 3d90207172 | |||
| ca7dc19c81 | |||
| d0f16dbbc4 | |||
| fd91c223b7 | |||
| 93690b766c | |||
| cc4af9950b | |||
| a6281483dd | |||
| f587816b30 | |||
| eea3c3ab0b | |||
| ba68ab9e06 | |||
| eb94d97ea4 | |||
| 04ad02719c | |||
| f68f25b92f | |||
| 767cca36c8 | |||
| b30172fe89 | |||
| b2934b0cc2 | |||
| 50686795d0 | |||
| 5561a4dc2f | |||
| 15b8780e17 | |||
| 15c4d312cb | |||
| ccbea89253 | |||
| 7b1e666b3b | |||
| 8af212fca8 | |||
| 352ee7a622 | |||
| f26d0fac0b | |||
| 9612fef2a4 | |||
| a691d49abc | |||
| 289eca35ec | |||
| e9ca30257c | |||
| 6b21997baa | |||
| 6d3dbf84ef | |||
| eb45339c81 | |||
| 65e3fdd26c | |||
| 34cad85942 | |||
| e407679d2b | |||
| 8a08a3e148 | |||
| a01d27d273 | |||
| 43f642ae8b | |||
| 93c1265de9 | |||
| d08600fbaa | |||
| fb9f8d28f4 | |||
| cd3e9f772d | |||
| 7a95441799 | |||
| 9345828c97 | |||
| 58d88dd079 | |||
| 6cbc2374b3 | |||
| 12bd11386d | |||
| 0497045388 | |||
| d3f1640855 | |||
| 960e2fcc61 | |||
| 6a012cf400 | |||
| d658ec2099 | |||
| c9a07bd9d7 | |||
| c0136585e6 | |||
| dc4cfc49ad | |||
| 9fc72868d7 | |||
| 7d4c9d6c1e | |||
| e68b1a52ee | |||
| 955e8622b5 | |||
| 82640de06b | |||
| c93cb19563 | |||
| ccb7855d7d | |||
| bad547356c | |||
| 1740bb0021 | |||
| 20f05bf317 | |||
| 669ede5482 | |||
| ab658f58f9 | |||
| 1b3916749a | |||
| e29ec657d3 | |||
| 04f3a226a1 | |||
| 099d1cc362 | |||
| c11e9bf704 | |||
| ffa0b984a0 | |||
| f0f9261ed3 | |||
| ba433bc244 | |||
| 27f8ad982a | |||
| 9b89ae7846 | |||
| c11b5342e3 | |||
| b9c88f5b12 | |||
| 02c7ab71d4 | |||
| 8ec1ac9875 | |||
| e03027a386 | |||
| 387cf566c9 | |||
| dff7665934 | |||
| 9421d788f3 | |||
| d960574b24 | |||
| e8671cbbf0 | |||
| cbf8039eea | |||
| d7415e3711 | |||
| 26e71ee6b5 | |||
| f8c42b3e48 | |||
| 886bed9634 | |||
| 7b7023fc99 | |||
| 701bfe4a54 | |||
| 290d7a93ab | |||
| 729bcc7ba0 | |||
| e46b68e2f3 | |||
| cdf73c4629 | |||
| 2f0dd2d172 | |||
| 7e5704bf80 | |||
| ef96fb9a0e | |||
| f2370ad814 | |||
| 0783620104 | |||
| 2193b02c0a | |||
| aef05bad20 | |||
| e002ab6378 | |||
| a2c2b4a2d4 | |||
| 3ae896457b | |||
| 683a90ed09 | |||
| 5685e25234 | |||
| 6edf083e7b | |||
| 995d1495eb | |||
| d5f74631cd | |||
| dd5b9b2e81 | |||
| ca78200c1c | |||
| d73c2a65ae | |||
| 0ee2cb8e3c | |||
| 5ba429ec73 | |||
| 709825d073 | |||
| 3fe73af5e8 | |||
| ccc0b0dd7d | |||
| 1114ede267 | |||
| 5546f12499 | |||
| 4f38acb592 | |||
| 39f59e023c | |||
| 5ed5322fce | |||
| 33b926d547 | |||
| af8778c6cd | |||
| bb4ad08cc5 | |||
| ee4eb7b3f0 | |||
| 5bc024a6ee | |||
| 0546caeb05 | |||
| a75348d3ee | |||
| 3f3f947f43 | |||
| 5127b7c959 | |||
| a28df78792 | |||
| 4f888f16bc | |||
| 55ed9d7132 | |||
| 779a56c3a1 | |||
| f6440bde07 | |||
| 930dc2fd1c | |||
| 1e96fd529b | |||
| 167c865bb2 | |||
| 4e6b6c11a3 | |||
| bfdb71ca91 | |||
| f590f9824e | |||
| b3a087702d | |||
| 1a786fb418 | |||
| 650d38e212 | |||
| 130cb7ad93 | |||
| 85e8ffeaa3 | |||
| db40f608fa | |||
| a7b471682a | |||
| 4595fea7d7 | |||
| 4580f8d9a4 | |||
| 37f76fa133 | |||
| 5507c35547 | |||
| 350328f99c | |||
| 91ec6b773d | |||
| c0fdf23133 | |||
| 28523a9593 | |||
| 07e0b52ea3 | |||
| 008e758da8 | |||
| 7ce219a6a8 | |||
| 1809d4a53f | |||
| 54ae026c2b | |||
| 57c647fa0e | |||
| 9745f23fb2 | |||
| 7d2062f298 | |||
| 306a1948cf | |||
| ac4c00d3f2 | |||
| db43bc300e | |||
| 6494cded62 | |||
| b5c1e7e2da | |||
| 83e311870f | |||
| 8fc6e3b335 | |||
| 18a69e97b3 | |||
| 80d8449f02 | |||
| d830aa1d1a | |||
| d473126d4c | |||
| 17f8fe69fb | |||
| 7263d102c7 | |||
| 815b2235a2 | |||
| f61247f8bb | |||
| 205d9d528e | |||
| 1044f901bd | |||
| de261a38fd | |||
| 81776e60fb | |||
| 0c179eae86 | |||
| c30de98449 | |||
| 47990cde46 | |||
| b098c210ce | |||
| 6edd8bf119 | |||
| de0e124ebb | |||
| 5fc012a557 | |||
| 32afcc3478 | |||
| a27f37945d | |||
| 0adfe6911f | |||
| 8bb1964b30 | |||
| cc95e1ae63 | |||
| 9843f18c96 | |||
| 18ea61fcc0 | |||
| 5b3fdb6e62 | |||
| c5220c8d06 | |||
| 74e87a7fae | |||
| c809f50c3b | |||
| e7e46f78c7 | |||
| 2b449a1f3d | |||
| f257f2c551 | |||
| 7f91d73d27 | |||
| 15a90edb03 | |||
| b2f1e16d9a | |||
| f544455708 | |||
| 5118997188 | |||
| b6ae649cd1 | |||
| a6d0d85b84 | |||
| c71c2494af | |||
| 4a3a7bc98d | |||
| 4e4f7f693d | |||
| 7a729c39a6 | |||
| 48b757f693 | |||
| b449938730 | |||
| ba55ff6a4b | |||
| 2b4bc621ac | |||
| acac2d0fc5 | |||
| 6ff5975918 | |||
| 8912cfe37b | |||
| 9e02c318a1 | |||
| f6b51c21d5 | |||
| df3e4b055f | |||
| ffcd2b4179 | |||
| 7afdbc2a53 | |||
| 5004481685 | |||
| 1905741a7c | |||
| 8bf7451107 | |||
| fdfb708ce0 | |||
| 68a4eee8bb | |||
| 746ebfffbd | |||
| 213f8645bc | |||
| 1b2541bd84 | |||
| 522de89792 | |||
| 5feaa45edf | |||
| 9333775e0e | |||
| afd15ad450 | |||
| e7fc9e6fe5 | |||
| 021f9b1d0e | |||
| dc21f05482 | |||
| c22ccf71a7 | |||
| d5b9a074e6 | |||
| 63e6104736 | |||
| de475c9561 | |||
| 3f4fa0c67c | |||
| ca3f228b89 | |||
| 2b159041ac | |||
| 5fa564969e | |||
| 8ba8627281 | |||
| a12cb59a51 | |||
| dd124fab33 | |||
| 9f8ef4c1f3 | |||
| 563c6d0403 | |||
| 1bad0fe048 | |||
| b40dcd3164 | |||
| 8a0b443d28 | |||
| 5f38503ac5 | |||
| 231d1209c9 | |||
| 06bc180127 | |||
| fb2221fa8d | |||
| e1c7f38bb8 | |||
| a82fa578eb | |||
| 0ecefdcc1c | |||
| 8bacc8128b | |||
| 3dff96152c | |||
| 79e198d441 | |||
| e6cf33e262 | |||
| f839ca674b | |||
| f9427d1258 | |||
| bd2cdc9bd0 | |||
| 67352311de | |||
| 555a7682e1 | |||
| 19d94489d7 | |||
| 0175cfa986 | |||
| 1b0ff36d51 | |||
| f5c499ab60 | |||
| 81e3fd346a | |||
| 3bca1f5304 | |||
| 593e96a31a | |||
| 169b783e4f | |||
| f129742a41 | |||
| 01016d1b14 | |||
| a883e43acb | |||
| e0d0657e7b | |||
| e4c27bd49c | |||
| 7a31fd31eb | |||
| 4f0f2c3213 | |||
| d9889b06ad | |||
| 9f5cff4ab8 | |||
| 958543a171 | |||
| b8e03f5e13 | |||
| e76218b39f | |||
| 7d1a375bda | |||
| dfaa80f1e4 | |||
| 8939a903be | |||
| d6f1ea78fa | |||
| f25c4c74be | |||
| 1619df1ba3 | |||
| 566169cdbf | |||
| ee3e631789 | |||
| 8202754988 | |||
| 447d7596dc | |||
| 7c4568066f | |||
| 46ee7a5983 | |||
| 5a8ef99afa | |||
| 571729cfe4 | |||
| c1de316ed6 | |||
| 1e316acd64 | |||
| f89460538d | |||
| 0bf0062c2c | |||
| 540d1b5801 | |||
| 8f0d1b7b7b | |||
| 5061791dcf | |||
| ce98264552 | |||
| 15e6750e11 | |||
| 7a20421580 | |||
| f42f06226c | |||
| cce3d74c52 | |||
| 7b9fb880f4 | |||
| 2a9b0a54d9 | |||
| 373e9e9755 | |||
| 4115dd5797 | |||
| 57bfd251dc | |||
| d324790f66 | |||
| 99d3dba440 | |||
| 5a44e79ad1 | |||
| 86808017db | |||
| f735db9843 | |||
| 5a333c23ac | |||
| 4d2d51edfa | |||
| dcfd5a0cd8 | |||
| a821c0da50 | |||
| 804147e2c4 | |||
| ed346291f9 | |||
| 2190f6eff8 | |||
| ce30e24e42 | |||
| d5e13541c8 | |||
| 2e707adaa0 | |||
| 4d8e36cb05 | |||
| 73b710cdde | |||
| 09182bf3f3 | |||
| 9a945837cd | |||
| b64c821ef4 | |||
| a9ab3bc519 | |||
| 94292cea6e | |||
| b5fdeb2a02 | |||
| fa3ac36e8c | |||
| 6632d1b65f | |||
| 8805693800 | |||
| 93db7ee0ee | |||
| 9ba4dd4099 | |||
| 36efacf43c | |||
| d9bf91afa5 | |||
| b0c96be841 | |||
| 673b08f174 | |||
| 989778af26 | |||
| f5a3a7eb1a | |||
| 7a97928c21 | |||
| 12a6d022cf | |||
| e3d2dfa99e | |||
| 35cc39e9e2 | |||
| 006b0b4a03 | |||
| ec939b9f78 | |||
| 7a94f53735 | |||
| a9efe146ba | |||
| 956e2f6b06 | |||
| 72623e0acf | |||
| e0f673bc3c | |||
| fe40f12d2e | |||
| 0fda7a8506 | |||
| 22887da769 | |||
| a1e0a8ffc1 | |||
| 62010116d7 | |||
| 6ebcd02ae6 | |||
| 051015656a | |||
| ee082762c6 | |||
| e967b5e052 | |||
| 36505e2fa1 | |||
| 502aa054f6 | |||
| cc5a880fd7 | |||
| 6f9ad8b0eb | |||
| ac6ab74d48 | |||
| 807ed2b247 | |||
| 367de5a8fd | |||
| 5bf2da714a | |||
| cc90f42deb | |||
| 20ceaead09 | |||
| 3ec2f8fb7b | |||
| 6c5fac154e | |||
| 540ab8f0d2 | |||
| 17d2ac8d70 | |||
| a6ebdead19 | |||
| aa7631ecd0 | |||
| 4e138cad9f | |||
| 7e55b5fcee | |||
| e19e5278b4 | |||
| dfcd2dc83e | |||
| 8775f842e6 | |||
| 7b07a4ba6c | |||
| d78e5973e9 | |||
| 110d296184 | |||
| 23cd5c117b | |||
| 131c4692bc | |||
| f6571367db | |||
| f5c64c7480 | |||
| 085f63a915 | |||
| 3e022e1931 | |||
| 0dba3725ae | |||
| 8bb409df4e | |||
| 603168a147 | |||
| 2a95edd860 | |||
| e94406fb45 | |||
| e5a7b5d0c6 | |||
| d6f816fe2f | |||
| c15d4a349f | |||
| 3ce832583c | |||
| 5514fd2645 | |||
| 66d07dcaca | |||
| c1cf8e88ee | |||
| 684bd739b9 | |||
| d4c0e07b1d | |||
| a6ea6fcfb2 | |||
| c366ec0c40 | |||
| 8d715e2e4e | |||
| dea3ec80ac | |||
| 4053f05ba9 | |||
| ef07ec2c62 | |||
| 0ffc64231b | |||
| fe112c3ba5 | |||
| 2fa03aac3b | |||
| 6ac5ad880d | |||
| 54f14392d9 | |||
| c7ecdaf8a8 | |||
| 497c24a3b6 | |||
| 00aa26e602 | |||
| 3796381156 | |||
| b0c5700cf5 | |||
| 78a4b1d2ce | |||
| e5e6b9848f | |||
| 3e36f57f14 | |||
| d794ede4d8 | |||
| 7a4d97d76a | |||
| e76ab1d367 | |||
| 9731feff7f | |||
| 4ce790082d | |||
| 065b748036 | |||
| 76891d246f | |||
| 0832007991 | |||
| 87302a046c | |||
| f0f7453b32 | |||
| 2991bea248 | |||
| 673b42c2f5 | |||
| 7d994e27e3 | |||
| 5f2b82aac7 | |||
| af1a03cb67 | |||
| 85fbbeca9d | |||
| a64272620f | |||
| ef9966d02b | |||
| a28e5bb38a | |||
| 34670759e9 | |||
| 7dd1d3881a | |||
| 7b93542014 | |||
| ea1ac0a154 | |||
| 35c7068fa6 | |||
| db4cbb2ea6 | |||
| fa7c073999 | |||
| 72a077662d | |||
| 137abb04b2 | |||
| 211b15332d | |||
| f6fbcade17 | |||
| 1ef115fee8 | |||
| 7973444fc5 | |||
| 2bdb0bfa69 | |||
| bfb6f82ea3 | |||
| 7729ce3753 | |||
| f6ea171669 | |||
| ff081ebc11 | |||
| 0818f971fe | |||
| 27070f44c7 | |||
| 8331cbe375 | |||
| f533ec34b0 | |||
| 23d4fcb827 | |||
| a248962f1b | |||
| afa8fad8e9 | |||
| 4c423bb493 | |||
| e660fe9e1e | |||
| 23c1d12e73 | |||
| 9cf2785626 | |||
| b8b65c7e3e | |||
| dcb467280a | |||
| 6aa802b42d | |||
| d78f6ca589 | |||
| 493f8e0cc0 | |||
| 119563c553 | |||
| 2d61e6af66 | |||
| aa1be934a9 | |||
| f9c98b0a60 | |||
| bd66fa3bf5 | |||
| 380f191f6a | |||
| 7e217b5fba | |||
| c4a516a858 | |||
| bdf181e348 | |||
| f4ea1343b4 | |||
| a37ccddd38 | |||
| 243ef8c0be | |||
| 00d099383b | |||
| dda3f0b8e9 | |||
| 00f4d8b1ee | |||
| 2f5198d533 | |||
| 148261f876 | |||
| 60843b3bb9 | |||
| 2ad9a525bd | |||
| 78e9f21439 | |||
| a66d60eaea | |||
| 3aa3c13477 | |||
| cc850522e6 | |||
| 6d28f57f88 | |||
| 563e0a7cd4 | |||
| 2f2f857e98 | |||
| 251c52a2ee | |||
| 7528bd343b | |||
| 06d9f279ac | |||
| 044403b829 | |||
| 5a24dd3b49 | |||
| 3189b3c7a5 | |||
| 3bf1c6a282 | |||
| 7c7309c3ab | |||
| da777da476 | |||
| 3d0c0a11ed | |||
| e545269b93 | |||
| 141ca76647 | |||
| 94557830f5 | |||
| 49acb7faba | |||
| 1904f67662 | |||
| 5b2bf38344 | |||
| 494c38a153 | |||
| dd4b85cbfe | |||
| 8f7b54a5a4 | |||
| cec502340e | |||
| 64e27c7fb0 | |||
| b2bbd31548 | |||
| 17df92a07c | |||
| 2b749b5ab7 | |||
| 28e3402d88 | |||
| d96bb061e0 | |||
| e6def804f8 | |||
| a5be48c07c | |||
| fe3909f594 | |||
| f37d91a530 | |||
| 614ef78771 | |||
| fb294fc03c | |||
| 8c7e7c3d48 | |||
| 98b6280652 | |||
| 7c4c2d6382 | |||
| 1a29ea302e | |||
| be39fce741 | |||
| 4fed2e6e5f | |||
| 14ae5809b0 | |||
| 0f66aa47c9 | |||
| cf85d300f4 | |||
| 91d60e56df | |||
| 1d3681beb4 | |||
| 0b403cf329 | |||
| 8ba75899e3 | |||
| 4d4ed72159 | |||
| b72bf072b3 | |||
| 4edcb54b31 | |||
| eae7fc0e53 | |||
| fddacd4410 | |||
| de6c4b9a30 | |||
| 6990e34138 | |||
| d9883697ef | |||
| 05a26ff8b2 | |||
| 84bb082d44 | |||
| a0db94d84f | |||
| 0367b4ecd7 | |||
| dbc425dce6 | |||
| 116c017c6d | |||
| 9c387475f0 | |||
| 0765c21cef | |||
| 2871038584 | |||
| 340885f939 | |||
| 1e32e47f54 | |||
| 3c759f3b01 | |||
| 6615f34d20 | |||
| 3a4aac1ee4 | |||
| 4d6c092615 | |||
| 93421a1dc9 | |||
| 209458a856 | |||
| 82bd94620e | |||
| af9f017871 | |||
| 2f8922ea87 | |||
| 0ea936f3fe | |||
| d1870ee0a8 | |||
| d2151690ad | |||
| dff373e7d5 | |||
| 0aec2a664f | |||
| 723b696393 | |||
| f5f89eb4e4 | |||
| df15332c7f | |||
| 61d9e5a869 | |||
| 9628b66a97 | |||
| 5e041366d4 | |||
| 3ecae8d72c | |||
| 5709ce9d82 | |||
| ce3c334ac5 | |||
| 2ed5aa8730 | |||
| 8480a63dda | |||
| 132a1695f3 | |||
| 21775630c3 | |||
| eac2ef7c6d | |||
| 386997f646 | |||
| 637b90ad62 | |||
| 95d755c2ec | |||
| 781aeebef6 | |||
| 5e133fd51a | |||
| 85f62a3b9d | |||
| a32de58c8b | |||
| 1cb2ea6c17 | |||
| 6a37ab7af2 | |||
| e0f0aaf767 | |||
| 3d7c5b050a | |||
| 6bc57c255f | |||
| b82edfe688 | |||
| 6b32eb3441 | |||
| 23a07fa8a5 | |||
| b019faedd2 | |||
| 4430433a10 | |||
| f52cd29e7b | |||
| 6a44e593a6 | |||
| f7065fe034 | |||
| 0a247956f7 | |||
| 03836acded | |||
| b5fb277982 | |||
| e523d3c166 | |||
| 41477e4aa6 | |||
| fbaf42a8c4 | |||
| 1c3668047b | |||
| 7d78c69b6e | |||
| 43bd96a679 | |||
| a87c83dae2 | |||
| 490d2b69e3 | |||
| 7eac936431 | |||
| 588b261d52 | |||
| daf2cf02b3 | |||
| b9d97f2434 | |||
| 1467dc963d | |||
| 89bf687fc4 | |||
| 2e7c80562b | |||
| 5d54b82be0 | |||
| 2ae1df4d42 | |||
| 26782e6f0c | |||
| cc84d5b562 | |||
| 8bb1efdca3 | |||
| 7a5794c8e2 | |||
| 9dd20661f5 | |||
| ad0ab66711 | |||
| 82396b5145 | |||
| 246e974dc1 | |||
| 203b6a4b4f | |||
| f204f3a209 | |||
| ba8e060370 | |||
| c51172acbc | |||
| 39100a5011 | |||
| fcee61a703 | |||
| ce8cc2cf37 | |||
| 3b5bdcfb51 | |||
| 4d67e97973 | |||
| da09f4d6d8 | |||
| dbc7e2e0bf | |||
| dd804b6665 | |||
| 33eb878834 | |||
| 2a2c456011 | |||
| 05a12f3899 | |||
| eb04710f71 | |||
| 257760453e | |||
| d11baeb08a | |||
| 47d322c993 | |||
| d316207a61 | |||
| e6daabbb47 | |||
| 53156449f1 | |||
| e3d4c32d03 | |||
| 9cd5c39bbe | |||
| a1fbf25465 | |||
| ec74037f05 | |||
| fff650b766 | |||
| bae2a18e24 | |||
| fd8a7ebb4c | |||
| 4a3a1491d4 | |||
| 9aa2c5ed8a | |||
| d5699a7472 | |||
| df98f8f626 | |||
| d37b298f07 | |||
| 62f7fc17d1 | |||
| 6e4929246d | |||
| 909461975a | |||
| dec191e5e7 | |||
| 07b0cd6f5a | |||
| b7a892ecfe | |||
| 79d16102fa | |||
| 67efdb47aa | |||
| 751c233b49 | |||
| f51734fea0 | |||
| 516a7111a7 | |||
| 1e3ff59b26 | |||
| 4c48f812d5 | |||
| a63a6f751f | |||
| 923860a90b | |||
| 5a4ca2f907 | |||
| ccc2ed0806 | |||
| 44c707e059 | |||
| e7c458de3f | |||
| 11b05b10a4 | |||
| d133ac0088 | |||
| 8951fcdebd | |||
| ecbd5677ce | |||
| e31b9c1a28 | |||
| faa7b7c742 | |||
| d08f326477 | |||
| 6500ed2075 | |||
| 56eb775a0f | |||
| e225bcfb96 | |||
| 4be6abe416 | |||
| c3de8b33de | |||
| 5658d6709c | |||
| 192538a741 | |||
| b8b9d7bf8c | |||
| 1db8603910 | |||
| 1c5774fca5 | |||
| 0b332b06b6 | |||
| 0be9b049a3 | |||
| 6993639cc0 | |||
| 74172e3123 | |||
| 6e1c4f682e | |||
| af0d282a1f | |||
| 09bf15fa05 | |||
| 43f7d95b4c | |||
| 8b536b1775 | |||
| 60a7f4bc35 | |||
| 0ed0e0c983 | |||
| f4b1ca9f21 | |||
| 609104cfa8 | |||
| 935d11b433 | |||
| e9eeb8335d | |||
| 4a2f42437f | |||
| 51b0693c99 | |||
| 185b366d8d | |||
| a40e533068 | |||
| 5b3918fcb1 | |||
| d315e99b63 | |||
| 8090ba0259 | |||
| 584400d011 | |||
| 124ad23a95 | |||
| 6b59aa38ae | |||
| 03554eb372 | |||
| dabfe41f29 | |||
| 6e2c6fc51e | |||
| d81986bcc5 | |||
| ea17e916b8 | |||
| ce56224038 | |||
| 9978932391 | |||
| 2ebc9e0deb | |||
| 7ddc621009 | |||
| aa4e574eef | |||
| 15bc964fcc | |||
| 99713ac964 | |||
| f9a6bd1343 | |||
| f4bee872e1 | |||
| be99b286b3 | |||
| c7d5212f82 | |||
| 75726b3d34 | |||
| 8d7e76ed81 | |||
| 01b08b2297 | |||
| cf51581ac1 | |||
| d99fd56995 | |||
| d149051c27 | |||
| 660aaa87ca | |||
| 4010f03b05 | |||
| e2d2dcf337 | |||
| b11aaf3f0c | |||
| 30ae065cb4 | |||
| 7f5b0559aa | |||
| 851954efb2 | |||
| b8c6a459a6 | |||
| 4523089440 | |||
| 217fe47878 | |||
| 525e3db24a | |||
| c3cac7d8c7 | |||
| a92717bb1d | |||
| 9dc32eada2 | |||
| d1644c0454 | |||
| 81ce84b3f1 | |||
| 18a8a405e0 | |||
| 07840b3b55 | |||
| f639c5b14e | |||
| a6851554e8 | |||
| 6859b83fe0 | |||
| 699da3cd26 | |||
| 6d429ef57b | |||
| 0bc7c20106 | |||
| d8f83d09ee | |||
| 43874d35ec | |||
| 5a186e4b02 | |||
| 70b3ac21ef | |||
| 7f24782aee | |||
| 009835c17f | |||
| 18db39d480 | |||
| 5956c358d0 | |||
| f18232eb6c | |||
| ac69d57de0 | |||
| 9297d4a7d2 | |||
| e6fd4e2255 | |||
| c0fa6db602 | |||
| d61d732a75 | |||
| e9c0ac3c6b | |||
| e071ecc1d7 | |||
| 14f6ef3da0 | |||
| edac6d1f22 | |||
| 525dfab3e3 | |||
| 0b77986034 | |||
| f0ab72db92 | |||
| 6243555db1 | |||
| abefb652c1 | |||
| 483e3ff800 | |||
| 0642e5a67e | |||
| 9553aa6e15 | |||
| 090fb1a33e | |||
| c4896a1c07 | |||
| 5f165d0a89 | |||
| df2c1ca6f2 | |||
| 5e41b6c471 | |||
| 1d3fff08ed | |||
| 73ffcbcb41 | |||
| 510535fdd4 | |||
| 4ade79a307 | |||
| 954abb974e | |||
| 8f3093ea2b | |||
| 2ead5b3c40 | |||
| 47cab96d10 | |||
| 7cfcebf0f5 | |||
| 12e310f1fe | |||
| f28b855189 | |||
| 43ed3aba41 | |||
| 1da4ee04d3 | |||
| 17b5e4015d | |||
| 1a8b8b7232 | |||
| fd2e8cfa8a | |||
| ccd05c04a5 | |||
| e70edffaa3 | |||
| 3ecaaf33b6 | |||
| c424ea52fa | |||
| ebb1aaeefd | |||
| f2d1f7bf52 | |||
| deb42da38e | |||
| 43df2a754d | |||
| d468d0ca28 | |||
| e586d0aecf | |||
| 8e36fed7bf | |||
| 2e3bf585ea | |||
| f57ad5a888 | |||
| 47fa31515d | |||
| 7e45c17078 | |||
| 32a0fc9b17 | |||
| 62c2757afc | |||
| 7a7596fb7b | |||
| 708b0d6365 | |||
| daf1a1baa2 | |||
| 59150a655a | |||
| 5594ad1ce3 | |||
| f12bd1368f | |||
| 6921ad49db | |||
| 4430fd21f5 | |||
| 64d8f22757 | |||
| 2a73524a19 | |||
| bfd7a02c9d | |||
| b108a0164a | |||
| 9f1aa916e4 | |||
| 5144a2a2df | |||
| c9c2deae1a | |||
| b220db9d85 | |||
| f9e3d96673 | |||
| 6c2b65055b | |||
| 5fa1efb1f2 | |||
| 6b5d4fc557 | |||
| 369b125c52 | |||
| 732e049252 | |||
| cdb419c3f1 | |||
| 2dde426df6 | |||
| 8c00499e76 | |||
| 32dac5fbe2 | |||
| 0d3f8de811 | |||
| 90b8747775 | |||
| 5333d23ad8 | |||
| e38e11b890 | |||
| 79303c90d7 | |||
| 4eac0978ed | |||
| aef5b9a057 | |||
| c65b951493 | |||
| 75385e5c01 | |||
| ad19baf1f6 | |||
| fb7acbac0a | |||
| 7cd752ceb7 | |||
| 9df288e7e3 | |||
| 26396bbe1a | |||
| a21850e85f | |||
| b19358914a | |||
| 5a296f2d06 | |||
| 755c84039a | |||
| 136ea44509 | |||
| ba758470fc | |||
| 878c39413a | |||
| a63a3a8fa5 | |||
| 6a2e1f0271 | |||
| 21f1ecfa15 | |||
| 9c5966c49c | |||
| ffc1d01a0a | |||
| e66e6e0b51 | |||
| b40a86e114 | |||
| ef23d6d51a | |||
| 4fea28b313 | |||
| 5502fa4584 | |||
| 0e2ae6840b | |||
| 3a4493c914 | |||
| 66deda7c76 | |||
| 44beeec1d7 | |||
| 824f74a4ce | |||
| 8d72d2b3a4 | |||
| 876f1231ee | |||
| ffbf395074 | |||
| 550b513f6b | |||
| 3f4ee39ca9 | |||
| a4887ec3c3 | |||
| f1185d8f34 | |||
| 030b2998eb | |||
| 3f8ae692ff | |||
| 07df92d75c | |||
| 8bbf98979e | |||
| 24408e53a3 | |||
| f8160e254b | |||
| ff9b585b29 | |||
| 095f413147 | |||
| 3dd1162a59 | |||
| 436225e341 | |||
| ca89073537 | |||
| 7663145821 | |||
| eed75eea58 | |||
| 194955770e | |||
| 3c3820cb38 | |||
| fc25a328bf | |||
| c7e857506d | |||
| d343cd4f08 | |||
| b4aa1b74ab | |||
| b043c5eade | |||
| c1a6ca80f5 | |||
| a010ef0f60 | |||
| 1240632dae | |||
| 87df6e08ca | |||
| d7ad947cbb | |||
| 434e7f75fa | |||
| 6f6e74e015 | |||
| 24f3176b1b | |||
| 2d0df240f4 | |||
| def658ccc6 | |||
| 676b35fb1c | |||
| 18a1a693b5 | |||
| 617a797743 | |||
| e1711b5d03 | |||
| 28ef4a0b2b | |||
| 8ee17150b8 | |||
| b52017625d | |||
| 60ac9dfca2 | |||
| c671735e84 | |||
| 29e5247131 | |||
| f5c533094e | |||
| 06a8e08244 | |||
| 30eda70dda | |||
| 38ec4cc3ce | |||
| 435271becf | |||
| 991bb12c80 | |||
| b8b643ebdf | |||
| 42ad8ba1ee | |||
| 9f425cce4c | |||
| 8be08acfa2 | |||
| 6422772530 | |||
| a008a425ec | |||
| 0936015d84 | |||
| 488a59df2e | |||
| 5bf1533067 | |||
| 77024cd651 | |||
| d0e209b2eb | |||
| d934fc181c | |||
| 2ca6235b5d | |||
| a271ed000e | |||
| 4d64ad8bb8 | |||
| 66a717b927 | |||
| 79114a90f5 | |||
| 4427748b8b | |||
| ae9b8326a4 | |||
| 619c240e62 | |||
| 19889e7d6f | |||
| 107d1fc8ff | |||
| 636fddd642 | |||
| 13532a1f0a | |||
| e1ace2ba08 | |||
| 96f0c1d148 | |||
| 03ecf8fe82 | |||
| 1c03a7fc5c | |||
| 85fe65612e | |||
| 88469f6141 | |||
| d81ba4a8c8 | |||
| 734c0a37f7 | |||
| 7becca450a | |||
| f2a5489363 | |||
| c7a8cb566b | |||
| a80031c3c5 | |||
| b6d9eb3dcc | |||
| 4a594a8e4f | |||
| 384a28c517 | |||
| 57ef3842e9 | |||
| 5b509466e6 | |||
| 7019947083 | |||
| 5ddc9243dd | |||
| 1dd89597fd | |||
| 2ac1de4ae9 | |||
| 1f783ddf44 | |||
| bee920f96a | |||
| 333e158ca5 | |||
| 67baf07efc | |||
| 4ba8ecd3fd | |||
| aee42cdecc | |||
| adb2a32fda | |||
| d2c373b27d | |||
| b0060bcb82 | |||
| 4f63db3f70 | |||
| 0d356514f2 | |||
| 6ce37944a7 | |||
| 8ef75f7c34 | |||
| bf2da499d6 | |||
| ef2fe6a775 | |||
| 2ac84f14be | |||
| 5078becbb3 | |||
| e19784a429 | |||
| 1628652264 | |||
| 668c7d301b | |||
| d56a63e712 | |||
| 8070b211ab | |||
| 5ed59d0282 | |||
| 70223cfcd6 | |||
| 7d0ff90b01 | |||
| ecc09a710a | |||
| 846b4faefa | |||
| 66da382789 | |||
| 3378723bdd | |||
| 56723330b3 | |||
| 08e2244975 | |||
| f9e934fa1a | |||
| 30eacc5019 | |||
| 3ba7c18288 | |||
| 958a09b280 | |||
| cc66e7e7c8 | |||
| 7f58e0413c | |||
| 3c256ddc5d | |||
| d2f8e62ecc | |||
| 5ce479b1af | |||
| be2b893a3f | |||
| 32994ce4af | |||
| 0c6a43fe0a | |||
| 81e0376102 | |||
| f21306a19d | |||
| 5f3dcac8e1 | |||
| 600bf94044 | |||
| 3b38b69034 | |||
| faa8f0f9b6 | |||
| 125624b09e | |||
| 7186afda93 | |||
| ee542abb5d | |||
| c7638a6485 | |||
| 16795871c3 | |||
| 54eff0e27f | |||
| 0a85521028 | |||
| 587af255a6 | |||
| 06326f4d55 | |||
| b73f9798e4 | |||
| 7ef5031717 | |||
| ba30258ee6 | |||
| 940a65cac0 | |||
| 7878b05030 | |||
| 16eb63d2ea | |||
| 8539bc5ade | |||
| 02f357a88d | |||
| 92f72b45d5 | |||
| 7755b19abc | |||
| 6b62ba1c5b | |||
| 2514aa2e3c | |||
| 82982c54f9 | |||
| 726718a6f5 | |||
| 1609e232a2 | |||
| fda2e265af | |||
| 5c19ae0e94 | |||
| f01b3caeb1 | |||
| 9e7262660f | |||
| 4489197d01 | |||
| 3d411fc6f3 | |||
| d4f26577a5 | |||
| c744f761a1 | |||
| 7224ca43f3 | |||
| b95cea35a2 | |||
| d891791929 | |||
| 826b0ad4ce | |||
| c99c61cc7a | |||
| 5eb57de264 | |||
| 976cfdbbc2 | |||
| a7aea8d1ed | |||
| 9caa89b733 | |||
| e42515c1ea | |||
| 901169775d | |||
| 3d86540d1d | |||
| 6cb97d067e | |||
| ad323ebf2b | |||
| 18efedf7c5 | |||
| 4eaf4ff26b | |||
| b1d2ac7822 | |||
| 87e6e583a6 | |||
| 2897e1a3ad | |||
| dfd5a7c21d | |||
| 093f6411c7 | |||
| 845bea522b | |||
| d0175f9c39 | |||
| 0b1d59e398 | |||
| 93bcb8994f | |||
| ecee464b34 | |||
| 42789fcc24 | |||
| f26b36f3e8 | |||
| 9f09052900 | |||
| 95a7ba0785 | |||
| ef4a3b405d | |||
| 7096fcd43e | |||
| eeab12ed86 | |||
| ca961e8498 | |||
| 40b0a37110 | |||
| 6daf0861c9 | |||
| 03bc1e722e | |||
| ce94c5e237 | |||
| 88aad1e107 | |||
| c4fe14bd45 | |||
| 2191b539e3 | |||
| dcd3495564 | |||
| 30559841d5 | |||
| 0dc0d5c981 | |||
| 3b981e2818 | |||
| 47bfc74ba9 | |||
| 8858e1bce8 | |||
| 04be430b8c | |||
| d46351b866 | |||
| d3ec29aa42 | |||
| 357abee462 | |||
| c86a040768 | |||
| d9e7dc158d | |||
| 46591968c0 | |||
| bb7c256ecf | |||
| d239ed1c9f | |||
| 604dece366 | |||
| 8b55f20684 | |||
| 8bf12ff32c | |||
| 0596482a8d | |||
| b5a77aa62a | |||
| 5db27d9d93 | |||
| a630e4937b | |||
| 96d27d17ce | |||
| 742b848f42 | |||
| 57884e3400 | |||
| 40459f4e01 | |||
| a461072113 | |||
| 0105931414 | |||
| c0b7b871ba | |||
| 1250c94600 | |||
| ccbd5b17d3 | |||
| 2fd3abbb14 | |||
| 5bc019d3d3 | |||
| 91c24d5501 | |||
| cd7f192fba | |||
| 7d0555fb67 | |||
| 3864708cf1 | |||
| ef22a58631 | |||
| acf4fa106f | |||
| 5a5704ee0b | |||
| 7e65416f10 | |||
| 2d7bc59db4 | |||
| 930469a006 | |||
| c657ba52ad | |||
| 46499512eb | |||
| c2779570fa | |||
| 750dc609bb | |||
| 1037183f4c | |||
| 979f959291 | |||
| 86cfe0465c | |||
| 61d461eea5 | |||
| ff1733225a | |||
| 784fca98c8 | |||
| 1f0ebd8ba1 | |||
| 2e645f04e7 | |||
| debe8eb81c | |||
| 2e8f8f258d | |||
| 697f40595e | |||
| 37404b6619 | |||
| 4b3a016d70 | |||
| ed2af5f9b4 | |||
| 5418ab52b7 | |||
| 61037af138 | |||
| f86cd11d74 | |||
| e46b335dc0 | |||
| 79299112eb | |||
| 3d5bee3a2c | |||
| 466cc84c16 | |||
| 27f9e2ea9f | |||
| 593e10eec2 | |||
| 5d5b8b78d4 | |||
| 620c4087bc | |||
| c5bf0e0cb7 | |||
| 7f7d53e00a | |||
| 073466bd6a | |||
| 151e88046d | |||
| c3b1d6735e | |||
| e3a5da64a5 | |||
| 3c4599423d | |||
| 2bb1f27e07 | |||
| e3adbbbd0b | |||
| 5e6838bdc9 | |||
| fc6720e090 | |||
| e29f671363 | |||
| 6e501c7b8a | |||
| 7dd3cc6f9f | |||
| f346f080ee | |||
| 9f19e585db | |||
| bc74487301 | |||
| 693baa8cef | |||
| 9ae60d4859 | |||
| 281efef207 | |||
| d893537ae6 | |||
| 986de265fe | |||
| d25633e213 | |||
| 0c8c7157f6 | |||
| e393442df9 | |||
| fc94fe30d0 | |||
| 1065079be6 | |||
| e426172b18 | |||
| effe93286a | |||
| 2a669618a2 | |||
| 44cd0e1e10 | |||
| 621155f06f | |||
| 6c116e261d | |||
| bcc89d91f7 | |||
| 8e874afbd5 | |||
| 9421cd0b2d | |||
| 37c46042c3 | |||
| c5429f0515 | |||
| f0375ca446 | |||
| b34bac9b5e | |||
| f42e418346 | |||
| a060c01f58 | |||
| f41686fab4 | |||
| be88d584cf | |||
| 80aa87935d | |||
| 405073cca9 | |||
| 22cb831138 | |||
| 95c8c75789 | |||
| e8e3cadeff | |||
| 8123ba9a01 | |||
| 5aa6c98dc1 | |||
| 8e16b8e4a8 | |||
| 90f0a5eac6 | |||
| 892d3be144 | |||
| 164242ae4b | |||
| 07b8c5087b | |||
| abba2e969b | |||
| 0a4b57ea01 | |||
| 422ecb758f | |||
| 20f953b9cb | |||
| d2f1488ef1 | |||
| a3aedd5fad | |||
| e2d07f7e78 | |||
| 3aa0fac852 | |||
| 238243a7d4 | |||
| 9533b62ebf | |||
| d06445e04b | |||
| 69255a938b | |||
| 773f24238f | |||
| 548ed4a1f0 | |||
| 76bf764262 | |||
| 075882b7b0 | |||
| d25299e1ba | |||
| 0160d42302 | |||
| 6ca7a013d6 | |||
| 60ed93e13b | |||
| 747e2e2e27 | |||
| 16d1694a78 | |||
| 17e74d9ed2 | |||
| 923a42e9a3 | |||
| bac66dfbf9 | |||
| 6895c0b392 | |||
| ef08319670 | |||
| 1aee758f87 | |||
| 3d0eca9432 | |||
| 93936a7498 | |||
| 8e873ccd00 | |||
| 7a188af002 | |||
| aaffeb9fac | |||
| 67711bcb0d | |||
| f93c158caa | |||
| c74d05a47b | |||
| f867c0f8a1 | |||
| 98d024295e | |||
| 963d3de709 | |||
| e20fa7f3a2 | |||
| 719d262a00 | |||
| 9ad9f28f33 | |||
| 6ac4a405b5 | |||
| 64fbdff13d | |||
| 65011f42da | |||
| d763967d4b | |||
| 44c807aedd | |||
| 6e6bb72245 | |||
| ca96b45637 | |||
| d8ff7786df | |||
| 35ed07e0ba | |||
| 7b002606bf | |||
| ac0d0a6680 | |||
| e5033cceec | |||
| b5d7e2969f | |||
| 6b4bd90bfa | |||
| b2aff55160 | |||
| 569c34bca0 | |||
| e63c966756 | |||
| 43f9efb9e4 | |||
| 82666d62ef | |||
| 72b62481a7 | |||
| 9cf13e320a | |||
| c8c4a0f61b | |||
| 801c44389e | |||
| fbdb060e10 | |||
| 09edc78bdc | |||
| 6951817088 | |||
| 2a22d25975 | |||
| 875adc8bdf | |||
| b8b914a99c | |||
| 59401d27ca | |||
| 26cb21f541 | |||
| 49154759c0 | |||
| abc07968e5 | |||
| d28899ae84 | |||
| 04c44de145 | |||
| 7997d52df5 | |||
| f3d899cbf5 | |||
| e540596c20 | |||
| b1aa7a8e88 | |||
| 3eef5b2c02 | |||
| 443aa0040c | |||
| 48c1db6a8a | |||
| b9f2530e2f | |||
| 3cb7c0206a | |||
| a7360f1145 | |||
| c4e3c4b34f | |||
| ae763ae25d | |||
| dd0b4f27d4 | |||
| faf5bcb32b | |||
| 93f2c83c0e | |||
| 63b5545502 | |||
| 31c99517d7 | |||
| 2e3a2ff69a | |||
| 4aa2c83e03 | |||
| 376d7a8878 | |||
| dd5f55f4fc | |||
| d2806c7ec0 | |||
| 2db34f4d49 | |||
| a451189d83 | |||
| f3a2be4a06 | |||
| dddd97bc22 | |||
| 8e53be6810 | |||
| d24b9ec0ae | |||
| d8900341ac | |||
| 0465d047e7 | |||
| cf1f3677ae | |||
| 354b02612d | |||
| f06d5b806a | |||
| 49385a08ad | |||
| d07c8c8851 | |||
| 803c3678e3 | |||
| 3118f743a0 | |||
| 2fa5c7eca4 | |||
| a09829ed4b | |||
| cf3d92d6e1 | |||
| 524b63e88c | |||
| 0a930db7d6 | |||
| e6338bbde1 | |||
| e4f7dd21b6 | |||
| b0bae93d06 | |||
| 9b93a5cd04 | |||
| 3142717154 | |||
| 3dcfacfeed | |||
| a51f5fefc4 | |||
| fcc3bbf9b6 | |||
| 109390d21e | |||
| c7dd91ef90 | |||
| 3d15abfdae | |||
| a9caaefd16 | |||
| 261c786a10 | |||
| a5270ccea8 | |||
| 08865dc218 | |||
| 3f50767e48 | |||
| 8ff5887955 | |||
| 2aa240337c | |||
| 44d75d9955 | |||
| 3b61b87501 | |||
| 04de362bc9 | |||
| e06175f600 | |||
| fe78d4f1e7 | |||
| 4a1de4c0da | |||
| 22b77411ec | |||
| 3f730ead84 | |||
| d52d70a014 | |||
| 0a7b485514 | |||
| 151c384313 | |||
| 8586d44d7e | |||
| 5c8b600cb1 | |||
| afd67d2f00 | |||
| 125a3fdc5e | |||
| 9963ff9c8e | |||
| 8547ea4c6a | |||
| fd434e8b21 | |||
| 1524866432 | |||
| 19fa9390ff | |||
| a335050332 | |||
| 8aa9ecffc5 | |||
| ef02e5169d | |||
| ba31a56949 | |||
| 6b7bd75b18 | |||
| 013d6625d5 | |||
| ca228ca23e | |||
| 1dce68212f | |||
| 2683c2433b | |||
| 2929800a9f | |||
| c1b04ace78 | |||
| 04a54dbc2a | |||
| e7c1f31315 | |||
| 62f6917d81 | |||
| babefc7bd3 | |||
| a09c9f4a2e | |||
| 5876598ca0 | |||
| f45d643739 | |||
| c1ba176e27 | |||
| 9b3013c51a | |||
| a746720a6e | |||
| a95c61c1d8 | |||
| d5da8e4e2b | |||
| 861e11a713 | |||
| 23ac209c05 | |||
| 8df789ae34 | |||
| 432e95e4ec | |||
| 401ed0a7a5 | |||
| dae6957093 | |||
| 7b6405fba7 | |||
| 6dc3e74607 | |||
| 617066340b | |||
| 2dd6db352f | |||
| 56e13641df | |||
| 9d62113388 | |||
| 47b4a6c498 | |||
| 7dfe3a1c59 | |||
| 44acfc7ec8 | |||
| 7933f5e357 | |||
| 858c5a27ad | |||
| 28e41a0ae9 | |||
| ae79f9be66 | |||
| 4a6d58c36e | |||
| 5bf6687f1f | |||
| beb80f0422 | |||
| 0e86d6fbdb | |||
| 2a8e4f19af | |||
| 6ed8b6d058 | |||
| 33812b3f4a | |||
| b62e542801 | |||
| 0080110fad | |||
| 19ef661457 | |||
| 7946221f0b | |||
| 203a42c043 | |||
| d3998dd03b | |||
| e752a48343 | |||
| 71955b777b | |||
| d686d07d53 | |||
| 72b4c9c6ab | |||
| 4b991ab67a | |||
| 59399124fa | |||
| 3eb4962d7d | |||
| 42d3b4c326 | |||
| f3931567f9 | |||
| 154adafbcb | |||
| 4a1e511262 | |||
| 3fc907e50f | |||
| bbf441c3b4 | |||
| bbf660d80e | |||
| a1c3bc26c9 | |||
| fc73e0e482 | |||
| e7727afbe8 | |||
| de5ce2660d | |||
| 2e661cb2f8 | |||
| ebeaad0b4e | |||
| ce60c048f8 | |||
| 6795ff7db9 | |||
| 931f1fb27d | |||
| 74625a8d65 | |||
| 03e8ccee7d | |||
| 01b9e4cb55 | |||
| b5d5014ba2 | |||
| 4f7bfd14c0 | |||
| 7347300f05 | |||
| aa6bdd2786 | |||
| db0aeeabbd | |||
| 9aa426ed56 | |||
| 30f9348c26 | |||
| baf76c51f9 | |||
| 6abef569e8 | |||
| ade88a6055 | |||
| 11d0274a1b | |||
| 957de5d6f7 | |||
| f5f4c5c672 | |||
| 5924495035 | |||
| c486f65e74 | |||
| b19a7d45d8 | |||
| 7f7397f684 | |||
| 39142909f6 | |||
| 23f5b84c5e | |||
| 630b6e6313 | |||
| 4396ca9420 | |||
| 5286a69c34 | |||
| 059d5542c9 | |||
| a09e77a17d | |||
| 5a236e3bea | |||
| aba5add7f9 | |||
| a4190672d3 | |||
| 10da9182d1 | |||
| 2de5bc2855 | |||
| b32db945be | |||
| 44d02e6dfd | |||
| fc096dc6fa | |||
| 1d52bbde58 | |||
| 1a049e854b | |||
| 5b15ef81f5 | |||
| 05088c1063 | |||
| 04c09efd75 | |||
| 2c0576099a | |||
| 27b49f9d7e | |||
| 6a247c425a | |||
| 7910034916 | |||
| c56e5d17be | |||
| 522eabce5e | |||
| 0e4b874994 | |||
| 319cc8a5f9 | |||
| b5c26392e1 | |||
| 7d53c55cc1 | |||
| 3e30994541 | |||
| a669fcba7a | |||
| 851053fd9a | |||
| b71a87e803 | |||
| c72658d21a | |||
| d0b8c59769 | |||
| 583b74fb7e | |||
| cf6f28aa3e | |||
| f3ece64635 | |||
| 6343b7087d | |||
| e279dc4cea | |||
| fc5763c8e8 | |||
| 9e32fcda88 | |||
| 7fe8c7b547 | |||
| 38329514f9 | |||
| 37705dfc71 | |||
| e8e7e3144f | |||
| 0da229e8f4 | |||
| 1029b2523d | |||
| 477b520cf0 | |||
| d54fdf5452 | |||
| bfb47aba87 | |||
| d241a57d38 | |||
| 01479a82de | |||
| 198820d95d | |||
| 86741a4e95 | |||
| 7d859661ba | |||
| d04c95514e | |||
| e6e3953a85 | |||
| 104f1232f3 | |||
| b55bf6e6c1 | |||
| d79af8fcc2 | |||
| ff05429f58 | |||
| 0dbabe629a | |||
| 3348d46b5a | |||
| aff1a245c2 | |||
| 51802f71a5 | |||
| 32f34f1444 | |||
| b7309ccdf1 | |||
| 8abaac56ff | |||
| a462ab98e9 | |||
| af602a414a | |||
| 67e426354c | |||
| e2f8e31fa0 | |||
| d67cce4184 | |||
| ca63b412cb | |||
| 89e372c229 | |||
| 4ae5b49764 | |||
| 1057f32b34 | |||
| e3948eb4ba | |||
| 6d363235a5 | |||
| fb50b4fa78 | |||
| 85fbaa2b97 | |||
| 1440453640 | |||
| e364959d25 | |||
| 89642e71b5 | |||
| d8413b3b7d | |||
| 92ba038af7 | |||
| 2ac2cc2ebf | |||
| 990ed86283 | |||
| 953d702ed6 | |||
| 1fa828ad1a | |||
| 9f16fa189f | |||
| a7e78cc3fb | |||
| 353c40cc46 | |||
| 3a7c8d660c | |||
| b2c9108ffc | |||
| ef447e2ba1 | |||
| 85f0b37b03 | |||
| 9f6cdfcd11 | |||
| 376477767b | |||
| f1376be2e0 | |||
| 5e57ec0e67 | |||
| 15721f1279 | |||
| e8fe58438d | |||
| 8ef04b1ad0 | |||
| a3d6776c02 | |||
| e7a529257a | |||
| 714afabf30 | |||
| f1df8d07bb | |||
| a18c4b6449 | |||
| 2099342352 | |||
| 50ee2727a2 | |||
| 571e918ee0 | |||
| 7c5d8721c0 | |||
| b5984a965e | |||
| 0e0bd94251 | |||
| a9d2d52c1e | |||
| b7c67a16d9 | |||
| 35615526a1 | |||
| c43ce52e78 | |||
| 61a313f24b | |||
| d71393cead | |||
| 64bb6714a3 | |||
| f8336d6adb | |||
| 30245f4aed | |||
| a861652d6f | |||
| 5fe5ba78da | |||
| 8fa9977617 | |||
| 0df2dd3ce2 | |||
| 80893f2592 | |||
| 91cf10541b | |||
| aaccfdee44 | |||
| a8449de731 | |||
| 9d98641a94 | |||
| 4f13227d9e | |||
| 70e9ee0dd1 | |||
| d43932a9d2 | |||
| 7118261fee | |||
| 89b171db36 | |||
| 1c4778790a | |||
| d20d1488de | |||
| c969f277ee | |||
| cf4a77af75 | |||
| 4042592420 | |||
| a750241ec3 | |||
| 8ab79ec1ae | |||
| a9289df93f | |||
| f6c42c4b53 | |||
| bfb121938a | |||
| 20d26d4bcd | |||
| 474b851275 | |||
| 5c541275c5 | |||
| 486e92dfc9 | |||
| 959ce3bc92 | |||
| 3866c740e9 | |||
| a65a85fb67 | |||
| c921b7bb1c | |||
| 7acf2d80d2 | |||
| 01b2d6cb39 | |||
| e2665e3cbe | |||
| 9b95094207 | |||
| 59b637c317 | |||
| 52963e8e80 | |||
| 43f42ac917 | |||
| 0352951d8c | |||
| fe334d0aed | |||
| f2371d8735 | |||
| 3c9608efbd | |||
| 8dff3b55c9 | |||
| b42b6303ae | |||
| 263e6d5cca | |||
| 5b5622b4a7 | |||
| e328bf616f | |||
| 52ba831b1c | |||
| 12b1016d66 | |||
| a471bfec6a | |||
| 3b6d3116bd | |||
| 5c0c680f56 | |||
| 3af8f4772f | |||
| 10b2593389 | |||
| cf67d58236 | |||
| 748227319c | |||
| 94c18e835a | |||
| cfb5693ffe | |||
| 3e1f3d5a51 | |||
| c0bf529a6b | |||
| f951ee1f91 | |||
| e8ac971f9e | |||
| b1278aff2e | |||
| c26c14227c | |||
| ce36bcc951 | |||
| 1134cd7788 | |||
| 7920d2225c | |||
| d0faaf2d53 | |||
| fc60878c25 | |||
| 1b941536d0 | |||
| 4215bfc6fe | |||
| 22d215b2c7 | |||
| 69eb7979c1 | |||
| 0f0c24d31b | |||
| 30c5a0d9cd | |||
| 93e0773758 | |||
| c92e64afbd | |||
| 6ba9dc74d8 | |||
| f69c244080 | |||
| f1b4a973a8 | |||
| efe49ee4c1 | |||
| 51573cb9f5 | |||
| f6ab374718 | |||
| 7ace88ae0d | |||
| 5f5e04506c | |||
| 9a58e121e8 | |||
| 7153347d67 | |||
| 611ccd9605 | |||
| 0befa2eb39 | |||
| b0677bb66d | |||
| f476f1447c | |||
| c8907232da | |||
| 864ca81d1d | |||
| 636427ac6b | |||
| 3d68caed6b | |||
| 4f7e25038c | |||
| ba0a95ed26 | |||
| 0cd25be408 | |||
| c6fa0ddde3 | |||
| b64a9b7a44 | |||
| 326bf39bed | |||
| ac4c37ba48 | |||
| 305017d523 | |||
| 19d1c552fe | |||
| 7d6908d146 | |||
| 9cf399f956 | |||
| a0b4450e5b | |||
| 9d983a7c96 | |||
| 89be08bc58 | |||
| 6dcf3e15da | |||
| e66cb9479f | |||
| bd8d9cc3aa | |||
| 9453b86907 | |||
| ecf9441fae | |||
| 70b659acbe | |||
| 2ebb9f2763 | |||
| 1231b13dfd | |||
| bebad4d361 | |||
| ed5a1887f4 | |||
| 342e689825 | |||
| c5c1a9b25b | |||
| 7637905de2 | |||
| ec780b7b93 | |||
| 3728c59f1d | |||
| 4442d9a706 | |||
| 8928b35f30 | |||
| fca189ee4c | |||
| d0fb3dde66 | |||
| fc4377abaf | |||
| 97a62752c6 | |||
| 87e1e55da7 | |||
| 5410cda182 | |||
| ee240ca201 | |||
| 98ffa0a256 | |||
| 2f450b7ea0 | |||
| 099b50dbfa | |||
| ece7a443fe | |||
| 984f2669b8 | |||
| 9b94b9e3fe | |||
| a781bcb7f4 | |||
| b596f38ab5 | |||
| f2b4d6f829 | |||
| d2d2dc64db | |||
| 5b8171a719 | |||
| 838ad5ec1a | |||
| 3d16323e33 | |||
| ba25eaa788 | |||
| 747412e522 | |||
| 059ff25d8e | |||
| 831143427d | |||
| de51d4860e | |||
| 6569038777 | |||
| 075aec3ac0 | |||
| 734847db68 | |||
| a99f2b78a1 | |||
| 037473f2e1 | |||
| 378f735e9f | |||
| 4cdac76ca5 | |||
| 9ed3c8a6bb | |||
| 252d289a8d | |||
| 832bad1fee | |||
| d5b99316e5 | |||
| 3723c5dae5 | |||
| 7c3fa7aef5 | |||
| 4ee31db7a0 | |||
| 0d8cb7393c | |||
| 0d3d0a8da1 | |||
| e45eb7f2be | |||
| 128e5c414c | |||
| 5a70d98c71 | |||
| 69d4215397 | |||
| 722190305b | |||
| 428d4859da | |||
| 9455860554 | |||
| eb4b42c089 | |||
| 79b3d7359a | |||
| c95ea19da1 | |||
| a8b3f2af14 | |||
| 2b8ccc38bb | |||
| baebadb7a6 | |||
| f73db056f2 | |||
| 429ac1e71c | |||
| 07cd91dda4 | |||
| 956f61e775 | |||
| 14fc2d2c70 | |||
| 9c07240948 | |||
| 7e2260578e | |||
| 7f2d781241 | |||
| df05ade4a7 | |||
| e4b45e512f | |||
| 623bb52fd8 | |||
| ab809dabf8 | |||
| 2ebf26c156 | |||
| 1ffc598fd4 | |||
| 0495d954bc | |||
| cc6a7a6a72 | |||
| b081bbb444 | |||
| 69cbb262ba | |||
| ba2a398da3 | |||
| a8a87e7f79 | |||
| 2ffd1e6e47 | |||
| efaebb43aa | |||
| d201350a08 | |||
| 81184d77fe | |||
| b9d60e0018 | |||
| f50ac49ede | |||
| 70d8b8a88a | |||
| 535f1b5988 | |||
| 366c903f84 | |||
| 1781ff36c6 | |||
| 4e131d9a54 | |||
| 7f6fec5530 | |||
| 2d43014029 | |||
| 33ea66ea88 | |||
| b74e449efd | |||
| afd4820713 | |||
| 8e032fa26a | |||
| 897669a069 | |||
| 03ea78b40b | |||
| 18465cf6cb | |||
| ac6ccd2dc7 | |||
| fb32d11d64 | |||
| ec75e911bd | |||
| 603f032e28 | |||
| cf76e43898 | |||
| a0df548dd3 | |||
| dff6048a07 | |||
| 478d465cb6 | |||
| 271f308c36 | |||
| 956958fa0e | |||
| 6a68da0559 | |||
| 6683eee49a | |||
| f258d5f932 | |||
| 600995a220 | |||
| 803dfed9f7 | |||
| 771cb45b49 | |||
| a2a7689881 | |||
| e9f4c3d0c7 | |||
| a5ef5769b9 | |||
| 9d251f94e5 | |||
| d1e8d52f5c | |||
| 14d2ae0c0e | |||
| dafa3bde8b | |||
| cb923cef47 | |||
| 93888aeed3 | |||
| c41e34e9d8 | |||
| 154aea7a61 | |||
| bc53cd1aa4 | |||
| ef51aa7e98 | |||
| 705c5ce790 | |||
| 26dee50e68 | |||
| be3d7ef11d | |||
| ef5897969f | |||
| d9cd0257ba | |||
| 95bb8cb1e5 | |||
| e1aa2f651a | |||
| 03c535e80d | |||
| 6f9c4f220e | |||
| 6f3813b685 | |||
| 510dde6923 | |||
| a7761c1867 | |||
| a2af965bad | |||
| 28b6ed500b | |||
| 6991fa39cc | |||
| 274469d299 | |||
| a0b8581ec5 | |||
| b844a0e98d | |||
| e5f14ac176 | |||
| a1110a847d | |||
| a3889fe369 | |||
| bd93918d9b | |||
| 421a6ba143 | |||
| f091256edb | |||
| c0e4733eb8 | |||
| e636a6ad58 | |||
| 071617bfc5 | |||
| 8a079c98cc | |||
| fa95f96a6d | |||
| c198abe5ac | |||
| ef5ba0f1d1 | |||
| fff7cd53cc | |||
| 64ce5138e1 | |||
| 3c07e7a0d8 | |||
| a1510ffcb5 | |||
| 1b96bce44f | |||
| 294a5e39a6 | |||
| 65fdd6e25f | |||
| 6d2ef1f985 | |||
| 14af19873c | |||
| 2f5dd470d2 | |||
| dc4592ae4d | |||
| baa3cfa636 | |||
| ad324df937 | |||
| f2b301cb17 | |||
| 6e369ea787 | |||
| f640abda91 | |||
| 77f8f11d99 | |||
| 12b4c27468 | |||
| 75f3073cbf | |||
| 5eddfaed98 | |||
| 89b4fdef56 | |||
| 6f08c1f758 | |||
| 1155789a75 | |||
| 8c4a74179d | |||
| bdbc94e54a | |||
| 30f880361e | |||
| c5ac347538 | |||
| 008da9467f | |||
| ee78249c2f | |||
| 412c67b47a | |||
| f4191ad984 | |||
| ea8d1100ac | |||
| 20e8c033cc | |||
| ff2d10e0e1 | |||
| 672c39e58d | |||
| 36d3849118 | |||
| 43d7dfde4c | |||
| 51a056b3d7 | |||
| 6e22d6687c | |||
| 369fdf07e2 | |||
| 8c6c1b3ccc | |||
| 160bdf5472 | |||
| 18c70002cb | |||
| 4b8cfd9fdd | |||
| ffa5fd5b2a | |||
| 9fb4798c77 | |||
| a6039278c3 | |||
| c50702c83b | |||
| d4c1e62c72 | |||
| 488c609109 | |||
| 0a6e24104e | |||
| d6acac0208 | |||
| b02f61ef21 | |||
| eb82b44726 | |||
| ca035f9447 | |||
| 19aa3c0854 | |||
| 93675d90bb | |||
| 0d90c1d960 | |||
| d826f968b5 | |||
| f682d7489f | |||
| 649b1adfdd | |||
| 3d67ea736f | |||
| ba88f837fe | |||
| 6728703473 | |||
| 55521afe35 | |||
| 8820b02406 | |||
| e79ba6117b | |||
| 668d29a9d7 | |||
| 6b97acdb50 | |||
| 9a11021560 | |||
| ee18217520 | |||
| 16de60f82d | |||
| 8cd94f976c | |||
| 9ab0662cbf | |||
| 5cceed1fd7 | |||
| 03a1b51bd7 | |||
| 1edf567ea7 | |||
| fa73eddf50 | |||
| 1923ae6f4b | |||
| 229f3dc061 | |||
| cc95042a94 | |||
| 302f54971d | |||
| dff88403b5 | |||
| cd3f9af232 | |||
| b217162cad | |||
| 7d24a61ae8 | |||
| ca1b37dbcf | |||
| 0e887fb2ea | |||
| a122adf05d | |||
| 04fc1e25e4 | |||
| cff402affb | |||
| e1e0368f8c | |||
| 8ee39d3822 | |||
| e2b533db64 | |||
| fc5db74f16 | |||
| 42e62a3698 | |||
| 62f03356ca | |||
| 25bb0912fb | |||
| beff1d8d42 | |||
| 7dc56cc075 | |||
| 394d653978 | |||
| 742fa27029 | |||
| 18b962dc57 | |||
| 233a179dad | |||
| 9cc76c5ee6 | |||
| 401ba40373 | |||
| 6dad50aab1 | |||
| 9a43f868aa | |||
| 294e9bb590 | |||
| d101311109 | |||
| e0e920bfe0 | |||
| 3a090d7c1d | |||
| dc851d856d | |||
| 2f8fe43ea3 | |||
| 959f96dd74 | |||
| 6620d2d023 | |||
| 408742494a | |||
| f7ae9c06d8 | |||
| b4c0d8e829 | |||
| b2a410d6cf | |||
| 16fa453163 | |||
| fc678684f5 | |||
| 8f7db7e52f | |||
| e9d6cf278a | |||
| d1c6f5f98e | |||
| 48f581525e | |||
| 8d967856b5 | |||
| ee83cf2989 | |||
| cb304d35e2 | |||
| 65185c5854 | |||
| 91526674be | |||
| 3e11ff65db | |||
| 4a3ac17a2f | |||
| 2283f1ddc9 | |||
| ae78887746 | |||
| 9de4bf839f | |||
| 295e51181b | |||
| 287e3a0090 | |||
| 4d150e71a3 | |||
| 0debbc263e | |||
| a9e5e40a54 | |||
| cf61080980 | |||
| 3e33731cf0 | |||
| d7cda90acd | |||
| 9a7dcc8c1a | |||
| d477121790 | |||
| 60e7a21e39 | |||
| b915ca2add | |||
| e90ef7aaee | |||
| e918daaa9b | |||
| 83b4f645db | |||
| 1797df7017 | |||
| 3beda9c201 | |||
| 68e6bd668c | |||
| 696cf61fe9 | |||
| 7fe56766fc | |||
| 1e62f8c951 | |||
| 82b995f96f | |||
| 05aa0c2482 | |||
| bca5f4bec4 | |||
| 8b599f78c0 | |||
| 38776e74bc | |||
| 21ce09a5b5 | |||
| 5fabec6a42 | |||
| 64f361d4b1 | |||
| f8ae842971 | |||
| ca01d6efb8 | |||
| 498ab6d165 | |||
| 8e8524ccc8 | |||
| 714936f710 | |||
| 11c0477a34 | |||
| 4f65c6bbcc | |||
| c7ee183fe4 | |||
| 80545ce1f2 | |||
| c80edcfe9f | |||
| 74318fb3c0 | |||
| 83a8942724 | |||
| 48f57068d8 | |||
| 3f85916764 | |||
| ab0f544267 | |||
| 14e16c874e | |||
| 7fb87ad67f | |||
| 1b85417ae6 | |||
| 8d41e6fece | |||
| ab2906991d | |||
| ac85c75cc4 | |||
| 7f1fae307c | |||
| cce41082a1 | |||
| 902a489b04 | |||
| 30006fe520 | |||
| 2ce1bb9c3f | |||
| c468fa2a95 | |||
| 06ef342953 | |||
| 02121105de | |||
| e24788157a | |||
| 63905c608e | |||
| 69127e6952 | |||
| 87a84f02a3 | |||
| 0b5bea9965 | |||
| 0d5e3a080c | |||
| a4ba07de99 | |||
| 970a5ec763 | |||
| 6f338c44c4 | |||
| f360fadd3f | |||
| 846fa4722b | |||
| 9752f1d492 | |||
| ce2d386b7e | |||
| 993d433d03 | |||
| ede344db3a | |||
| 6c397da1f8 | |||
| 1d9862fe11 | |||
| 2d3db00639 | |||
| 7269233134 | |||
| 27be0ad808 | |||
| 51e875694c | |||
| 92d64d77c5 | |||
| 579e9e0b8c | |||
| 25b9c491c3 | |||
| 8f1b48b0ab | |||
| 611d3c3b63 | |||
| 3a658e10f1 | |||
| 92a182e5a8 | |||
| 954f0cc26f | |||
| 5e0b4e1be6 | |||
| c72511da6a | |||
| 15c65ad4e1 | |||
| d06657e2ff | |||
| 5dae1ee34f | |||
| 88a5d33bad | |||
| 42e02a6129 | |||
| 7d56164882 | |||
| 3e60ad21bf | |||
| 55c4e9635a | |||
| 101d0212a7 | |||
| f0338b5247 | |||
| 80136b4aff | |||
| f0a89e99f5 | |||
| 09f830c315 | |||
| ace92f8be0 | |||
| 7e08f495a7 | |||
| a8299b4f54 | |||
| bdb00b8d58 | |||
| 4e18127371 | |||
| e5165e6fba | |||
| 77dc237549 | |||
| e3cc04f538 | |||
| 322d1ad75c | |||
| 4767903a07 | |||
| 0064ce97f0 | |||
| 71f067f678 | |||
| 8f37dc8570 | |||
| b15b640652 | |||
| 3e892f30ca | |||
| 20f3efced9 | |||
| 25bcd0f2a9 | |||
| d990bf794a | |||
| aa25feb57e | |||
| b203fc7c68 | |||
| 9298e045d3 | |||
| e3255588d3 | |||
| 514f23cf4d | |||
| ab206b2abf | |||
| 2f8a65d364 | |||
| a30e47d094 | |||
| 0a78a87a17 | |||
| 886ec9c574 | |||
| 434fe894bc | |||
| cde9dbe406 | |||
| c5262f188e | |||
| 8f57364547 | |||
| eb193f230b | |||
| 6a868f975e | |||
| 66a8db598f | |||
| bfa13583e3 | |||
| d865e646b9 | |||
| f630537897 | |||
| aaf7ae8fc3 | |||
| 52cba23190 | |||
| c48c74a0cf | |||
| 9c6c27909d | |||
| 3ba3e5cf76 | |||
| df4040a96a | |||
| 6932e468cf | |||
| a82279b068 | |||
| 0ae7b9c120 | |||
| 72f86069a2 | |||
| 509c32db70 | |||
| 7fa45c0658 | |||
| 4e81a6af83 | |||
| 66d3eb3400 | |||
| 7e36cb76ba | |||
| fc47651c33 | |||
| 6948d70d94 | |||
| eee2db8ad9 | |||
| 318a36a831 | |||
| 4d90bd9e31 | |||
| 4e81151e11 | |||
| 18195b778a | |||
| 326bddad2a | |||
| f4b41a43cf | |||
| 38c762f775 | |||
| 9833d466c4 | |||
| 4d965333ef | |||
| a6ec18690d | |||
| d3fe0b48d8 | |||
| 35220e1c4d | |||
| de7bc9870d | |||
| 680a3a5d13 | |||
| cf8612c99f | |||
| e817d3355c | |||
| 53b7f33ec4 | |||
| 5ed9a29f81 | |||
| 89406c4754 | |||
| b27b26837f | |||
| 0595008fce | |||
| d6b2e019c7 | |||
| 25cfb1f5c0 | |||
| 10a159b315 | |||
| 60c113b0e8 | |||
| ec4330aea7 | |||
| f263fe30a4 | |||
| 0ec2f68a96 | |||
| 1156b909eb | |||
| 9ed246e482 | |||
| c342c5c4f6 | |||
| 3b7ef5b262 | |||
| 79bf3d8d4f | |||
| cfe91a8722 | |||
| c3e2d34372 | |||
| 56b6ee5d2a | |||
| 3f34ac1c75 | |||
| be5015d16b | |||
| 2c1d4cfc40 | |||
| 5bf75e122c | |||
| aa51048990 | |||
| 6d117f824d | |||
| 0a591e748f | |||
| be2cb5d2f2 | |||
| 280ec22348 | |||
| f9a815bba6 | |||
| 1534dcc932 | |||
| e19ae7af59 | |||
| 1726d6f59e | |||
| 756f395158 | |||
| 90d2cf4970 | |||
| 37e70fd5bb | |||
| 126481833b | |||
| b2630cfb49 | |||
| 579e2e95e3 | |||
| 6580436900 | |||
| a0ecf72a1c | |||
| bc3aaa94de | |||
| 482ca5ed15 | |||
| 66894c2e8a | |||
| 5d5a1fadd9 | |||
| 2d531c3a15 | |||
| e4da11727a | |||
| 309f97b443 | |||
| 98240e910e | |||
| 6d79d3d1f8 | |||
| def4e19359 | |||
| 6cfd055bec | |||
| 1208b25b66 | |||
| b25bf70224 | |||
| 7ecb139dba | |||
| 2a7720852a | |||
| 627eb556cb | |||
| e251155f4f | |||
| 4e9c44e574 | |||
| 5b662df7cd | |||
| 802574fb44 | |||
| 51f3334711 | |||
| 254b5c963b | |||
| d5f64a79b2 | |||
| dd54a714cb | |||
| d40e9e3d73 | |||
| d5066adb4b | |||
| f933aae562 | |||
| db65a80a7f | |||
| 2f9658fa22 | |||
| ab06365a81 | |||
| 2bc27bd6f5 | |||
| 078ac1abe4 | |||
| 05259eb0d2 | |||
| 8c272ea032 | |||
| f58f29389c | |||
| 854b137820 | |||
| fb8b5ac6b6 | |||
| 35106f5e67 | |||
| a5ea58dbf7 | |||
| b4ddc8e5fc | |||
| 2223405e79 | |||
| 7260e0d0f5 | |||
| 44cf7f7330 | |||
| 465ceab271 | |||
| fa818d50d1 | |||
| deb75df77a | |||
| aae47e103b | |||
| 957dbecb3d | |||
| 75fe59ef79 | |||
| 63ba5cf39d | |||
| 8a0b7fbc47 | |||
| ea9fb5bcb0 | |||
| 21d8ceacf0 | |||
| 986cf025a0 | |||
| 85b936ead2 | |||
| f241679087 | |||
| 6618cdc0ad | |||
| fe2de5d8ae | |||
| bd954e7292 | |||
| 86367298cc | |||
| 08346a2326 | |||
| 1bb9090434 | |||
| 879bcdf8d8 | |||
| 1b7658d8f9 | |||
| d635705449 | |||
| af1b17cb98 | |||
| 944ef83805 | |||
| 20b3e95b4f | |||
| c2de0dd613 | |||
| 2a7f5f55b0 | |||
| a03539ad8b | |||
| c420a8a686 | |||
| 8d6a2e8882 | |||
| 8da9156bb5 | |||
| ffc8a5188d | |||
| 4eb1117659 | |||
| b4602c1e48 | |||
| 80ede42191 | |||
| ea6937c0ac | |||
| 2f99ba8526 | |||
| 16324e16c7 | |||
| c2e1ecf5b1 | |||
| cd353217d6 | |||
| 6903f67f0f | |||
| 487dab793a | |||
| 4cd3f9bd38 | |||
| f581e76057 | |||
| 312aa856ad | |||
| 6f4363e95b | |||
| 595ba92f06 | |||
| df9114d7b0 | |||
| 1496d0e482 | |||
| 92f5783f35 | |||
| da1895125a | |||
| bcdd470567 | |||
| 7cd0070d90 | |||
| 8217949fa2 | |||
| 4bdf1553c9 | |||
| 6fd5d38cd8 | |||
| 8a737104ef | |||
| a608da759b | |||
| c828625cc1 | |||
| 7ead247fcb | |||
| 7a62af78d0 | |||
| 9896d6e6b2 | |||
| 9c376183e0 | |||
| e5cc9a3cbf | |||
| 985979ab14 | |||
| 377a4c2284 | |||
| 24242db981 | |||
| 73a4152b5d | |||
| b5eb094566 | |||
| 71140381e0 | |||
| 22387b2610 | |||
| da53a7d469 | |||
| b9b30f9f53 | |||
| c984eb9b8c | |||
| eef548f6a1 | |||
| 2da8ffb8cd | |||
| 8188c21641 | |||
| 19dca8c5bb | |||
| 5de5b6047a | |||
| 695c4159dc | |||
| 2624315bd5 | |||
| fff6e50702 | |||
| 753b69726a | |||
| df273aca7e | |||
| 9e9feb5980 | |||
| e71eb465fb | |||
| e9f2550a26 | |||
| 12bfd98011 | |||
| 2b45056842 | |||
| c86fdf07db | |||
| 385ffd5b27 | |||
| ad6b746124 | |||
| 57e8b59db6 | |||
| ecb869695e | |||
| c99c7ee6c9 | |||
| d3c47fe1a4 | |||
| 1c21b23e8e | |||
| bb55cd3c42 | |||
| 4a74190241 | |||
| 586f9594fb | |||
| e68facfd51 | |||
| 7d423fa49d | |||
| 886553a923 | |||
| ff4a00d103 | |||
| dbdf8a97ae | |||
| 2433e31db0 | |||
| 5538aac732 | |||
| c05f169d54 | |||
| 24aaefe342 | |||
| c167e881fb | |||
| 9a8c22f487 | |||
| fd8077a5cd | |||
| 6dd196a7a6 | |||
| bdfe14e278 | |||
| 1c7b318c3f | |||
| a6acc30eea | |||
| c54a849243 | |||
| 1f2871f649 | |||
| 147d5902ed | |||
| ec47ca1983 | |||
| 6ca6fc6989 | |||
| f29015426f | |||
| 57f09fd64d | |||
| 2477ee4952 | |||
| 2bd7fd2aa7 | |||
| bc5529ded8 | |||
| b05736e4fb | |||
| 6a187881c0 | |||
| d1accc4e09 | |||
| e7e130460e | |||
| d33a02f6cd | |||
| 8f6c511598 | |||
| 7fb04d51ae | |||
| 64d5275554 | |||
| 76041a9f55 | |||
| 502f7de040 | |||
| a86512e807 | |||
| 2e19dbe05f | |||
| 18d8f07242 | |||
| fb0ff734d7 | |||
| 13a5482f91 | |||
| 4a5794ad5f | |||
| 7652aca406 | |||
| f1dfb1c952 | |||
| b4f866be03 | |||
| 4fed35d189 | |||
| c3cff6a911 | |||
| 91f718f39d | |||
| e4ea01cfd2 | |||
| dd7d5a46e8 | |||
| 3be4b08bb0 | |||
| 2fde7b7879 | |||
| 60777e5714 | |||
| 9d7fcc4987 | |||
| 95ed1c3ac3 | |||
| 7e285969a7 | |||
| 083f1be766 | |||
| d39ed3c03f | |||
| a068bda34f | |||
| 559f37e5d2 | |||
| d50b7de668 | |||
| 35c5191a4d | |||
| 6df6296cd1 | |||
| b88a565344 | |||
| d98bc1ec63 | |||
| 74886db10d | |||
| 48e4fd8088 | |||
| 54c7f896ad | |||
| ccd14e6505 | |||
| dc33534478 | |||
| 83d79e23d5 | |||
| 151f99e343 | |||
| ccea63f1e2 | |||
| 1019419d85 | |||
| a234823d0a | |||
| d2015845e8 | |||
| 1857cca081 | |||
| 195bbf4652 | |||
| af76d937e1 | |||
| 885780a92b | |||
| 547bfe26d7 | |||
| bc87fdde1f | |||
| 6fc8324337 | |||
| 5103d91db0 | |||
| b67ab5ecd3 | |||
| 3aa085f17e | |||
| 1b9a78827a | |||
| 3299e0fa6a | |||
| 79e12c8dd8 | |||
| ced33114cc | |||
| 294324e31b | |||
| 1b27d652af | |||
| e98209249f | |||
| 9ddf09e187 | |||
| 73e975cfa1 | |||
| a942ea6ee2 | |||
| 38ca7c030f | |||
| 7d59edc8a5 | |||
| 7d05450244 | |||
| 8e5ba70706 | |||
| d43c40fc59 | |||
| 02ce707546 | |||
| 4a11ed6dc4 | |||
| 72d07fc3fb | |||
| 9b910b26a9 | |||
| 4a9be7b4a5 | |||
| 34958c654f | |||
| bf91156182 | |||
| b774b6920d | |||
| 56f6143873 | |||
| 7ba051c7ff | |||
| 63f5c240a4 | |||
| ba91e433c3 | |||
| 2818b207db | |||
| 0527066a4c | |||
| a48400535f | |||
| fa201d21e2 | |||
| c9222ff6f6 | |||
| a45d311339 | |||
| b59278601c | |||
| b48a913fbf | |||
| a0ef779aee | |||
| 861ff636b1 | |||
| bbce4bdeb0 | |||
| df0ceb4850 | |||
| b1e45756dd | |||
| 4b52878dc1 | |||
| 62a4f18866 | |||
| b00faf3930 | |||
| d6ddb2b247 | |||
| 900510e7f2 | |||
| 7370af0084 | |||
| 9644693de3 | |||
| b6bc269505 | |||
| 82161c10f8 | |||
| c939033031 | |||
| 8c8c040fb3 | |||
| 48a07e2b34 | |||
| 7bdfbb17b5 | |||
| 492646edc3 | |||
| 975096b221 | |||
| 0c4c790a65 | |||
| 012539b5d3 | |||
| f4cd9f27e4 | |||
| 449c2322b8 | |||
| 7efdcdc592 | |||
| 356aa27f70 | |||
| a579c7882f | |||
| 3077cf6bb5 | |||
| 7c46ee50a4 | |||
| 1d37910bd4 | |||
| 5555ff2ac1 | |||
| 2ad93d79f5 | |||
| 6d32fe743b | |||
| f2dd96c3bf | |||
| 9b4384d3e6 | |||
| c1831e58f5 | |||
| 991f241209 | |||
| b565cf4be1 | |||
| 8a5e1cd4f1 | |||
| 4445121023 | |||
| 1421869094 | |||
| 24480ef13a | |||
| 73bd7991bb | |||
| 6e74761977 | |||
| a7f7a226d6 | |||
| 491cdc5fef | |||
| 98fe47d085 | |||
| 2290627c5f | |||
| 656d436028 | |||
| cff8e822f2 | |||
| 5dfd46f809 | |||
| 97a6395ddd | |||
| 5226106ff0 | |||
| 5e838e3f10 | |||
| 574860f117 | |||
| c19857d463 | |||
| 6741d58bec | |||
| eb806dbca5 | |||
| 7d8aa683ff | |||
| 3bceaa7a2f | |||
| bfa45b66ac | |||
| e39b89859f | |||
| 1f14a01994 | |||
| 91310ce98c | |||
| 94dcfbf197 | |||
| d2b4b8b279 | |||
| aaa9495ceb | |||
| a8b75c9b13 | |||
| f21685e81d | |||
| e24a7f887a | |||
| 54b5ab6d79 | |||
| 361b2ed7c7 | |||
| cee6c7c999 | |||
| 3e5efd4090 | |||
| 10b8ba1ec8 | |||
| be90380775 | |||
| 3b2e775698 | |||
| 01ba90689f | |||
| e53fa82c46 | |||
| 48ccbfd860 | |||
| 601d4e1f48 | |||
| 7fce3bdd25 | |||
| 4b1bccd4b5 | |||
| 28e809f92f | |||
| 866e0e3fa9 | |||
| ecaa388bbe | |||
| 4cfdc5bf30 | |||
| ed1090430a | |||
| 3322cd73ea | |||
| 824f6ac4aa | |||
| d5dfa89fdd | |||
| 9628a98568 | |||
| cc3428d24b | |||
| fe684e9bbd | |||
| f31ea9b7cf | |||
| 7eeebb66ab | |||
| e4f53c1daf | |||
| 98bd58a92c | |||
| f9a7c12620 | |||
| d04223e174 | |||
| e71c514ef6 | |||
| 57b089ac89 | |||
| b97ff6fe14 | |||
| c57ac4c9fe | |||
| 9cf60ca227 | |||
| 8c9684e35c | |||
| 00854e2ea7 | |||
| dcae312d2c | |||
| 963a851382 | |||
| 1d233c5250 | |||
| 34919ec961 | |||
| d15228656f | |||
| dd20a06f80 | |||
| a4bbad3243 | |||
| 343ae99b54 | |||
| 41d1714d28 | |||
| c20a2ecd9a | |||
| b609daf89d | |||
| f1cdcdaf3e | |||
| 01da323ddb | |||
| 9675a608a8 | |||
| 8231f28ded | |||
| 772ecea71a | |||
| 63a74f2dc9 | |||
| 4f3286f520 | |||
| 26ca025070 | |||
| 49b1c553ba | |||
| 55948d5b79 | |||
| 3aeee71873 | |||
| d669e733cd | |||
| d9af34ee81 | |||
| 8649dbda35 | |||
| c7d05fbdb1 | |||
| f2cfd50a44 | |||
| a8146a2e81 | |||
| d166cc2a4a | |||
| 4558cb4846 | |||
| 143349be3a | |||
| 82ac92dfed | |||
| 2643eb2e37 | |||
| ac03b9f539 | |||
| a4370ba55a | |||
| 7aa4a01355 | |||
| 349a9de592 | |||
| 58c0b45ac8 | |||
| af0a923210 | |||
| 9bcaeee794 | |||
| b6d8771792 | |||
| f5fed50e4d | |||
| 5a00f56a39 | |||
| 0bb48f8af0 | |||
| f3ceda1b3d | |||
| 24c4cbe901 | |||
| 26e2b2507d | |||
| d3378c638a | |||
| aa90274cc2 | |||
| d0a281c116 | |||
| 4cbf192e74 | |||
| 1edafc40d0 | |||
| 6a4a25db40 | |||
| e5ca66305c | |||
| 3250566949 | |||
| 4ced362dd0 | |||
| 4b87ac2907 | |||
| 44e1b8f159 | |||
| 81b1f06eec | |||
| 441d36e2e3 | |||
| 7cba76b1e8 | |||
| c3304a78fb | |||
| e6113b4aa1 | |||
| 044c7f997d | |||
| abfba739a8 | |||
| cec67484e5 | |||
| aafed9b1b7 | |||
| 552a87462a | |||
| 4c8013e6d2 | |||
| 4f5efba173 | |||
| e0c94af535 | |||
| a8ff0b5e62 | |||
| 21a6a19caf | |||
| b1d25ec35c | |||
| 685de56fbb | |||
| e8adb7d533 | |||
| c2af0e8b48 | |||
| 8ea7b09482 | |||
| 5180d1a17a | |||
| d7e3493a59 | |||
| 5ea0a53995 | |||
| dd4c66dbd8 | |||
| 0e8c2476b2 | |||
| 53fdcce361 | |||
| a4a8d78b1f | |||
| 8b7bb7faa2 | |||
| 28430cc1c3 | |||
| 672f2d717d | |||
| 8362800a54 | |||
| 731908c265 | |||
| 3eb864deb9 | |||
| cb27043d12 | |||
| 5b90fce1f5 | |||
| 7f49ee3db6 | |||
| 72b645916f | |||
| 4700c3711c | |||
| feaf21c702 | |||
| 4fa8fff3db | |||
| 4e670f85f5 | |||
| 11a484714a | |||
| 6c425947bf | |||
| e06d34f546 | |||
| 9ffd0dcdbe | |||
| 0ce51709da | |||
| 073944dd96 | |||
| 874589c772 | |||
| 4a05031a0f | |||
| 38185ef2f1 | |||
| 66dde6c215 | |||
| a438817d00 | |||
| a92e651b43 | |||
| 618da86b44 | |||
| 46d810eddf | |||
| 031601bfa6 | |||
| bb5f777cea | |||
| fa6c2fb63e | |||
| d561348c4f | |||
| c290dba3bb | |||
| 1477180fa5 | |||
| 6cd4b7c433 | |||
| 36f56c2040 | |||
| f763cc4020 | |||
| 180d2addd0 | |||
| 8fe21d6d05 | |||
| 3274fb9615 | |||
| 1c5863e6de | |||
| 60b424c101 | |||
| 2a51454682 | |||
| 1d052231f0 | |||
| f120c41518 | |||
| 30b8619dd1 | |||
| 1e832ed0eb | |||
| 6260c07cd6 | |||
| 241d8fd321 | |||
| 5ddbddf5cc | |||
| 38854b7d6a | |||
| 958c5ea195 | |||
| 4329623341 | |||
| 658b0595ff | |||
| 2869a2157b | |||
| 11d5d74c11 | |||
| 54dd379cf5 | |||
| 7eef4ed876 | |||
| dd7a8bae16 | |||
| 93508d7514 | |||
| 1493beb79b | |||
| b7b15a8f7b | |||
| 6cb15d004e | |||
| 22220b1b95 | |||
| 7fe418d529 | |||
| ed8b0a078d | |||
| 71affb0a70 | |||
| 52f829f5d1 | |||
| 518d10fc8f | |||
| 62bbeb9471 | |||
| 36986d7ddd | |||
| b266b8b341 | |||
| c95c4b5f7d | |||
| 68c55086cf | |||
| 6b77bba46d | |||
| 675c7f8572 | |||
| 7fff705897 | |||
| 45a7e6a6ea | |||
| 1db0f3f536 | |||
| 2f086a335d | |||
| 9fe9c2a9dd | |||
| 2cd9a25791 | |||
| 45cef6a752 | |||
| 3811dd3554 | |||
| b2c944af43 | |||
| 76ca4f2183 | |||
| 72d7154a96 | |||
| ee6e9b059a | |||
| 2afb468ae4 | |||
| 125f705a6a | |||
| 3884af64a7 | |||
| 6bf65c7c74 | |||
| 7ddaa3ce65 | |||
| 5786fe78b2 | |||
| f50a2fd0ad | |||
| 3f5b799728 | |||
| 0cd4997174 | |||
| f70046bc73 | |||
| feb246c559 | |||
| 1dd18c3c0a | |||
| 36b3521b2a | |||
| c0df132c93 | |||
| 0a08b4b0b9 | |||
| 8a4208ab18 | |||
| 2b8d5541af | |||
| b81765adda | |||
| f7bd48cc9a | |||
| db7681676e | |||
| 7d9cfb6ff9 | |||
| 14f0a3ce05 | |||
| 3c650a7c50 | |||
| a9a964cc75 | |||
| 5085c89c22 | |||
| 9720ada87f | |||
| 9adbbcee93 | |||
| 160eee04ff | |||
| 8353c5105b | |||
| 6fd53d6e95 | |||
| 7530c586f7 | |||
| 8deb326ea1 | |||
| 4b76b592d3 | |||
| 3f9288c60f | |||
| 91e1b614f3 | |||
| b6f3e3a62f | |||
| 4a30b6d264 | |||
| 73bfa9beb8 | |||
| 18e203f007 | |||
| c6aad25ef2 | |||
| bc8f25ff4b | |||
| 4627303008 | |||
| e46a36d1c4 | |||
| 62325d9784 | |||
| 38d0894c36 | |||
| f7e379c83f | |||
| 185cc994f7 | |||
| 8815518af9 | |||
| 233144eede | |||
| 1028ebccbb | |||
| 791fd4bc55 | |||
| 5a557e6f00 | |||
| 948f823744 | |||
| c05d22b550 | |||
| f062d5b894 | |||
| 1af7ee8e8d | |||
| 53e8c9141c | |||
| 64f27e7d39 | |||
| a77c99d050 | |||
| 9c4ccc51c7 | |||
| 7937d8d041 | |||
| cca66f1f32 | |||
| dc4f0c1159 | |||
| fb07b280be | |||
| 2915ef885e | |||
| 152a4af549 | |||
| c7508441ec | |||
| 3073d3aa9d | |||
| 78eebc255e | |||
| 5e6ec3b217 | |||
| a6d6814f27 | |||
| 9d634ba4d7 | |||
| fcf8a80b0a | |||
| dfaf361bf2 | |||
| 6c791c034b | |||
| 8935c51cc9 | |||
| b148fa94c3 | |||
| 70fef94d15 | |||
| a921845ae3 | |||
| 8dd858e813 | |||
| 634e7ad7ee | |||
| e926a08def | |||
| f4eb0cfbcb | |||
| ee557d8191 | |||
| 7d37954495 | |||
| 3dc7bd74fe | |||
| 56ae387f7b | |||
| 5878128746 | |||
| 5994cc66b4 | |||
| a9410d92a2 | |||
| 89da5fe08a | |||
| bb69ae887f | |||
| 540656870a | |||
| 4f3b6e4029 | |||
| cec56c8406 | |||
| 7bbae51c7d | |||
| 3d00e96f89 | |||
| 6f29da1926 | |||
| 6f028eae6e | |||
| b44501566c | |||
| 5f50fa4202 | |||
| 5487cd7858 | |||
| 89e1af456f | |||
| 439838abd2 | |||
| 1cf60544e1 | |||
| 70ae1cce4b | |||
| b5fa41cdc1 | |||
| 0cce35908d | |||
| 5a1d8348d8 | |||
| 63cd793407 | |||
| f5abee1f92 | |||
| 1b0ed90a54 | |||
| 5003f69e8e | |||
| 68214cf16f | |||
| 1365d442e9 | |||
| 2f29953ccb | |||
| 9929479bf0 | |||
| b52c71c0f3 | |||
| 712be51888 | |||
| 3e4cc8841d | |||
| 3742d5ef81 | |||
| 310b12c81d | |||
| ab64a8f8e2 | |||
| 9d18a1297b | |||
| e73534ba2f | |||
| 73360bb66c | |||
| 5b74f8f836 | |||
| 0b87cd9aef | |||
| c5c971eeae | |||
| b554e2ee6c | |||
| 436a8c4d8e | |||
| a5725439ee | |||
| 30bc725631 | |||
| d261347b06 | |||
| 9bc65601c0 | |||
| 4a82def974 | |||
| 0755e8f618 | |||
| 44ef039321 | |||
| a7f5451ea4 | |||
| 4902e5b9bb | |||
| f3eb1bb45e | |||
| 01a9a13402 | |||
| ff6f76e018 | |||
| 01110d0835 | |||
| b6fdde403a | |||
| ad01a157ce | |||
| ac04ef34d7 | |||
| 58845ae79f | |||
| d6b7a4bdba | |||
| 34040abc8f | |||
| 03f535e85e | |||
| 6ea0bb30e0 | |||
| dd083f73f7 | |||
| 8dee20295e | |||
| 6e47e4bcd2 | |||
| ff825b85aa | |||
| 802bc50102 | |||
| 6d08faa30f | |||
| 116ab72f4e | |||
| 44d86bc350 | |||
| e0fc4b9443 | |||
| d9e18e0f3b | |||
| 6b8610b72d | |||
| 468448ff6e | |||
| b85efcd85a | |||
| 657f581a3d | |||
| 0dbff85bd8 | |||
| bf9c4c602b | |||
| 4c44957764 | |||
| ec39d7146c | |||
| c60a772e84 | |||
| 9ba47c1b6a | |||
| 4fc8a5ad0c | |||
| 50ffcceaf4 | |||
| 05060e76cb | |||
| 5139b587f6 | |||
| 63870b75a7 | |||
| cf0a8b3e95 | |||
| 8006482c22 | |||
| cda15170bd | |||
| 9ececd80d6 | |||
| 5ea938a20b | |||
| f4056a3fca | |||
| 23188c3898 | |||
| 7322f1a9fb | |||
| 4f6a676c32 | |||
| f2e3914ad8 | |||
| 25f2189a08 | |||
| 448c406e3e | |||
| d7c9640dc9 | |||
| 08949ab25f | |||
| 24ae8d1679 | |||
| adb7b13599 | |||
| eddf94a340 | |||
| 3df741c4fc | |||
| 12e7c51c39 | |||
| c8e2bcd6e4 | |||
| 14d5286eb0 | |||
| 397a2b0d82 | |||
| 35cf439592 | |||
| 2f6d69efac | |||
| d00ab3f8d6 | |||
| 05d3adff36 | |||
| 4c24c570a8 | |||
| 19f78f769b | |||
| 44358c122f | |||
| 8b720e103f | |||
| bd5ca76ef4 | |||
| 5e4136d9a1 | |||
| 14029aa60f | |||
| fa99b967ed | |||
| a9120edb70 | |||
| cfc8458841 | |||
| 7a7fee1d8a | |||
| b87153a445 | |||
| f97cd10bf6 | |||
| 736deca1fc | |||
| 7f3b9ec89a | |||
| f2339f26ca | |||
| b74a00de22 | |||
| d71297a114 | |||
| fb006a3032 | |||
| 536ed2b967 | |||
| 1173c3d053 | |||
| c7c1b2b8c1 | |||
| 6c8db56474 | |||
| 3292dc47ab | |||
| 98889780b9 | |||
| 11703afd6b | |||
| 3c1597f42f | |||
| 9d828ad291 | |||
| 84506c65ad | |||
| c5bd594798 | |||
| 13805edba9 | |||
| 79bd586def | |||
| 0e86e942ce | |||
| 708959a0c5 | |||
| 3bf3e2605b | |||
| 39b539aaed | |||
| c9648a5ec4 | |||
| acaa93367f | |||
| 2c830eacdd | |||
| 4c0e918559 | |||
| f79a3d15d1 | |||
| f2b44d8f99 | |||
| a319314d04 | |||
| 2c8c23522b | |||
| c05696df8c | |||
| 97019f1a2a | |||
| aa7a1ddd4e | |||
| 6bbe1c8518 | |||
| 0cf0d68026 | |||
| 6ff583a916 | |||
| f1856afa77 | |||
| 6a03b4b125 | |||
| 9e062acafb | |||
| aff7e49024 | |||
| 39b4649a04 | |||
| ca8c7229ce | |||
| 4634284e77 | |||
| e92fa789e9 | |||
| d29b4e486d | |||
| 56eb0b8ef1 | |||
| dcda5741f1 | |||
| 46dd19c326 | |||
| 5ef9ccb466 | |||
| 5fef1b3f30 | |||
| 5606da3449 | |||
| 844d500037 | |||
| 672a66695e | |||
| 125a89b5f9 | |||
| c65c197fbe | |||
| 98b1302b77 | |||
| e75afe6c41 | |||
| 361d02e075 | |||
| 1b20d3b6ac | |||
| c4536d789b | |||
| a7f8d9126e | |||
| 84e39add27 | |||
| 4d352b231c | |||
| 59eda04949 | |||
| 695cf24861 | |||
| 85d4dd9491 | |||
| 3a0fcd1517 | |||
| e7e649ad66 | |||
| b875a4898b | |||
| a33cd35ae8 | |||
| 6e294d1363 | |||
| 7d7269bfe6 | |||
| 1306a5bca1 | |||
| 0dd45cdfa2 | |||
| 4a11ac917b | |||
| 460be03a20 | |||
| 9f3e5fea38 | |||
| 49b494ed02 | |||
| c72559c31a | |||
| 2b8e189966 | |||
| 09e37f3951 | |||
| c90359716b | |||
| 4a1ad1c625 | |||
| 27694036a5 | |||
| ed40dc34cf | |||
| af29b2833f | |||
| 0ee1f5c47f | |||
| 018eb2fdcd | |||
| 368a74dfc9 | |||
| 1a0475433c | |||
| e841bfad68 | |||
| 5d3faba762 | |||
| 87cce116af | |||
| 356c97a4b2 | |||
| dd6c20ce74 | |||
| e939ffaf2d | |||
| 5a6f6fba2e | |||
| 99022055cf | |||
| 68030a4ae7 | |||
| 75ca9240a9 | |||
| 6d917b31e0 | |||
| 75e508bfd0 | |||
| 23136be280 | |||
| 527a0f5c70 | |||
| 9fbf9539ee | |||
| dab69b2ffe | |||
| 7fdbebfb70 | |||
| 06ffe5cd06 | |||
| 56203c3ee2 | |||
| 4f5b562e04 | |||
| e247753f96 | |||
| 575b5482b6 | |||
| aa5a72b788 | |||
| d8cb9f2c03 | |||
| 5241182520 | |||
| 61d8f2e1d4 | |||
| a8031a510f | |||
| c191060b73 | |||
| bfe7cf0ed7 | |||
| a4d72eefbc | |||
| 7b98105a0f | |||
| e741dd6f65 | |||
| 9ef1c1935d | |||
| 43562580d6 | |||
| 986250543e | |||
| 90ed2f084d | |||
| d353e186a5 | |||
| a8b684d966 | |||
| 3ec70f9fb3 | |||
| 6199256058 | |||
| 265936a791 | |||
| 2422013ddf | |||
| 2641220fb0 | |||
| 68e1830d8e | |||
| fd3f20ed52 | |||
| ccdb136b9a | |||
| 0a0617c5fc | |||
| b3e1c48f97 | |||
| 88d589bae5 | |||
| 0fb5cbdb06 | |||
| d47da11ce8 | |||
| 743a4f97ba | |||
| eac1247f36 | |||
| 57f60326f3 | |||
| 3235f6e388 | |||
| a607a6a137 | |||
| 2348d4a855 | |||
| e16f6c87c2 | |||
| 598962f5f3 | |||
| 8a241bb671 | |||
| 8bcb3d9e0e | |||
| 2317220666 | |||
| 47c1b58aa5 | |||
| 0039582f28 | |||
| 60a529375c | |||
| d42bcb0ab9 | |||
| 4b11bcb77b | |||
| 6775edddab | |||
| 039a732148 | |||
| 9aac70c734 | |||
| dc6c38cf24 | |||
| a2b09cb2aa | |||
| 6e6453f897 | |||
| f16de65934 | |||
| fa0a5efb25 | |||
| 1ffb6b9395 | |||
| 16d746fb8d | |||
| e8629fab66 | |||
| 9cb9719461 | |||
| 564f02b188 | |||
| d67596117f | |||
| ffecbb2298 | |||
| b5fe2bcc60 | |||
| 7641d6dbdf | |||
| 54c5e3a8e3 | |||
| d8427cdbcd | |||
| fe03a12bec | |||
| 37fca3fc3e | |||
| 4dcdbdec3a | |||
| 564c79c411 | |||
| ea46c78a15 | |||
| 93529cda60 | |||
| 13bf6a89cb | |||
| 468c69d2dd | |||
| 4ccfa8c3b6 | |||
| b9a447453f | |||
| 3daf03d89a | |||
| 1a7d4c2c37 | |||
| c7f612dac0 | |||
| 1a7aa1218a | |||
| e530e30878 | |||
| c5ef4df15a | |||
| dba07c1832 | |||
| d9d0f426e4 | |||
| 1b88d3d785 | |||
| 1e188fe737 | |||
| 335d4abbe1 | |||
| 1087d27523 | |||
| f8f8b884bc | |||
| c84c462aeb | |||
| 3ba4384c84 | |||
| d4c72c34c3 | |||
| 60e950abcb | |||
| 6cb48e76a8 | |||
| 5e811b188b | |||
| 31320b4ab7 | |||
| 38e62a9c98 | |||
| 74e59fa47b | |||
| 80b1374a61 | |||
| bd3e9d01ec | |||
| f3e7d188f9 | |||
| c45044b5c2 | |||
| ac2dcee6c3 | |||
| e72ea21b6b | |||
| a46f57eaab | |||
| 843b5d7154 | |||
| 4f7d659700 | |||
| 0a195e0c11 | |||
| 9a9a21350e | |||
| 5a4cbc74f7 | |||
| 27e06ceb2c | |||
| 68f2640ceb | |||
| cd9cf902bf | |||
| 03f3b4f8d3 | |||
| 6e430861d7 | |||
| b7d658cff3 | |||
| b1b85e7135 | |||
| f28bdccd0f | |||
| d2ea288009 | |||
| e0a60e3ef3 | |||
| 563bf12de0 | |||
| ef4037274d | |||
| 639294af2d | |||
| 9e51a1617f | |||
| d317ac5236 | |||
| 42907b5a5d | |||
| 5d35fc80d5 | |||
| 11cab800e3 | |||
| 9272765f3c | |||
| 30e1d9552c | |||
| 1bafdf0feb | |||
| 1618d54b70 | |||
| be6739ecb8 | |||
| 200e576ba8 | |||
| ed6fe5631a | |||
| b0ee9b27a1 | |||
| eb0de84ce3 | |||
| c52b3d27bc | |||
| 352adbc9cb | |||
| c418683b3d | |||
| 5bd22f25b8 | |||
| 087ab3815f | |||
| 6af2055fd2 | |||
| 05e351be9c | |||
| 9bdcd6aace | |||
| 265807941a | |||
| 88b60ea945 | |||
| f5854f9342 | |||
| 8ffe6fa8fb | |||
| 1d11585fcd | |||
| b0d8b9a078 | |||
| 9f1b66fb91 | |||
| 55fde2d3fb | |||
| 649becb98e | |||
| 0b9ea265f0 | |||
| 3e92154e27 | |||
| bdb86808a5 | |||
| 1993639bf6 | |||
| 90a5398e98 | |||
| c5ab77b8ac | |||
| 7cd268bb4c | |||
| 036dc289d5 | |||
| 035f2a0236 | |||
| 86669fb6e8 | |||
| 5a2bb741a7 | |||
| 28e8a234e9 | |||
| bd48e77e7c | |||
| 5b6f1182fe | |||
| 44d800de1e | |||
| fc55622cc8 | |||
| 0e217e431b | |||
| e4cbfcfedc | |||
| ccd59278e5 | |||
| a27bb66fef | |||
| ef6036a7d0 | |||
| b158ea9e78 | |||
| fbc02881d7 | |||
| 4340d584c1 | |||
| a4a8678116 | |||
| ab4bee5d69 | |||
| fad17c9cd2 | |||
| e7f0ba1982 | |||
| 92bee965f0 | |||
| dd4edb8ba5 | |||
| b8f58b9cc7 | |||
| f334b0137e | |||
| e732c8ff6a | |||
| 60bb232d2a | |||
| 4f18f26935 | |||
| 7dc095b87a | |||
| 759780f10e | |||
| 77ff87a9c4 | |||
| 82d6719681 | |||
| 073998789c | |||
| 3f4347289c | |||
| dc2a3cde47 | |||
| 252eebe5d8 | |||
| bdd0da82e7 | |||
| d144b9ba0b | |||
| b201cf1990 | |||
| 286c4da2f3 | |||
| 959a5b5c70 | |||
| 607a7be337 | |||
| 3663b9f957 | |||
| 9aa6d1c0fc | |||
| e6b59bc16d | |||
| a478428721 | |||
| 8bb01a2412 | |||
| 4a98fb6c9d | |||
| ea5dd69719 | |||
| 6d3a35517b | |||
| 9be6e12a5c | |||
| e8addbc954 | |||
| 683becabda | |||
| 8c0cbfe93a | |||
| 57cb8d5bda | |||
| 0b583bcf80 | |||
| 9880248e49 | |||
| 20b715f7cb | |||
| ed9ddc0825 | |||
| 5ab2bae5d7 | |||
| 8a2b69464f | |||
| ff78830ead | |||
| 01fd89f60c | |||
| 44b6396b78 | |||
| 998e1b62a5 | |||
| 273f7c7a73 | |||
| e9544bb610 | |||
| fecf7f987d | |||
| 308395aa86 | |||
| 69218b6710 | |||
| 8bf7fcff28 | |||
| 416fbc093f | |||
| 2500fa54e4 | |||
| ccc7a947cd | |||
| 83772bed42 | |||
| 7f3b7df710 | |||
| 39b7563d3a | |||
| 7b94d753b8 | |||
| ab89d6a22f | |||
| bff242dc99 | |||
| ec452125e2 | |||
| faa4ca33ad | |||
| 0b2284f163 | |||
| b8c4d66160 | |||
| 4dfd033435 | |||
| 9ca8739514 | |||
| 1d8691fed8 | |||
| 522aec1d3a | |||
| 6153b25bc7 | |||
| bb7e277f19 | |||
| 8a7bc3c17d | |||
| af22751210 | |||
| 81f6ce26d4 | |||
| 633fea8c68 | |||
| 0c5855a82d | |||
| c07b4e9f98 | |||
| 2407c3f2f8 | |||
| d89371a279 | |||
| 86be84b015 | |||
| da79b4e2ea | |||
| e54f7d9c06 | |||
| c0991e46ec | |||
| ddd29529ea | |||
| a1d6431348 | |||
| 31bd6a0c0d | |||
| 7f98aff531 | |||
| 948317c27a | |||
| 7406da66a7 | |||
| 8586ec67b6 | |||
| 991aaa8677 | |||
| a4d1b5b972 | |||
| 887d355dea | |||
| 93866dc466 | |||
| 6c014e25b5 | |||
| b1663c040f | |||
| 1f63b47500 | |||
| ce0249f28e | |||
| 5d096092b9 | |||
| a6b65c8d76 | |||
| d3a4b0451d | |||
| e0714637ff | |||
| e777566339 | |||
| 185d0d8817 | |||
| 4132c0552f | |||
| c158e74fb5 | |||
| e5384509cf | |||
| 60324c041c | |||
| 5be89f3f31 | |||
| 4207330dcf | |||
| 05ab44d50b | |||
| 2a849c417f | |||
| d5d7e9a952 | |||
| f1e3210c7a | |||
| cd07f29038 | |||
| a7428838b3 | |||
| 58b00b6ed5 | |||
| 9d2544f8cb | |||
| 4f99743db7 | |||
| e5d622780e | |||
| 905625d18e | |||
| b529cc5691 | |||
| aca279c951 | |||
| 086bb764a5 | |||
| 4304cad029 | |||
| 79e2bdb587 | |||
| 51d55eed05 | |||
| ef5115b76c | |||
| 58a5aaed30 | |||
| b6592582b3 | |||
| 7863660a47 | |||
| ea3e7f9ace | |||
| 92e7ac7f20 | |||
| 75f6a93024 | |||
| d679660e8a | |||
| f3d87f09d7 | |||
| 71c90c6d31 | |||
| 3b76bb64a9 | |||
| e5cdd9a7f1 | |||
| 121653a8f2 | |||
| 9d326d240a | |||
| d92c96ecc2 | |||
| aec5486144 | |||
| 773dd2fd1a | |||
| 36e5fec284 | |||
| 84e81e9ce0 | |||
| 0ecde9531e | |||
| 7d4b980e59 | |||
| bccb4e154d | |||
| 45dd039c53 | |||
| 108499706a | |||
| 1ae1cebab6 | |||
| 77786af4d7 | |||
| 809959fde8 | |||
| a47ea13a9b | |||
| dddbcdf088 | |||
| e1067aeb83 | |||
| e5e46f3239 | |||
| a28c08be99 | |||
| 279fcad10a | |||
| 7505d645eb | |||
| 5af98c5683 | |||
| f8cb0caa80 | |||
| e3747d3cfd | |||
| ebe96c0dad | |||
| bbd394b95e | |||
| e1824ffda1 | |||
| d42be730c3 | |||
| a75817dde1 | |||
| 066fb7fba0 | |||
| 30dc6e865f | |||
| ae5d0b282c | |||
| a9ce316d28 | |||
| 6c28e270d2 | |||
| 72d4c7eb40 | |||
| 921e5912b3 | |||
| aa168fab8c | |||
| dffa357c0a | |||
| 7c6442d51f | |||
| f995f6568c | |||
| 5b8d812fc8 | |||
| 27ce6ad5ba | |||
| 58dcb72adc | |||
| 202a7106a7 | |||
| b8f79f6559 | |||
| e491401ca0 | |||
| 89b236ae9e | |||
| 0566b30406 | |||
| 14e63bd47d | |||
| 1b2ce750ca | |||
| 17df8f23c5 | |||
| eca3174214 | |||
| 5d7126f772 | |||
| a82ba6503f | |||
| cecbad2eb9 | |||
| a97cb3df4f | |||
| 59f29eb6b4 | |||
| ba9cb41d47 | |||
| 67a2ad708f | |||
| e385e3cec8 | |||
| 6373478d13 | |||
| 7cc6dd03ff | |||
| 30ffc8cd11 | |||
| b61a71b3b4 | |||
| 1c7a7f6edf | |||
| 5d410764e1 | |||
| f645589cda | |||
| b49c1e6e47 | |||
| 58558732be | |||
| 784ef277fc | |||
| eb359f79cb | |||
| 31bf39d2e5 | |||
| 08159923d6 | |||
| 7b8b8d6b75 | |||
| e52f5571a7 | |||
| ea458935f0 | |||
| 64674026a6 | |||
| ea71474b62 | |||
| 5d6348b83c | |||
| 199a26afbc | |||
| c1971bffa8 | |||
| 51832005de | |||
| 9d5ae41c49 | |||
| fa1d2416e0 | |||
| c212bc294b | |||
| ca21758889 | |||
| 75da6d4df1 | |||
| 0483bc0afe | |||
| 0e12c6906f | |||
| 1e633278dd | |||
| 554756851f | |||
| ac8ac06c8a | |||
| f6780fcb76 | |||
| 31c2ea888b | |||
| 2702c0f2e4 | |||
| 2aef5f94c0 | |||
| a4b571ccbc | |||
| fd561cd45d | |||
| 1357d817e6 | |||
| ba2613d3c7 | |||
| b2e1717dc3 | |||
| ace5abea23 | |||
| 2ea1bd5e67 | |||
| 8ced379e44 | |||
| f5ab7f3102 | |||
| 445ce46357 | |||
| 1b9c7d48e4 | |||
| 596c3368e0 | |||
| 3a699d34eb | |||
| 451eca0aa9 | |||
| 165517608d | |||
| 05eb0b20be | |||
| c47526ce8e | |||
| 75d9bf458a | |||
| 3b90a58418 | |||
| 84e49a266d | |||
| 446430be4f | |||
| e0a5ecc156 | |||
| 59e8ba2297 | |||
| 9f8caff571 | |||
| f68c206a17 | |||
| 8c5fdd4673 | |||
| c62be9573d | |||
| c4d2e16433 | |||
| ccf36f23c3 | |||
| 495dcd94d8 | |||
| 417a759c40 | |||
| 90846401a5 | |||
| 8e28c59163 | |||
| a3eb0bb9fc | |||
| 9aac129aad | |||
| 9a6e3a80c7 | |||
| 7e45835424 | |||
| 58632b8da3 | |||
| acaf95c017 | |||
| 1c3ad6a925 | |||
| 9bf49d8732 | |||
| 6895390f2b | |||
| 116831f6d9 | |||
| 51c401d6db | |||
| b13bc0060b | |||
| e68d573209 | |||
| 2d6f3db8fe | |||
| 1a68a60479 | |||
| da55612726 | |||
| 1b88e132cc | |||
| 6179dbda8a | |||
| 2a7b2343b2 | |||
| f7de416070 | |||
| ead4b9e695 | |||
| 362adedae6 | |||
| 4d88c1f798 | |||
| f664142094 | |||
| 0d533a71b6 | |||
| d88a6c5db8 | |||
| 4379deaca2 | |||
| 67325fdb79 | |||
| 77b6239b16 | |||
| 248f2ca3c4 | |||
| 8b672a68e6 | |||
| ce8127456c | |||
| 30185626a6 | |||
| 431eea9152 | |||
| d53ec07857 | |||
| 2670a41545 | |||
| 4825b21951 | |||
| ac2974d290 | |||
| ad7a4503e0 | |||
| 4094034aaf | |||
| 11e74ef6cc | |||
| 1930b911b3 | |||
| 7e9417b186 | |||
| c620a9388f | |||
| 0d55247dd9 | |||
| 9940e61fec | |||
| feb4f77e39 | |||
| d1925201ef | |||
| 7e9cea94f3 | |||
| 152b1bd24e | |||
| 5d6da6034d | |||
| 6ea203f6b9 | |||
| 79aabc6feb | |||
| 0ef5b017df | |||
| 8ab969688e | |||
| 7e5b94cc5f | |||
| 8cd02543bc | |||
| 6f68e8b2c7 | |||
| e7baa90792 | |||
| 4199824275 | |||
| fc2abb0f3c | |||
| 5b275e4170 | |||
| a7e58b715b | |||
| 8b8d59065f | |||
| ed0efec628 | |||
| f8a40b8fb3 | |||
| ef8d48846c | |||
| 8f973c8dc1 | |||
| e1e47caf72 | |||
| c2d7fe8612 | |||
| 93e02093be | |||
| 7934be70ee | |||
| ec43d2012e | |||
| e2c8b6ae02 | |||
| 4ce37da17e | |||
| 92ce6e5a8d | |||
| fd16fd2890 | |||
| 9f66725b4a | |||
| 373d75609a | |||
| 00d4f7640e | |||
| 98a26a40b1 | |||
| ba84ac56e9 | |||
| 05f501a66a |
-32
@@ -1,32 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<classpath>
|
|
||||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
|
||||||
<attributes>
|
|
||||||
<attribute name="optional" value="true"/>
|
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
|
||||||
</attributes>
|
|
||||||
</classpathentry>
|
|
||||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
|
||||||
<attributes>
|
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
|
||||||
</attributes>
|
|
||||||
</classpathentry>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
|
||||||
<attributes>
|
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
|
||||||
</attributes>
|
|
||||||
</classpathentry>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
|
||||||
<attributes>
|
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
|
||||||
</attributes>
|
|
||||||
</classpathentry>
|
|
||||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
|
||||||
<attributes>
|
|
||||||
<attribute name="optional" value="true"/>
|
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
|
||||||
<attribute name="test" value="true"/>
|
|
||||||
</attributes>
|
|
||||||
</classpathentry>
|
|
||||||
<classpathentry kind="output" path="target/classes"/>
|
|
||||||
</classpath>
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
name: Iris Bug Report
|
||||||
|
description: File a bug report for Iris
|
||||||
|
labels: [ bug ]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thank you for taking the time to fill this out!
|
||||||
|
If this does not work for you, feel free to use the [blank](https://github.com/VolmitSoftware/Iris/issues/new) format.
|
||||||
|
- type: textarea
|
||||||
|
id: how
|
||||||
|
attributes:
|
||||||
|
label: Problem
|
||||||
|
description: Please give a text description of how you reached the problem
|
||||||
|
value: |
|
||||||
|
1. Install Iris...
|
||||||
|
2. Do this...
|
||||||
|
3. Do that...
|
||||||
|
4. Observe the error...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: what
|
||||||
|
attributes:
|
||||||
|
label: Solution
|
||||||
|
description: Explain where you think the problem comes from (optional)
|
||||||
|
placeholder: The code to place a is missing b and c...
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: dropdown
|
||||||
|
id: mcversion
|
||||||
|
attributes:
|
||||||
|
label: Minecraft Version
|
||||||
|
description: What version of Minecraft is the server on?
|
||||||
|
options:
|
||||||
|
- 1.14.X
|
||||||
|
- 1.15.X
|
||||||
|
- 1.16.X
|
||||||
|
- 1.17
|
||||||
|
- 1.17.1
|
||||||
|
- 1.18
|
||||||
|
- 1.19
|
||||||
|
- 1.20
|
||||||
|
- 1.21
|
||||||
|
- 1.22
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: irisversion
|
||||||
|
attributes:
|
||||||
|
label: Iris Version
|
||||||
|
description: What version of Iris are you running? (see console)
|
||||||
|
placeholder: DO NOT SAY "LATEST"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Log
|
||||||
|
description: Paste a full log. Always use [mclogs](https://mclo.gs) Or [Pastebin](https://pastebin.com/). Must not be a crash report. Must be a full log. Must not be a screenshot of a log.
|
||||||
|
placeholder: https://mslog.gs/...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: markdown
|
||||||
|
id: thanks
|
||||||
|
attributes:
|
||||||
|
value: "Thank you for filling out the form! We will be with you soon. Please do not ask support to review your report."
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
name: Iris Feature Request
|
||||||
|
description: File a feature request for Iris. If you want to report a bug this is not the place.
|
||||||
|
labels: [ feature ]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thank you for taking the time to fill this out!
|
||||||
|
If this does not work for you, feel free to use the [blank](https://github.com/VolmitSoftware/Iris/issues/new) format.
|
||||||
|
- type: dropdown
|
||||||
|
id: arc
|
||||||
|
attributes:
|
||||||
|
label: Adding, Removing, or Changing
|
||||||
|
description: What are you doing
|
||||||
|
options:
|
||||||
|
- Adding
|
||||||
|
- Removing
|
||||||
|
- Changing
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: atype
|
||||||
|
attributes:
|
||||||
|
label: Type of Modification
|
||||||
|
description: What is it for?
|
||||||
|
value: I want to ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: desc
|
||||||
|
attributes:
|
||||||
|
label: What are you trying to modify
|
||||||
|
description: Give as detailed of a description as you can for the modification that you want done (include pictures if applicable)
|
||||||
|
value: The way I would implement this is ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternative
|
||||||
|
attributes:
|
||||||
|
label: Alternatives
|
||||||
|
description: What alternatives have you considered?
|
||||||
|
value: If this could not be implemented I would ...
|
||||||
|
- type: markdown
|
||||||
|
id: thanks
|
||||||
|
attributes:
|
||||||
|
value: "Thank you for filling out the form! We will be with you soon. Please do not ask support to review your report."
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ci-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify:
|
||||||
|
name: Verify (core, plugin, spi, probes)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up JDK 25
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: '25'
|
||||||
|
- name: Set up Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
- name: Run verification gates
|
||||||
|
run: ./gradlew :core:check :adapters:bukkit:plugin:test :spi:build :probe:test :probe:run :probe:deserializationProbe -PuseLocalVolmLib=false --console=plain --stacktrace
|
||||||
|
- name: Run modded shared tests
|
||||||
|
run: ./gradlew -p adapters/fabric test -PuseLocalVolmLib=false --console=plain --stacktrace
|
||||||
+41
-2
@@ -1,3 +1,42 @@
|
|||||||
/target/
|
|
||||||
|
|
||||||
lib/
|
build/
|
||||||
|
libs/
|
||||||
|
|
||||||
|
.gradle/
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
collection/
|
||||||
|
|
||||||
|
out/
|
||||||
|
tools/simd-bench/out/
|
||||||
|
tools/simd-bench/simd-bench.jar
|
||||||
|
|
||||||
|
DataPackExamples/
|
||||||
|
|
||||||
|
TreeGenStuff/
|
||||||
|
dist/
|
||||||
|
core/plugins/
|
||||||
|
adapters/bukkit/plugin/plugins/
|
||||||
|
adapters/*/run/
|
||||||
|
|
||||||
|
.codegraph/
|
||||||
|
|
||||||
|
plans/
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
local.properties
|
||||||
|
*.jks
|
||||||
|
*.p12
|
||||||
|
*.pfx
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.hprof
|
||||||
|
credentials.json
|
||||||
|
service-account*.json
|
||||||
|
|
||||||
|
docs/
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<projectDescription>
|
|
||||||
<name>Iris</name>
|
|
||||||
<comment></comment>
|
|
||||||
<projects>
|
|
||||||
</projects>
|
|
||||||
<buildSpec>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
</buildSpec>
|
|
||||||
<natures>
|
|
||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
|
||||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
|
||||||
</natures>
|
|
||||||
</projectDescription>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
eclipse.preferences.version=1
|
|
||||||
encoding//src/main/java=UTF-8
|
|
||||||
encoding//src/main/resources=UTF-8
|
|
||||||
encoding/<project>=UTF-8
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
eclipse.preferences.version=1
|
|
||||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
|
||||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
|
||||||
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
|
||||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
|
||||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
|
|
||||||
org.eclipse.jdt.core.compiler.release=disabled
|
|
||||||
org.eclipse.jdt.core.compiler.source=1.8
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
activeProfiles=
|
|
||||||
eclipse.preferences.version=1
|
|
||||||
resolveWorkspaceProjects=true
|
|
||||||
version=1
|
|
||||||
+518
@@ -0,0 +1,518 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed to take away your freedom to share and change the
|
||||||
|
works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all
|
||||||
|
versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use
|
||||||
|
the GNU General Public License for most of our software; it applies also to any other work released this way by its
|
||||||
|
authors. You can apply it to your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make
|
||||||
|
sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive
|
||||||
|
source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and
|
||||||
|
that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights.
|
||||||
|
Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it:
|
||||||
|
responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients
|
||||||
|
the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must
|
||||||
|
show them these terms so they know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute
|
||||||
|
and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software.
|
||||||
|
For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems
|
||||||
|
will not be attributed erroneously to authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run modified versions of the software inside them, although
|
||||||
|
the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the
|
||||||
|
software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely
|
||||||
|
where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we stand ready to extend this provision to those
|
||||||
|
domains in future versions of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents. States should not allow patents to restrict
|
||||||
|
development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger
|
||||||
|
that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "
|
||||||
|
Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission,
|
||||||
|
other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a
|
||||||
|
work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily
|
||||||
|
liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy.
|
||||||
|
Propagation includes copying, distribution (with or without modification), making available to the public, and in some
|
||||||
|
countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction
|
||||||
|
with a user through a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright
|
||||||
|
notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the extent that warranties are provided), that
|
||||||
|
licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a
|
||||||
|
list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means
|
||||||
|
any non-source form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body,
|
||||||
|
or, in the case of interfaces specified for a particular programming language, one that is widely used among developers
|
||||||
|
working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in
|
||||||
|
the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to
|
||||||
|
enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is
|
||||||
|
available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used
|
||||||
|
to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (
|
||||||
|
for an executable work) run the object code and to modify the work, including scripts to control those activities.
|
||||||
|
However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs
|
||||||
|
which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding
|
||||||
|
Source includes interface definition files associated with source files for the work, and the source code for shared
|
||||||
|
libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data
|
||||||
|
communication or control flow between those subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided
|
||||||
|
the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program.
|
||||||
|
The output from running a covered work is covered by this License only if the output, given its content, constitutes a
|
||||||
|
covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not convey, without conditions so long as your license
|
||||||
|
otherwise remains in force. You may convey covered works to others for the sole purpose of having them make
|
||||||
|
modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do not control copyright. Those thus making or running
|
||||||
|
the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that
|
||||||
|
prohibit them from making any copies of your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not
|
||||||
|
allowed; section 10 makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling
|
||||||
|
obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or
|
||||||
|
restricting circumvention of such measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the
|
||||||
|
extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you
|
||||||
|
disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users,
|
||||||
|
your or third parties' legal rights to forbid circumvention of technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating
|
||||||
|
that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices
|
||||||
|
of the absence of any warranty; and give all recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for
|
||||||
|
a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source
|
||||||
|
code under the terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of
|
||||||
|
the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or
|
||||||
|
distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the
|
||||||
|
compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause
|
||||||
|
this License to apply to the other parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License, in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library,
|
||||||
|
need not be included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used
|
||||||
|
for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In
|
||||||
|
determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a
|
||||||
|
particular product received by a particular user, "normally used" refers to a typical or common use of that class of
|
||||||
|
product, regardless of the status of the particular user or of the way in which the particular user actually uses, or
|
||||||
|
expects or is expected to use, the product. A product is a consumer product regardless of whether the product has
|
||||||
|
substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of
|
||||||
|
the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information
|
||||||
|
required to install and execute modified versions of a covered work in that User Product from a modified version of its
|
||||||
|
Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code
|
||||||
|
is in no case prevented or interfered with solely because modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the
|
||||||
|
conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to
|
||||||
|
the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding
|
||||||
|
Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not
|
||||||
|
apply if neither you nor any third party retains the ability to install modified object code on the User Product (for
|
||||||
|
example, the work has been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a requirement to continue to provide support
|
||||||
|
service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product
|
||||||
|
in which it has been modified or installed. Access to a network may be denied when the modification itself materially
|
||||||
|
and adversely affects the operation of the network or violates the rules and protocols for communication across the
|
||||||
|
network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format
|
||||||
|
that is publicly documented (and with an implementation available to the public in source code form), and must require
|
||||||
|
no special password or key for unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of
|
||||||
|
its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were
|
||||||
|
included in this License, to the extent that they are valid under applicable law. If additional permissions apply only
|
||||||
|
to part of the Program, that part may be used separately under those permissions, but the entire Program remains
|
||||||
|
governed by this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or
|
||||||
|
from any part of it. (Additional permissions may be written to require their own removal in certain cases when you
|
||||||
|
modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have
|
||||||
|
or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by
|
||||||
|
the copyright holders of that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the
|
||||||
|
Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with
|
||||||
|
a term that is a further restriction, you may remove that term. If a license document contains a further restriction but
|
||||||
|
permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of
|
||||||
|
that license document, provided that the further restriction does not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a
|
||||||
|
statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as
|
||||||
|
exceptions; the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to
|
||||||
|
propagate or modify it is void, and will automatically terminate your rights under this License (including any patent
|
||||||
|
licenses granted under the third paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your license from a particular copyright holder is
|
||||||
|
reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b)
|
||||||
|
permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days
|
||||||
|
after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you
|
||||||
|
of the violation by some reasonable means, this is the first time you have received notice of violation of this
|
||||||
|
License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the
|
||||||
|
notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the licenses of parties who have received copies or
|
||||||
|
rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not
|
||||||
|
qualify to receive new licenses for the same material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a
|
||||||
|
covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not
|
||||||
|
require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered
|
||||||
|
work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run,
|
||||||
|
modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third
|
||||||
|
parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or
|
||||||
|
subdividing an organization, or merging organizations. If propagation of a covered work results from an entity
|
||||||
|
transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work
|
||||||
|
the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with
|
||||||
|
reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For
|
||||||
|
example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License,
|
||||||
|
and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using,
|
||||||
|
selling, offering for sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the
|
||||||
|
Program is based. The work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already
|
||||||
|
acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or
|
||||||
|
selling its contributor version, but do not include claims that would be infringed only as a consequence of further
|
||||||
|
modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent
|
||||||
|
sublicenses in a manner consistent with the requirements of this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential
|
||||||
|
patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its
|
||||||
|
contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not
|
||||||
|
to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a
|
||||||
|
patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not
|
||||||
|
available for anyone to copy, free of charge and under the terms of this License, through a publicly available network
|
||||||
|
server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available,
|
||||||
|
or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a
|
||||||
|
manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "
|
||||||
|
Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in
|
||||||
|
a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in
|
||||||
|
that country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring
|
||||||
|
conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing
|
||||||
|
them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is
|
||||||
|
automatically extended to all recipients of the covered work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of,
|
||||||
|
or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You
|
||||||
|
may not convey a covered work if you are a party to an arrangement with a third party that is in the business of
|
||||||
|
distributing software, under which you make payment to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a
|
||||||
|
discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from
|
||||||
|
those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered
|
||||||
|
work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to
|
||||||
|
infringement that may otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this
|
||||||
|
License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to
|
||||||
|
satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence
|
||||||
|
you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further
|
||||||
|
conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License
|
||||||
|
would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work
|
||||||
|
licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the
|
||||||
|
resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special
|
||||||
|
requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply
|
||||||
|
to the combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to
|
||||||
|
time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new
|
||||||
|
problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the
|
||||||
|
GNU General Public License "or any later version" applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that numbered version or of any later version published by the Free Software Foundation. If the
|
||||||
|
Program does not specify a version number of the GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used,
|
||||||
|
that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the
|
||||||
|
Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different permissions. However, no additional obligations are imposed
|
||||||
|
on any author or copyright holder as a result of your choosing to follow a later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING
|
||||||
|
THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
|
||||||
|
IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU
|
||||||
|
ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO
|
||||||
|
MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
|
||||||
|
INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO
|
||||||
|
LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
|
||||||
|
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to
|
||||||
|
their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil
|
||||||
|
liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program
|
||||||
|
in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve
|
||||||
|
this is to make it free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to
|
||||||
|
most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer
|
||||||
|
to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of
|
||||||
|
course, your program's commands might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for
|
||||||
|
the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is
|
||||||
|
a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If
|
||||||
|
this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
@@ -1,2 +1,136 @@
|
|||||||
# Iris
|
# Iris
|
||||||
World Generator
|
|
||||||
|
Iris is a world generation engine for Minecraft servers and mod loaders. It generates terrain,
|
||||||
|
biomes, caves, structures, objects, and entities from editable JSON packs, with a full in-game
|
||||||
|
studio authoring workflow. The same engine runs as a Bukkit-family plugin and as a Fabric, Forge,
|
||||||
|
or NeoForge server mod, and produces bit-identical terrain on every platform for the same pack and
|
||||||
|
seed. The master branch targets the current Minecraft version (26.2).
|
||||||
|
|
||||||
|
# [Support](https://discord.gg/3xxPTpT) **|** [Documentation](https://docs.volmit.com/iris/) **|** [Git](https://github.com/IrisDimensions)
|
||||||
|
|
||||||
|
Consider supporting development by buying Iris on Spigot.
|
||||||
|
|
||||||
|
## Platforms
|
||||||
|
|
||||||
|
| Platform | Artifact | Minecraft | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Paper / Purpur / Leaf / Canvas | plugin jar | 26.2 | Full feature set |
|
||||||
|
| Folia | plugin jar | 26.2 | Region-safe scheduling throughout |
|
||||||
|
| Spigot / CraftBukkit | plugin jar | 26.2 | Full feature set |
|
||||||
|
| Fabric | mod jar | 26.2 | Server worldgen + client HUD; requires Fabric Loader 0.19.3+ |
|
||||||
|
| Forge | mod jar | 26.2 | Server worldgen + client HUD; requires Forge 65.0.0+ |
|
||||||
|
| NeoForge | mod jar | 26.2 | Server worldgen + client HUD; requires NeoForge 26.2+ |
|
||||||
|
|
||||||
|
Java 25 is required on every platform.
|
||||||
|
|
||||||
|
The modded feature set matches the plugin wherever the operation is not Bukkit-bound: worldgen
|
||||||
|
from Iris packs, authoring with mod blocks/items/entities (with validation suggestions), studio
|
||||||
|
workspaces with schema autocomplete over the server's live registries, entity spawning parity
|
||||||
|
including death loot, pregeneration with a boss bar or client HUD, the `/iris` command tree, and
|
||||||
|
the goldenhash determinism gate, which is interchangeable across all four platforms.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
**Plugin (Paper/Purpur/Leaf/Canvas/Folia/Spigot):** drop the plugin jar into `plugins/` and start
|
||||||
|
the server. On first boot Iris downloads the default `overworld` pack automatically.
|
||||||
|
|
||||||
|
**Mod (Fabric/Forge/NeoForge):** drop the mod jar into `mods/` and start the server. The jar is
|
||||||
|
self-contained (core, SPI, and required Fabric API modules are bundled). On first boot Iris
|
||||||
|
downloads the default `overworld` pack before the worldgen datapack is written, so the default
|
||||||
|
pack is fully active immediately. Packs installed later register their custom dimension types
|
||||||
|
(height ranges) and custom biomes through the forced datapack at server start — restart once after
|
||||||
|
adding a pack so worlds get its full heights and biomes; worlds created before that restart run
|
||||||
|
with fallback heights.
|
||||||
|
|
||||||
|
**Singleplayer (modded clients):** installed Iris packs appear as selectable World Types on the
|
||||||
|
Create New World screen; the integrated server runs the same engine.
|
||||||
|
|
||||||
|
## The client mod
|
||||||
|
|
||||||
|
Installing the mod jar on a client adds a native pregeneration HUD: a top-left panel with a
|
||||||
|
progress bar, chunks done/total, percent, chunks per second, and ETA, turning yellow while the
|
||||||
|
pregen is paused. The `H` key (rebindable, under the "Iris" controls category) toggles it.
|
||||||
|
|
||||||
|
The HUD works against modded Iris servers and against Bukkit/Paper Iris servers, both over the
|
||||||
|
`irisworldgen:main` channel (custom payloads on modded, plugin messaging on Bukkit). Vanilla
|
||||||
|
clients are unaffected and get the server-side boss bar instead; on non-Iris servers the client
|
||||||
|
mod is inert.
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
Create and enter an Iris world.
|
||||||
|
|
||||||
|
Plugin (optional arguments are keyed):
|
||||||
|
|
||||||
|
```
|
||||||
|
/iris create myworld type=overworld seed=1337
|
||||||
|
/iris load myworld
|
||||||
|
```
|
||||||
|
|
||||||
|
Mod (positional arguments):
|
||||||
|
|
||||||
|
```
|
||||||
|
/iris create myworld overworld 1337
|
||||||
|
```
|
||||||
|
|
||||||
|
Pregeneration requires a radius in blocks. On the plugin, optional arguments are keyed; on modded
|
||||||
|
servers they are positional and composable:
|
||||||
|
|
||||||
|
```
|
||||||
|
/iris pregen start 352 world=myworld center=0,0 gui=false
|
||||||
|
/iris pregen start 352 irisworldgen:myworld at 0 0 sync
|
||||||
|
```
|
||||||
|
|
||||||
|
Players with the client mod see the native HUD; everyone else gets a boss bar (modded) or
|
||||||
|
console/status output. `/iris pregen status` reports progress on the plugin.
|
||||||
|
|
||||||
|
## Studio and VSCode workspace
|
||||||
|
|
||||||
|
The studio is the pack authoring environment, available on all platforms. Studio worlds are
|
||||||
|
transient — they are deleted on close and purged at startup.
|
||||||
|
|
||||||
|
```
|
||||||
|
/iris studio create <name> [template] scaffold a new pack (default template: example)
|
||||||
|
/iris studio open <pack> [seed] open a temporary studio world for live editing
|
||||||
|
/iris studio vscode [pack] write a .code-workspace with JSON schemas
|
||||||
|
/iris studio update [pack] regenerate the workspace schemas
|
||||||
|
/iris studio close close and discard the studio world
|
||||||
|
```
|
||||||
|
|
||||||
|
As in the quickstart, optional arguments are keyed on the plugin (`seed=1234`) and positional on
|
||||||
|
the mod.
|
||||||
|
|
||||||
|
The generated VSCode workspace wires per-type JSON schemas (dimensions, biomes, regions, objects,
|
||||||
|
loot, entities, snippets) for full autocomplete. Schemas are generated from the server's live
|
||||||
|
registries, so on modded servers block, item, entity, enchantment, and potion-effect completion
|
||||||
|
includes installed mod content (for example `create:brass_ingot`). Editing an open studio's pack
|
||||||
|
files hotloads the changes and regenerates the schemas.
|
||||||
|
|
||||||
|
## Building from source
|
||||||
|
|
||||||
|
Requirements: JDK 25 (set `JAVA_HOME` to it). The Gradle wrapper handles everything else.
|
||||||
|
|
||||||
|
```
|
||||||
|
./gradlew buildAllToOut
|
||||||
|
```
|
||||||
|
|
||||||
|
builds every platform artifact into `dist/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
Iris v<version> [CraftBukkit] <mc>.jar
|
||||||
|
Iris v<version> [Fabric] <mc>+<loader>.jar
|
||||||
|
Iris v<version> [Forge] <mc>+<loader>.jar
|
||||||
|
Iris v<version> [NeoForge] <mc>+<loader>.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
Per-platform tasks: `./gradlew buildBukkit`, `buildFabric`, `buildForge`, `buildNeoforge`. The
|
||||||
|
developer SPI jar (the pure-JVM platform API contract) is built to `spi/build/libs/` by
|
||||||
|
`./gradlew :spi:jar`.
|
||||||
|
|
||||||
|
If you need help compiling as a developer or contributor, ask in the Discord. Do not come to the
|
||||||
|
Discord asking for free copies or a compile tutorial.
|
||||||
|
|
||||||
|
## Maintainer docs
|
||||||
|
|
||||||
|
- [Minecraft version bump checklist](docs/mc-version-bump.md)
|
||||||
|
- [Release checklist](docs/release-checklist.md)
|
||||||
|
|||||||
+438
@@ -0,0 +1,438 @@
|
|||||||
|
package art.arcane.iris.core.nms.v26_2_R1;
|
||||||
|
|
||||||
|
import com.mojang.serialization.MapCodec;
|
||||||
|
import art.arcane.iris.spi.IrisLogging;
|
||||||
|
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.object.IrisBiome;
|
||||||
|
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||||
|
import art.arcane.volmlib.util.collection.KMap;
|
||||||
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
|
import net.minecraft.core.Holder;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.core.RegistryAccess;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import net.minecraft.world.level.biome.BiomeSource;
|
||||||
|
import net.minecraft.world.level.biome.Climate;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.craftbukkit.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.CraftWorld;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class CustomBiomeSource extends BiomeSource {
|
||||||
|
private static final int NOISE_BIOME_CACHE_MAX = 262144;
|
||||||
|
|
||||||
|
private final long seed;
|
||||||
|
private final Engine engine;
|
||||||
|
private final Registry<Biome> biomeCustomRegistry;
|
||||||
|
private final Registry<Biome> biomeRegistry;
|
||||||
|
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||||
|
private final KMap<String, Holder<Biome>> customBiomes;
|
||||||
|
private final Map<Biome, Holder<Biome>> vanillaSpawnBiomes;
|
||||||
|
private final Holder<Biome> fallbackBiome;
|
||||||
|
private final ConcurrentHashMap<Long, Holder<Biome>> noiseBiomeCache = new ConcurrentHashMap<>();
|
||||||
|
private final ConcurrentHashMap<Long, Holder<Biome>> structureBiomeCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public CustomBiomeSource(long seed, Engine engine, World world) {
|
||||||
|
this.engine = engine;
|
||||||
|
this.seed = seed;
|
||||||
|
this.biomeCustomRegistry = registry().lookup(Registries.BIOME).orElse(null);
|
||||||
|
this.biomeRegistry = ((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())).lookup(Registries.BIOME).orElse(null);
|
||||||
|
this.fallbackBiome = resolveFallbackBiome(this.biomeRegistry, this.biomeCustomRegistry);
|
||||||
|
this.customBiomes = fillCustomBiomes(this.biomeCustomRegistry, engine, this.fallbackBiome);
|
||||||
|
this.vanillaSpawnBiomes = fillVanillaSpawnBiomes(this.biomeCustomRegistry, this.biomeRegistry, engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Holder<Biome>> getAllBiomes(Registry<Biome> customRegistry, Registry<Biome> registry, Engine engine, Holder<Biome> fallback) {
|
||||||
|
LinkedHashSet<Holder<Biome>> biomes = new LinkedHashSet<>();
|
||||||
|
if (fallback != null) {
|
||||||
|
biomes.add(fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (IrisBiome i : engine.getAllBiomes()) {
|
||||||
|
Holder<Biome> vanillaHolder = NMSBinding.biomeToBiomeBase(registry, i.getVanillaDerivative());
|
||||||
|
if (vanillaHolder != null) {
|
||||||
|
biomes.add(vanillaHolder);
|
||||||
|
} else if (!i.isCustom() && fallback != null) {
|
||||||
|
biomes.add(fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i.isCustom()) {
|
||||||
|
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||||
|
Holder<Biome> customHolder = resolveCustomBiomeHolder(customRegistry, engine, j.getId());
|
||||||
|
if (customHolder != null) {
|
||||||
|
biomes.add(customHolder);
|
||||||
|
} else if (fallback != null) {
|
||||||
|
biomes.add(fallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList<>(biomes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object getFor(Class<?> type, Object source) {
|
||||||
|
Object o = fieldFor(type, source);
|
||||||
|
|
||||||
|
if (o != null) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
return invokeFor(type, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object fieldFor(Class<?> returns, Object in) {
|
||||||
|
return fieldForClass(returns, in.getClass(), in);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object invokeFor(Class<?> returns, Object in) {
|
||||||
|
for (Method i : in.getClass().getMethods()) {
|
||||||
|
if (i.getReturnType().equals(returns)) {
|
||||||
|
i.setAccessible(true);
|
||||||
|
try {
|
||||||
|
IrisLogging.debug("[NMS] Found " + returns.getSimpleName() + " in " + in.getClass().getSimpleName() + "." + i.getName() + "()");
|
||||||
|
return i.invoke(in);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T> T fieldForClass(Class<T> returnType, Class<?> sourceType, Object in) {
|
||||||
|
for (Field i : sourceType.getDeclaredFields()) {
|
||||||
|
if (i.getType().equals(returnType)) {
|
||||||
|
i.setAccessible(true);
|
||||||
|
try {
|
||||||
|
IrisLogging.debug("[NMS] Found " + returnType.getSimpleName() + " in " + sourceType.getSimpleName() + "." + i.getName());
|
||||||
|
return (T) i.get(in);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Stream<Holder<Biome>> collectPossibleBiomes() {
|
||||||
|
return getAllBiomes(
|
||||||
|
((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()))
|
||||||
|
.lookup(Registries.BIOME).orElse(null),
|
||||||
|
((CraftWorld) engine.getWorld().realWorld()).getHandle().registryAccess().lookup(Registries.BIOME).orElse(null),
|
||||||
|
engine,
|
||||||
|
fallbackBiome).stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private KMap<String, Holder<Biome>> fillCustomBiomes(Registry<Biome> customRegistry, Engine engine, Holder<Biome> fallback) {
|
||||||
|
KMap<String, Holder<Biome>> m = new KMap<>();
|
||||||
|
if (customRegistry == null) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (IrisBiome i : engine.getAllBiomes()) {
|
||||||
|
if (i.isCustom()) {
|
||||||
|
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||||
|
Holder<Biome> holder = resolveCustomBiomeHolder(customRegistry, engine, j.getId());
|
||||||
|
if (holder == null) {
|
||||||
|
if (fallback != null) {
|
||||||
|
m.put(j.getId(), fallback);
|
||||||
|
}
|
||||||
|
IrisLogging.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m.put(j.getId(), holder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Biome, Holder<Biome>> fillVanillaSpawnBiomes(Registry<Biome> customRegistry, Registry<Biome> registry, Engine engine) {
|
||||||
|
IdentityHashMap<Biome, Holder<Biome>> spawnBiomes = new IdentityHashMap<>();
|
||||||
|
if (customRegistry == null || registry == null) {
|
||||||
|
return Collections.unmodifiableMap(spawnBiomes);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (IrisBiome irisBiome : engine.getAllBiomes()) {
|
||||||
|
if (!irisBiome.isCustom()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Holder<Biome> vanillaHolder = NMSBinding.biomeToBiomeBase(registry, irisBiome.getVanillaDerivative());
|
||||||
|
if (vanillaHolder == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
|
||||||
|
Holder<Biome> customHolder = resolveCustomBiomeHolder(customRegistry, engine, customBiome.getId());
|
||||||
|
if (customHolder != null) {
|
||||||
|
spawnBiomes.putIfAbsent(customHolder.value(), vanillaHolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableMap(spawnBiomes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Holder<Biome> getVanillaSpawnBiome(Holder<Biome> biome) {
|
||||||
|
if (biome == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return vanillaSpawnBiomes.get(biome.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
private RegistryAccess registry() {
|
||||||
|
return registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MapCodec<? extends BiomeSource> codec() {
|
||||||
|
throw new UnsupportedOperationException("Not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Holder<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
|
||||||
|
long cacheKey = packNoiseKey(x, y, z);
|
||||||
|
Holder<Biome> cachedHolder = structureBiomeCache.get(cacheKey);
|
||||||
|
if (cachedHolder != null) {
|
||||||
|
return cachedHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
Holder<Biome> resolvedHolder = resolveStructureBiomeHolder(x, y, z);
|
||||||
|
Holder<Biome> existingHolder = structureBiomeCache.putIfAbsent(cacheKey, resolvedHolder);
|
||||||
|
if (existingHolder != null) {
|
||||||
|
return existingHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (structureBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
|
||||||
|
structureBiomeCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Holder<Biome> getVisibleNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
|
||||||
|
long cacheKey = packNoiseKey(x, y, z);
|
||||||
|
Holder<Biome> cachedHolder = noiseBiomeCache.get(cacheKey);
|
||||||
|
if (cachedHolder != null) {
|
||||||
|
return cachedHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
Holder<Biome> resolvedHolder = resolveVisibleBiomeHolder(x, y, z);
|
||||||
|
Holder<Biome> existingHolder = noiseBiomeCache.putIfAbsent(cacheKey, resolvedHolder);
|
||||||
|
if (existingHolder != null) {
|
||||||
|
return existingHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noiseBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
|
||||||
|
noiseBiomeCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Holder<Biome> resolveStructureBiomeHolder(int x, int y, int z) {
|
||||||
|
BiomeResolution resolution = resolveBiomeResolution(x, y, z);
|
||||||
|
if (resolution == null) {
|
||||||
|
return getFallbackBiome();
|
||||||
|
}
|
||||||
|
|
||||||
|
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, resolution.irisBiome.getVanillaDerivative());
|
||||||
|
if (holder != null) {
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolution.irisBiome.isCustom()) {
|
||||||
|
return resolveCustomHolder(resolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getFallbackBiome();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Holder<Biome> resolveVisibleBiomeHolder(int x, int y, int z) {
|
||||||
|
BiomeResolution resolution = resolveBiomeResolution(x, y, z);
|
||||||
|
if (resolution == null) {
|
||||||
|
return getFallbackBiome();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolution.irisBiome.isCustom()) {
|
||||||
|
return resolveCustomHolder(resolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
org.bukkit.block.Biome vanillaBiome = resolution.underground
|
||||||
|
? resolution.irisBiome.getGroundBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ)
|
||||||
|
: resolution.irisBiome.getSkyBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
|
||||||
|
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, vanillaBiome);
|
||||||
|
if (holder != null) {
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getFallbackBiome();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Holder<Biome> resolveCustomHolder(BiomeResolution resolution) {
|
||||||
|
IrisBiomeCustom customBiome = resolution.irisBiome.getCustomBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
|
||||||
|
if (customBiome != null) {
|
||||||
|
Holder<Biome> holder = customBiomes.get(customBiome.getId());
|
||||||
|
if (holder != null) {
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getFallbackBiome();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BiomeResolution resolveBiomeResolution(int x, int y, int z) {
|
||||||
|
if (engine == null || engine.isClosed()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (engine.getComplex() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int blockX = x << 2;
|
||||||
|
int blockZ = z << 2;
|
||||||
|
int blockY = y << 2;
|
||||||
|
int worldMinHeight = engine.getWorld().minHeight();
|
||||||
|
int internalY = blockY - worldMinHeight;
|
||||||
|
int surfaceInternalY = engine.getComplex().getHeightStream().get(blockX, blockZ).intValue();
|
||||||
|
int caveSwitchInternalY = Math.max(-8 - worldMinHeight, 40);
|
||||||
|
boolean deepUnderground = internalY <= caveSwitchInternalY;
|
||||||
|
boolean belowSurface = internalY <= surfaceInternalY - 8;
|
||||||
|
boolean underground = deepUnderground && belowSurface;
|
||||||
|
IrisBiome irisBiome = underground
|
||||||
|
? engine.getCaveBiome(blockX, internalY, blockZ)
|
||||||
|
: engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||||
|
if (irisBiome == null && underground) {
|
||||||
|
irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||||
|
}
|
||||||
|
if (irisBiome == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
RNG noiseRng = new RNG(seed
|
||||||
|
^ (((long) blockX) * 341873128712L)
|
||||||
|
^ (((long) blockY) * 132897987541L)
|
||||||
|
^ (((long) blockZ) * 42317861L));
|
||||||
|
|
||||||
|
return new BiomeResolution(irisBiome, underground, blockX, blockY, blockZ, noiseRng);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Holder<Biome> getFallbackBiome() {
|
||||||
|
if (fallbackBiome != null) {
|
||||||
|
return fallbackBiome;
|
||||||
|
}
|
||||||
|
|
||||||
|
Holder<Biome> holder = resolveFallbackBiome(biomeRegistry, biomeCustomRegistry);
|
||||||
|
if (holder != null) {
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException("Unable to resolve any biome holder fallback for Iris biome source");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long packNoiseKey(int x, int y, int z) {
|
||||||
|
return (((long) x & 67108863L) << 38)
|
||||||
|
| (((long) z & 67108863L) << 12)
|
||||||
|
| ((long) y & 4095L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Holder<Biome> resolveCustomBiomeHolder(Registry<Biome> customRegistry, Engine engine, String customBiomeId) {
|
||||||
|
if (customRegistry == null || engine == null || customBiomeId == null || customBiomeId.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Identifier resourceLocation = Identifier.fromNamespaceAndPath(
|
||||||
|
engine.getDimension().getLoadKey().toLowerCase(java.util.Locale.ROOT),
|
||||||
|
customBiomeId.toLowerCase(java.util.Locale.ROOT)
|
||||||
|
);
|
||||||
|
Biome biome = customRegistry.getValue(resourceLocation);
|
||||||
|
if (biome == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
|
||||||
|
if (optionalBiomeKey.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<Holder.Reference<Biome>> optionalReferenceHolder = customRegistry.get(optionalBiomeKey.get());
|
||||||
|
if (optionalReferenceHolder.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return optionalReferenceHolder.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Holder<Biome> resolveFallbackBiome(Registry<Biome> registry, Registry<Biome> customRegistry) {
|
||||||
|
Holder<Biome> plains = NMSBinding.biomeToBiomeBase(registry, org.bukkit.block.Biome.PLAINS);
|
||||||
|
if (plains != null) {
|
||||||
|
return plains;
|
||||||
|
}
|
||||||
|
|
||||||
|
Holder<Biome> vanilla = firstHolder(registry);
|
||||||
|
if (vanilla != null) {
|
||||||
|
return vanilla;
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstHolder(customRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Holder<Biome> firstHolder(Registry<Biome> registry) {
|
||||||
|
if (registry == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Biome biome : registry) {
|
||||||
|
Optional<ResourceKey<Biome>> optionalBiomeKey = registry.getResourceKey(biome);
|
||||||
|
if (optionalBiomeKey.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<Holder.Reference<Biome>> optionalHolder = registry.get(optionalBiomeKey.get());
|
||||||
|
if (optionalHolder.isPresent()) {
|
||||||
|
return optionalHolder.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class BiomeResolution {
|
||||||
|
private final IrisBiome irisBiome;
|
||||||
|
private final boolean underground;
|
||||||
|
private final int blockX;
|
||||||
|
private final int blockY;
|
||||||
|
private final int blockZ;
|
||||||
|
private final RNG rng;
|
||||||
|
|
||||||
|
private BiomeResolution(IrisBiome irisBiome, boolean underground, int blockX, int blockY, int blockZ, RNG rng) {
|
||||||
|
this.irisBiome = irisBiome;
|
||||||
|
this.underground = underground;
|
||||||
|
this.blockX = blockX;
|
||||||
|
this.blockY = blockY;
|
||||||
|
this.blockZ = blockZ;
|
||||||
|
this.rng = rng;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+478
@@ -0,0 +1,478 @@
|
|||||||
|
package art.arcane.iris.core.nms.v26_2_R1;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Holder;
|
||||||
|
import net.minecraft.core.HolderLookup;
|
||||||
|
import net.minecraft.core.HolderSet;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.core.RegistryAccess;
|
||||||
|
import net.minecraft.core.SectionPos;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.LevelHeightAccessor;
|
||||||
|
import net.minecraft.world.level.NoiseColumn;
|
||||||
|
import net.minecraft.world.level.StructureManager;
|
||||||
|
import net.minecraft.world.level.WorldGenLevel;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import net.minecraft.world.level.biome.BiomeManager;
|
||||||
|
import net.minecraft.world.level.biome.BiomeSource;
|
||||||
|
import net.minecraft.world.level.biome.MobSpawnSettings;
|
||||||
|
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||||
|
import net.minecraft.world.level.levelgen.Heightmap;
|
||||||
|
import net.minecraft.world.level.levelgen.RandomState;
|
||||||
|
import net.minecraft.world.level.levelgen.RandomSupport;
|
||||||
|
import net.minecraft.world.level.levelgen.WorldgenRandom;
|
||||||
|
import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import com.mojang.serialization.MapCodec;
|
||||||
|
import art.arcane.iris.spi.IrisLogging;
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.object.IrisImportedStructureControl;
|
||||||
|
import art.arcane.iris.util.common.reflect.WrappedField;
|
||||||
|
import art.arcane.iris.util.common.reflect.WrappedReturningMethod;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.WorldGenRegion;
|
||||||
|
import net.minecraft.util.random.Weighted;
|
||||||
|
import net.minecraft.util.random.WeightedList;
|
||||||
|
import net.minecraft.world.entity.EntityType;
|
||||||
|
import net.minecraft.world.entity.MobCategory;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||||
|
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||||
|
import net.minecraft.world.level.levelgen.blending.Blender;
|
||||||
|
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||||
|
import net.minecraft.world.level.levelgen.structure.StructureSet;
|
||||||
|
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||||
|
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||||
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.craftbukkit.CraftWorld;
|
||||||
|
import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
|
||||||
|
import org.spigotmc.SpigotWorldConfig;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||||
|
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
|
||||||
|
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
|
||||||
|
private final ChunkGenerator delegate;
|
||||||
|
private final Engine engine;
|
||||||
|
private final CustomBiomeSource customBiomeSource;
|
||||||
|
private final ConcurrentHashMap<SpawnTableKey, WeightedList<MobSpawnSettings.SpawnerData>> mergedSpawnTables = new ConcurrentHashMap<>();
|
||||||
|
private volatile Set<String> reachableStructureKeysCache;
|
||||||
|
|
||||||
|
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
|
||||||
|
this(delegate, engine, world, new CustomBiomeSource(seed, engine, world));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisChunkGenerator(ChunkGenerator delegate, Engine engine, World world, CustomBiomeSource customBiomeSource) {
|
||||||
|
super(((CraftWorld) world).getHandle(), edit(delegate, customBiomeSource), world.getGenerator());
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.engine = engine;
|
||||||
|
this.customBiomeSource = customBiomeSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
||||||
|
try {
|
||||||
|
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||||
|
BlockPos best = null;
|
||||||
|
Holder<Structure> bestHolder = null;
|
||||||
|
long bestDist = Long.MAX_VALUE;
|
||||||
|
for (Holder<Structure> holder : holders) {
|
||||||
|
Object id = registry.getKey(holder.value());
|
||||||
|
if (id == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int[] at = IrisStructureLocator.locate(engine, id.toString(), pos.getX(), pos.getZ(), Math.max(1, radius));
|
||||||
|
if (at == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
long dx = (long) at[0] - pos.getX();
|
||||||
|
long dz = (long) at[2] - pos.getZ();
|
||||||
|
long d = dx * dx + dz * dz;
|
||||||
|
if (d < bestDist) {
|
||||||
|
bestDist = d;
|
||||||
|
best = new BlockPos(at[0], at[1], at[2]);
|
||||||
|
bestHolder = holder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best != null) {
|
||||||
|
return Pair.of(best, bestHolder);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
IrisLogging.reportError(e);
|
||||||
|
}
|
||||||
|
if (!importedControl().active()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
HolderSet<Structure> reachable = filterReachableStructures(level, holders);
|
||||||
|
if (reachable == null || reachable.size() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return delegate.findNearestMapStructure(level, reachable, pos, radius, findUnexplored);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
IrisLogging.error("Vanilla structure locate failed near " + pos.getX() + ", " + pos.getZ() + ": " + e);
|
||||||
|
IrisLogging.reportError(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HolderSet<Structure> filterReachableStructures(ServerLevel level, HolderSet<Structure> holders) {
|
||||||
|
Set<String> reachable = reachableStructureKeysCache;
|
||||||
|
if (reachable == null) {
|
||||||
|
reachable = VanillaStructureBiomes.reachableStructureKeys(level, delegate.getBiomeSource());
|
||||||
|
reachableStructureKeysCache = reachable;
|
||||||
|
}
|
||||||
|
if (reachable.isEmpty()) {
|
||||||
|
return holders;
|
||||||
|
}
|
||||||
|
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||||
|
List<Holder<Structure>> kept = new ArrayList<>();
|
||||||
|
for (Holder<Structure> holder : holders) {
|
||||||
|
Object id = registry.getKey(holder.value());
|
||||||
|
if (id != null && reachable.contains(id.toString())) {
|
||||||
|
kept.add(holder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (kept.size() == holders.size()) {
|
||||||
|
return holders;
|
||||||
|
}
|
||||||
|
return HolderSet.direct(kept);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MapCodec<? extends ChunkGenerator> codec() {
|
||||||
|
return MapCodec.unit(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChunkGenerator getDelegate() {
|
||||||
|
if (delegate instanceof CustomChunkGenerator chunkGenerator)
|
||||||
|
return chunkGenerator.getDelegate();
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinY() {
|
||||||
|
return delegate.getMinY();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSeaLevel() {
|
||||||
|
return delegate.getSeaLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager, ResourceKey<Level> levelKey) {
|
||||||
|
if (!importedControl().active()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.createStructures(registryAccess, structureState, structureManager, access, templateManager, levelKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisImportedStructureControl importedControl() {
|
||||||
|
return engine.getDimension().getImportedStructures();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> holderlookup, RandomState randomstate, long i, SpigotWorldConfig conf) {
|
||||||
|
return delegate.createState(holderlookup, randomstate, i, conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createReferences(WorldGenLevel generatoraccessseed, StructureManager structuremanager, ChunkAccess ichunkaccess) {
|
||||||
|
delegate.createReferences(generatoraccessseed, structuremanager, ichunkaccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<ChunkAccess> createBiomes(RandomState randomstate, Blender blender, StructureManager structuremanager, ChunkAccess ichunkaccess) {
|
||||||
|
ichunkaccess.fillBiomesFromNoise(customBiomeSource::getVisibleNoiseBiome, randomstate.sampler());
|
||||||
|
return CompletableFuture.completedFuture(ichunkaccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) {
|
||||||
|
delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess) {
|
||||||
|
delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<ChunkAccess> fillFromNoise(Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) {
|
||||||
|
return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WeightedList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
|
||||||
|
Holder<Biome> vanillaSpawnBiome = customBiomeSource.getVanillaSpawnBiome(holder);
|
||||||
|
if (vanillaSpawnBiome == null) {
|
||||||
|
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
|
||||||
|
}
|
||||||
|
|
||||||
|
WeightedList<MobSpawnSettings.SpawnerData> vanillaSpawns = vanillaSpawnBiome.value().getMobSettings().getMobs(enumcreaturetype);
|
||||||
|
WeightedList<MobSpawnSettings.SpawnerData> resolvedSpawns = delegate.getMobsAt(
|
||||||
|
vanillaSpawnBiome, structuremanager, enumcreaturetype, blockposition);
|
||||||
|
if (resolvedSpawns != vanillaSpawns) {
|
||||||
|
return resolvedSpawns;
|
||||||
|
}
|
||||||
|
|
||||||
|
WeightedList<MobSpawnSettings.SpawnerData> explicitSpawns = holder.value().getMobSettings().getMobs(enumcreaturetype);
|
||||||
|
if (explicitSpawns.isEmpty()) {
|
||||||
|
return vanillaSpawns;
|
||||||
|
}
|
||||||
|
if (vanillaSpawns.isEmpty()) {
|
||||||
|
return explicitSpawns;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpawnTableKey key = new SpawnTableKey(holder.value(), enumcreaturetype);
|
||||||
|
return mergedSpawnTables.computeIfAbsent(key, ignored -> mergeSpawnTables(vanillaSpawns, explicitSpawns));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
|
||||||
|
applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addDebugScreenInfo(List<String> list, RandomState randomstate, BlockPos blockposition) {
|
||||||
|
delegate.addDebugScreenInfo(list, randomstate, blockposition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
|
||||||
|
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
|
||||||
|
if (importedControl().active()) {
|
||||||
|
placeVanillaStructures(generatoraccessseed, ichunkaccess, structuremanager);
|
||||||
|
}
|
||||||
|
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void placeVanillaStructures(WorldGenLevel world, ChunkAccess chunk, StructureManager structureManager) {
|
||||||
|
if (!structureManager.shouldGenerateStructures()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ChunkPos chunkPos = chunk.getPos();
|
||||||
|
SectionPos sectionPos = SectionPos.of(chunkPos, world.getMinSectionY());
|
||||||
|
BlockPos origin = sectionPos.origin();
|
||||||
|
Registry<Structure> registry = world.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||||
|
Map<Integer, List<Structure>> byStep = registry.stream().collect(Collectors.groupingBy(s -> s.step().ordinal()));
|
||||||
|
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
|
||||||
|
long decoSeed = random.setDecorationSeed(world.getSeed(), origin.getX(), origin.getZ());
|
||||||
|
BoundingBox area = writableArea(chunk);
|
||||||
|
int steps = GenerationStep.Decoration.values().length;
|
||||||
|
IrisImportedStructureControl control = importedControl();
|
||||||
|
for (int step = 0; step < steps; step++) {
|
||||||
|
int index = 0;
|
||||||
|
for (Structure structure : byStep.getOrDefault(step, List.of())) {
|
||||||
|
Object id = registry.getKey(structure);
|
||||||
|
String structureId = id == null ? null : id.toString();
|
||||||
|
if (control.shouldGenerate(structureId) && !IrisStructureLocator.suppressesVanilla(engine, structureId)) {
|
||||||
|
random.setFeatureSeed(decoSeed, index, step);
|
||||||
|
int[] offset = control.resolveOffset(structureId, isUndergroundStep(structure.step()));
|
||||||
|
boolean shifted = offset[0] != 0 || offset[1] != 0 || offset[2] != 0;
|
||||||
|
WorldGenLevel target = shifted ? shiftedLevel(world, offset[0], offset[1], offset[2]) : world;
|
||||||
|
BoundingBox placeArea = shifted
|
||||||
|
? new BoundingBox(area.minX() - offset[0], area.minY(), area.minZ() - offset[2], area.maxX() - offset[0], area.maxY(), area.maxZ() - offset[2])
|
||||||
|
: area;
|
||||||
|
try {
|
||||||
|
structureManager.startsForStructure(sectionPos, structure)
|
||||||
|
.forEach(start -> start.placeInChunk(target, structureManager, this, random, placeArea, chunkPos));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
IrisLogging.reportError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isUndergroundStep(GenerationStep.Decoration step) {
|
||||||
|
return step == GenerationStep.Decoration.UNDERGROUND_STRUCTURES
|
||||||
|
|| step == GenerationStep.Decoration.STRONGHOLDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WorldGenLevel shiftedLevel(WorldGenLevel world, int dx, int dy, int dz) {
|
||||||
|
return (WorldGenLevel) Proxy.newProxyInstance(
|
||||||
|
WorldGenLevel.class.getClassLoader(),
|
||||||
|
new Class<?>[]{WorldGenLevel.class},
|
||||||
|
(proxy, method, args) -> {
|
||||||
|
if (args != null) {
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
if (args[i] instanceof BlockPos bp) {
|
||||||
|
args[i] = new BlockPos(bp.getX() + dx, bp.getY() + dy, bp.getZ() + dz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return method.invoke(world, args);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw e.getCause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private BoundingBox writableArea(ChunkAccess chunk) {
|
||||||
|
ChunkPos cp = chunk.getPos();
|
||||||
|
int i = cp.getMinBlockX();
|
||||||
|
int j = cp.getMinBlockZ();
|
||||||
|
int minY = getMinY() + 1;
|
||||||
|
int maxY = getMinY() + engine.getHeight() - 1;
|
||||||
|
return new BoundingBox(i, minY, j, i + 15, maxY, j + 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
|
||||||
|
SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSectionY());
|
||||||
|
BlockPos blockPos = sectionPos.origin();
|
||||||
|
|
||||||
|
Heightmap surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG);
|
||||||
|
Heightmap ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG);
|
||||||
|
Heightmap motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING);
|
||||||
|
Heightmap motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES);
|
||||||
|
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
int wX = x + blockPos.getX();
|
||||||
|
int wZ = z + blockPos.getZ();
|
||||||
|
|
||||||
|
int terrainTop = engine.getHeight(wX, wZ, false) + engine.getMinHeight() + 1;
|
||||||
|
int terrainNoFluid = engine.getHeight(wX, wZ, true) + engine.getMinHeight() + 1;
|
||||||
|
SET_HEIGHT.invoke(ocean, x, z, terrainNoFluid);
|
||||||
|
SET_HEIGHT.invoke(surface, x, z, terrainTop);
|
||||||
|
SET_HEIGHT.invoke(motion, x, z, terrainTop);
|
||||||
|
SET_HEIGHT.invoke(motionNoLeaves, x, z, terrainTop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) {
|
||||||
|
delegate.spawnOriginalMobs(regionlimitedworldaccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WeightedList<MobSpawnSettings.SpawnerData> mergeSpawnTables(
|
||||||
|
WeightedList<MobSpawnSettings.SpawnerData> vanillaSpawns,
|
||||||
|
WeightedList<MobSpawnSettings.SpawnerData> explicitSpawns) {
|
||||||
|
List<Weighted<MobSpawnSettings.SpawnerData>> entries = new ArrayList<>(
|
||||||
|
vanillaSpawns.unwrap().size() + explicitSpawns.unwrap().size());
|
||||||
|
Set<EntityType<?>> explicitTypes = new HashSet<>();
|
||||||
|
for (Weighted<MobSpawnSettings.SpawnerData> entry : explicitSpawns.unwrap()) {
|
||||||
|
explicitTypes.add(entry.value().type());
|
||||||
|
}
|
||||||
|
for (Weighted<MobSpawnSettings.SpawnerData> entry : vanillaSpawns.unwrap()) {
|
||||||
|
if (!explicitTypes.contains(entry.value().type())) {
|
||||||
|
entries.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entries.addAll(explicitSpawns.unwrap());
|
||||||
|
return WeightedList.of(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) {
|
||||||
|
return delegate.getSpawnHeight(levelheightaccessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getGenDepth() {
|
||||||
|
return delegate.getGenDepth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
|
||||||
|
return levelheightaccessor.getMinY() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
|
||||||
|
int block = engine.getHeight(i, j, true);
|
||||||
|
int water = engine.getHeight(i, j, false);
|
||||||
|
BlockState[] column = new BlockState[levelheightaccessor.getHeight()];
|
||||||
|
for (int k = 0; k < column.length; k++) {
|
||||||
|
if (k <= block) column[k] = Blocks.STONE.defaultBlockState();
|
||||||
|
else if (k <= water) column[k] = Blocks.WATER.defaultBlockState();
|
||||||
|
else column[k] = Blocks.AIR.defaultBlockState();
|
||||||
|
}
|
||||||
|
return new NoiseColumn(levelheightaccessor.getMinY(), column);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Identifier> getTypeNameForDataFixer() {
|
||||||
|
return delegate.getTypeNameForDataFixer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate() {
|
||||||
|
delegate.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
Field biomeSource = null;
|
||||||
|
for (Field field : ChunkGenerator.class.getDeclaredFields()) {
|
||||||
|
if (!field.getType().equals(BiomeSource.class))
|
||||||
|
continue;
|
||||||
|
biomeSource = field;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (biomeSource == null)
|
||||||
|
throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!");
|
||||||
|
|
||||||
|
Method setHeight = null;
|
||||||
|
for (Method method : Heightmap.class.getDeclaredMethods()) {
|
||||||
|
var types = method.getParameterTypes();
|
||||||
|
if (types.length != 3 || !Arrays.equals(types, new Class<?>[]{int.class, int.class, int.class})
|
||||||
|
|| !method.getReturnType().equals(void.class))
|
||||||
|
continue;
|
||||||
|
setHeight = method;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (setHeight == null)
|
||||||
|
throw new RuntimeException("Could not find setHeight method in Heightmap!");
|
||||||
|
|
||||||
|
BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName());
|
||||||
|
SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) {
|
||||||
|
try {
|
||||||
|
BIOME_SOURCE.set(generator, source);
|
||||||
|
if (generator instanceof CustomChunkGenerator custom)
|
||||||
|
BIOME_SOURCE.set(custom.getDelegate(), source);
|
||||||
|
|
||||||
|
return generator;
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record SpawnTableKey(Biome biome, MobCategory category) {
|
||||||
|
}
|
||||||
|
}
|
||||||
+1356
File diff suppressed because it is too large
Load Diff
+77
@@ -0,0 +1,77 @@
|
|||||||
|
package art.arcane.iris.core.nms.v26_2_R1;
|
||||||
|
|
||||||
|
import net.minecraft.core.Holder;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.core.RegistryAccess;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import net.minecraft.world.level.biome.BiomeSource;
|
||||||
|
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
final class VanillaStructureBiomes {
|
||||||
|
private VanillaStructureBiomes() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static Set<String> possibleBiomeKeys(BiomeSource source) {
|
||||||
|
Set<String> keys = new LinkedHashSet<>();
|
||||||
|
if (source == null) {
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
for (Holder<Biome> holder : source.possibleBiomes()) {
|
||||||
|
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
|
||||||
|
if (key.isPresent()) {
|
||||||
|
keys.add(key.get().identifier().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Set<String> structureBiomeKeys(RegistryAccess access, String structureKey) {
|
||||||
|
Set<String> keys = new LinkedHashSet<>();
|
||||||
|
if (access == null || structureKey == null || structureKey.isEmpty()) {
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
Registry<Structure> registry = access.lookupOrThrow(Registries.STRUCTURE);
|
||||||
|
Structure structure = registry.getValue(Identifier.parse(structureKey));
|
||||||
|
if (structure == null) {
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
for (Holder<Biome> holder : structure.biomes()) {
|
||||||
|
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
|
||||||
|
if (key.isPresent()) {
|
||||||
|
keys.add(key.get().identifier().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Set<String> reachableStructureKeys(ServerLevel level, BiomeSource source) {
|
||||||
|
Set<String> reachable = new LinkedHashSet<>();
|
||||||
|
if (level == null || source == null) {
|
||||||
|
return reachable;
|
||||||
|
}
|
||||||
|
Set<String> possible = possibleBiomeKeys(source);
|
||||||
|
if (possible.isEmpty()) {
|
||||||
|
return reachable;
|
||||||
|
}
|
||||||
|
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||||
|
for (Map.Entry<ResourceKey<Structure>, Structure> entry : registry.entrySet()) {
|
||||||
|
for (Holder<Biome> holder : entry.getValue().biomes()) {
|
||||||
|
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
|
||||||
|
if (key.isPresent() && possible.contains(key.get().identifier().toString())) {
|
||||||
|
reachable.add(entry.getKey().identifier().toString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
String apiVersion = providers.gradleProperty('minecraftVersion').get()
|
||||||
|
def mainClass = 'art.arcane.iris.Iris'
|
||||||
|
def bootstrapperClass = 'art.arcane.iris.IrisBootstrap'
|
||||||
|
|
||||||
|
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
|
||||||
|
.orElse('com.github.VolmitSoftware:VolmLib:cdf7e8bb880199734dacd386a6616bda799fcde1')
|
||||||
|
.get()
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly(project(':core'))
|
||||||
|
compileOnly(volmLibCoordinate) {
|
||||||
|
transitive = false
|
||||||
|
}
|
||||||
|
compileOnly(libs.paper.api)
|
||||||
|
testImplementation('junit:junit:4.13.2')
|
||||||
|
testImplementation(libs.paper.api)
|
||||||
|
testImplementation(libs.bstats)
|
||||||
|
testImplementation(libs.sentry)
|
||||||
|
testImplementation(project(':core'))
|
||||||
|
testImplementation(volmLibCoordinate) {
|
||||||
|
transitive = false
|
||||||
|
}
|
||||||
|
compileOnly(libs.placeholderApi)
|
||||||
|
compileOnly(libs.multiverseCore)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('processResources').configure {
|
||||||
|
def pluginProperties = [
|
||||||
|
name : rootProject.name,
|
||||||
|
version : rootProject.version,
|
||||||
|
apiVersion: apiVersion,
|
||||||
|
main : mainClass,
|
||||||
|
bootstrapper: bootstrapperClass,
|
||||||
|
]
|
||||||
|
inputs.properties(pluginProperties)
|
||||||
|
filesMatching(['**/paper-plugin.yml', '**/plugin.yml']) {
|
||||||
|
expand(pluginProperties)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('jar', Jar).configure {
|
||||||
|
archiveBaseName.set('iris-bukkit-plugin')
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
|||||||
|
package art.arcane.iris;
|
||||||
|
|
||||||
|
import art.arcane.iris.core.pack.DefaultPackBootstrapProvisioner;
|
||||||
|
import art.arcane.iris.core.pack.DefaultPackBootstrapProvisioner.ProvisionResult;
|
||||||
|
import io.papermc.paper.datapack.Datapack;
|
||||||
|
import io.papermc.paper.datapack.DatapackRegistrar;
|
||||||
|
import io.papermc.paper.datapack.DiscoveredDatapack;
|
||||||
|
import io.papermc.paper.plugin.bootstrap.BootstrapContext;
|
||||||
|
import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
|
||||||
|
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
|
public final class IrisBootstrap implements PluginBootstrap {
|
||||||
|
static final String PACK_ID = "generated";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bootstrap(BootstrapContext context) {
|
||||||
|
ProvisionResult provisioned = provision(context);
|
||||||
|
Path datapackRoot = provisioned.datapackRoot();
|
||||||
|
context.getLogger().info("Iris startup datapack is {} at {}", provisioned.status(), datapackRoot);
|
||||||
|
context.getLifecycleManager().registerEventHandler(LifecycleEvents.DATAPACK_DISCOVERY, event -> {
|
||||||
|
try {
|
||||||
|
discoverPack(event.registrar(), datapackRoot);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Unable to discover the Iris startup datapack at " + datapackRoot, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static DiscoveredDatapack discoverPack(DatapackRegistrar registrar, Path datapackRoot) throws IOException {
|
||||||
|
DiscoveredDatapack datapack = registrar.discoverPack(
|
||||||
|
datapackRoot,
|
||||||
|
PACK_ID,
|
||||||
|
configurer -> configurer
|
||||||
|
.autoEnableOnServerStart(true)
|
||||||
|
.position(true, Datapack.Position.TOP)
|
||||||
|
);
|
||||||
|
if (datapack == null) {
|
||||||
|
throw new IllegalStateException("Paper did not accept the Iris startup datapack at " + datapackRoot);
|
||||||
|
}
|
||||||
|
return datapack;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProvisionResult provision(BootstrapContext context) {
|
||||||
|
try {
|
||||||
|
return DefaultPackBootstrapProvisioner.provision(
|
||||||
|
context.getDataDirectory(),
|
||||||
|
message -> context.getLogger().info(message)
|
||||||
|
);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Unable to provision the Iris startup datapack", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.datapack.DatapackIngestService;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||||
|
import art.arcane.iris.util.common.format.C;
|
||||||
|
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||||
|
import art.arcane.iris.util.common.scheduling.J;
|
||||||
|
import art.arcane.volmlib.util.director.annotations.Director;
|
||||||
|
import art.arcane.volmlib.util.director.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Director(name = "datapack", aliases = {"datapacks", "dp"}, description = "Download & manage external datapack imports (Modrinth)")
|
||||||
|
public class CommandDatapack implements DirectorExecutor {
|
||||||
|
@Director(description = "Download/update every datapack listed in a pack dimension's 'datapackImports' and install it into the world so its structures register like vanilla", aliases = {"pull"})
|
||||||
|
public void ingest(
|
||||||
|
@Param(description = "Restart the server when new datapacks are installed (required for new structures to register and generate)", defaultValue = "false")
|
||||||
|
boolean restart
|
||||||
|
) {
|
||||||
|
VolmitSender sender = sender();
|
||||||
|
sender.sendMessage(C.GRAY + "Starting datapack ingest...");
|
||||||
|
J.a(() -> DatapackIngestService.ingestAll(sender, restart));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "List configured datapack imports and their installed versions", aliases = {"ls"})
|
||||||
|
public void list() {
|
||||||
|
VolmitSender sender = sender();
|
||||||
|
KList<String> configured = DatapackIngestService.collectConfiguredImports();
|
||||||
|
List<DatapackIngestService.Entry> installed = DatapackIngestService.installed();
|
||||||
|
|
||||||
|
sender.sendMessage(C.GREEN + "Configured datapack imports: " + C.WHITE + configured.size());
|
||||||
|
for (String url : configured) {
|
||||||
|
sender.sendMessage(C.GRAY + " - " + C.WHITE + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage(C.GREEN + "Installed datapacks: " + C.WHITE + installed.size());
|
||||||
|
for (DatapackIngestService.Entry entry : installed) {
|
||||||
|
sender.sendMessage(C.GRAY + " - " + C.WHITE + entry.id + C.GRAY + " " + (entry.versionNumber == null ? "?" : entry.versionNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configured.isEmpty()) {
|
||||||
|
sender.sendMessage(C.YELLOW + "Add Modrinth URLs to a dimension's 'datapackImports' list, then run /iris datapack ingest.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Remove an installed datapack by id (also delete its URL from datapackImports to keep it gone)", aliases = {"rm"})
|
||||||
|
public void remove(
|
||||||
|
@Param(description = "The datapack id (folder name) shown by /iris datapack list")
|
||||||
|
String id
|
||||||
|
) {
|
||||||
|
DatapackIngestService.remove(sender(), id);
|
||||||
|
}
|
||||||
|
}
|
||||||
+422
@@ -0,0 +1,422 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.google.gson.JsonObject;
|
||||||
|
import art.arcane.iris.Iris;
|
||||||
|
import art.arcane.iris.core.ServerConfigurator;
|
||||||
|
import art.arcane.iris.core.nms.datapack.DataVersion;
|
||||||
|
import art.arcane.iris.core.runtime.ChunkClearer;
|
||||||
|
import art.arcane.iris.core.runtime.GoldenHashScanner;
|
||||||
|
import art.arcane.iris.core.runtime.InPlaceChunkRegenerator;
|
||||||
|
import art.arcane.iris.core.service.IrisEngineSVC;
|
||||||
|
import art.arcane.iris.core.service.StudioSVC;
|
||||||
|
import art.arcane.iris.core.tools.IrisPackBenchmarking;
|
||||||
|
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||||
|
import art.arcane.iris.engine.IrisEngineMantle;
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.object.IrisDimension;
|
||||||
|
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||||
|
import art.arcane.iris.util.project.context.IrisContext;
|
||||||
|
import art.arcane.iris.util.project.matter.IrisMatterContext;
|
||||||
|
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||||
|
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.io.CountingDataInputStream;
|
||||||
|
import art.arcane.volmlib.util.mantle.runtime.TectonicPlate;
|
||||||
|
import art.arcane.volmlib.util.math.M;
|
||||||
|
import art.arcane.volmlib.util.matter.Matter;
|
||||||
|
import art.arcane.iris.util.nbt.common.mca.MCAFile;
|
||||||
|
import art.arcane.iris.util.nbt.common.mca.MCAUtil;
|
||||||
|
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 java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
@Director(name = "Developer", origin = DirectorOrigin.BOTH, description = "Iris World Manager", aliases = {"dev"})
|
||||||
|
public class CommandDeveloper implements DirectorExecutor {
|
||||||
|
@Director(description = "Get Loaded TectonicPlates Count", origin = DirectorOrigin.BOTH, sync = true)
|
||||||
|
public void EngineStatus() {
|
||||||
|
Iris.service(IrisEngineSVC.class)
|
||||||
|
.engineStatus(sender());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Send a test exception to sentry")
|
||||||
|
public void Sentry() {
|
||||||
|
Engine engine = engine();
|
||||||
|
Exception testException = new Exception("This is a test");
|
||||||
|
if (engine == null) {
|
||||||
|
Iris.reportError(testException);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (IrisContext.Scope scope = IrisContext.open(engine, engine.getGenerationSessionId(), null)) {
|
||||||
|
Iris.reportError(testException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Hash generated block output of a fixed area for determinism/identity testing", origin = DirectorOrigin.BOTH)
|
||||||
|
public void genhash(
|
||||||
|
@Param(description = "The world to hash", contextual = true)
|
||||||
|
World world,
|
||||||
|
@Param(description = "Radius in chunks around the center", defaultValue = "4")
|
||||||
|
int radius,
|
||||||
|
@Param(description = "Center chunk X", defaultValue = "0")
|
||||||
|
int centerX,
|
||||||
|
@Param(description = "Center chunk Z", defaultValue = "0")
|
||||||
|
int centerZ) {
|
||||||
|
if (world == null) {
|
||||||
|
sender().sendMessage(C.RED + "World is null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VolmitSender sender = sender();
|
||||||
|
sender.sendMessage(C.GREEN + "genhash started: " + ((radius * 2 + 1) * (radius * 2 + 1)) + " chunks...");
|
||||||
|
J.a(() -> runGenhash(sender, world, radius, centerX, centerZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runGenhash(VolmitSender sender, World world, int radius, int centerX, int centerZ) {
|
||||||
|
long startMs = M.ms();
|
||||||
|
int minY = world.getMinHeight();
|
||||||
|
int maxY = world.getMaxHeight();
|
||||||
|
long globalHash = 0L;
|
||||||
|
long solidBlocks = 0L;
|
||||||
|
Map<String, Long> histogram = new TreeMap<>();
|
||||||
|
JsonObject chunkHashes = new JsonObject();
|
||||||
|
|
||||||
|
for (int rx = centerX - radius; rx <= centerX + radius; rx++) {
|
||||||
|
for (int rz = centerZ - radius; rz <= centerZ + radius; rz++) {
|
||||||
|
org.bukkit.ChunkSnapshot snapshot;
|
||||||
|
try {
|
||||||
|
org.bukkit.Chunk loaded = art.arcane.iris.platform.bukkit.BukkitPlatform.chunkAtAsync(world, rx, rz, true).get();
|
||||||
|
snapshot = loaded.getChunkSnapshot(false, false, false);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
sender.sendMessage(C.RED + "genhash failed at chunk " + rx + "," + rz + ": " + e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long chunkHash = 0L;
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
int worldX = (rx << 4) + x;
|
||||||
|
int worldZ = (rz << 4) + z;
|
||||||
|
for (int y = minY; y < maxY; y++) {
|
||||||
|
org.bukkit.Material material = snapshot.getBlockType(x, y, z);
|
||||||
|
long positionSeed = ((long) worldX * 0x9E3779B97F4A7C15L)
|
||||||
|
^ ((long) y * 0xC2B2AE3D27D4EB4FL)
|
||||||
|
^ ((long) worldZ * 0x165667B19E3779F9L);
|
||||||
|
long blockHash = genHashMix(positionSeed ^ ((long) (material.ordinal() + 1) * 0xD6E8FEB86659FD93L));
|
||||||
|
chunkHash ^= blockHash;
|
||||||
|
if (material != org.bukkit.Material.AIR
|
||||||
|
&& material != org.bukkit.Material.CAVE_AIR
|
||||||
|
&& material != org.bukkit.Material.VOID_AIR) {
|
||||||
|
histogram.merge(material.name(), 1L, Long::sum);
|
||||||
|
solidBlocks++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
globalHash ^= chunkHash;
|
||||||
|
chunkHashes.addProperty(rx + "," + rz, Long.toHexString(chunkHash));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int side = radius * 2 + 1;
|
||||||
|
JsonObject result = new JsonObject();
|
||||||
|
result.addProperty("global", Long.toHexString(globalHash));
|
||||||
|
result.addProperty("chunks", side * side);
|
||||||
|
result.addProperty("solidBlocks", solidBlocks);
|
||||||
|
result.addProperty("minY", minY);
|
||||||
|
result.addProperty("maxY", maxY);
|
||||||
|
JsonObject hist = new JsonObject();
|
||||||
|
for (Map.Entry<String, Long> entry : histogram.entrySet()) {
|
||||||
|
hist.addProperty(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
result.add("histogram", hist);
|
||||||
|
result.add("chunkHashes", chunkHashes);
|
||||||
|
|
||||||
|
File out = new File(Iris.instance.getDataFolder(), "genhash.json");
|
||||||
|
try {
|
||||||
|
Files.writeString(out.toPath(), result.toString());
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage(C.GREEN + "genhash global=" + C.GOLD + Long.toHexString(globalHash)
|
||||||
|
+ C.GREEN + " chunks=" + (side * side) + " solid=" + solidBlocks
|
||||||
|
+ " in " + Form.duration((long) (M.ms() - startMs), 1));
|
||||||
|
Iris.info("genhash world=" + world.getName() + " global=" + Long.toHexString(globalHash)
|
||||||
|
+ " chunks=" + (side * side) + " solidBlocks=" + solidBlocks + " -> " + out.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long genHashMix(long z) {
|
||||||
|
z = (z ^ (z >>> 30)) * 0xBF58476D1CE4E5B9L;
|
||||||
|
z = (z ^ (z >>> 27)) * 0x94D049BB133111EBL;
|
||||||
|
return z ^ (z >>> 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Update the pack of a world (UNSAFE!)", name = "update-world", aliases = "^world")
|
||||||
|
public void updateWorld(
|
||||||
|
@Param(description = "The world to update", contextual = true)
|
||||||
|
World world,
|
||||||
|
@Param(description = "The pack to install into the world", contextual = true, aliases = "dimension")
|
||||||
|
IrisDimension pack,
|
||||||
|
@Param(description = "Make sure to make a backup & read the warnings first!", defaultValue = "false", aliases = "c")
|
||||||
|
boolean confirm,
|
||||||
|
@Param(description = "Should Iris download the pack again for you", defaultValue = "false", name = "fresh-download", aliases = {"fresh", "new"})
|
||||||
|
boolean freshDownload
|
||||||
|
) {
|
||||||
|
if (!confirm) {
|
||||||
|
sender().sendMessage(new String[]{
|
||||||
|
C.RED + "You should always make a backup before using this",
|
||||||
|
C.YELLOW + "Issues caused by this can be, but are not limited to:",
|
||||||
|
C.YELLOW + " - Broken chunks (cut-offs) between old and new chunks (before & after the update)",
|
||||||
|
C.YELLOW + " - Regenerated chunks that do not fit in with the old chunks",
|
||||||
|
C.YELLOW + " - Structures not spawning again when regenerating",
|
||||||
|
C.YELLOW + " - Caves not lining up",
|
||||||
|
C.YELLOW + " - Terrain layers not lining up",
|
||||||
|
C.RED + "Now that you are aware of the risks, and have made a back-up:",
|
||||||
|
C.RED + "/iris developer update-world " + world.getName() + " " + pack.getLoadKey() + " confirm=true"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File folder = world.getWorldFolder();
|
||||||
|
folder.mkdirs();
|
||||||
|
|
||||||
|
if (freshDownload) {
|
||||||
|
Iris.service(StudioSVC.class).downloadSearch(sender(), pack.getLoadKey(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iris.service(StudioSVC.class).installIntoWorld(sender(), pack, folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Test")
|
||||||
|
public void mantle(
|
||||||
|
@Param(name = "plate", description = "Dump the whole tectonic plate instead of a single section", defaultValue = "false")
|
||||||
|
boolean plate,
|
||||||
|
@Param(name = "name", description = "The dump file id under plugins/Iris/dump (pv.<id>.*)", defaultValue = "21474836474")
|
||||||
|
String name
|
||||||
|
) throws Throwable {
|
||||||
|
Engine activeEngine = engine();
|
||||||
|
if (activeEngine == null) {
|
||||||
|
sender().sendMessage(C.RED + "Target an Iris world before reading a mantle dump.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File base = Iris.instance.getDataFile("dump", "pv." + name + ".ttp.lz4b.bin");
|
||||||
|
File section = Iris.instance.getDataFile("dump", "pv." + name + ".section.bin");
|
||||||
|
|
||||||
|
try (IrisMatterContext.Scope scope = IrisMatterContext.open(activeEngine.getData())) {
|
||||||
|
if (plate) {
|
||||||
|
try (CountingDataInputStream in = CountingDataInputStream.wrap(new BufferedInputStream(new FileInputStream(base)))) {
|
||||||
|
TectonicPlate.read(1088, in, true, IrisEngineMantle.createRuntimeDataAdapter(activeEngine.getData()), IrisEngineMantle.createRuntimeHooks());
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Matter.read(section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!TectonicPlate.hasError()) {
|
||||||
|
Iris.info("Read " + (plate ? base : section).length() + " bytes from " + (plate ? base : section).getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Test")
|
||||||
|
public void packBenchmark(
|
||||||
|
@Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld")
|
||||||
|
IrisDimension dimension,
|
||||||
|
@Param(description = "Radius in regions", defaultValue = "2048")
|
||||||
|
int radius,
|
||||||
|
@Param(description = "Open GUI while benchmarking", defaultValue = "false")
|
||||||
|
boolean gui
|
||||||
|
) {
|
||||||
|
new IrisPackBenchmarking(dimension, radius, gui);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Upgrade to another Minecraft version")
|
||||||
|
public void upgrade(
|
||||||
|
@Param(description = "The version to upgrade to", defaultValue = "latest") DataVersion version) {
|
||||||
|
sender().sendMessage(C.GREEN + "Upgrading to " + version.getVersion() + "...");
|
||||||
|
ServerConfigurator.installDataPacks(version.get(), false);
|
||||||
|
sender().sendMessage(C.GREEN + "Done upgrading! You can now update your server version to " + version.getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "test")
|
||||||
|
public void mca (
|
||||||
|
@Param(description = "The world folder to scan for .mca region files") String world) {
|
||||||
|
try {
|
||||||
|
File[] McaFiles = new File(world, "region").listFiles((dir, name) -> name.endsWith(".mca"));
|
||||||
|
for (File mca : McaFiles) {
|
||||||
|
MCAFile MCARegion = MCAUtil.read(mca);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Delete nearby chunk blocks for regen testing", name = "delete-chunk", aliases = {"dc"}, origin = DirectorOrigin.PLAYER, sync = true)
|
||||||
|
public void deleteChunk(
|
||||||
|
@Param(description = "Radius in chunks around your current chunk", defaultValue = "0")
|
||||||
|
int radius
|
||||||
|
) {
|
||||||
|
if (radius < 0) {
|
||||||
|
sender().sendMessage(C.RED + "Radius must be 0 or greater.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
World world = player().getWorld();
|
||||||
|
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||||
|
sender().sendMessage(C.RED + "This is not an Iris world.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformChunkGenerator access = IrisToolbelt.access(world);
|
||||||
|
if (access == null || access.getEngine() == null) {
|
||||||
|
sender().sendMessage(C.RED + "The engine access for this world is null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int centerX = player().getLocation().getBlockX() >> 4;
|
||||||
|
int centerZ = player().getLocation().getBlockZ() >> 4;
|
||||||
|
int chunks = (radius * 2 + 1) * (radius * 2 + 1);
|
||||||
|
|
||||||
|
sender().sendMessage(C.GREEN + "Delete started: " + C.GOLD + chunks + C.GREEN
|
||||||
|
+ " chunk(s) around " + C.GOLD + centerX + "," + centerZ + C.GREEN
|
||||||
|
+ ". Clearing blocks to air.");
|
||||||
|
|
||||||
|
new ChunkClearer(world, access.getEngine(), sender(), centerX, centerZ, radius).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Test", aliases = {"ip"})
|
||||||
|
public void network() {
|
||||||
|
try {
|
||||||
|
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||||
|
for (NetworkInterface ni : Collections.list(networkInterfaces)) {
|
||||||
|
Iris.info("Display Name: %s", ni.getDisplayName());
|
||||||
|
Enumeration<InetAddress> inetAddresses = ni.getInetAddresses();
|
||||||
|
for (InetAddress ia : Collections.list(inetAddresses)) {
|
||||||
|
Iris.info("IP: %s", ia.getHostAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Regen ---
|
||||||
|
|
||||||
|
@Director(name = "regen", aliases = {"rg"}, description = "Delete and regenerate nearby chunks in place using Iris generation", origin = DirectorOrigin.PLAYER, sync = true)
|
||||||
|
public void regen(
|
||||||
|
@Param(name = "radius", description = "The radius of nearby chunks", defaultValue = "5")
|
||||||
|
int radius
|
||||||
|
) {
|
||||||
|
if (radius < 0) {
|
||||||
|
sender().sendMessage(C.RED + "Radius must be 0 or greater.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
World world = player().getWorld();
|
||||||
|
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||||
|
sender().sendMessage(C.RED + "You must be in an Iris world to use regen.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine engine = IrisToolbelt.access(world).getEngine();
|
||||||
|
if (engine == null) {
|
||||||
|
sender().sendMessage(C.RED + "The engine access for this world is null. Generate nearby chunks first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int centerX = player().getLocation().getBlockX() >> 4;
|
||||||
|
int centerZ = player().getLocation().getBlockZ() >> 4;
|
||||||
|
int chunks = (radius * 2 + 1) * (radius * 2 + 1);
|
||||||
|
|
||||||
|
sender().sendMessage(C.GREEN + "Regen started: " + C.GOLD + chunks + C.GREEN
|
||||||
|
+ " chunk(s) around " + C.GOLD + centerX + "," + centerZ + C.GREEN
|
||||||
|
+ ". Deleting and regenerating in place.");
|
||||||
|
Iris.info("Regen run start: world=" + world.getName()
|
||||||
|
+ " center=" + centerX + "," + centerZ
|
||||||
|
+ " radius=" + radius
|
||||||
|
+ " chunks=" + chunks);
|
||||||
|
|
||||||
|
new InPlaceChunkRegenerator(world, engine, sender(), centerX, centerZ, radius).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(name = "goldenhash", aliases = {"gold"}, description = "Generate chunks into buffers (no world writes) and hash blocks+biomes; captures a golden file or verifies against an existing one. Resets mantle in the scanned area - use on disposable test worlds.", origin = DirectorOrigin.BOTH)
|
||||||
|
public void goldenhash(
|
||||||
|
@Param(description = "The world to scan", contextual = true)
|
||||||
|
World world,
|
||||||
|
@Param(name = "radius", description = "Radius in chunks around the center", defaultValue = "8")
|
||||||
|
int radius,
|
||||||
|
@Param(name = "center-x", description = "Center chunk X", defaultValue = "0")
|
||||||
|
int centerX,
|
||||||
|
@Param(name = "center-z", description = "Center chunk Z", defaultValue = "0")
|
||||||
|
int centerZ,
|
||||||
|
@Param(name = "reset-mantle", description = "Delete mantle data in the scan area first for full regeneration from scratch", defaultValue = "true")
|
||||||
|
boolean resetMantle,
|
||||||
|
@Param(name = "threads", description = "Concurrent chunk generations; 1 = strictly serial for order-dependence testing", defaultValue = "8")
|
||||||
|
int threads,
|
||||||
|
@Param(name = "deep", description = "Also dump full per-chunk non-air blockstates for offline diffing", defaultValue = "false")
|
||||||
|
boolean deep
|
||||||
|
) {
|
||||||
|
if (radius < 0) {
|
||||||
|
sender().sendMessage(C.RED + "Radius must be 0 or greater.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (world == null || !IrisToolbelt.isIrisWorld(world)) {
|
||||||
|
sender().sendMessage(C.RED + "Target must be an Iris world.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformChunkGenerator access = IrisToolbelt.access(world);
|
||||||
|
if (access == null || access.getEngine() == null) {
|
||||||
|
sender().sendMessage(C.RED + "The engine access for this world is null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int chunks = (radius * 2 + 1) * (radius * 2 + 1);
|
||||||
|
sender().sendMessage(C.GREEN + "GoldenHash started: " + C.GOLD + chunks + C.GREEN
|
||||||
|
+ " chunk(s) around " + C.GOLD + centerX + "," + centerZ + C.GREEN
|
||||||
|
+ " in buffers (world untouched).");
|
||||||
|
Iris.info("goldenhash start: world=" + world.getName()
|
||||||
|
+ " center=" + centerX + "," + centerZ
|
||||||
|
+ " radius=" + radius
|
||||||
|
+ " chunks=" + chunks);
|
||||||
|
|
||||||
|
new GoldenHashScanner(world, access.getEngine(), sender(), centerX, centerZ, radius, resetMantle, threads, deep).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* 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.IrisBiome;
|
||||||
|
import art.arcane.iris.engine.object.IrisDimension;
|
||||||
|
import art.arcane.iris.engine.object.IrisRegion;
|
||||||
|
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||||
|
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.Desktop;
|
||||||
|
import java.awt.GraphicsEnvironment;
|
||||||
|
|
||||||
|
|
||||||
|
@Director(name = "edit", origin = DirectorOrigin.PLAYER, description = "Edit something")
|
||||||
|
public class CommandEdit implements DirectorExecutor {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* 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.platform.bukkit.BukkitPlatform;
|
||||||
|
import art.arcane.iris.core.service.ObjectStudioSaveService;
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||||
|
import art.arcane.iris.engine.platform.EngineBukkitOps;
|
||||||
|
import art.arcane.iris.engine.framework.StructureReachability;
|
||||||
|
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.director.specialhandlers.StructureHandler;
|
||||||
|
import art.arcane.iris.util.common.format.C;
|
||||||
|
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||||
|
import art.arcane.iris.util.common.scheduling.J;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||||
|
import art.arcane.volmlib.util.director.annotations.Director;
|
||||||
|
import art.arcane.volmlib.util.director.annotations.Param;
|
||||||
|
import io.papermc.paper.registry.RegistryAccess;
|
||||||
|
import io.papermc.paper.registry.RegistryKey;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.Registry;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.generator.structure.Structure;
|
||||||
|
import org.bukkit.util.StructureSearchResult;
|
||||||
|
|
||||||
|
@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,
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
EngineBukkitOps.gotoBiome(e, biome, player(), teleport);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Find a region")
|
||||||
|
public void region(
|
||||||
|
@Param(description = "The region to look for")
|
||||||
|
IrisRegion region,
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
EngineBukkitOps.gotoRegion(e, region, player(), teleport);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Find a point of interest.")
|
||||||
|
public void poi(
|
||||||
|
@Param(description = "The type of PoI to look for.")
|
||||||
|
String type,
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
EngineBukkitOps.gotoPOI(e, type, player(), teleport);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Find a structure (a vanilla key like minecraft:village_plains or minecraft:stronghold, or an imported iris structure key)")
|
||||||
|
public void structure(
|
||||||
|
@Param(description = "The structure to look for (e.g. minecraft:village_plains, minecraft:stronghold, minecraft_ancient_city)", customHandler = StructureHandler.class)
|
||||||
|
String structure
|
||||||
|
) {
|
||||||
|
VolmitSender commandSender = sender();
|
||||||
|
if (commandSender == null) {
|
||||||
|
Iris.reportError("Structure lookup started without a command sender context.", new IllegalStateException("Missing command sender context"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine e = engine();
|
||||||
|
|
||||||
|
if (e == null) {
|
||||||
|
commandSender.sendMessage(C.GOLD + "Not in an Iris World!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IrisStructureLocator.isPlaced(e, structure)) {
|
||||||
|
EngineBukkitOps.gotoStructure(e, structure, player(), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player target = player();
|
||||||
|
if (target == null) {
|
||||||
|
commandSender.sendMessage(C.GOLD + "Run this in-game to teleport to a structure.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
commandSender.sendMessage(C.GRAY + "Locating " + structure + "...");
|
||||||
|
J.s(() -> {
|
||||||
|
try {
|
||||||
|
Registry<Structure> structureRegistry = RegistryAccess.registryAccess().getRegistry(RegistryKey.STRUCTURE);
|
||||||
|
Structure match = null;
|
||||||
|
for (Structure candidate : structureRegistry) {
|
||||||
|
NamespacedKey key = structureRegistry.getKey(candidate);
|
||||||
|
if (key != null && key.toString().equalsIgnoreCase(structure)) {
|
||||||
|
match = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match == null) {
|
||||||
|
commandSender.sendMessage(C.RED + "Unknown structure: " + structure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!StructureReachability.isReachable(e, structure)) {
|
||||||
|
KList<String> miss = StructureReachability.missingBiomeKeys(e, structure);
|
||||||
|
commandSender.sendMessage(C.YELLOW + structure + " cannot generate in this world (its required biomes are not produced by this pack"
|
||||||
|
+ (miss.isEmpty() ? "" : ": needs " + String.join("/", miss)) + ").");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StructureSearchResult result = target.getWorld().locateNearestStructure(target.getLocation(), match, 100, true);
|
||||||
|
if (result == null || result.getLocation() == null) {
|
||||||
|
commandSender.sendMessage(C.YELLOW + "No " + structure + " found within range of you.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Location at = result.getLocation();
|
||||||
|
int y = target.getWorld().getHighestBlockYAt(at.getBlockX(), at.getBlockZ()) + 2;
|
||||||
|
Location dest = new Location(target.getWorld(), at.getBlockX() + 0.5, y, at.getBlockZ() + 0.5);
|
||||||
|
BukkitPlatform.teleportAsync(target, dest);
|
||||||
|
commandSender.sendMessage(C.GREEN + "Teleported to " + structure + " @ " + at.getBlockX() + ", " + at.getBlockZ());
|
||||||
|
} catch (Throwable t) {
|
||||||
|
commandSender.sendMessage(C.RED + "Could not locate " + structure + ": " + t.getClass().getSimpleName());
|
||||||
|
Iris.reportError("Could not locate structure '" + structure + "'.", t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Find an object")
|
||||||
|
public void object(
|
||||||
|
@Param(description = "The object to look for", customHandler = ObjectHandler.class)
|
||||||
|
String object,
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
EngineBukkitOps.gotoObject(e, object, player(), teleport);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sender().sendMessage(C.RED + object + " is not configured in any region/biome object placements.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,642 @@
|
|||||||
|
/*
|
||||||
|
* 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.platform.bukkit.BukkitPlatform;
|
||||||
|
import art.arcane.iris.core.IrisSettings;
|
||||||
|
import art.arcane.iris.core.IrisWorldStorage;
|
||||||
|
import art.arcane.iris.core.IrisWorlds;
|
||||||
|
import art.arcane.iris.core.ServerConfigurator;
|
||||||
|
import art.arcane.iris.core.lifecycle.WorldLifecycleService;
|
||||||
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
|
import art.arcane.iris.core.pack.PackDownloader;
|
||||||
|
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.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.bukkit.WorldIdentity;
|
||||||
|
import art.arcane.volmlib.util.director.DirectorParameterHandler;
|
||||||
|
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||||
|
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.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.Location;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
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 {
|
||||||
|
private CommandStudio studio;
|
||||||
|
private CommandPregen pregen;
|
||||||
|
private CommandObject object;
|
||||||
|
private CommandStructure structure;
|
||||||
|
private CommandWhat what;
|
||||||
|
private CommandEdit edit;
|
||||||
|
private CommandDeveloper developer;
|
||||||
|
private CommandPack pack;
|
||||||
|
private CommandFind find;
|
||||||
|
private CommandDatapack datapack;
|
||||||
|
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
|
||||||
|
) {
|
||||||
|
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 (IrisWorldStorage.dimensionRoot(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 " + resolvedType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (J.isFolia()) {
|
||||||
|
if (stageFoliaWorldCreation(name, dimension, seed, main)) {
|
||||||
|
sender().sendMessage(C.GREEN + "World staging completed. Restart the server to generate/load \"" + name + "\".");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
worldCreation = true;
|
||||||
|
IrisToolbelt.createWorld()
|
||||||
|
.dimension(resolvedType)
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 oldLevelRoot = IrisWorldStorage.levelRoot();
|
||||||
|
File worldContainer = oldLevelRoot.getParentFile();
|
||||||
|
Properties data = ServerProperties.DATA;
|
||||||
|
try (FileInputStream in = new FileInputStream(ServerProperties.SERVER_PROPERTIES)) {
|
||||||
|
data.load(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
File sourceDimensionRoot = IrisWorldStorage.dimensionRoot(IrisWorldStorage.keyFromLegacyName(newName));
|
||||||
|
if (!sourceDimensionRoot.isDirectory()) {
|
||||||
|
throw new IllegalStateException("Source dimension folder does not exist: " + sourceDimensionRoot.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
File newLevelRoot = new File(worldContainer, newName);
|
||||||
|
if (!newLevelRoot.exists() && !newLevelRoot.mkdirs()) {
|
||||||
|
throw new IllegalStateException("Could not create target level folder: " + newLevelRoot.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String sub : List.of("data", "datapacks", "players")) {
|
||||||
|
File source = new File(oldLevelRoot, sub);
|
||||||
|
if (!source.exists()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IO.copyDirectory(source.toPath(), new File(newLevelRoot, sub).toPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
File targetDimensionRoot = IrisWorldStorage.dimensionRoot(newLevelRoot, NamespacedKey.minecraft("overworld"));
|
||||||
|
IO.copyDirectory(sourceDimensionRoot.toPath(), targetDimensionRoot.toPath());
|
||||||
|
|
||||||
|
World sourceWorld = WorldIdentity.resolve(IrisWorldStorage.keyFromLegacyName(newName)).orElse(null);
|
||||||
|
Long stagedSeed = IrisWorlds.readBukkitWorldSeed(newName);
|
||||||
|
if (sourceWorld == null && stagedSeed == null) {
|
||||||
|
throw new IllegalStateException("Cannot determine the promoted world's seed.");
|
||||||
|
}
|
||||||
|
long promotedSeed = sourceWorld == null ? stagedSeed : sourceWorld.getSeed();
|
||||||
|
data.setProperty("level-name", newName);
|
||||||
|
data.setProperty("level-seed", Long.toString(promotedSeed));
|
||||||
|
try (FileOutputStream 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) {
|
||||||
|
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 = IrisWorldStorage.dimensionRoot(name);
|
||||||
|
IrisDimension installed = Iris.service(StudioSVC.class).installIntoWorld(sender(), dimension, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Location spawn = world.getSpawnLocation();
|
||||||
|
final Runnable teleportTask = () -> {
|
||||||
|
BukkitPlatform.teleportAsync(target, spawn);
|
||||||
|
new VolmitSender(target).sendMessage(C.GREEN + "You have been teleported to " + world.getName() + ".");
|
||||||
|
};
|
||||||
|
if (!J.runEntity(target, teleportTask)) {
|
||||||
|
teleportTask.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 = {"rm"}, sync = true)
|
||||||
|
public void remove(
|
||||||
|
@Param(description = "The world to remove")
|
||||||
|
World world,
|
||||||
|
@Param(description = "Whether to also remove the folder (if set to false, just does not load the world)", defaultValue = "true")
|
||||||
|
boolean delete
|
||||||
|
) {
|
||||||
|
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||||
|
sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", 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() {
|
||||||
|
boolean to = !IrisSettings.get().getGeneral().isDebug();
|
||||||
|
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", 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
|
||||||
|
) {
|
||||||
|
if (PackDownloader.isDefaultOverworld(pack)) {
|
||||||
|
sender().sendMessage(C.GREEN + "Downloading pack: " + pack + " (beta release)" + (overwrite ? " overwriting" : ""));
|
||||||
|
Iris.service(StudioSVC.class).downloadDefaultOverworld(sender(), overwrite);
|
||||||
|
} else {
|
||||||
|
sender().sendMessage(C.GREEN + "Downloading pack: " + pack + "/" + branch + (overwrite ? " overwriting" : ""));
|
||||||
|
Iris.service(StudioSVC.class).downloadSearch(sender(), "IrisDimensions/" + pack + "/" + branch, overwrite);
|
||||||
|
}
|
||||||
|
ServerConfigurator.installDataPacksIfChanged(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
) {
|
||||||
|
worldNameToCheck = world;
|
||||||
|
boolean worldExists = doesWorldExist(worldNameToCheck);
|
||||||
|
WorldEngine = world;
|
||||||
|
|
||||||
|
if (!worldExists) {
|
||||||
|
sender().sendMessage(C.YELLOW + world + " Doesnt exist on the server.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File directory = new File(IrisWorldStorage.packRoot(IrisWorldStorage.keyFromLegacyName(world)), "dimensions");
|
||||||
|
|
||||||
|
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 worldDirectory = IrisWorldStorage.dimensionRoot(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);
|
||||||
|
options.add(pack.getName() + ":" + 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+902
@@ -0,0 +1,902 @@
|
|||||||
|
/*
|
||||||
|
* 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.platform.bukkit.BukkitPlatform;
|
||||||
|
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.IObjectPlacer;
|
||||||
|
import art.arcane.iris.engine.object.IrisDimension;
|
||||||
|
import art.arcane.iris.engine.object.IrisObject;
|
||||||
|
import art.arcane.iris.engine.object.IrisObjectPlacement;
|
||||||
|
import art.arcane.iris.engine.object.IrisObjectPlacementScaleInterpolator;
|
||||||
|
import art.arcane.iris.engine.object.IrisObjectRotation;
|
||||||
|
import art.arcane.iris.engine.object.StudioMode;
|
||||||
|
import art.arcane.iris.engine.object.TileData;
|
||||||
|
import art.arcane.iris.platform.bukkit.BukkitBlockState;
|
||||||
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
|
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.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 org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.GameMode;
|
||||||
|
import org.bukkit.HeightMap;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.World;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
@Director(name = "object", aliases = "o", origin = DirectorOrigin.PLAYER, description = "Iris object manipulation")
|
||||||
|
public class CommandObject implements DirectorExecutor {
|
||||||
|
@Director(description = "Open an object studio world (grid of every object; dimension optional, defaults to all packs)", 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, () -> {
|
||||||
|
BukkitPlatform.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);
|
||||||
|
|
||||||
|
public static IObjectPlacer createPlacer(World world, Map<Block, BlockData> futureBlockChanges) {
|
||||||
|
|
||||||
|
return new IObjectPlacer() {
|
||||||
|
@Override
|
||||||
|
public int getHighest(int x, int z, IrisData data) {
|
||||||
|
return world.getHighestBlockYAt(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHighest(int x, int z, IrisData data, boolean ignoreFluid) {
|
||||||
|
return world.getHighestBlockYAt(x, z, ignoreFluid ? HeightMap.OCEAN_FLOOR : HeightMap.MOTION_BLOCKING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(int x, int y, int z, PlatformBlockState s) {
|
||||||
|
BlockData d = (BlockData) s.nativeHandle();
|
||||||
|
Block block = world.getBlockAt(x, y, z);
|
||||||
|
|
||||||
|
//Prevent blocks being set in or bellow bedrock
|
||||||
|
if (y <= world.getMinHeight() || block.getType() == Material.BEDROCK) return;
|
||||||
|
|
||||||
|
futureBlockChanges.put(block, block.getBlockData());
|
||||||
|
|
||||||
|
if (d instanceof IrisCustomData data) {
|
||||||
|
block.setBlockData(data.getBase(), false);
|
||||||
|
Iris.warn("Tried to place custom block at " + x + ", " + y + ", " + z + " which is not supported!");
|
||||||
|
} else block.setBlockData(d, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PlatformBlockState get(int x, int y, int z) {
|
||||||
|
return BukkitBlockState.of(world.getBlockAt(x, y, z).getBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPreventingDecay() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCarved(int x, int y, int z) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSolid(int x, int y, int z) {
|
||||||
|
return world.getBlockAt(x, y, z).getType().isSolid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUnderwater(int x, int z) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFluidHeight() {
|
||||||
|
return 63;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDebugSmartBore() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTile(int xx, int yy, int zz, TileData tile) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@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, data());
|
||||||
|
sender().sendMessage("Object Size: " + o.getW() + " * " + o.getH() + " * " + o.getD() + "");
|
||||||
|
sender().sendMessage("Blocks Used: " + NumberFormat.getIntegerInstance().format(o.getBlocks().size()));
|
||||||
|
|
||||||
|
var queue = o.getBlocks().values();
|
||||||
|
Map<Material, Set<BlockData>> unsorted = new HashMap<>();
|
||||||
|
Map<BlockData, Integer> amounts = new HashMap<>();
|
||||||
|
Map<Material, Integer> materials = new HashMap<>();
|
||||||
|
while (queue.hasNext()) {
|
||||||
|
BlockData block = (BlockData) queue.next().nativeHandle();
|
||||||
|
|
||||||
|
//unsorted.put(block.getMaterial(), block);
|
||||||
|
|
||||||
|
if (!amounts.containsKey(block)) {
|
||||||
|
amounts.put(block, 1);
|
||||||
|
|
||||||
|
|
||||||
|
} else
|
||||||
|
amounts.put(block, amounts.get(block) + 1);
|
||||||
|
|
||||||
|
if (!materials.containsKey(block.getMaterial())) {
|
||||||
|
materials.put(block.getMaterial(), 1);
|
||||||
|
unsorted.put(block.getMaterial(), new HashSet<>());
|
||||||
|
unsorted.get(block.getMaterial()).add(block);
|
||||||
|
} else {
|
||||||
|
materials.put(block.getMaterial(), materials.get(block.getMaterial()) + 1);
|
||||||
|
unsorted.get(block.getMaterial()).add(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Material> sortedMatsList = amounts.keySet().stream().map(BlockData::getMaterial)
|
||||||
|
.sorted().toList();
|
||||||
|
Set<Material> sortedMats = new TreeSet<>(Comparator.comparingInt(materials::get).reversed());
|
||||||
|
sortedMats.addAll(sortedMatsList);
|
||||||
|
sender().sendMessage("== Blocks in object ==");
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
for (Material mat : sortedMats) {
|
||||||
|
int amount = materials.get(mat);
|
||||||
|
List<BlockData> set = new ArrayList<>(unsorted.get(mat));
|
||||||
|
set.sort(Comparator.comparingInt(amounts::get).reversed());
|
||||||
|
BlockData data = set.get(0);
|
||||||
|
int dataAmount = amounts.get(data);
|
||||||
|
|
||||||
|
String string = " - " + mat.toString() + "*" + amount;
|
||||||
|
if (data.getAsString(true).contains("[")) {
|
||||||
|
string = string + " --> [" + data.getAsString(true).split("\\[")[1]
|
||||||
|
.replaceAll("true", ChatColor.GREEN + "true" + ChatColor.GRAY)
|
||||||
|
.replaceAll("false", ChatColor.RED + "false" + ChatColor.GRAY) + "*" + dataAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
sender().sendMessage(string);
|
||||||
|
|
||||||
|
n++;
|
||||||
|
|
||||||
|
if (n >= 10) {
|
||||||
|
sender().sendMessage(" + " + (sortedMats.size() - n) + " other block types");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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, 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());
|
||||||
|
try {
|
||||||
|
o.write(o.getLoadFile());
|
||||||
|
} catch (IOException e) {
|
||||||
|
sender().sendMessage("Failed to save object " + o.getLoadFile() + ": " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Make tree leaves vanilla-decay-plausible (every leaf within 6 blocks of a log)",
|
||||||
|
origin = DirectorOrigin.BOTH)
|
||||||
|
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());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Get a powder that reveals objects", aliases = "d")
|
||||||
|
public void dust() {
|
||||||
|
player().getInventory().addItem(WandSVC.createDust());
|
||||||
|
sender().playSound(Sound.AMBIENT_SOUL_SAND_VALLEY_ADDITIONS, 1f, 1.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
) {
|
||||||
|
if (!WandSVC.isHoldingWand(player())) {
|
||||||
|
sender().sendMessage("Hold your wand.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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();
|
||||||
|
Cuboid cursor = new Cuboid(a1, a2);
|
||||||
|
Direction d = Direction.closest(player().getLocation().getDirection()).reverse();
|
||||||
|
assert d != null;
|
||||||
|
cursor = cursor.expand(d.f(), -amount);
|
||||||
|
b[0] = cursor.getLowerNE();
|
||||||
|
b[1] = cursor.getUpperSW();
|
||||||
|
player().getInventory().setItemInMainHand(WandSVC.createWand(b[0], b[1]));
|
||||||
|
player().updateInventory();
|
||||||
|
sender().playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
) {
|
||||||
|
if (!WandSVC.isHoldingWand(player())) {
|
||||||
|
sender().sendMessage("Ready your Wand.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WandSVC.isHoldingWand(player())) {
|
||||||
|
Location[] g = WandSVC.getCuboid(player());
|
||||||
|
|
||||||
|
if (g == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!here) {
|
||||||
|
// TODO: WARNING HEIGHT
|
||||||
|
g[1] = player().getTargetBlock(null, 256).getLocation().clone();
|
||||||
|
} else {
|
||||||
|
g[1] = player().getLocation().getBlock().getLocation().clone().add(0, -1, 0);
|
||||||
|
}
|
||||||
|
player().getInventory().setItemInMainHand(WandSVC.createWand(g[0], g[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
) {
|
||||||
|
if (!WandSVC.isHoldingWand(player())) {
|
||||||
|
sender().sendMessage("Ready your Wand.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WandSVC.isHoldingIrisWand(player())) {
|
||||||
|
Location[] g = WandSVC.getCuboid(player());
|
||||||
|
|
||||||
|
if (g == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!here) {
|
||||||
|
// TODO: WARNING HEIGHT
|
||||||
|
g[0] = player().getTargetBlock(null, 256).getLocation().clone();
|
||||||
|
} else {
|
||||||
|
g[0] = player().getLocation().getBlock().getLocation().clone().add(0, -1, 0);
|
||||||
|
}
|
||||||
|
player().getInventory().setItemInMainHand(WandSVC.createWand(g[0], g[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Paste an object", sync = true)
|
||||||
|
public void paste(
|
||||||
|
@Param(description = "The object to paste", customHandler = ObjectHandler.class)
|
||||||
|
String object,
|
||||||
|
@Param(description = "Whether or not to edit the object (need to hold wand)", defaultValue = "false")
|
||||||
|
boolean edit,
|
||||||
|
@Param(description = "The amount of degrees to rotate by", defaultValue = "0")
|
||||||
|
int rotate,
|
||||||
|
@Param(description = "The factor by which to scale the object placement", defaultValue = "1")
|
||||||
|
double scale
|
||||||
|
// ,
|
||||||
|
// @Param(description = "The scale interpolator to use", defaultValue = "none")
|
||||||
|
// IrisObjectPlacementScaleInterpolator interpolator
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
scale = maxScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
sender().playSound(Sound.BLOCK_ENCHANTMENT_TABLE_USE, 1f, 1.5f);
|
||||||
|
|
||||||
|
IrisObjectPlacement placement = new IrisObjectPlacement();
|
||||||
|
placement.setRotation(IrisObjectRotation.of(0, rotate, 0));
|
||||||
|
|
||||||
|
ItemStack wand = player().getInventory().getItemInMainHand();
|
||||||
|
Location block = player().getTargetBlock(skipBlocks, 256).getLocation().clone().add(0, 1, 0);
|
||||||
|
|
||||||
|
Map<Block, BlockData> futureChanges = new HashMap<>();
|
||||||
|
|
||||||
|
if (scale != 1) {
|
||||||
|
o = o.scaled(scale, IrisObjectPlacementScaleInterpolator.TRICUBIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
o.place(block.getBlockX(), block.getBlockY() + (int) o.getCenter().getY(), block.getBlockZ(), createPlacer(block.getWorld(), futureChanges), placement, new RNG(), null);
|
||||||
|
|
||||||
|
Iris.service(ObjectSVC.class).addChanges(futureChanges);
|
||||||
|
|
||||||
|
if (edit) {
|
||||||
|
Vector center = new Vector(o.getCenter().getX(), o.getCenter().getY(), o.getCenter().getZ());
|
||||||
|
ItemStack newWand = WandSVC.createWand(block.clone().subtract(center).add(o.getW() - 1,
|
||||||
|
o.getH() + center.getY() - 1, o.getD() - 1), block.clone().subtract(center.clone().setY(0)));
|
||||||
|
if (WandSVC.isWand(wand)) {
|
||||||
|
wand = newWand;
|
||||||
|
player().getInventory().setItemInMainHand(wand);
|
||||||
|
sender().sendMessage("Updated wand for " + "objects/" + o.getLoadKey() + ".iob ");
|
||||||
|
} else {
|
||||||
|
int slot = WandSVC.findWand(player().getInventory());
|
||||||
|
if (slot == -1) {
|
||||||
|
player().getInventory().addItem(newWand);
|
||||||
|
sender().sendMessage("Given new wand for " + "objects/" + o.getLoadKey() + ".iob ");
|
||||||
|
} else {
|
||||||
|
player().getInventory().setItem(slot, newWand);
|
||||||
|
sender().sendMessage("Updated wand for " + "objects/" + o.getLoadKey() + ".iob ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender().sendMessage(C.IRIS + "Placed " + object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Save an object")
|
||||||
|
public void save(
|
||||||
|
@Param(description = "The dimension to store the object in", contextual = true)
|
||||||
|
IrisDimension dimension,
|
||||||
|
@Param(description = "The file to store it in, can use / for subfolders")
|
||||||
|
String name,
|
||||||
|
@Param(description = "Overwrite existing object files", defaultValue = "false", aliases = "force")
|
||||||
|
boolean overwrite,
|
||||||
|
@Param(description = "Use legacy TileState serialization if possible", defaultValue = "true")
|
||||||
|
boolean legacy
|
||||||
|
) {
|
||||||
|
IrisObject o = WandSVC.createSchematic(player(), legacy);
|
||||||
|
|
||||||
|
if (o == null) {
|
||||||
|
sender().sendMessage(C.YELLOW + "You need to hold your wand!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File file = Iris.service(StudioSVC.class).getWorkspaceFile(dimension.getLoadKey(), "objects", name + ".iob");
|
||||||
|
|
||||||
|
if (file.exists() && !overwrite) {
|
||||||
|
sender().sendMessage(C.RED + "File already exists. Set overwrite=true to overwrite it.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
o.write(file, sender());
|
||||||
|
} catch (IOException e) {
|
||||||
|
sender().sendMessage(C.RED + "Failed to save object because of an IOException: " + e.getMessage());
|
||||||
|
Iris.reportError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
sender().playSound(Sound.BLOCK_ENCHANTMENT_TABLE_USE, 1f, 1.5f);
|
||||||
|
sender().sendMessage(C.GREEN + "Successfully object to saved: " + dimension.getLoadKey() + "/objects/" + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Shift a selection in your looking direction")
|
||||||
|
public void shift(
|
||||||
|
@Param(description = "The amount to shift by", defaultValue = "1")
|
||||||
|
int amount
|
||||||
|
) {
|
||||||
|
if (!WandSVC.isHoldingWand(player())) {
|
||||||
|
sender().sendMessage("Hold your wand.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
if (d == null) {
|
||||||
|
return; // HOW DID THIS HAPPEN
|
||||||
|
}
|
||||||
|
a1.add(d.toVector().multiply(amount));
|
||||||
|
a2.add(d.toVector().multiply(amount));
|
||||||
|
Cuboid cursor = new Cuboid(a1, a2);
|
||||||
|
b[0] = cursor.getLowerNE();
|
||||||
|
b[1] = cursor.getUpperSW();
|
||||||
|
player().getInventory().setItemInMainHand(WandSVC.createWand(b[0], b[1]));
|
||||||
|
player().updateInventory();
|
||||||
|
sender().playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Undo a number of pastes", aliases = "u")
|
||||||
|
public void undo(
|
||||||
|
@Param(description = "The amount of pastes to undo", defaultValue = "1")
|
||||||
|
int amount
|
||||||
|
) {
|
||||||
|
ObjectSVC service = Iris.service(ObjectSVC.class);
|
||||||
|
int actualReverts = Math.min(service.getUndos().size(), amount);
|
||||||
|
service.revertChanges(actualReverts);
|
||||||
|
sender().sendMessage(C.BLUE + "Reverted " + actualReverts + C.BLUE +" pastes!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Gets an object wand and grabs the current WorldEdit selection.", aliases = "we", origin = DirectorOrigin.PLAYER)
|
||||||
|
public void we() {
|
||||||
|
if (!Bukkit.getPluginManager().isPluginEnabled("WorldEdit")) {
|
||||||
|
sender().sendMessage(C.RED + "You can't get a WorldEdit selection without WorldEdit, you know.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cuboid locs = WorldEditLink.getSelection(sender().player());
|
||||||
|
|
||||||
|
if (locs == null) {
|
||||||
|
sender().sendMessage(C.RED + "You don't have a WorldEdit selection in this world.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sender().player().getInventory().addItem(WandSVC.createWand(locs.getLowerNE(), locs.getUpperSW()));
|
||||||
|
sender().sendMessage(C.GREEN + "A fresh wand with your current WorldEdit selection on it!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@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!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@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!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
Location a2x = b[1].clone();
|
||||||
|
Cuboid cursor = new Cuboid(a1, a2);
|
||||||
|
Cuboid cursorx = new Cuboid(a1, a2);
|
||||||
|
|
||||||
|
while (!cursor.containsOnly(Material.AIR)) {
|
||||||
|
a1.add(new org.bukkit.util.Vector(0, 1, 0));
|
||||||
|
a2.add(new org.bukkit.util.Vector(0, 1, 0));
|
||||||
|
cursor = new Cuboid(a1, a2);
|
||||||
|
}
|
||||||
|
|
||||||
|
a1.add(new org.bukkit.util.Vector(0, -1, 0));
|
||||||
|
a2.add(new org.bukkit.util.Vector(0, -1, 0));
|
||||||
|
|
||||||
|
while (!cursorx.containsOnly(Material.AIR)) {
|
||||||
|
a1x.add(new org.bukkit.util.Vector(0, -1, 0));
|
||||||
|
a2x.add(new org.bukkit.util.Vector(0, -1, 0));
|
||||||
|
cursorx = new Cuboid(a1x, a2x);
|
||||||
|
}
|
||||||
|
|
||||||
|
a1x.add(new org.bukkit.util.Vector(0, 1, 0));
|
||||||
|
a2x.add(new Vector(0, 1, 0));
|
||||||
|
b[0] = a1;
|
||||||
|
b[1] = a2x;
|
||||||
|
cursor = new Cuboid(b[0], b[1]);
|
||||||
|
cursor = cursor.contract(Cuboid.CuboidDirection.North);
|
||||||
|
cursor = cursor.contract(Cuboid.CuboidDirection.South);
|
||||||
|
cursor = cursor.contract(Cuboid.CuboidDirection.East);
|
||||||
|
cursor = cursor.contract(Cuboid.CuboidDirection.West);
|
||||||
|
b[0] = cursor.getLowerNE();
|
||||||
|
b[1] = cursor.getUpperSW();
|
||||||
|
player().getInventory().setItemInMainHand(WandSVC.createWand(b[0], b[1]));
|
||||||
|
player().updateInventory();
|
||||||
|
sender().playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f);
|
||||||
|
sender().sendMessage(C.GREEN + "Auto-select complete!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(name = "x+y", description = "Autoselect up & out", sync = true)
|
||||||
|
public void xpy() {
|
||||||
|
if (!WandSVC.isHoldingWand(player())) {
|
||||||
|
sender().sendMessage(C.YELLOW + "Hold your wand!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
Location a2 = b[1].clone();
|
||||||
|
Cuboid cursor = new Cuboid(a1, a2);
|
||||||
|
|
||||||
|
while (!cursor.containsOnly(Material.AIR)) {
|
||||||
|
a1.add(new Vector(0, 1, 0));
|
||||||
|
a2.add(new Vector(0, 1, 0));
|
||||||
|
cursor = new Cuboid(a1, a2);
|
||||||
|
}
|
||||||
|
|
||||||
|
a1.add(new Vector(0, -1, 0));
|
||||||
|
a2.add(new Vector(0, -1, 0));
|
||||||
|
b[0] = a1;
|
||||||
|
a2 = b[1];
|
||||||
|
cursor = new Cuboid(a1, a2);
|
||||||
|
cursor = cursor.contract(Cuboid.CuboidDirection.North);
|
||||||
|
cursor = cursor.contract(Cuboid.CuboidDirection.South);
|
||||||
|
cursor = cursor.contract(Cuboid.CuboidDirection.East);
|
||||||
|
cursor = cursor.contract(Cuboid.CuboidDirection.West);
|
||||||
|
b[0] = cursor.getLowerNE();
|
||||||
|
b[1] = cursor.getUpperSW();
|
||||||
|
player().getInventory().setItemInMainHand(WandSVC.createWand(b[0], b[1]));
|
||||||
|
player().updateInventory();
|
||||||
|
sender().playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f);
|
||||||
|
sender().sendMessage(C.GREEN + "Auto-select complete!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
* 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.PackDirectoryResolver;
|
||||||
|
import art.arcane.iris.core.pack.PackResourceCleanup;
|
||||||
|
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.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;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Director(name = "pack", aliases = {"pk"}, description = "Pack validation and maintenance")
|
||||||
|
public class CommandPack implements DirectorExecutor {
|
||||||
|
@Director(description = "Validate a pack (or all packs) and re-publish results", aliases = {"v"})
|
||||||
|
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 = PackDirectoryResolver.resolveExisting(packsRoot, pack);
|
||||||
|
if (target == null) {
|
||||||
|
s.sendMessage(C.RED + "Pack '" + pack + "' not found under packs/.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
runValidate(s, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Preview or apply unused-resource cleanup", aliases = {"c"})
|
||||||
|
public void cleanup(
|
||||||
|
@Param(description = "The pack folder name to clean")
|
||||||
|
String pack,
|
||||||
|
@Param(description = "preview or apply", defaultValue = "preview")
|
||||||
|
String mode
|
||||||
|
) {
|
||||||
|
VolmitSender s = sender();
|
||||||
|
File packFolder = findPack(s, pack);
|
||||||
|
if (packFolder == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ("apply".equalsIgnoreCase(mode)) {
|
||||||
|
PackResourceCleanup.ApplyResult result = PackResourceCleanup.apply(packFolder);
|
||||||
|
if (!result.success()) {
|
||||||
|
s.sendMessage(C.RED + result.error());
|
||||||
|
reportPaths(s, result.quarantinedPaths(), "still quarantined");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!result.changed()) {
|
||||||
|
s.sendMessage(C.GREEN + "No cleanup candidates found for pack '" + pack + "'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s.sendMessage(C.GREEN + "Quarantined " + result.quarantinedPaths().size()
|
||||||
|
+ " cleanup candidate(s) under " + result.quarantinePath() + ".");
|
||||||
|
reportPaths(s, result.quarantinedPaths(), "quarantined");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!"preview".equalsIgnoreCase(mode)) {
|
||||||
|
s.sendMessage(C.RED + "Cleanup mode must be preview or apply.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PackResourceCleanup.Preview preview = PackResourceCleanup.preview(packFolder);
|
||||||
|
if (!preview.success()) {
|
||||||
|
s.sendMessage(C.RED + preview.error());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!preview.hasCandidates()) {
|
||||||
|
s.sendMessage(C.GREEN + "No cleanup candidates found for pack '" + pack + "'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s.sendMessage(C.YELLOW + "Cleanup preview for pack '" + pack + "': "
|
||||||
|
+ preview.candidatePaths().size() + " candidate(s). No files were changed.");
|
||||||
|
reportPaths(s, preview.candidatePaths(), "candidate");
|
||||||
|
s.sendMessage(C.GRAY + "Run /iris pack cleanup " + pack + " mode=apply to quarantine these candidates after a fresh scan.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Preview or apply restoration of the latest quarantine", aliases = {"r"})
|
||||||
|
public void restore(
|
||||||
|
@Param(description = "The pack folder name to restore")
|
||||||
|
String pack,
|
||||||
|
@Param(description = "preview or apply", defaultValue = "preview")
|
||||||
|
String mode
|
||||||
|
) {
|
||||||
|
VolmitSender s = sender();
|
||||||
|
File packFolder = findPack(s, pack);
|
||||||
|
if (packFolder == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ("apply".equalsIgnoreCase(mode)) {
|
||||||
|
PackResourceCleanup.RestoreResult result = PackResourceCleanup.restoreLatest(packFolder);
|
||||||
|
if (!result.conflicts().isEmpty()) {
|
||||||
|
s.sendMessage(C.RED + "Restore refused because " + result.conflicts().size() + " destination(s) already exist.");
|
||||||
|
reportPaths(s, result.conflicts(), "conflict");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!result.success()) {
|
||||||
|
s.sendMessage(C.RED + result.error());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!result.changed()) {
|
||||||
|
s.sendMessage(C.YELLOW + "Nothing to restore for pack '" + pack + "'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s.sendMessage(C.GREEN + "Restored " + result.restoredPaths().size()
|
||||||
|
+ " file(s) from " + result.dumpPath() + ".");
|
||||||
|
reportPaths(s, result.restoredPaths(), "restored");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!"preview".equalsIgnoreCase(mode)) {
|
||||||
|
s.sendMessage(C.RED + "Restore mode must be preview or apply.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PackResourceCleanup.RestorePreview preview = PackResourceCleanup.previewRestore(packFolder);
|
||||||
|
if (!preview.success()) {
|
||||||
|
s.sendMessage(C.RED + preview.error());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!preview.hasFiles()) {
|
||||||
|
s.sendMessage(C.YELLOW + "Nothing to restore for pack '" + pack + "'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s.sendMessage(C.YELLOW + "Restore preview for " + preview.dumpPath() + ": "
|
||||||
|
+ preview.filePaths().size() + " file(s). No files were changed.");
|
||||||
|
reportPaths(s, preview.filePaths(), "file");
|
||||||
|
if (!preview.conflicts().isEmpty()) {
|
||||||
|
s.sendMessage(C.RED + "Restore is blocked by " + preview.conflicts().size() + " existing destination(s).");
|
||||||
|
reportPaths(s, preview.conflicts(), "conflict");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s.sendMessage(C.GRAY + "Run /iris pack restore " + pack + " mode=apply to restore after a fresh conflict check.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Show cached validation status for a pack", aliases = {"s"})
|
||||||
|
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() + ")");
|
||||||
|
});
|
||||||
|
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() + ")");
|
||||||
|
} 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).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File findPack(VolmitSender sender, String pack) {
|
||||||
|
if (pack == null || pack.isBlank()) {
|
||||||
|
sender.sendMessage(C.RED + "You must specify a pack name.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
File packFolder = PackDirectoryResolver.resolveExisting(Iris.instance.getDataFolder("packs"), pack);
|
||||||
|
if (packFolder == null) {
|
||||||
|
sender.sendMessage(C.RED + "Pack '" + pack + "' not found under packs/.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return packFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reportPaths(VolmitSender sender, List<String> paths, String label) {
|
||||||
|
int max = Math.min(10, paths.size());
|
||||||
|
for (int i = 0; i < max; i++) {
|
||||||
|
sender.sendMessage(C.GRAY + " - " + label + ": " + paths.get(i));
|
||||||
|
}
|
||||||
|
if (paths.size() > max) {
|
||||||
|
sender.sendMessage(C.GRAY + " ... and " + (paths.size() - max) + " more.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.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.math.Position2;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
@Director(name = "pregen", aliases = "pregenerate", description = "Pregenerate your Iris worlds!")
|
||||||
|
public class CommandPregen implements DirectorExecutor {
|
||||||
|
@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,
|
||||||
|
@Param(description = "Open the Iris pregen gui", defaultValue = "true")
|
||||||
|
boolean gui,
|
||||||
|
@Param(name = "serial", description = "Generate only one chunk at a time", defaultValue = "false")
|
||||||
|
boolean serial
|
||||||
|
) {
|
||||||
|
if (radius <= 0) {
|
||||||
|
sender().sendMessage(C.RED + "Pregen radius must be greater than zero blocks.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (serial && !IrisToolbelt.supportsStrictSerialPregeneration()) {
|
||||||
|
sender().sendMessage(C.RED + "Strict serial pregeneration requires Paper or a Paper-compatible server.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (sender().isPlayer() && access() == null) {
|
||||||
|
sender().sendMessage(C.RED + "The engine access for this world is null!");
|
||||||
|
sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example.");
|
||||||
|
}
|
||||||
|
PregenTask task = PregenTask
|
||||||
|
.builder()
|
||||||
|
.center(new Position2(center.getBlockX(), center.getBlockZ()))
|
||||||
|
.gui(gui)
|
||||||
|
.radiusX(radius)
|
||||||
|
.radiusZ(radius)
|
||||||
|
.build();
|
||||||
|
if (serial) {
|
||||||
|
IrisToolbelt.pregenerateSerial(task, world);
|
||||||
|
} else {
|
||||||
|
IrisToolbelt.pregenerate(task, 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);
|
||||||
|
Iris.info(msg);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
sender().sendMessage(C.RED + "Failed to start pregeneration. See console for details.");
|
||||||
|
Iris.reportError(e);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Stop the active pregeneration task", aliases = "x")
|
||||||
|
public void stop() {
|
||||||
|
if (PregeneratorJob.shutdownInstance()) {
|
||||||
|
String message = C.BLUE + "Pregen stop requested; finishing active work before cancellation.";
|
||||||
|
sender().sendMessage(message);
|
||||||
|
Iris.info(message);
|
||||||
|
} else {
|
||||||
|
sender().sendMessage(C.YELLOW + "No active pregeneration tasks to stop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Pause / continue the active pregeneration task", aliases = {"resume"})
|
||||||
|
public void pause() {
|
||||||
|
if (PregeneratorJob.pauseResume()) {
|
||||||
|
sender().sendMessage(C.GREEN + "Paused/unpaused pregeneration task, now: " + (PregeneratorJob.isPaused() ? "Paused" : "Running") + ".");
|
||||||
|
} else {
|
||||||
|
sender().sendMessage(C.YELLOW + "No active pregeneration tasks to pause/unpause.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Show the active pregeneration status")
|
||||||
|
public void status() {
|
||||||
|
PregeneratorJob.PregenProgress progress = PregeneratorJob.progressSnapshot();
|
||||||
|
if (progress == null) {
|
||||||
|
sender().sendMessage(C.YELLOW + "No active pregeneration task.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String world = progress.worldName() == null ? "?" : progress.worldName();
|
||||||
|
sender().sendMessage(C.GREEN + "Pregen " + C.GOLD + world + C.GREEN + ": " + C.GOLD + Form.f(progress.generated()) + "/" + Form.f(progress.totalChunks())
|
||||||
|
+ C.GREEN + " (" + C.GOLD + String.format("%.1f", progress.percent()) + "%" + C.GREEN + ")"
|
||||||
|
+ (progress.paused() ? C.YELLOW + " PAUSED" : ""));
|
||||||
|
sender().sendMessage(C.GREEN + "Speed: " + C.GOLD + Form.f((int) progress.chunksPerSecond()) + "/s" + C.GREEN
|
||||||
|
+ " ETA: " + C.GOLD + Form.duration(progress.eta(), 2) + C.GREEN
|
||||||
|
+ " Elapsed: " + C.GOLD + Form.duration(progress.elapsed(), 2) + C.GREEN
|
||||||
|
+ " Method: " + C.GOLD + progress.method()
|
||||||
|
+ (progress.failed() > 0 ? C.RED + " Failed: " + Form.f(progress.failed()) : ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
+284
@@ -0,0 +1,284 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.loader.IrisData;
|
||||||
|
import art.arcane.iris.core.structure.BulkStructureImporter;
|
||||||
|
import art.arcane.iris.core.structure.StructureCaptureImporter;
|
||||||
|
import art.arcane.iris.core.structure.StructureImporter;
|
||||||
|
import art.arcane.iris.core.structure.StructureIndexService;
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||||
|
import art.arcane.iris.engine.framework.PlacedStructurePiece;
|
||||||
|
import art.arcane.iris.engine.framework.StructureAssembler;
|
||||||
|
import art.arcane.iris.engine.framework.StructureReachability;
|
||||||
|
import art.arcane.iris.engine.object.IObjectPlacer;
|
||||||
|
import art.arcane.iris.engine.object.IrisDimension;
|
||||||
|
import art.arcane.iris.engine.object.IrisObjectPlacement;
|
||||||
|
import art.arcane.iris.engine.object.IrisStructure;
|
||||||
|
import art.arcane.iris.engine.object.ObjectPlaceMode;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.iris.util.common.director.DirectorExecutor;
|
||||||
|
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 art.arcane.iris.core.tools.IrisToolbelt;
|
||||||
|
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||||
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
|
import io.papermc.paper.registry.RegistryAccess;
|
||||||
|
import io.papermc.paper.registry.RegistryKey;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.Registry;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.generator.structure.Structure;
|
||||||
|
import org.bukkit.util.StructureSearchResult;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Director(name = "structure", aliases = {"struct", "str"}, description = "Iris structure tools (index, import, info)")
|
||||||
|
public class CommandStructure implements DirectorExecutor {
|
||||||
|
@Director(description = "Regenerate structure-index.json listing all vanilla, datapack & iris structures", aliases = {"ls"}, origin = DirectorOrigin.BOTH)
|
||||||
|
public void list(
|
||||||
|
@Param(description = "The dimension whose pack to index", aliases = "dim")
|
||||||
|
IrisDimension dimension
|
||||||
|
) {
|
||||||
|
IrisData data = dimension.getLoader();
|
||||||
|
if (data == null) {
|
||||||
|
sender().sendMessage(C.RED + "Could not resolve the pack for dimension " + dimension.getLoadKey());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File file = StructureIndexService.write(data);
|
||||||
|
sender().sendMessage(C.GREEN + "Wrote structure index: " + C.WHITE + file.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(name = "import", description = "Import EVERY structure - vanilla AND ingested datapacks - into this pack as editable Iris resources, always overwriting. Rebuilds jigsaw structures (villages, outposts, datapack jigsaws) as editable pool/piece graphs, imports every structure template NBT as an object, and assembles the multi-template structures (shipwrecks, ruined portals, ocean ruins, nether fossils). Run after ingesting a datapack and restarting. Regenerate chunks or use a fresh world for the imported copies to place.", aliases = {"import-all", "reimport", "imp", "all"}, origin = DirectorOrigin.BOTH)
|
||||||
|
public void importAll(
|
||||||
|
@Param(description = "The dimension whose pack to import into", aliases = "dim")
|
||||||
|
IrisDimension dimension
|
||||||
|
) {
|
||||||
|
IrisData data = dimension.getLoader();
|
||||||
|
if (data == null) {
|
||||||
|
sender().sendMessage(C.RED + "Could not resolve the pack for dimension " + dimension.getLoadKey());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sender().sendMessage(C.GREEN + "Importing all vanilla & datapack structures into " + C.WHITE + dimension.getLoadKey() + C.GREEN + " (overwrite)...");
|
||||||
|
BulkStructureImporter.Report jigsaws = BulkStructureImporter.importAllVanilla(data, StructureImporter.Mode.OVERWRITE, true, sender());
|
||||||
|
BulkStructureImporter.Report templates = BulkStructureImporter.importAllTemplates(data, StructureImporter.Mode.OVERWRITE, sender());
|
||||||
|
BulkStructureImporter.Report groups = BulkStructureImporter.importTemplateGroups(data, StructureImporter.Mode.OVERWRITE, sender());
|
||||||
|
StructureCaptureImporter.Report captured = StructureCaptureImporter.importAllStructures(data, StructureImporter.Mode.OVERWRITE, sender());
|
||||||
|
int imported = jigsaws.imported() + templates.imported() + groups.imported() + captured.imported();
|
||||||
|
int failed = jigsaws.failed() + templates.failed() + groups.failed() + captured.failed();
|
||||||
|
sender().sendMessage(C.GREEN + "Import complete: " + C.WHITE + imported + C.GREEN + " structures/objects written, " + C.WHITE + failed + C.GREEN + " failed.");
|
||||||
|
sender().sendMessage(C.GRAY + "Reference them from a biome/region/dimension 'structures' list, or run /iris structure list " + dimension.getLoadKey() + " to refresh the index. Regenerate chunks for changes to take effect.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(name = "capture", description = "Capture code-generated structures that have no NBT template (swamp huts, igloos, etc.) into editable Iris objects by generating each one in a throwaway scratch world and reading back its blocks. Skips structures that already import as a structure, structures wider/taller than the capture cap (strongholds, mansions, monuments stay vanilla), and anything that will not generate in a flat overworld. Each captured structure becomes a single-piece Iris structure you can place from a biome/region/dimension 'structures' list. Runs automatically as the last pass of /iris structure import.", aliases = {"cap"}, origin = DirectorOrigin.BOTH)
|
||||||
|
public void capture(
|
||||||
|
@Param(description = "The dimension whose pack to capture into", aliases = "dim")
|
||||||
|
IrisDimension dimension
|
||||||
|
) {
|
||||||
|
IrisData data = dimension.getLoader();
|
||||||
|
if (data == null) {
|
||||||
|
sender().sendMessage(C.RED + "Could not resolve the pack for dimension " + dimension.getLoadKey());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StructureCaptureImporter.Report report = StructureCaptureImporter.importAllStructures(data, StructureImporter.Mode.OVERWRITE, sender());
|
||||||
|
sender().sendMessage(C.GRAY + "Captured " + report.imported() + " structures. Place them from a 'structures' list and regenerate chunks. Delete a structures/*.json to re-capture it.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Locate every vanilla/datapack/iris structure to verify which are locatable in this world. Heavy synchronous search per structure - keep the radius modest. 'Not found' can mean rarer than the radius, a different dimension (nether/end structures never appear in the overworld), or a structure that generates but cannot be located; generation itself happens during chunk decoration, independent of locate.", aliases = {"locateall"}, origin = DirectorOrigin.BOTH, sync = true)
|
||||||
|
public void verify(
|
||||||
|
@Param(description = "The dimension to verify", aliases = "dim")
|
||||||
|
IrisDimension dimension,
|
||||||
|
@Param(description = "Search radius in chunks around the origin (larger is much slower)", defaultValue = "48")
|
||||||
|
int radius
|
||||||
|
) {
|
||||||
|
World world = resolveIrisWorld(dimension);
|
||||||
|
if (world == null) {
|
||||||
|
sender().sendMessage(C.RED + "No loaded Iris world found for " + dimension.getLoadKey() + ". Join or create one first (the search runs against a live world).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean senderIsPlayer = sender() != null && sender().isPlayer();
|
||||||
|
Location center = (senderIsPlayer && player().getWorld() == world) ? player().getLocation() : world.getSpawnLocation();
|
||||||
|
int searchRadius = Math.max(1, Math.min(radius, 1000));
|
||||||
|
|
||||||
|
sender().sendMessage(C.GREEN + "Verifying structures in " + C.WHITE + world.getName() + C.GREEN + " from " + center.getBlockX() + "," + center.getBlockZ() + " within " + searchRadius + " chunks...");
|
||||||
|
|
||||||
|
Engine engine = null;
|
||||||
|
PlatformChunkGenerator access = IrisToolbelt.access(world);
|
||||||
|
if (access != null) {
|
||||||
|
engine = access.getEngine();
|
||||||
|
}
|
||||||
|
Set<String> reachable = engine == null ? Collections.emptySet() : StructureReachability.reachableKeys(engine);
|
||||||
|
|
||||||
|
int found = 0;
|
||||||
|
int missing = 0;
|
||||||
|
int unreachable = 0;
|
||||||
|
int irisPlaced = 0;
|
||||||
|
KList<String> notFound = new KList<>();
|
||||||
|
KList<String> cannotGenerate = new KList<>();
|
||||||
|
Registry<Structure> structureRegistry = RegistryAccess.registryAccess().getRegistry(RegistryKey.STRUCTURE);
|
||||||
|
for (Structure structure : structureRegistry) {
|
||||||
|
NamespacedKey key = structureRegistry.getKey(structure);
|
||||||
|
String keyName = key == null ? structure.toString() : key.toString();
|
||||||
|
boolean isIrisPlaced = engine != null && IrisStructureLocator.suppressesVanilla(engine, keyName);
|
||||||
|
boolean isReachable = engine != null && reachable.contains(keyName.toLowerCase());
|
||||||
|
if (engine != null && !isIrisPlaced && !isReachable) {
|
||||||
|
unreachable++;
|
||||||
|
KList<String> miss = StructureReachability.missingBiomeKeys(engine, keyName);
|
||||||
|
cannotGenerate.add(keyName + (miss.isEmpty() ? "" : " (needs " + String.join("/", miss) + ")"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isIrisPlaced) {
|
||||||
|
irisPlaced++;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
StructureSearchResult result = world.locateNearestStructure(center, structure, searchRadius, true);
|
||||||
|
if (result != null && result.getLocation() != null) {
|
||||||
|
found++;
|
||||||
|
Location l = result.getLocation();
|
||||||
|
sender().sendMessage((isIrisPlaced ? C.AQUA + "[iris] " : C.GREEN + "[ok] ") + C.WHITE + keyName + C.GREEN + " @ " + l.getBlockX() + "," + l.getBlockZ());
|
||||||
|
} else {
|
||||||
|
missing++;
|
||||||
|
notFound.add(keyName);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
missing++;
|
||||||
|
notFound.add(keyName + " (error: " + e.getClass().getSimpleName() + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sender().sendMessage(C.GREEN + "Structure verify: " + C.WHITE + found + C.GREEN + " located (" + irisPlaced + " iris-placed), "
|
||||||
|
+ C.WHITE + unreachable + C.GREEN + " cannot generate here, "
|
||||||
|
+ C.WHITE + missing + C.GREEN + " reachable-but-not-found within " + searchRadius + " chunks.");
|
||||||
|
if (!cannotGenerate.isEmpty()) {
|
||||||
|
sender().sendMessage(C.RED + "Cannot generate (required biomes absent from this pack): " + C.GRAY + String.join(", ", cannotGenerate));
|
||||||
|
}
|
||||||
|
if (!notFound.isEmpty()) {
|
||||||
|
sender().sendMessage(C.YELLOW + "Reachable but not found (rarer than radius): " + C.GRAY + String.join(", ", notFound));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private World resolveIrisWorld(IrisDimension dimension) {
|
||||||
|
if (sender() != null && sender().isPlayer() && IrisToolbelt.isIrisWorld(player().getWorld())) {
|
||||||
|
return player().getWorld();
|
||||||
|
}
|
||||||
|
World fallback = null;
|
||||||
|
for (World w : Bukkit.getWorlds()) {
|
||||||
|
if (!IrisToolbelt.isIrisWorld(w)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (fallback == null) {
|
||||||
|
fallback = w;
|
||||||
|
}
|
||||||
|
PlatformChunkGenerator gen = IrisToolbelt.access(w);
|
||||||
|
if (gen != null && gen.getEngine() != null && gen.getEngine().getDimension() != null
|
||||||
|
&& dimension.getLoadKey().equals(gen.getEngine().getDimension().getLoadKey())) {
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Resolve an iris structure's jigsaw graph and report piece count & bounds", origin = DirectorOrigin.BOTH)
|
||||||
|
public void info(
|
||||||
|
@Param(description = "The dimension whose pack holds the structure", aliases = "dim")
|
||||||
|
IrisDimension dimension,
|
||||||
|
@Param(description = "The iris structure key to inspect")
|
||||||
|
String structure
|
||||||
|
) {
|
||||||
|
IrisData data = dimension.getLoader();
|
||||||
|
if (data == null) {
|
||||||
|
sender().sendMessage(C.RED + "Could not resolve the pack for dimension " + dimension.getLoadKey());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IrisStructure s = IrisData.loadAnyStructure(structure, data);
|
||||||
|
if (s == null) {
|
||||||
|
sender().sendMessage(C.RED + "No iris structure '" + structure + "' in this pack");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StructureAssembler assembler = new StructureAssembler(data, s, 0, 64, 0);
|
||||||
|
KList<PlacedStructurePiece> pieces = assembler.assemble(new RNG(1234));
|
||||||
|
if (pieces == null || pieces.isEmpty()) {
|
||||||
|
sender().sendMessage(C.RED + "Structure '" + structure + "' assembled 0 pieces (check startPool '" + s.getStartPool() + "')");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int minX = Integer.MAX_VALUE;
|
||||||
|
int minZ = Integer.MAX_VALUE;
|
||||||
|
int maxX = Integer.MIN_VALUE;
|
||||||
|
int maxZ = Integer.MIN_VALUE;
|
||||||
|
for (PlacedStructurePiece p : pieces) {
|
||||||
|
minX = Math.min(minX, p.getMinX());
|
||||||
|
minZ = Math.min(minZ, p.getMinZ());
|
||||||
|
maxX = Math.max(maxX, p.getMaxX());
|
||||||
|
maxZ = Math.max(maxZ, p.getMaxZ());
|
||||||
|
}
|
||||||
|
sender().sendMessage(C.GREEN + "Structure '" + structure + "': " + C.WHITE + pieces.size() + C.GREEN + " pieces, footprint " + C.WHITE + (maxX - minX + 1) + "x" + (maxZ - minZ + 1) + C.GREEN + " blocks (sample seed 1234)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Assemble and place an iris structure at your location (studio testing)", aliases = {"p"}, origin = DirectorOrigin.PLAYER, sync = true)
|
||||||
|
public void place(
|
||||||
|
@Param(description = "The dimension whose pack holds the structure", aliases = "dim")
|
||||||
|
IrisDimension dimension,
|
||||||
|
@Param(description = "The iris structure key to place")
|
||||||
|
String structure
|
||||||
|
) {
|
||||||
|
IrisData data = dimension.getLoader();
|
||||||
|
if (data == null) {
|
||||||
|
sender().sendMessage(C.RED + "Could not resolve the pack for dimension " + dimension.getLoadKey());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IrisStructure s = IrisData.loadAnyStructure(structure, data);
|
||||||
|
if (s == null) {
|
||||||
|
sender().sendMessage(C.RED + "No iris structure '" + structure + "' in this pack");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Location loc = player().getLocation();
|
||||||
|
StructureAssembler assembler = new StructureAssembler(data, s, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
|
||||||
|
RNG rng = new RNG((long) loc.getBlockX() * 341873128712L + loc.getBlockZ());
|
||||||
|
KList<PlacedStructurePiece> pieces = assembler.assemble(rng);
|
||||||
|
if (pieces == null || pieces.isEmpty()) {
|
||||||
|
sender().sendMessage(C.RED + "Structure '" + structure + "' assembled 0 pieces");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<Block, BlockData> future = new HashMap<>();
|
||||||
|
IObjectPlacer placer = CommandObject.createPlacer(player().getWorld(), future);
|
||||||
|
for (PlacedStructurePiece p : pieces) {
|
||||||
|
IrisObjectPlacement config = new IrisObjectPlacement();
|
||||||
|
config.setMode(ObjectPlaceMode.STRUCTURE_PIECE);
|
||||||
|
config.setRotation(p.getRotation());
|
||||||
|
config.getPlace().add(p.getObject().getLoadKey());
|
||||||
|
if (!s.getEdit().isEmpty()) {
|
||||||
|
config.setEdit(s.getEdit());
|
||||||
|
}
|
||||||
|
p.getObject().place(p.getX(), p.getY(), p.getZ(), placer, config, rng, null, null, data);
|
||||||
|
}
|
||||||
|
sender().sendMessage(C.GREEN + "Placed '" + structure + "' (" + pieces.size() + " pieces) at your location.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+880
@@ -0,0 +1,880 @@
|
|||||||
|
/*
|
||||||
|
* 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.platform.bukkit.BukkitPlatform;
|
||||||
|
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.structure.BulkStructureImporter;
|
||||||
|
import art.arcane.iris.core.structure.FeatureImporter;
|
||||||
|
import art.arcane.iris.core.structure.StructureImporter;
|
||||||
|
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.object.InventorySlotType;
|
||||||
|
import art.arcane.iris.engine.object.IrisBiome;
|
||||||
|
import art.arcane.iris.engine.object.IrisBiomePaletteLayer;
|
||||||
|
import art.arcane.iris.engine.object.IrisDimension;
|
||||||
|
import art.arcane.iris.engine.object.IrisEntity;
|
||||||
|
import art.arcane.iris.engine.object.IrisGenerator;
|
||||||
|
import art.arcane.iris.engine.object.IrisInterpolator;
|
||||||
|
import art.arcane.iris.engine.object.IrisLootTable;
|
||||||
|
import art.arcane.iris.engine.object.IrisNoiseGenerator;
|
||||||
|
import art.arcane.iris.engine.object.IrisObject;
|
||||||
|
import art.arcane.iris.engine.object.IrisObjectPlacement;
|
||||||
|
import art.arcane.iris.engine.object.IrisRegion;
|
||||||
|
import art.arcane.iris.engine.object.NoiseStyle;
|
||||||
|
import art.arcane.iris.engine.platform.EngineBukkitOps;
|
||||||
|
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.DirectorExecutor;
|
||||||
|
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.math.M;
|
||||||
|
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.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 org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.FluidCollisionMode;
|
||||||
|
import org.bukkit.GameMode;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.inventory.InventoryType;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.attribute.FileTime;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@Director(name = "studio", aliases = {"std", "s"}, description = "Studio Commands")
|
||||||
|
public class CommandStudio implements DirectorExecutor {
|
||||||
|
private CommandEdit edit;
|
||||||
|
//private CommandDeepSearch deepSearch;
|
||||||
|
|
||||||
|
public static String hrf(Duration duration) {
|
||||||
|
return duration.toString().substring(2).replaceAll("(\\d[HMS])(?!$)", "$1 ").toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Open a new studio world", aliases = "o", sync = true)
|
||||||
|
public void open(
|
||||||
|
@Param(description = "The dimension pack 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) {
|
||||||
|
sender().sendMessage(C.GREEN + "Opening studio for the \"" + dimension.getName() + "\" pack (seed: " + seed + ")");
|
||||||
|
Iris.service(StudioSVC.class).open(sender(), seed, dimension.getLoadKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Import vanilla trees, mushrooms & objects (and structures/jigsaw) from the server into a pack's objects/vanilla folder", aliases = {"importv", "iv"}, origin = DirectorOrigin.BOTH)
|
||||||
|
public void importvanilla(
|
||||||
|
@Param(description = "The dimension pack to import vanilla content into", aliases = {"pack", "dim"}, customHandler = DimensionHandler.class)
|
||||||
|
IrisDimension dimension,
|
||||||
|
@Param(description = "How many variants to capture per tree/object feature", defaultValue = "3")
|
||||||
|
int variants,
|
||||||
|
@Param(description = "Also import vanilla & datapack structures/jigsaw into the pack", defaultValue = "true")
|
||||||
|
boolean structures
|
||||||
|
) {
|
||||||
|
if (dimension == null) {
|
||||||
|
sender().sendMessage(C.RED + "Provide a dimension pack: /iris std importvanilla pack=<dimension>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IrisData data = dimension.getLoader();
|
||||||
|
if (data == null) {
|
||||||
|
sender().sendMessage(C.RED + "Could not resolve the pack for dimension " + dimension.getLoadKey());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VolmitSender sender = sender();
|
||||||
|
sender.sendMessage(C.GREEN + "Importing vanilla content into " + C.WHITE + dimension.getLoadKey() + C.GREEN + "...");
|
||||||
|
|
||||||
|
FeatureImporter.Report features = FeatureImporter.importAllObjectFeatures(data, variants, sender);
|
||||||
|
int imported = features.imported();
|
||||||
|
int failed = features.failed();
|
||||||
|
|
||||||
|
if (structures) {
|
||||||
|
BulkStructureImporter.Report jigsaws = BulkStructureImporter.importAllVanilla(data, StructureImporter.Mode.OVERWRITE, true, sender);
|
||||||
|
BulkStructureImporter.Report templates = BulkStructureImporter.importAllTemplates(data, StructureImporter.Mode.OVERWRITE, sender);
|
||||||
|
BulkStructureImporter.Report groups = BulkStructureImporter.importTemplateGroups(data, StructureImporter.Mode.OVERWRITE, sender);
|
||||||
|
imported += jigsaws.imported() + templates.imported() + groups.imported();
|
||||||
|
failed += jigsaws.failed() + templates.failed() + groups.failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage(C.GREEN + "importvanilla complete: " + C.WHITE + imported + C.GREEN + " objects/structures written, " + C.WHITE + failed + C.GREEN + " failed.");
|
||||||
|
sender.sendMessage(C.GRAY + "Trees/objects are under objects/vanilla/...; reference them from biome object placements.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Open VSCode for a dimension", aliases = {"vsc"})
|
||||||
|
public void vscode(
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Close an open studio project", aliases = {"x"}, sync = true)
|
||||||
|
public void close() {
|
||||||
|
VolmitSender commandSender = sender();
|
||||||
|
if (!Iris.service(StudioSVC.class).isProjectOpen()) {
|
||||||
|
commandSender.sendMessage(C.RED + "No open studio projects.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.");
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Create a new studio project", aliases = "+", sync = true)
|
||||||
|
public void create(
|
||||||
|
@Param(description = "The name of this new Iris Project.", defaultValue = "studio")
|
||||||
|
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,
|
||||||
|
customHandler = NullableDimensionHandler.class
|
||||||
|
)
|
||||||
|
IrisDimension template) {
|
||||||
|
String projectName = name;
|
||||||
|
if (name.equals("studio")) {
|
||||||
|
File workspace = Iris.service(StudioSVC.class).getWorkspaceFolder();
|
||||||
|
int suffix = 2;
|
||||||
|
while (new File(workspace, projectName).exists()) {
|
||||||
|
projectName = "studio" + suffix++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template != null) {
|
||||||
|
Iris.service(StudioSVC.class).create(sender(), projectName, template.getLoadKey());
|
||||||
|
} else {
|
||||||
|
Iris.service(StudioSVC.class).create(sender(), projectName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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, customHandler = DimensionHandler.class)
|
||||||
|
IrisDimension dimension
|
||||||
|
) {
|
||||||
|
sender().sendMessage(C.GREEN + "The \"" + dimension.getName() + "\" pack has version: " + dimension.getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Open the noise explorer (External GUI)", aliases = {"nmap"})
|
||||||
|
public void noise(
|
||||||
|
@Param(description = "Optional pack generator to preview", defaultValue = "null", contextual = true)
|
||||||
|
IrisGenerator generator,
|
||||||
|
@Param(description = "The seed to preview the generator with", defaultValue = "12345")
|
||||||
|
long seed
|
||||||
|
) {
|
||||||
|
if (noGUI()) return;
|
||||||
|
sender().sendMessage(C.GREEN + "Opening Noise Explorer!");
|
||||||
|
|
||||||
|
if (generator == null) {
|
||||||
|
NoiseExplorerGUI.launch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Supplier<Function2<Double, Double, Double>> supplier = () -> (x, z) -> generator.getHeight(x, z, new RNG(seed).nextParallelRNG(3245).lmax());
|
||||||
|
NoiseExplorerGUI.launch(supplier, "Custom Generator");
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,
|
||||||
|
@Param(description = "Whether or not to append to the inventory currently open (if false, clears opened inventory)", defaultValue = "true")
|
||||||
|
boolean add
|
||||||
|
) {
|
||||||
|
if (noStudio()) return;
|
||||||
|
|
||||||
|
KList<IrisLootTable> tables = EngineBukkitOps.getLootTables(engine(), RNG.r, player().getLocation().getBlock());
|
||||||
|
Inventory inv = Bukkit.createInventory(null, 27 * 2);
|
||||||
|
|
||||||
|
try {
|
||||||
|
EngineBukkitOps.addItems(engine(), true, inv, RNG.r, tables, InventorySlotType.STORAGE, player().getWorld(), player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
sender().sendMessage(C.RED + "Cannot add items to virtual inventory because of: " + e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
O<Integer> ta = new O<>();
|
||||||
|
ta.set(-1);
|
||||||
|
|
||||||
|
var sender = sender();
|
||||||
|
var player = player();
|
||||||
|
var engine = engine();
|
||||||
|
|
||||||
|
ta.set(J.sr(() ->
|
||||||
|
{
|
||||||
|
if (!player.getOpenInventory().getType().equals(InventoryType.CHEST)) {
|
||||||
|
J.csr(ta.get());
|
||||||
|
sender.sendMessage(C.GREEN + "Opened inventory!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!add) {
|
||||||
|
inv.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
EngineBukkitOps.addItems(engine, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
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.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) + "%"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
) {
|
||||||
|
if (noGUI()) return;
|
||||||
|
|
||||||
|
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||||
|
sender().sendMessage(C.RED + "You need to be in or specify an Iris-generated world!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VisionGUI.launch(IrisToolbelt.access(world).getEngine());
|
||||||
|
sender().sendMessage(C.GREEN + "Opening map!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@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", customHandler = DimensionHandler.class)
|
||||||
|
IrisDimension dimension,
|
||||||
|
@Param(name = "obfuscate", description = "Whether or not to obfuscate the pack", defaultValue = "false")
|
||||||
|
boolean obfuscate,
|
||||||
|
@Param(name = "minify", description = "Whether or not to minify the pack", defaultValue = "true")
|
||||||
|
boolean minify
|
||||||
|
) {
|
||||||
|
Iris.service(StudioSVC.class).compilePackage(sender(), dimension.getLoadKey(), obfuscate, minify);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Profiles the performance of a dimension", origin = DirectorOrigin.PLAYER)
|
||||||
|
public void profile(
|
||||||
|
@Param(description = "The dimension to profile", contextual = true, defaultValue = "default", customHandler = DimensionHandler.class)
|
||||||
|
IrisDimension dimension
|
||||||
|
) {
|
||||||
|
// Todo: Make this more accurate
|
||||||
|
File pack = dimension.getLoadFile().getParentFile().getParentFile();
|
||||||
|
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<>();
|
||||||
|
|
||||||
|
KMap<NoiseStyle, Double> styleTimings = new KMap<>();
|
||||||
|
KMap<InterpolationMethod, Double> interpolatorTimings = new KMap<>();
|
||||||
|
KMap<String, Double> generatorTimings = new KMap<>();
|
||||||
|
KMap<String, Double> biomeTimings = new KMap<>();
|
||||||
|
KMap<String, Double> regionTimings = new KMap<>();
|
||||||
|
|
||||||
|
sender().sendMessage("Calculating Performance Metrics for Noise generators");
|
||||||
|
|
||||||
|
for (NoiseStyle i : NoiseStyle.values()) {
|
||||||
|
CNG c = i.create(new RNG(i.hashCode()));
|
||||||
|
|
||||||
|
for (int j = 0; j < 3000; j++) {
|
||||||
|
c.noise(j, j + 1000, j * j);
|
||||||
|
c.noise(j, -j);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrecisionStopwatch px = PrecisionStopwatch.start();
|
||||||
|
|
||||||
|
for (int j = 0; j < 100000; j++) {
|
||||||
|
c.noise(j, j + 1000, j * j);
|
||||||
|
c.noise(j, -j);
|
||||||
|
}
|
||||||
|
|
||||||
|
styleTimings.put(i, px.getMilliseconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
fileText.add("Noise Style Performance Impacts: ");
|
||||||
|
|
||||||
|
for (NoiseStyle i : styleTimings.sortKNumber()) {
|
||||||
|
fileText.add(i.name() + ": " + styleTimings.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
fileText.add("");
|
||||||
|
|
||||||
|
sender().sendMessage("Calculating Interpolator Timings...");
|
||||||
|
|
||||||
|
for (InterpolationMethod i : InterpolationMethod.values()) {
|
||||||
|
IrisInterpolator in = new IrisInterpolator();
|
||||||
|
in.setFunction(i);
|
||||||
|
in.setHorizontalScale(8);
|
||||||
|
|
||||||
|
NoiseProvider np = (x, z) -> Math.random();
|
||||||
|
|
||||||
|
for (int j = 0; j < 3000; j++) {
|
||||||
|
in.interpolate(j, -j, np);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrecisionStopwatch px = PrecisionStopwatch.start();
|
||||||
|
|
||||||
|
for (int j = 0; j < 100000; j++) {
|
||||||
|
in.interpolate(j + 10000, -j - 100000, np);
|
||||||
|
}
|
||||||
|
|
||||||
|
interpolatorTimings.put(i, px.getMilliseconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
fileText.add("Noise Interpolator Performance Impacts: ");
|
||||||
|
|
||||||
|
for (InterpolationMethod i : interpolatorTimings.sortKNumber()) {
|
||||||
|
fileText.add(i.name() + ": " + interpolatorTimings.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
fileText.add("");
|
||||||
|
|
||||||
|
sender().sendMessage("Processing Generator Scores: ");
|
||||||
|
|
||||||
|
KMap<String, KList<String>> btx = new KMap<>();
|
||||||
|
|
||||||
|
for (String i : data.getGeneratorLoader().getPossibleKeys()) {
|
||||||
|
KList<String> vv = new KList<>();
|
||||||
|
IrisGenerator g = data.getGeneratorLoader().load(i);
|
||||||
|
KList<IrisNoiseGenerator> composites = g.getAllComposites();
|
||||||
|
double score = 0;
|
||||||
|
int m = 0;
|
||||||
|
for (IrisNoiseGenerator j : composites) {
|
||||||
|
m++;
|
||||||
|
score += styleTimings.get(j.getStyle().getStyle());
|
||||||
|
vv.add("Composite Noise Style " + m + " " + j.getStyle().getStyle().name() + ": " + styleTimings.get(j.getStyle().getStyle()));
|
||||||
|
}
|
||||||
|
|
||||||
|
score += interpolatorTimings.get(g.getInterpolator().getFunction());
|
||||||
|
vv.add("Interpolator " + g.getInterpolator().getFunction().name() + ": " + interpolatorTimings.get(g.getInterpolator().getFunction()));
|
||||||
|
generatorTimings.put(i, score);
|
||||||
|
btx.put(i, vv);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileText.add("Project Generator Performance Impacts: ");
|
||||||
|
|
||||||
|
for (String i : generatorTimings.sortKNumber()) {
|
||||||
|
fileText.add(i + ": " + generatorTimings.get(i));
|
||||||
|
|
||||||
|
btx.get(i).forEach((ii) -> fileText.add(" " + ii));
|
||||||
|
}
|
||||||
|
|
||||||
|
fileText.add("");
|
||||||
|
|
||||||
|
KMap<String, KList<String>> bt = new KMap<>();
|
||||||
|
|
||||||
|
for (String i : data.getBiomeLoader().getPossibleKeys()) {
|
||||||
|
KList<String> vv = new KList<>();
|
||||||
|
IrisBiome b = data.getBiomeLoader().load(i);
|
||||||
|
double score = 0;
|
||||||
|
|
||||||
|
int m = 0;
|
||||||
|
for (IrisBiomePaletteLayer j : b.getLayers()) {
|
||||||
|
m++;
|
||||||
|
score += styleTimings.get(j.getStyle().getStyle());
|
||||||
|
vv.add("Palette Layer " + m + ": " + styleTimings.get(j.getStyle().getStyle()));
|
||||||
|
}
|
||||||
|
|
||||||
|
score += styleTimings.get(b.getBiomeStyle().getStyle());
|
||||||
|
vv.add("Biome Style: " + styleTimings.get(b.getBiomeStyle().getStyle()));
|
||||||
|
score += styleTimings.get(b.getChildStyle().getStyle());
|
||||||
|
vv.add("Child Style: " + styleTimings.get(b.getChildStyle().getStyle()));
|
||||||
|
biomeTimings.put(i, score);
|
||||||
|
bt.put(i, vv);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileText.add("Project Biome Performance Impacts: ");
|
||||||
|
|
||||||
|
for (String i : biomeTimings.sortKNumber()) {
|
||||||
|
fileText.add(i + ": " + biomeTimings.get(i));
|
||||||
|
|
||||||
|
bt.get(i).forEach((ff) -> fileText.add(" " + ff));
|
||||||
|
}
|
||||||
|
|
||||||
|
fileText.add("");
|
||||||
|
|
||||||
|
for (String i : data.getRegionLoader().getPossibleKeys()) {
|
||||||
|
IrisRegion b = data.getRegionLoader().load(i);
|
||||||
|
double score = 0;
|
||||||
|
|
||||||
|
score += styleTimings.get(b.getLakeStyle().getStyle());
|
||||||
|
score += styleTimings.get(b.getRiverStyle().getStyle());
|
||||||
|
regionTimings.put(i, score);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileText.add("Project Region Performance Impacts: ");
|
||||||
|
|
||||||
|
for (String i : regionTimings.sortKNumber()) {
|
||||||
|
fileText.add(i + ": " + regionTimings.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
fileText.add("");
|
||||||
|
|
||||||
|
double m = 0;
|
||||||
|
for (double i : biomeTimings.v()) {
|
||||||
|
m += i;
|
||||||
|
}
|
||||||
|
m /= biomeTimings.size();
|
||||||
|
double mm = 0;
|
||||||
|
for (double i : generatorTimings.v()) {
|
||||||
|
mm += i;
|
||||||
|
}
|
||||||
|
mm /= generatorTimings.size();
|
||||||
|
m += mm;
|
||||||
|
double mmm = 0;
|
||||||
|
for (double i : regionTimings.v()) {
|
||||||
|
mmm += i;
|
||||||
|
}
|
||||||
|
mmm /= regionTimings.size();
|
||||||
|
m += mmm;
|
||||||
|
|
||||||
|
fileText.add("Average Score: " + m);
|
||||||
|
sender().sendMessage("Score: " + Form.duration(m, 0));
|
||||||
|
|
||||||
|
try {
|
||||||
|
IO.writeAll(report, fileText.toString("\n"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
sender().sendMessage(C.GREEN + "Done! " + report.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
@Param(description = "The location to spawn the entity at", contextual = true)
|
||||||
|
Vector location
|
||||||
|
) {
|
||||||
|
if (!IrisToolbelt.isIrisWorld(player().getWorld())) {
|
||||||
|
sender().sendMessage(C.RED + "You have to be in an Iris world to spawn entities properly. Trying to spawn the best we can do.");
|
||||||
|
}
|
||||||
|
entity.spawn(engine(), new Location(world(), location.getX(), location.getY(), location.getZ()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IrisToolbelt.isIrisWorld(world()) && engine().isStudio()) {
|
||||||
|
sender().sendMessage(C.RED + "You are already in a studio world!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sender().sendMessage(C.GREEN + "Sending you to the studio world!");
|
||||||
|
var player = player();
|
||||||
|
BukkitPlatform.teleportAsync(player(), Iris.service(StudioSVC.class)
|
||||||
|
.getActiveProject()
|
||||||
|
.getActiveProvider()
|
||||||
|
.getTarget()
|
||||||
|
.getWorld()
|
||||||
|
.spawnLocation()
|
||||||
|
).thenRun(() -> player.setGameMode(GameMode.SPECTATOR));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "Update your dimension projects VSCode workspace")
|
||||||
|
public void update(
|
||||||
|
@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() + "...");
|
||||||
|
if (new IrisProject(dimension.getLoader().getDataFolder()).updateWorkspace()) {
|
||||||
|
sender().sendMessage(C.GREEN + "Updated Code Workspace for " + dimension.getName());
|
||||||
|
} else {
|
||||||
|
sender().sendMessage(C.RED + "Invalid project: " + dimension.getName() + ". Try deleting the code-workspace file and try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(aliases = "find-objects", description = "Capture an IGenData chunk report for nearby chunks")
|
||||||
|
public void objects() {
|
||||||
|
if (!IrisToolbelt.isIrisWorld(player().getWorld())) {
|
||||||
|
sender().sendMessage(C.RED + "You must be in an Iris world");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
World world = player().getWorld();
|
||||||
|
|
||||||
|
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||||
|
sender().sendMessage("You must be in an iris world.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
KList<Chunk> chunks = new KList<>();
|
||||||
|
int bx = player().getLocation().getChunk().getX();
|
||||||
|
int bz = player().getLocation().getChunk().getZ();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Location l = player().getTargetBlockExact(48, FluidCollisionMode.NEVER).getLocation();
|
||||||
|
|
||||||
|
int cx = l.getChunk().getX();
|
||||||
|
int cz = l.getChunk().getZ();
|
||||||
|
new Spiraler(3, 3, (x, z) -> chunks.addIfMissing(world.getChunkAt(x + cx, z + cz))).drain();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
new Spiraler(3, 3, (x, z) -> chunks.addIfMissing(world.getChunkAt(x + bx, z + bz))).drain();
|
||||||
|
sender().sendMessage("Capturing IGenData from " + chunks.size() + " nearby chunks.");
|
||||||
|
try {
|
||||||
|
File ff = Iris.instance.getDataFile("reports/" + M.ms() + ".txt");
|
||||||
|
PrintWriter pw = new PrintWriter(ff);
|
||||||
|
pw.println("=== Iris Chunk Report ===");
|
||||||
|
pw.println("== General Info ==");
|
||||||
|
pw.println("Iris Version: " + Iris.instance.getDescription().getVersion());
|
||||||
|
pw.println("Bukkit Version: " + Bukkit.getBukkitVersion());
|
||||||
|
pw.println("MC Version: " + Bukkit.getVersion());
|
||||||
|
pw.println("PaperSpigot: " + (BukkitPlatform.isPaperServer() ? "Yup!" : "Nope!"));
|
||||||
|
pw.println("Report Captured At: " + new Date());
|
||||||
|
pw.println("Chunks: (" + chunks.size() + "): ");
|
||||||
|
|
||||||
|
for (Chunk i : chunks) {
|
||||||
|
pw.println("- [" + i.getX() + ", " + i.getZ() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
int regions = 0;
|
||||||
|
long size = 0;
|
||||||
|
String age = "No idea...";
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (File i : Objects.requireNonNull(new File(world.getWorldFolder(), "region").listFiles())) {
|
||||||
|
if (i.isFile()) {
|
||||||
|
size += i.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileTime creationTime = (FileTime) Files.getAttribute(world.getWorldFolder().toPath(), "creationTime");
|
||||||
|
age = hrf(Duration.of(M.ms() - creationTime.toMillis(), ChronoUnit.MILLIS));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
KList<String> biomes = new KList<>();
|
||||||
|
KList<String> caveBiomes = new KList<>();
|
||||||
|
KMap<String, KMap<String, KList<String>>> objects = new KMap<>();
|
||||||
|
|
||||||
|
for (Chunk i : chunks) {
|
||||||
|
for (int j = 0; j < 16; j += 3) {
|
||||||
|
|
||||||
|
for (int k = 0; k < 16; k += 3) {
|
||||||
|
|
||||||
|
assert engine() != null;
|
||||||
|
IrisBiome bb = engine().getSurfaceBiome((i.getX() * 16) + j, (i.getZ() * 16) + k);
|
||||||
|
IrisBiome bxf = engine().getCaveBiome((i.getX() * 16) + j, (i.getZ() * 16) + k);
|
||||||
|
biomes.addIfMissing(bb.getName() + " [" + Form.capitalize(bb.getInferredType().name().toLowerCase()) + "] " + " (" + bb.getLoadFile().getName() + ")");
|
||||||
|
caveBiomes.addIfMissing(bxf.getName() + " (" + bxf.getLoadFile().getName() + ")");
|
||||||
|
exportObjects(bb, pw, engine(), objects);
|
||||||
|
exportObjects(bxf, pw, engine(), objects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
regions = Objects.requireNonNull(new File(world.getWorldFolder().getPath() + "/region").list()).length;
|
||||||
|
|
||||||
|
pw.println();
|
||||||
|
pw.println("== World Info ==");
|
||||||
|
pw.println("World Name: " + world.getName());
|
||||||
|
pw.println("Age: " + age);
|
||||||
|
pw.println("Folder: " + world.getWorldFolder().getPath());
|
||||||
|
pw.println("Regions: " + Form.f(regions));
|
||||||
|
pw.println("Chunks: max. " + Form.f(regions * 32 * 32));
|
||||||
|
pw.println("World Size: min. " + Form.fileSize(size));
|
||||||
|
pw.println();
|
||||||
|
pw.println("== Biome Info ==");
|
||||||
|
pw.println("Found " + biomes.size() + " Biome(s): ");
|
||||||
|
|
||||||
|
for (String i : biomes) {
|
||||||
|
pw.println("- " + i);
|
||||||
|
}
|
||||||
|
pw.println();
|
||||||
|
|
||||||
|
pw.println("== Object Info ==");
|
||||||
|
|
||||||
|
for (String i : objects.k()) {
|
||||||
|
pw.println("- " + i);
|
||||||
|
|
||||||
|
for (String j : objects.get(i).k()) {
|
||||||
|
pw.println(" @ " + j);
|
||||||
|
|
||||||
|
for (String k : objects.get(i).get(j)) {
|
||||||
|
pw.println(" * " + k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.println();
|
||||||
|
pw.close();
|
||||||
|
|
||||||
|
sender().sendMessage("Reported to: " + ff.getPath());
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Iris.reportError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportObjects(IrisBiome bb, PrintWriter pw, Engine g, KMap<String, KMap<String, KList<String>>> objects) {
|
||||||
|
String n1 = bb.getName() + " [" + Form.capitalize(bb.getInferredType().name().toLowerCase()) + "] " + " (" + bb.getLoadFile().getName() + ")";
|
||||||
|
int m = 0;
|
||||||
|
KSet<String> stop = new KSet<>();
|
||||||
|
for (IrisObjectPlacement f : bb.getObjects()) {
|
||||||
|
m++;
|
||||||
|
String n2 = "Placement #" + m + " (" + f.getPlace().size() + " possible objects)";
|
||||||
|
|
||||||
|
for (String i : f.getPlace()) {
|
||||||
|
String nn3 = i + ": [ERROR] Failed to find object!";
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (stop.contains(i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
File ff = g.getData().getObjectLoader().findFile(i);
|
||||||
|
art.arcane.iris.util.common.math.IrisBlockVector sz = IrisObject.sampleSize(ff);
|
||||||
|
nn3 = i + ": size=[" + sz.getBlockX() + "," + sz.getBlockY() + "," + sz.getBlockZ() + "] location=[" + ff.getPath() + "]";
|
||||||
|
stop.add(i);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
String n3 = nn3;
|
||||||
|
objects.computeIfAbsent(n1, (k1) -> new KMap<>())
|
||||||
|
.computeIfAbsent(n2, (k) -> new KList<>()).addIfMissing(n3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if server GUIs are not enabled
|
||||||
|
*/
|
||||||
|
private boolean noGUI() {
|
||||||
|
if (!IrisSettings.get().getGui().isUseServerLaunchedGuis()) {
|
||||||
|
sender().sendMessage(C.RED + "You must have server launched GUIs enabled in the settings!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if no studio is open or the player is not in one
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void files(File clean, KList<File> files) {
|
||||||
|
if (clean.isDirectory()) {
|
||||||
|
for (File i : clean.listFiles()) {
|
||||||
|
files(i, files);
|
||||||
|
}
|
||||||
|
} else if (clean.getName().endsWith(".json")) {
|
||||||
|
try {
|
||||||
|
files.add(clean);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
Iris.error("Failed to beautify " + clean.getAbsolutePath() + " You may have errors in your json!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fixBlocks(JSONObject obj) {
|
||||||
|
for (String i : obj.keySet()) {
|
||||||
|
Object o = obj.get(i);
|
||||||
|
|
||||||
|
if (i.equals("block") && o instanceof String && !o.toString().trim().isEmpty() && !o.toString().contains(":")) {
|
||||||
|
obj.put(i, "minecraft:" + o);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (o instanceof JSONObject) {
|
||||||
|
fixBlocks((JSONObject) o);
|
||||||
|
} else if (o instanceof JSONArray) {
|
||||||
|
fixBlocks((JSONArray) o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fixBlocks(JSONArray obj) {
|
||||||
|
for (int i = 0; i < obj.length(); i++) {
|
||||||
|
Object o = obj.get(i);
|
||||||
|
|
||||||
|
if (o instanceof JSONObject) {
|
||||||
|
fixBlocks((JSONObject) o);
|
||||||
|
} else if (o instanceof JSONArray) {
|
||||||
|
fixBlocks((JSONArray) o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
* 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.platform.bukkit.BukkitBlockResolution;
|
||||||
|
|
||||||
|
import art.arcane.iris.platform.bukkit.BukkitPlatform;
|
||||||
|
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.platform.EngineBukkitOps;
|
||||||
|
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.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.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@Director(name = "what", origin = DirectorOrigin.PLAYER, description = "Iris What?")
|
||||||
|
public class CommandWhat implements DirectorExecutor {
|
||||||
|
@Director(description = "What is in my hand?", origin = DirectorOrigin.PLAYER)
|
||||||
|
public void hand() {
|
||||||
|
try {
|
||||||
|
BlockData bd = player().getInventory().getItemInMainHand().getType().createBlockData();
|
||||||
|
if (!bd.getMaterial().equals(Material.AIR)) {
|
||||||
|
sender().sendMessage("Material: " + C.GREEN + bd.getMaterial().name());
|
||||||
|
sender().sendMessage("Full: " + C.WHITE + bd.getAsString(true));
|
||||||
|
} else {
|
||||||
|
sender().sendMessage("Please hold a block/item");
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
Material bd = player().getInventory().getItemInMainHand().getType();
|
||||||
|
if (!bd.equals(Material.AIR)) {
|
||||||
|
sender().sendMessage("Material: " + C.GREEN + bd.name());
|
||||||
|
} else {
|
||||||
|
sender().sendMessage("Please hold a block/item");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
Biome derivative = b.getDerivative();
|
||||||
|
NamespacedKey derivativeKey = resolveBiomeKey(derivative);
|
||||||
|
sender().sendMessage("IBiome: " + b.getLoadKey() + " (" + (derivativeKey == null ? "unregistered" : derivativeKey.getKey()) + ")");
|
||||||
|
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
Biome biome = player().getLocation().getBlock().getBiome();
|
||||||
|
NamespacedKey key = resolveBiomeKey(biome);
|
||||||
|
sender().sendMessage("Non-Iris Biome: " + (key == null ? "unregistered" : key));
|
||||||
|
|
||||||
|
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) {
|
||||||
|
Iris.reportError(ee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "What region am i in?", origin = DirectorOrigin.PLAYER)
|
||||||
|
public void region() {
|
||||||
|
try {
|
||||||
|
Chunk chunk = world().getChunkAt(player().getLocation().getBlockX() >> 4, player().getLocation().getBlockZ() >> 4);
|
||||||
|
IrisRegion r = EngineBukkitOps.getRegion(engine(), chunk);
|
||||||
|
sender().sendMessage("IRegion: " + r.getLoadKey() + " (" + r.getName() + ")");
|
||||||
|
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
sender().sendMessage(C.IRIS + "Iris worlds only.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Director(description = "What block am i looking at?", origin = DirectorOrigin.PLAYER)
|
||||||
|
public void block() {
|
||||||
|
BlockData bd;
|
||||||
|
try {
|
||||||
|
bd = player().getTargetBlockExact(128, FluidCollisionMode.NEVER).getBlockData();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
sender().sendMessage("Please look at any block, not at the sky");
|
||||||
|
bd = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bd != null) {
|
||||||
|
sender().sendMessage("Material: " + C.GREEN + bd.getMaterial().name());
|
||||||
|
sender().sendMessage("Full: " + C.WHITE + bd.getAsString(true));
|
||||||
|
|
||||||
|
if (BukkitBlockResolution.isStorage(bd)) {
|
||||||
|
sender().sendMessage(C.YELLOW + "* Storage Block (Loot Capable)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BukkitBlockResolution.isLit(bd)) {
|
||||||
|
sender().sendMessage(C.YELLOW + "* Lit Block (Light Capable)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BukkitBlockResolution.isFoliage(bd)) {
|
||||||
|
sender().sendMessage(C.YELLOW + "* Foliage Block");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BukkitBlockResolution.isDecorant(bd)) {
|
||||||
|
sender().sendMessage(C.YELLOW + "* Decorant Block");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BukkitBlockResolution.isFluid(bd)) {
|
||||||
|
sender().sendMessage(C.YELLOW + "* Fluid Block");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BukkitBlockResolution.isFoliagePlantable(bd)) {
|
||||||
|
sender().sendMessage(C.YELLOW + "* Plantable Foliage Block");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BukkitBlockResolution.isSolid(bd)) {
|
||||||
|
sender().sendMessage(C.YELLOW + "* Solid Block");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
|
||||||
|
if (IrisToolbelt.isIrisWorld(c.getWorld())) {
|
||||||
|
int m = 1;
|
||||||
|
AtomicInteger v = new AtomicInteger(0);
|
||||||
|
|
||||||
|
for (int xxx = c.getX() - 4; xxx <= c.getX() + 4; xxx++) {
|
||||||
|
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) -> BukkitPlatform.toLocation(i, c.getWorld())).forEach((i) -> {
|
||||||
|
BlockSignal.of(i.getWorld(), i.getBlockX(), i.getBlockY(), i.getBlockZ(), 100);
|
||||||
|
v.incrementAndGet();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sender().sendMessage("Found " + v.get() + " Nearby Markers (" + marker + ")");
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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.edit;
|
||||||
|
|
||||||
|
import org.bukkit.block.Biome;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
public interface BlockEditor extends Closeable {
|
||||||
|
long last();
|
||||||
|
|
||||||
|
void set(int x, int y, int z, BlockData d);
|
||||||
|
|
||||||
|
BlockData get(int x, int y, int z);
|
||||||
|
|
||||||
|
void setBiome(int x, int z, Biome b);
|
||||||
|
|
||||||
|
void setBiome(int x, int y, int z, Biome b);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void close();
|
||||||
|
|
||||||
|
Biome getBiome(int x, int y, int z);
|
||||||
|
|
||||||
|
Biome getBiome(int x, int z);
|
||||||
|
}
|
||||||
@@ -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.edit;
|
||||||
|
|
||||||
|
import art.arcane.volmlib.util.math.M;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Biome;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
|
||||||
|
@SuppressWarnings("ClassCanBeRecord")
|
||||||
|
public class BukkitBlockEditor implements BlockEditor {
|
||||||
|
private final World world;
|
||||||
|
|
||||||
|
public BukkitBlockEditor(World world) {
|
||||||
|
this.world = world;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(int x, int y, int z, BlockData d) {
|
||||||
|
world.getBlockAt(x, y, z).setBlockData(d, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockData get(int x, int y, int z) {
|
||||||
|
return world.getBlockAt(x, y, z).getBlockData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long last() {
|
||||||
|
return M.ms();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBiome(int x, int z, Biome b) {
|
||||||
|
int minHeight = world.getMinHeight();
|
||||||
|
int maxHeight = world.getMaxHeight();
|
||||||
|
for (int y = minHeight; y < maxHeight; y++) {
|
||||||
|
world.setBiome(x, y, z, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBiome(int x, int y, int z, Biome b) {
|
||||||
|
world.setBiome(x, y, z, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int y, int z) {
|
||||||
|
return world.getBiome(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int z) {
|
||||||
|
return world.getBiome(x, world.getMinHeight(), z);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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.events.IrisEngineHotloadEvent;
|
||||||
|
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public final class BukkitGuiHost implements GuiHost.Provider {
|
||||||
|
private final Map<Runnable, Listener> hotloadHooks = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static void install() {
|
||||||
|
GuiHost.set(new BukkitGuiHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Engine findActiveEngine() {
|
||||||
|
try {
|
||||||
|
for (World world : new ArrayList<>(Bukkit.getWorlds())) {
|
||||||
|
try {
|
||||||
|
PlatformChunkGenerator access = IrisToolbelt.access(world);
|
||||||
|
if (access != null && access.getEngine() != null && !access.getEngine().isClosed()) {
|
||||||
|
return access.getEngine();
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerHotloadHook(Runnable onHotload) {
|
||||||
|
Listener listener = new HotloadListener(onHotload);
|
||||||
|
hotloadHooks.put(onHotload, listener);
|
||||||
|
Iris.instance.registerListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregisterHotloadHook(Runnable onHotload) {
|
||||||
|
Listener listener = hotloadHooks.remove(onHotload);
|
||||||
|
if (listener != null) {
|
||||||
|
Iris.instance.unregisterListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GuiOverlay overlayFor(Engine engine) {
|
||||||
|
return engine == null ? null : new BukkitVisionOverlay(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class HotloadListener implements Listener {
|
||||||
|
private final Runnable onHotload;
|
||||||
|
|
||||||
|
private HotloadListener(Runnable onHotload) {
|
||||||
|
this.onHotload = onHotload;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void on(IrisEngineHotloadEvent event) {
|
||||||
|
onHotload.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* 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.engine.IrisComplex;
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.framework.render.RenderType;
|
||||||
|
import art.arcane.iris.engine.object.IrisWorld;
|
||||||
|
import art.arcane.iris.util.common.scheduling.J;
|
||||||
|
import art.arcane.volmlib.util.format.Form;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static art.arcane.iris.util.common.data.registry.Attributes.MAX_HEALTH;
|
||||||
|
|
||||||
|
public final class BukkitVisionOverlay implements GuiOverlay {
|
||||||
|
private final Engine engine;
|
||||||
|
|
||||||
|
public BukkitVisionOverlay(Engine engine) {
|
||||||
|
this.engine = engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<GuiMarker> players() {
|
||||||
|
IrisWorld world = engine.getWorld();
|
||||||
|
List<GuiMarker> markers = new ArrayList<>();
|
||||||
|
for (Player player : world.getPlayers()) {
|
||||||
|
markers.add(GuiMarker.player(player.getName(), player.getLocation().getX(), player.getLocation().getZ()));
|
||||||
|
}
|
||||||
|
return markers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestEntities(Consumer<List<GuiMarker>> sink) {
|
||||||
|
J.s(() -> {
|
||||||
|
IrisWorld world = engine.getWorld();
|
||||||
|
List<GuiMarker> markers = new ArrayList<>();
|
||||||
|
for (LivingEntity entity : world.getEntitiesByClass(LivingEntity.class)) {
|
||||||
|
if (entity instanceof Player) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String label = Form.capitalizeWords(entity.getType().name().toLowerCase(Locale.ROOT).replaceAll("\\Q_\\E", " "));
|
||||||
|
double maxHealth = 0;
|
||||||
|
try {
|
||||||
|
maxHealth = entity.getAttribute(MAX_HEALTH).getValue();
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
markers.add(GuiMarker.entity(label, entity.getLocation().getX(), entity.getLocation().getY(), entity.getLocation().getZ(),
|
||||||
|
entity.getHealth(), maxHealth));
|
||||||
|
}
|
||||||
|
sink.accept(markers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void teleport(double worldX, double worldZ) {
|
||||||
|
IrisWorld world = engine.getWorld();
|
||||||
|
if (!world.hasRealWorld()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
J.s(() -> {
|
||||||
|
List<Player> players = world.getPlayers();
|
||||||
|
if (players.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Player player = players.get(0);
|
||||||
|
int xx = (int) worldX;
|
||||||
|
int zz = (int) worldZ;
|
||||||
|
int yy = player.getWorld().getHighestBlockYAt(xx, zz) + 1;
|
||||||
|
player.teleport(new Location(player.getWorld(), xx, yy, zz));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String openInEditor(double worldX, double worldZ, RenderType type) {
|
||||||
|
IrisComplex complex = engine.getComplex();
|
||||||
|
File file = switch (type) {
|
||||||
|
case BIOME, LAYER_LOAD, DECORATOR_LOAD, OBJECT_LOAD, HEIGHT ->
|
||||||
|
complex.getTrueBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||||
|
case BIOME_LAND -> complex.getLandBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||||
|
case BIOME_SEA -> complex.getSeaBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||||
|
case REGION -> complex.getRegionStream().get(worldX, worldZ).openInVSCode();
|
||||||
|
case CAVE_LAND -> complex.getCaveBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
return file == null ? null : file.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* 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 art.arcane.iris.Iris;
|
||||||
|
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||||
|
import art.arcane.iris.engine.object.IrisBiome;
|
||||||
|
import art.arcane.iris.engine.platform.EngineBukkitOps;
|
||||||
|
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||||
|
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
// See/update https://app.gitbook.com/@volmitsoftware/s/iris/compatability/papi/
|
||||||
|
public class IrisPapiExpansion extends PlaceholderExpansion {
|
||||||
|
@Override
|
||||||
|
public @NotNull String getIdentifier() {
|
||||||
|
return "iris";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getAuthor() {
|
||||||
|
return "Volmit Software";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getVersion() {
|
||||||
|
return Iris.instance.getDescription().getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean persist() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String onRequest(OfflinePlayer player, String p) {
|
||||||
|
Location l = null;
|
||||||
|
PlatformChunkGenerator a = null;
|
||||||
|
|
||||||
|
if (player.isOnline() && player.getPlayer() != null) {
|
||||||
|
l = player.getPlayer().getLocation().add(0, 2, 0);
|
||||||
|
a = IrisToolbelt.access(l.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.equalsIgnoreCase("biome_name")) {
|
||||||
|
if (a != null) {
|
||||||
|
return getBiome(a, l).getName();
|
||||||
|
}
|
||||||
|
} else if (p.equalsIgnoreCase("biome_id")) {
|
||||||
|
if (a != null) {
|
||||||
|
return getBiome(a, l).getLoadKey();
|
||||||
|
}
|
||||||
|
} else if (p.equalsIgnoreCase("biome_file")) {
|
||||||
|
if (a != null) {
|
||||||
|
return getBiome(a, l).getLoadFile().getPath();
|
||||||
|
}
|
||||||
|
} else if (p.equalsIgnoreCase("region_name")) {
|
||||||
|
if (a != null) {
|
||||||
|
return EngineBukkitOps.getRegion(a.getEngine(), l).getName();
|
||||||
|
}
|
||||||
|
} else if (p.equalsIgnoreCase("region_id")) {
|
||||||
|
if (a != null) {
|
||||||
|
return EngineBukkitOps.getRegion(a.getEngine(), l).getLoadKey();
|
||||||
|
}
|
||||||
|
} else if (p.equalsIgnoreCase("region_file")) {
|
||||||
|
if (a != null) {
|
||||||
|
return EngineBukkitOps.getRegion(a.getEngine(), l).getLoadFile().getPath();
|
||||||
|
}
|
||||||
|
} else if (p.equalsIgnoreCase("terrain_slope")) {
|
||||||
|
if (a != null) {
|
||||||
|
return (a.getEngine())
|
||||||
|
.getComplex().getSlopeStream()
|
||||||
|
.get(l.getX(), l.getZ()) + "";
|
||||||
|
}
|
||||||
|
} else if (p.equalsIgnoreCase("terrain_height")) {
|
||||||
|
if (a != null) {
|
||||||
|
return Math.round(a.getEngine().getHeight(l.getBlockX(), l.getBlockZ())) + "";
|
||||||
|
}
|
||||||
|
} else if (p.equalsIgnoreCase("world_mode")) {
|
||||||
|
if (a != null) {
|
||||||
|
return a.isStudio() ? "Studio" : "Production";
|
||||||
|
}
|
||||||
|
} else if (p.equalsIgnoreCase("world_seed")) {
|
||||||
|
if (a != null) {
|
||||||
|
return a.getEngine().getSeedManager().getSeed() + "";
|
||||||
|
}
|
||||||
|
} else if (p.equalsIgnoreCase("world_speed")) {
|
||||||
|
if (a != null) {
|
||||||
|
return a.getEngine().getGeneratedPerSecond() + "/s";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisBiome getBiome(PlatformChunkGenerator a, Location l) {
|
||||||
|
return a.getEngine().getBiome(l.getBlockX(), l.getBlockY() - l.getWorld().getMinHeight(), l.getBlockZ());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
/*
|
||||||
|
* 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.service;
|
||||||
|
|
||||||
|
import art.arcane.iris.Iris;
|
||||||
|
import art.arcane.iris.core.IrisSettings;
|
||||||
|
import art.arcane.iris.core.commands.CommandIris;
|
||||||
|
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||||
|
import art.arcane.iris.util.common.director.DirectorContext;
|
||||||
|
import art.arcane.iris.util.common.director.DirectorContextHandler;
|
||||||
|
import art.arcane.iris.util.common.director.DirectorSystem;
|
||||||
|
import art.arcane.iris.util.common.format.C;
|
||||||
|
import art.arcane.iris.util.common.plugin.IrisService;
|
||||||
|
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||||
|
import art.arcane.iris.util.common.scheduling.J;
|
||||||
|
import art.arcane.volmlib.util.director.compat.DirectorEngineFactory;
|
||||||
|
import art.arcane.volmlib.util.director.context.DirectorContextRegistry;
|
||||||
|
import art.arcane.volmlib.util.director.runtime.DirectorExecutionMode;
|
||||||
|
import art.arcane.volmlib.util.director.runtime.DirectorExecutionResult;
|
||||||
|
import art.arcane.volmlib.util.director.runtime.DirectorInvocation;
|
||||||
|
import art.arcane.volmlib.util.director.runtime.DirectorInvocationHook;
|
||||||
|
import art.arcane.volmlib.util.director.runtime.DirectorRuntimeEngine;
|
||||||
|
import art.arcane.volmlib.util.director.runtime.DirectorRuntimeNode;
|
||||||
|
import art.arcane.volmlib.util.director.runtime.DirectorSender;
|
||||||
|
import art.arcane.volmlib.util.director.visual.DirectorVisualCommand;
|
||||||
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.PluginCommand;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, DirectorInvocationHook {
|
||||||
|
private static final String ROOT_COMMAND = "iris";
|
||||||
|
private static final String ROOT_PERMISSION = "iris.all";
|
||||||
|
|
||||||
|
private final transient AtomicCache<DirectorRuntimeEngine> directorCache = new AtomicCache<>();
|
||||||
|
private final transient AtomicCache<DirectorVisualCommand> helpCache = new AtomicCache<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
PluginCommand command = findBukkitCommand();
|
||||||
|
if (command != null) {
|
||||||
|
command.setExecutor(this);
|
||||||
|
command.setTabCompleter(this);
|
||||||
|
} else {
|
||||||
|
registerPaperCommand();
|
||||||
|
}
|
||||||
|
J.a(this::getDirector);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirectorRuntimeEngine getDirector() {
|
||||||
|
return directorCache.aquireNastyPrint(() -> DirectorEngineFactory.create(
|
||||||
|
new CommandIris(),
|
||||||
|
null,
|
||||||
|
buildDirectorContexts(),
|
||||||
|
this::dispatchDirector,
|
||||||
|
this,
|
||||||
|
DirectorSystem.handlers
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private DirectorContextRegistry buildDirectorContexts() {
|
||||||
|
DirectorContextRegistry contexts = new DirectorContextRegistry();
|
||||||
|
|
||||||
|
for (Map.Entry<Class<?>, DirectorContextHandler<?>> entry : DirectorContextHandler.contextHandlers.entrySet()) {
|
||||||
|
registerContextHandler(contexts, entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return contexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
|
private void registerContextHandler(DirectorContextRegistry contexts, Class<?> type, DirectorContextHandler<?> handler) {
|
||||||
|
contexts.register((Class) type, (invocation, map) -> {
|
||||||
|
if (invocation.getSender() instanceof BukkitDirectorSender sender) {
|
||||||
|
return ((DirectorContextHandler) handler).handle(new VolmitSender(sender.sender()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dispatchDirector(DirectorExecutionMode mode, Runnable runnable) {
|
||||||
|
if (mode == DirectorExecutionMode.SYNC) {
|
||||||
|
J.s(runnable);
|
||||||
|
} else {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeInvoke(DirectorInvocation invocation, DirectorRuntimeNode node) {
|
||||||
|
if (invocation.getSender() instanceof BukkitDirectorSender sender) {
|
||||||
|
DirectorContext.touch(new VolmitSender(sender.sender()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterInvoke(DirectorInvocation invocation, DirectorRuntimeNode node) {
|
||||||
|
DirectorContext.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
|
||||||
|
if (!command.getName().equalsIgnoreCase(ROOT_COMMAND)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return tabCompleteRoot(sender, alias, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!command.getName().equalsIgnoreCase(ROOT_COMMAND)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
executeRoot(sender, label, args);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void executeRoot(CommandSender sender, String label, String[] args) {
|
||||||
|
if (!sender.hasPermission(ROOT_PERMISSION)) {
|
||||||
|
sender.sendMessage("You lack the Permission '" + ROOT_PERMISSION + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
J.aBukkit(() -> executeCommand(sender, label, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> tabCompleteRoot(CommandSender sender, String alias, String[] args) {
|
||||||
|
List<String> suggestions = runDirectorTab(sender, alias, args);
|
||||||
|
if (sender instanceof Player player && IrisSettings.get().getGeneral().isCommandSounds()) {
|
||||||
|
player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.25f, RNG.r.f(0.125f, 1.95f));
|
||||||
|
}
|
||||||
|
return suggestions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PluginCommand findBukkitCommand() {
|
||||||
|
try {
|
||||||
|
return Iris.instance.getCommand(ROOT_COMMAND);
|
||||||
|
} catch (UnsupportedOperationException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerPaperCommand() {
|
||||||
|
try {
|
||||||
|
Class<?> registrarType = Class.forName(
|
||||||
|
"art.arcane.iris.core.service.PaperCommandRegistrar",
|
||||||
|
true,
|
||||||
|
getClass().getClassLoader()
|
||||||
|
);
|
||||||
|
Method register = registrarType.getDeclaredMethod("register", Iris.class, CommandSVC.class);
|
||||||
|
register.invoke(null, Iris.instance, this);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
Throwable cause = e.getCause() == null ? e : e.getCause();
|
||||||
|
throw new IllegalStateException("Paper command registration failed", cause);
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new IllegalStateException("Paper command registrar is unavailable", e);
|
||||||
|
} catch (LinkageError e) {
|
||||||
|
throw new IllegalStateException("Paper command APIs are unavailable", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeCommand(CommandSender sender, String label, String[] args) {
|
||||||
|
if (sendHelpIfRequested(sender, args)) {
|
||||||
|
playSuccessSound(sender);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectorExecutionResult result = runDirector(sender, label, args);
|
||||||
|
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
playSuccessSound(sender);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
playFailureSound(sender);
|
||||||
|
if (result.getMessage() == null || result.getMessage().trim().isEmpty()) {
|
||||||
|
new VolmitSender(sender).sendMessage(C.RED + "Unknown Iris Command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean sendHelpIfRequested(CommandSender sender, String[] args) {
|
||||||
|
Optional<DirectorVisualCommand.HelpRequest> request = DirectorVisualCommand.resolveHelp(getHelpRoot(), Arrays.asList(args));
|
||||||
|
if (request.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
VolmitSender volmitSender = new VolmitSender(sender);
|
||||||
|
volmitSender.sendDirectorHelp(request.get().command(), request.get().page());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DirectorVisualCommand getHelpRoot() {
|
||||||
|
return helpCache.aquireNastyPrint(() -> DirectorVisualCommand.createRoot(getDirector()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private DirectorExecutionResult runDirector(CommandSender sender, String label, String[] args) {
|
||||||
|
try {
|
||||||
|
return getDirector().execute(new DirectorInvocation(new BukkitDirectorSender(sender), label, Arrays.asList(args)));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.warn("Director command execution failed: " + e.getClass().getSimpleName() + " " + e.getMessage());
|
||||||
|
return DirectorExecutionResult.notHandled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> runDirectorTab(CommandSender sender, String alias, String[] args) {
|
||||||
|
DirectorContext.touch(new VolmitSender(sender));
|
||||||
|
try {
|
||||||
|
return getDirector().tabComplete(new DirectorInvocation(new BukkitDirectorSender(sender), alias, Arrays.asList(args)));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.warn("Director tab completion failed: " + e.getClass().getSimpleName() + " " + e.getMessage());
|
||||||
|
return List.of();
|
||||||
|
} finally {
|
||||||
|
DirectorContext.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void playFailureSound(CommandSender sender) {
|
||||||
|
if (!IrisSettings.get().getGeneral().isCommandSounds()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sender instanceof Player player) {
|
||||||
|
J.s(() -> playSounds(player, Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 0.25f, Sound.BLOCK_BEACON_DEACTIVATE, 0.2f, 0.45f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void playSuccessSound(CommandSender sender) {
|
||||||
|
if (!IrisSettings.get().getGeneral().isCommandSounds()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sender instanceof Player player) {
|
||||||
|
J.s(() -> playSounds(player, Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 1.65f, Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.125f, 2.99f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final java.util.concurrent.atomic.AtomicBoolean SOUND_LOGGED = new java.util.concurrent.atomic.AtomicBoolean(false);
|
||||||
|
|
||||||
|
private void playSounds(Player player, Sound a, float av, float ap, Sound b, float bv, float bp) {
|
||||||
|
try {
|
||||||
|
player.playSound(player.getLocation(), a, av, ap);
|
||||||
|
player.playSound(player.getLocation(), b, bv, bp);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (SOUND_LOGGED.compareAndSet(false, true)) {
|
||||||
|
try {
|
||||||
|
java.io.File f = Iris.instance.getDataFile("adventure-debug.txt");
|
||||||
|
java.nio.file.Files.writeString(f.toPath(), "SOUND FAIL: " + e.getClass().getName() + ": " + e.getMessage() + "\n", java.nio.file.StandardOpenOption.CREATE, java.nio.file.StandardOpenOption.APPEND);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record BukkitDirectorSender(CommandSender sender) implements DirectorSender {
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return sender.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlayer() {
|
||||||
|
return sender instanceof Player;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(String message) {
|
||||||
|
if (message != null && !message.trim().isEmpty()) {
|
||||||
|
sender.sendMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* 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.service;
|
||||||
|
|
||||||
|
import art.arcane.iris.Iris;
|
||||||
|
import art.arcane.iris.core.edit.BlockEditor;
|
||||||
|
import art.arcane.iris.core.edit.BukkitBlockEditor;
|
||||||
|
import art.arcane.iris.engine.framework.BlockEditAccess;
|
||||||
|
import art.arcane.volmlib.util.collection.KMap;
|
||||||
|
import art.arcane.volmlib.util.math.M;
|
||||||
|
import art.arcane.iris.util.common.plugin.IrisService;
|
||||||
|
import art.arcane.iris.util.common.scheduling.J;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Biome;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.world.WorldUnloadEvent;
|
||||||
|
|
||||||
|
public class EditSVC implements IrisService, BlockEditAccess<World, BlockData, Biome> {
|
||||||
|
private KMap<World, BlockEditor> editors;
|
||||||
|
private int updateTaskId = -1;
|
||||||
|
public static boolean deletingWorld = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
this.editors = new KMap<>();
|
||||||
|
updateTaskId = J.sr(this::update, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
if (updateTaskId != -1) {
|
||||||
|
J.csr(updateTaskId);
|
||||||
|
updateTaskId = -1;
|
||||||
|
}
|
||||||
|
if (editors == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
flushNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockData get(World world, int x, int y, int z) {
|
||||||
|
return open(world).get(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(World world, int x, int y, int z, BlockData d) {
|
||||||
|
open(world).set(x, y, z, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBiome(World world, int x, int y, int z, Biome d) {
|
||||||
|
open(world).setBiome(x, y, z, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBiome(World world, int x, int z, Biome d) {
|
||||||
|
open(world).setBiome(x, z, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Biome getBiome(World world, int x, int y, int z) {
|
||||||
|
return open(world).getBiome(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Biome getBiome(World world, int x, int z) {
|
||||||
|
return open(world).getBiome(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void on(WorldUnloadEvent e) {
|
||||||
|
if (editors == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (editors.containsKey(e.getWorld()) && !deletingWorld) {
|
||||||
|
editors.remove(e.getWorld()).close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void update() {
|
||||||
|
if (editors == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (World i : editors.k()) {
|
||||||
|
if (M.ms() - editors.get(i).last() > 1000) {
|
||||||
|
editors.remove(i).close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flushNow() {
|
||||||
|
if (editors == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (World i : editors.k()) {
|
||||||
|
editors.remove(i).close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockEditor open(World world) {
|
||||||
|
if (editors == null) {
|
||||||
|
editors = new KMap<>();
|
||||||
|
}
|
||||||
|
if (editors.containsKey(world)) {
|
||||||
|
return editors.get(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockEditor e = new BukkitBlockEditor(world);
|
||||||
|
editors.put(world, e);
|
||||||
|
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,453 @@
|
|||||||
|
package art.arcane.iris.core.service;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.AtomicDouble;
|
||||||
|
import art.arcane.iris.spi.IrisLogging;
|
||||||
|
import art.arcane.iris.spi.IrisServices;
|
||||||
|
import art.arcane.iris.core.IrisSettings;
|
||||||
|
import art.arcane.iris.core.loader.ResourceLoader;
|
||||||
|
import art.arcane.iris.core.runtime.GoldenHashScanner;
|
||||||
|
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.KMap;
|
||||||
|
import art.arcane.iris.util.common.format.C;
|
||||||
|
import art.arcane.volmlib.util.format.Form;
|
||||||
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
|
import art.arcane.iris.util.common.plugin.IrisService;
|
||||||
|
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||||
|
import art.arcane.volmlib.util.scheduling.Looper;
|
||||||
|
import art.arcane.iris.util.project.stream.utility.CachedDoubleStream2D;
|
||||||
|
import art.arcane.iris.util.project.stream.utility.CachedStream2D;
|
||||||
|
import art.arcane.iris.util.project.stream.utility.CachedStream3D;
|
||||||
|
import art.arcane.iris.core.gui.PregeneratorJob;
|
||||||
|
import art.arcane.iris.core.pregenerator.MantleHeapPressure;
|
||||||
|
import lombok.Synchronized;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.world.WorldLoadEvent;
|
||||||
|
import org.bukkit.event.world.WorldUnloadEvent;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
public class IrisEngineSVC implements IrisService {
|
||||||
|
private static final int TRIM_PERIOD = 2_000;
|
||||||
|
private final AtomicInteger tectonicLimit = new AtomicInteger(30);
|
||||||
|
private final AtomicInteger tectonicPlates = new AtomicInteger();
|
||||||
|
private final AtomicInteger queuedTectonicPlates = new AtomicInteger();
|
||||||
|
private final AtomicInteger trimmerAlive = new AtomicInteger();
|
||||||
|
private final AtomicInteger unloaderAlive = new AtomicInteger();
|
||||||
|
private final AtomicInteger totalWorlds = new AtomicInteger();
|
||||||
|
private final AtomicDouble maxIdleDuration = new AtomicDouble();
|
||||||
|
private final AtomicDouble minIdleDuration = new AtomicDouble();
|
||||||
|
private final AtomicLong loadedChunks = new AtomicLong();
|
||||||
|
private final KMap<World, Registered> worlds = new KMap<>();
|
||||||
|
private ScheduledExecutorService service;
|
||||||
|
private Looper updateTicker;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
var settings = IrisSettings.get().getPerformance();
|
||||||
|
var engine = settings.getEngineSVC();
|
||||||
|
service = Executors.newScheduledThreadPool(0,
|
||||||
|
(engine.isUseVirtualThreads()
|
||||||
|
? Thread.ofVirtual()
|
||||||
|
: Thread.ofPlatform().priority(engine.getPriority()))
|
||||||
|
.name("Iris EngineSVC-", 0)
|
||||||
|
.factory());
|
||||||
|
tectonicLimit.set(settings.getTectonicPlateSize());
|
||||||
|
Bukkit.getWorlds().forEach(this::add);
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
for (World world : worlds.keySet()) {
|
||||||
|
PlatformChunkGenerator gen = IrisToolbelt.access(world);
|
||||||
|
if (gen == null) continue;
|
||||||
|
try {
|
||||||
|
gen.close();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
IrisLogging.reportError(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (service != null) {
|
||||||
|
service.shutdown();
|
||||||
|
}
|
||||||
|
if (updateTicker != null) {
|
||||||
|
updateTicker.interrupt();
|
||||||
|
}
|
||||||
|
worlds.keySet().forEach(this::remove);
|
||||||
|
worlds.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQueuedTectonicPlateCount() {
|
||||||
|
return queuedTectonicPlates.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getAverageIdleDuration() {
|
||||||
|
double min = minIdleDuration.get();
|
||||||
|
double max = maxIdleDuration.get();
|
||||||
|
if (!Double.isFinite(min) || !Double.isFinite(max) || min < 0D || max < 0D) {
|
||||||
|
return 0D;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max < min) {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (min + max) / 2D;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getBiomeCacheUsageRatio() {
|
||||||
|
PreservationSVC preservation = IrisServices.get(PreservationSVC.class);
|
||||||
|
if (preservation == null) {
|
||||||
|
return 0D;
|
||||||
|
}
|
||||||
|
|
||||||
|
double total = 0D;
|
||||||
|
int count = 0;
|
||||||
|
for (var cache : preservation.getCaches()) {
|
||||||
|
if (!(cache instanceof CachedStream2D<?>) && !(cache instanceof CachedDoubleStream2D)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double usage = cache.getUsage();
|
||||||
|
if (!Double.isFinite(usage)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
total += Math.max(0D, Math.min(1D, usage));
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count <= 0) {
|
||||||
|
return 0D;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total / count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void engineStatus(VolmitSender sender) {
|
||||||
|
long[] sizes = new long[4];
|
||||||
|
long[] count = new long[4];
|
||||||
|
|
||||||
|
for (var cache : IrisServices.get(PreservationSVC.class).getCaches()) {
|
||||||
|
var type = switch (cache) {
|
||||||
|
case ResourceLoader<?> ignored -> 0;
|
||||||
|
case CachedStream2D<?> ignored -> 1;
|
||||||
|
case CachedDoubleStream2D ignored -> 1;
|
||||||
|
case CachedStream3D<?> ignored -> 2;
|
||||||
|
default -> 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
sizes[type] += cache.getSize();
|
||||||
|
count[type]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "-------------------------");
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "Status:");
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Service: " + C.LIGHT_PURPLE + (service.isShutdown() ? "Shutdown" : "Running"));
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Updater: " + C.LIGHT_PURPLE + (updateTicker.isAlive() ? "Running" : "Stopped"));
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Period: " + C.LIGHT_PURPLE + Form.duration(TRIM_PERIOD));
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Trimmers: " + C.LIGHT_PURPLE + trimmerAlive.get());
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Unloaders: " + C.LIGHT_PURPLE + unloaderAlive.get());
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "Tectonic Plates:");
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Limit: " + C.LIGHT_PURPLE + tectonicLimit.get());
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Total: " + C.LIGHT_PURPLE + tectonicPlates.get());
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Queued: " + C.LIGHT_PURPLE + queuedTectonicPlates.get());
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Max Idle Duration: " + C.LIGHT_PURPLE + Form.duration(maxIdleDuration.get(), 2));
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Min Idle Duration: " + C.LIGHT_PURPLE + Form.duration(minIdleDuration.get(), 2));
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "Caches:");
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Resource: " + C.LIGHT_PURPLE + sizes[0] + " (" + count[0] + ")");
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- 2D Stream: " + C.LIGHT_PURPLE + sizes[1] + " (" + count[1] + ")");
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- 3D Stream: " + C.LIGHT_PURPLE + sizes[2] + " (" + count[2] + ")");
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Other: " + C.LIGHT_PURPLE + sizes[3] + " (" + count[3] + ")");
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "Other:");
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Iris Worlds: " + C.LIGHT_PURPLE + totalWorlds.get());
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Loaded Chunks: " + C.LIGHT_PURPLE + loadedChunks.get());
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "-------------------------");
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onWorldUnload(WorldUnloadEvent event) {
|
||||||
|
remove(event.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onWorldLoad(WorldLoadEvent event) {
|
||||||
|
add(event.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void remove(World world) {
|
||||||
|
var entry = worlds.remove(world);
|
||||||
|
if (entry == null) return;
|
||||||
|
entry.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(World world) {
|
||||||
|
if (world == null || worlds.containsKey(world)) return;
|
||||||
|
PlatformChunkGenerator access = IrisToolbelt.access(world);
|
||||||
|
if (access == null) return;
|
||||||
|
worlds.put(world, new Registered(world.getName(), access));
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void setup() {
|
||||||
|
if (updateTicker != null && updateTicker.isAlive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
updateTicker = new Looper() {
|
||||||
|
@Override
|
||||||
|
protected long loop() {
|
||||||
|
try {
|
||||||
|
for (World world : Bukkit.getWorlds()) {
|
||||||
|
add(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
int queuedPlates = 0;
|
||||||
|
int totalPlates = 0;
|
||||||
|
long chunks = 0;
|
||||||
|
int unloaders = 0;
|
||||||
|
int trimmers = 0;
|
||||||
|
int iris = 0;
|
||||||
|
|
||||||
|
double maxDuration = Long.MIN_VALUE;
|
||||||
|
double minDuration = Long.MAX_VALUE;
|
||||||
|
for (var entry : worlds.entrySet()) {
|
||||||
|
var registered = entry.getValue();
|
||||||
|
if (registered.closed) continue;
|
||||||
|
|
||||||
|
iris++;
|
||||||
|
if (registered.unloaderAlive()) unloaders++;
|
||||||
|
if (registered.trimmerAlive()) trimmers++;
|
||||||
|
|
||||||
|
var engine = registered.getEngine();
|
||||||
|
if (engine == null) continue;
|
||||||
|
|
||||||
|
queuedPlates += engine.getMantle().getUnloadRegionCount();
|
||||||
|
totalPlates += engine.getMantle().getLoadedRegionCount();
|
||||||
|
chunks += entry.getKey().getLoadedChunks().length;
|
||||||
|
|
||||||
|
double duration = engine.getMantle().getAdjustedIdleDuration();
|
||||||
|
if (duration > maxDuration) maxDuration = duration;
|
||||||
|
if (duration < minDuration) minDuration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmerAlive.set(trimmers);
|
||||||
|
unloaderAlive.set(unloaders);
|
||||||
|
tectonicPlates.set(totalPlates);
|
||||||
|
queuedTectonicPlates.set(queuedPlates);
|
||||||
|
maxIdleDuration.set(maxDuration);
|
||||||
|
minIdleDuration.set(minDuration);
|
||||||
|
loadedChunks.set(chunks);
|
||||||
|
totalWorlds.set(iris);
|
||||||
|
|
||||||
|
worlds.values().forEach(Registered::update);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
updateTicker.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isMantleClosed(Throwable throwable) {
|
||||||
|
Throwable current = throwable;
|
||||||
|
while (current != null) {
|
||||||
|
String message = current.getMessage();
|
||||||
|
if (message != null && message.toLowerCase(Locale.ROOT).contains("mantle is closed")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.getCause();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean shouldSkipMantleReductionForMaintenance(boolean maintenanceActive, boolean pregeneratorTargetsWorld) {
|
||||||
|
return maintenanceActive && !pregeneratorTargetsWorld;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class Registered {
|
||||||
|
private final String name;
|
||||||
|
private final PlatformChunkGenerator access;
|
||||||
|
private final int offset = RNG.r.nextInt(TRIM_PERIOD);
|
||||||
|
private transient ScheduledFuture<?> trimmer;
|
||||||
|
private transient ScheduledFuture<?> unloader;
|
||||||
|
private transient boolean closed;
|
||||||
|
|
||||||
|
private Registered(String name, PlatformChunkGenerator access) {
|
||||||
|
this.name = name;
|
||||||
|
this.access = access;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean unloaderAlive() {
|
||||||
|
return unloader != null && !unloader.isDone() && !unloader.isCancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean trimmerAlive() {
|
||||||
|
return trimmer != null && !trimmer.isDone() && !trimmer.isCancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private void update() {
|
||||||
|
if (closed || service == null || service.isShutdown())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (trimmer == null || trimmer.isDone() || trimmer.isCancelled()) {
|
||||||
|
trimmer = service.scheduleAtFixedRate(() -> {
|
||||||
|
Engine engine = getEngine();
|
||||||
|
if (engine == null
|
||||||
|
|| engine.isClosed()
|
||||||
|
|| engine.getMantle().getMantle().isClosed()
|
||||||
|
|| !shouldReduce(engine))
|
||||||
|
return;
|
||||||
|
World engineWorld = engine.getWorld().realWorld();
|
||||||
|
if (shouldSkipForMaintenance(engineWorld)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (GoldenHashScanner.isScanActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (pregenTargets(engine) && MantleHeapPressure.overHighWater()) {
|
||||||
|
engine.getMantle().trim(0L, 0);
|
||||||
|
} else {
|
||||||
|
engine.getMantle().trim(activeIdleDuration(engine), activeTectonicLimit(engine));
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (isMantleClosed(e)) {
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IrisLogging.reportError(e);
|
||||||
|
IrisLogging.error("EngineSVC: Failed to trim for " + name);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, offset, TRIM_PERIOD, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unloader == null || unloader.isDone() || unloader.isCancelled()) {
|
||||||
|
unloader = service.scheduleAtFixedRate(() -> {
|
||||||
|
Engine engine = getEngine();
|
||||||
|
if (engine == null
|
||||||
|
|| engine.isClosed()
|
||||||
|
|| engine.getMantle().getMantle().isClosed()
|
||||||
|
|| !shouldReduce(engine))
|
||||||
|
return;
|
||||||
|
World engineWorld = engine.getWorld().realWorld();
|
||||||
|
if (shouldSkipForMaintenance(engineWorld)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GoldenHashScanner.isScanActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
long unloadStart = System.currentTimeMillis();
|
||||||
|
boolean heapPressure = pregenTargets(engine) && MantleHeapPressure.overHighWater();
|
||||||
|
int unloadLimit = (heapPressure || IrisSettings.get().getPerformance().getEngineSVC().forceMulticoreWrite) ? 0 : activeTectonicLimit(engine);
|
||||||
|
int count = engine.getMantle().unloadTectonicPlate(unloadLimit);
|
||||||
|
if (heapPressure && MantleHeapPressure.overPanicWater()) {
|
||||||
|
MantleHeapPressure.requestPanicReclaim();
|
||||||
|
}
|
||||||
|
if (count > 0) {
|
||||||
|
IrisLogging.debug(C.GOLD + "Unloaded " + C.YELLOW + count + " TectonicPlates in " + C.RED + Form.duration(System.currentTimeMillis() - unloadStart, 2));
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (isMantleClosed(e)) {
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IrisLogging.reportError(e);
|
||||||
|
IrisLogging.error("EngineSVC: Failed to unload for " + name);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, offset + TRIM_PERIOD / 2, TRIM_PERIOD, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int tectonicLimit() {
|
||||||
|
return tectonicLimit.get() / Math.max(worlds.size(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean pregenTargets(Engine engine) {
|
||||||
|
if (engine == null || engine.getWorld() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PregeneratorJob pregeneratorJob = PregeneratorJob.getInstance();
|
||||||
|
return pregeneratorJob != null && pregeneratorJob.targetsWorldIdentity(engine.getWorld().identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int activeTectonicLimit(Engine engine) {
|
||||||
|
int limit = tectonicLimit();
|
||||||
|
if (!pregenTargets(engine)) {
|
||||||
|
return limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(limit, IrisSettings.get().getPregen().getEffectiveResidentTectonicPlates(engine.getHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private long activeIdleDuration(Engine engine) {
|
||||||
|
return TimeUnit.SECONDS.toMillis(IrisSettings.get().getPerformance().getMantleKeepAlive());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private void close() {
|
||||||
|
if (closed) return;
|
||||||
|
closed = true;
|
||||||
|
|
||||||
|
if (trimmer != null) {
|
||||||
|
trimmer.cancel(false);
|
||||||
|
trimmer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unloader != null) {
|
||||||
|
unloader.cancel(false);
|
||||||
|
unloader = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Engine getEngine() {
|
||||||
|
if (closed) return null;
|
||||||
|
return access.getEngine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldReduce(Engine engine) {
|
||||||
|
if (!engine.isStudio() || IrisSettings.get().getPerformance().isTrimMantleInStudio()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pregenTargets(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldSkipForMaintenance(@Nullable World world) {
|
||||||
|
if (world == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean maintenanceActive = IrisToolbelt.isWorldMaintenanceActive(world);
|
||||||
|
if (!maintenanceActive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PregeneratorJob pregeneratorJob = PregeneratorJob.getInstance();
|
||||||
|
boolean pregeneratorTargetsWorld = pregeneratorJob != null && pregeneratorJob.targetsWorld(world);
|
||||||
|
return shouldSkipMantleReductionForMaintenance(maintenanceActive, pregeneratorTargetsWorld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+210
@@ -0,0 +1,210 @@
|
|||||||
|
package art.arcane.iris.core.service;
|
||||||
|
|
||||||
|
import art.arcane.iris.Iris;
|
||||||
|
import art.arcane.iris.core.gui.PregeneratorJob;
|
||||||
|
import art.arcane.iris.util.common.plugin.IrisService;
|
||||||
|
import art.arcane.volmlib.integration.IntegrationHandshakeRequest;
|
||||||
|
import art.arcane.volmlib.integration.IntegrationHandshakeResponse;
|
||||||
|
import art.arcane.volmlib.integration.IntegrationHeartbeat;
|
||||||
|
import art.arcane.volmlib.integration.IntegrationMetricDescriptor;
|
||||||
|
import art.arcane.volmlib.integration.IntegrationMetricSample;
|
||||||
|
import art.arcane.volmlib.integration.IntegrationMetricSchema;
|
||||||
|
import art.arcane.volmlib.integration.IntegrationProtocolNegotiator;
|
||||||
|
import art.arcane.volmlib.integration.IntegrationProtocolVersion;
|
||||||
|
import art.arcane.volmlib.integration.IntegrationServiceContract;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.plugin.ServicePriority;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class IrisIntegrationService implements IrisService, IntegrationServiceContract {
|
||||||
|
private static final Set<IntegrationProtocolVersion> SUPPORTED_PROTOCOLS = Set.of(
|
||||||
|
new IntegrationProtocolVersion(1, 0),
|
||||||
|
new IntegrationProtocolVersion(1, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Set<String> CAPABILITIES = Set.of(
|
||||||
|
"handshake",
|
||||||
|
"heartbeat",
|
||||||
|
"metrics",
|
||||||
|
"iris-engine-metrics"
|
||||||
|
);
|
||||||
|
|
||||||
|
private volatile IntegrationProtocolVersion negotiatedProtocol = new IntegrationProtocolVersion(1, 1);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
Bukkit.getServicesManager().register(IntegrationServiceContract.class, this, Iris.instance, ServicePriority.Normal);
|
||||||
|
Iris.verbose("Integration provider registered for Iris");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
Bukkit.getServicesManager().unregister(IntegrationServiceContract.class, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String pluginId() {
|
||||||
|
return "iris";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String pluginVersion() {
|
||||||
|
return Iris.instance.getDescription().getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<IntegrationProtocolVersion> supportedProtocols() {
|
||||||
|
return SUPPORTED_PROTOCOLS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> capabilities() {
|
||||||
|
return CAPABILITIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<IntegrationMetricDescriptor> metricDescriptors() {
|
||||||
|
return IntegrationMetricSchema.descriptors().stream()
|
||||||
|
.filter(descriptor -> descriptor.key().startsWith("iris."))
|
||||||
|
.collect(java.util.stream.Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IntegrationHandshakeResponse handshake(IntegrationHandshakeRequest request) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (request == null) {
|
||||||
|
return new IntegrationHandshakeResponse(
|
||||||
|
pluginId(),
|
||||||
|
pluginVersion(),
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
SUPPORTED_PROTOCOLS,
|
||||||
|
CAPABILITIES,
|
||||||
|
"missing request",
|
||||||
|
now
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<IntegrationProtocolVersion> negotiated = IntegrationProtocolNegotiator.negotiate(
|
||||||
|
SUPPORTED_PROTOCOLS,
|
||||||
|
request.supportedProtocols()
|
||||||
|
);
|
||||||
|
if (negotiated.isEmpty()) {
|
||||||
|
return new IntegrationHandshakeResponse(
|
||||||
|
pluginId(),
|
||||||
|
pluginVersion(),
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
SUPPORTED_PROTOCOLS,
|
||||||
|
CAPABILITIES,
|
||||||
|
"no-common-protocol",
|
||||||
|
now
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
negotiatedProtocol = negotiated.get();
|
||||||
|
return new IntegrationHandshakeResponse(
|
||||||
|
pluginId(),
|
||||||
|
pluginVersion(),
|
||||||
|
true,
|
||||||
|
negotiatedProtocol,
|
||||||
|
SUPPORTED_PROTOCOLS,
|
||||||
|
CAPABILITIES,
|
||||||
|
"ok",
|
||||||
|
now
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IntegrationHeartbeat heartbeat() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
return new IntegrationHeartbeat(negotiatedProtocol, true, now, "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, IntegrationMetricSample> sampleMetrics(Set<String> metricKeys) {
|
||||||
|
Set<String> requested = metricKeys == null || metricKeys.isEmpty()
|
||||||
|
? IntegrationMetricSchema.irisKeys()
|
||||||
|
: metricKeys;
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
Map<String, IntegrationMetricSample> out = new HashMap<>();
|
||||||
|
|
||||||
|
for (String key : requested) {
|
||||||
|
switch (key) {
|
||||||
|
case IntegrationMetricSchema.IRIS_CHUNK_STREAM_MS -> out.put(key, sampleChunkStreamMetric(now));
|
||||||
|
case IntegrationMetricSchema.IRIS_PREGEN_QUEUE -> out.put(key, samplePregenQueueMetric(now));
|
||||||
|
case IntegrationMetricSchema.IRIS_BIOME_CACHE_HIT_RATE -> out.put(key, sampleBiomeCacheHitRateMetric(now));
|
||||||
|
default -> out.put(key, IntegrationMetricSample.unavailable(
|
||||||
|
IntegrationMetricSchema.descriptor(key),
|
||||||
|
"unsupported-key",
|
||||||
|
now
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntegrationMetricSample sampleChunkStreamMetric(long now) {
|
||||||
|
IntegrationMetricDescriptor descriptor = IntegrationMetricSchema.descriptor(IntegrationMetricSchema.IRIS_CHUNK_STREAM_MS);
|
||||||
|
|
||||||
|
double chunksPerSecond = PregeneratorJob.chunksPerSecond();
|
||||||
|
|
||||||
|
if (chunksPerSecond > 0D) {
|
||||||
|
return IntegrationMetricSample.available(descriptor, 1000D / chunksPerSecond, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisEngineSVC engineService = Iris.service(IrisEngineSVC.class);
|
||||||
|
if (engineService != null) {
|
||||||
|
double idle = engineService.getAverageIdleDuration();
|
||||||
|
if (idle > 0D && Double.isFinite(idle)) {
|
||||||
|
return IntegrationMetricSample.available(descriptor, idle, now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return IntegrationMetricSample.available(descriptor, 0D, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntegrationMetricSample samplePregenQueueMetric(long now) {
|
||||||
|
IntegrationMetricDescriptor descriptor = IntegrationMetricSchema.descriptor(IntegrationMetricSchema.IRIS_PREGEN_QUEUE);
|
||||||
|
long totalQueue = 0L;
|
||||||
|
boolean hasAnySource = false;
|
||||||
|
|
||||||
|
long pregenRemaining = PregeneratorJob.chunksRemaining();
|
||||||
|
if (pregenRemaining >= 0L) {
|
||||||
|
totalQueue += pregenRemaining;
|
||||||
|
hasAnySource = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisEngineSVC engineService = Iris.service(IrisEngineSVC.class);
|
||||||
|
if (engineService != null) {
|
||||||
|
totalQueue += Math.max(0, engineService.getQueuedTectonicPlateCount());
|
||||||
|
hasAnySource = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasAnySource) {
|
||||||
|
return IntegrationMetricSample.unavailable(descriptor, "queue-not-available", now);
|
||||||
|
}
|
||||||
|
|
||||||
|
return IntegrationMetricSample.available(descriptor, totalQueue, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntegrationMetricSample sampleBiomeCacheHitRateMetric(long now) {
|
||||||
|
IntegrationMetricDescriptor descriptor = IntegrationMetricSchema.descriptor(IntegrationMetricSchema.IRIS_BIOME_CACHE_HIT_RATE);
|
||||||
|
IrisEngineSVC engineService = Iris.service(IrisEngineSVC.class);
|
||||||
|
if (engineService == null) {
|
||||||
|
return IntegrationMetricSample.unavailable(descriptor, "engine-service-unavailable", now);
|
||||||
|
}
|
||||||
|
|
||||||
|
double ratio = engineService.getBiomeCacheUsageRatio();
|
||||||
|
if (!Double.isFinite(ratio)) {
|
||||||
|
return IntegrationMetricSample.unavailable(descriptor, "biome-cache-ratio-invalid", now);
|
||||||
|
}
|
||||||
|
|
||||||
|
return IntegrationMetricSample.available(descriptor, Math.max(0D, Math.min(1D, ratio)), now);
|
||||||
|
}
|
||||||
|
}
|
||||||
+209
@@ -0,0 +1,209 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||||
|
* Copyright (c) 2026 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.service;
|
||||||
|
|
||||||
|
import art.arcane.iris.Iris;
|
||||||
|
import art.arcane.iris.core.protocol.EngineResolver;
|
||||||
|
import art.arcane.iris.core.protocol.IrisProtocolServer;
|
||||||
|
import art.arcane.iris.core.protocol.IrisServerTransport;
|
||||||
|
import art.arcane.iris.core.protocol.IrisSession;
|
||||||
|
import art.arcane.iris.core.protocol.IrisSessionRegistry;
|
||||||
|
import art.arcane.iris.core.protocol.IrisVisionRequestService;
|
||||||
|
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||||
|
import art.arcane.iris.spi.IrisServices;
|
||||||
|
import art.arcane.iris.spi.protocol.IrisProtocol;
|
||||||
|
import art.arcane.iris.util.common.plugin.IrisService;
|
||||||
|
import art.arcane.iris.util.common.scheduling.J;
|
||||||
|
import art.arcane.volmlib.util.bukkit.WorldIdentity;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
import org.bukkit.plugin.messaging.Messenger;
|
||||||
|
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class IrisProtocolService implements IrisService, PluginMessageListener, IrisServerTransport {
|
||||||
|
private static final long SERVER_CAPABILITIES = IrisProtocol.CAPABILITY_PREGEN | IrisProtocol.CAPABILITY_VISION | IrisProtocol.CAPABILITY_CURSOR;
|
||||||
|
private static final int DIMENSION_SYNC_RETRY_TICKS = 20;
|
||||||
|
private static final int DIMENSION_SYNC_MAX_ATTEMPTS = 15;
|
||||||
|
|
||||||
|
private IrisSessionRegistry registry;
|
||||||
|
private IrisProtocolServer protocolServer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
Messenger messenger = Bukkit.getMessenger();
|
||||||
|
messenger.registerOutgoingPluginChannel(Iris.instance, IrisProtocol.CHANNEL);
|
||||||
|
messenger.registerIncomingPluginChannel(Iris.instance, IrisProtocol.CHANNEL, this);
|
||||||
|
registry = new IrisSessionRegistry();
|
||||||
|
protocolServer = new IrisProtocolServer(registry, SERVER_CAPABILITIES, brand(), true);
|
||||||
|
EngineResolver engineResolver = IrisProtocolService::resolveEngine;
|
||||||
|
protocolServer.setEngineResolver(engineResolver);
|
||||||
|
protocolServer.setVisionTileHandler(IrisVisionRequestService.create(engineResolver, registry));
|
||||||
|
IrisServices.register(IrisProtocolServer.class, protocolServer);
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
registry.register(new IrisSession(player.getUniqueId().toString(), this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
IrisServices.remove(IrisProtocolServer.class);
|
||||||
|
Messenger messenger = Bukkit.getMessenger();
|
||||||
|
messenger.unregisterIncomingPluginChannel(Iris.instance, IrisProtocol.CHANNEL, this);
|
||||||
|
messenger.unregisterOutgoingPluginChannel(Iris.instance, IrisProtocol.CHANNEL);
|
||||||
|
IrisSessionRegistry current = registry;
|
||||||
|
if (current != null) {
|
||||||
|
for (IrisSession session : current.all()) {
|
||||||
|
current.unregister(session.id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registry = null;
|
||||||
|
protocolServer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
IrisSessionRegistry current = registry;
|
||||||
|
if (current == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
current.register(new IrisSession(event.getPlayer().getUniqueId().toString(), this));
|
||||||
|
syncDimension(event.getPlayer(), DIMENSION_SYNC_MAX_ATTEMPTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerChangedWorld(PlayerChangedWorldEvent event) {
|
||||||
|
if (registry == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
syncDimension(event.getPlayer(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
|
IrisSessionRegistry current = registry;
|
||||||
|
if (current == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
current.unregister(event.getPlayer().getUniqueId().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
|
||||||
|
IrisProtocolServer current = protocolServer;
|
||||||
|
if (current == null || !IrisProtocol.CHANNEL.equals(channel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
current.onClientFrame(player.getUniqueId().toString(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendToClient(String sessionId, byte[] frame) {
|
||||||
|
if (sessionId == null || frame == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UUID playerId = parseUuid(sessionId);
|
||||||
|
if (playerId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Player player = Bukkit.getPlayer(playerId);
|
||||||
|
if (player == null || !player.isOnline()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
J.runEntity(player, () -> deliver(player, frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deliver(Player player, byte[] frame) {
|
||||||
|
if (!player.isOnline() || !player.getListeningPluginChannels().contains(IrisProtocol.CHANNEL)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.sendPluginMessage(Iris.instance, IrisProtocol.CHANNEL, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncDimension(Player player, int attemptsLeft) {
|
||||||
|
IrisProtocolServer protocol = protocolServer;
|
||||||
|
IrisSessionRegistry current = registry;
|
||||||
|
if (protocol == null || current == null || !player.isOnline()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String sessionId = player.getUniqueId().toString();
|
||||||
|
IrisSession session = current.get(sessionId);
|
||||||
|
if (session == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!session.isReady()) {
|
||||||
|
if (attemptsLeft > 1) {
|
||||||
|
J.runEntity(player, () -> syncDimension(player, attemptsLeft - 1), DIMENSION_SYNC_RETRY_TICKS);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
World world = player.getWorld();
|
||||||
|
Engine engine = engineFor(world);
|
||||||
|
if (engine != null) {
|
||||||
|
protocol.sendDimensionStatus(sessionId, engine.getDimension().getLoadKey(), engine.getData().getDataFolder().getName(),
|
||||||
|
world.getSeed(), engine.getMinHeight(), engine.getMaxHeight(), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
protocol.sendDimensionStatus(sessionId, WorldIdentity.serialize(world), "", world.getSeed(), world.getMinHeight(), world.getMaxHeight(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Engine resolveEngine(String sessionId) {
|
||||||
|
UUID playerId = parseUuid(sessionId);
|
||||||
|
if (playerId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Player player = Bukkit.getPlayer(playerId);
|
||||||
|
if (player == null || !player.isOnline()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return engineFor(player.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Engine engineFor(World world) {
|
||||||
|
PlatformChunkGenerator access = IrisToolbelt.access(world);
|
||||||
|
if (access == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Engine engine = access.getEngine();
|
||||||
|
if (engine == null || engine.isClosed()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UUID parseUuid(String sessionId) {
|
||||||
|
try {
|
||||||
|
return UUID.fromString(sessionId);
|
||||||
|
} catch (IllegalArgumentException invalid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String brand() {
|
||||||
|
return "Iris " + Iris.instance.getDescription().getVersion() + " (Bukkit)";
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
package art.arcane.iris.core.service;
|
||||||
|
|
||||||
|
import art.arcane.iris.Iris;
|
||||||
|
import io.papermc.paper.command.brigadier.BasicCommand;
|
||||||
|
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||||
|
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
final class PaperCommandRegistrar {
|
||||||
|
private static final String ROOT_COMMAND = "iris";
|
||||||
|
|
||||||
|
private PaperCommandRegistrar() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void register(Iris plugin, CommandSVC commandService) {
|
||||||
|
plugin.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event ->
|
||||||
|
event.registrar().register(
|
||||||
|
ROOT_COMMAND,
|
||||||
|
"Iris world generation command.",
|
||||||
|
List.of("ir", "irs"),
|
||||||
|
command(commandService)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BasicCommand command(CommandSVC commandService) {
|
||||||
|
return new PaperCommand(commandService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record PaperCommand(CommandSVC commandService) implements BasicCommand {
|
||||||
|
@Override
|
||||||
|
public void execute(CommandSourceStack source, String[] args) {
|
||||||
|
commandService.executeRoot(source.getSender(), ROOT_COMMAND, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> suggest(CommandSourceStack source, String[] args) {
|
||||||
|
return commandService.tabCompleteRoot(source.getSender(), ROOT_COMMAND, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canUse(CommandSender sender) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,545 @@
|
|||||||
|
/*
|
||||||
|
* 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.service;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import art.arcane.iris.Iris;
|
||||||
|
import art.arcane.iris.core.IrisSettings;
|
||||||
|
import art.arcane.iris.core.edit.DustRevealer;
|
||||||
|
import art.arcane.iris.core.link.WorldEditLink;
|
||||||
|
import art.arcane.iris.core.wand.WandSelection;
|
||||||
|
import art.arcane.iris.engine.object.IrisObject;
|
||||||
|
import art.arcane.iris.util.project.matter.WorldMatter;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.bukkit.WorldIdentity;
|
||||||
|
import art.arcane.volmlib.util.data.Cuboid;
|
||||||
|
import art.arcane.iris.util.common.format.C;
|
||||||
|
import art.arcane.volmlib.util.math.M;
|
||||||
|
import art.arcane.volmlib.util.matter.Matter;
|
||||||
|
import art.arcane.iris.util.common.plugin.IrisService;
|
||||||
|
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||||
|
import art.arcane.iris.util.common.scheduling.J;
|
||||||
|
import art.arcane.volmlib.util.scheduling.SR;
|
||||||
|
import art.arcane.iris.util.common.scheduling.jobs.Job;
|
||||||
|
import org.bukkit.enchantments.Enchantment;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.block.Action;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
import org.bukkit.inventory.EquipmentSlot;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemFlag;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.bukkit.util.BlockVector;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
import static art.arcane.iris.util.common.data.registry.Particles.CRIT_MAGIC;
|
||||||
|
import static art.arcane.iris.util.common.data.registry.Particles.REDSTONE;
|
||||||
|
|
||||||
|
public class WandSVC implements IrisService {
|
||||||
|
private static final int MS_PER_TICK = Integer.parseInt(System.getProperty("iris.ms_per_tick", "30"));
|
||||||
|
|
||||||
|
private static ItemStack dust;
|
||||||
|
private static ItemStack wand;
|
||||||
|
|
||||||
|
public static void pasteSchematic(IrisObject s, Location at) {
|
||||||
|
s.place(at);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an Iris Object from the 2 coordinates selected with a wand
|
||||||
|
*
|
||||||
|
* @param p The wand player
|
||||||
|
* @return The new object
|
||||||
|
*/
|
||||||
|
public static IrisObject createSchematic(Player p, boolean legacy) {
|
||||||
|
if (!isHoldingWand(p)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Location[] f = getCuboid(p);
|
||||||
|
if (f == null || f[0] == null || f[1] == null)
|
||||||
|
return null;
|
||||||
|
Cuboid c = new Cuboid(f[0], f[1]);
|
||||||
|
IrisObject s = new IrisObject(c.getSizeX(), c.getSizeY(), c.getSizeZ());
|
||||||
|
|
||||||
|
var it = c.chunkedIterator();
|
||||||
|
|
||||||
|
int total = c.getSizeX() * c.getSizeY() * c.getSizeZ();
|
||||||
|
var latch = new CountDownLatch(1);
|
||||||
|
var holder = Iris.tickets.getHolder(p.getWorld());
|
||||||
|
new Job() {
|
||||||
|
private int i;
|
||||||
|
private Chunk chunk;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Scanning Selection";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
new SR() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
var time = M.ms() + MS_PER_TICK;
|
||||||
|
while (time > M.ms()) {
|
||||||
|
if (!it.hasNext()) {
|
||||||
|
if (chunk != null) {
|
||||||
|
holder.removeTicket(chunk);
|
||||||
|
chunk = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel();
|
||||||
|
latch.countDown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var b = it.next();
|
||||||
|
var bChunk = b.getChunk();
|
||||||
|
if (chunk == null) {
|
||||||
|
chunk = bChunk;
|
||||||
|
holder.addTicket(chunk);
|
||||||
|
} else if (chunk != bChunk) {
|
||||||
|
holder.removeTicket(chunk);
|
||||||
|
holder.addTicket(bChunk);
|
||||||
|
chunk = bChunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b.getType().equals(Material.AIR))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
BlockVector bv = b.getLocation().subtract(c.getLowerNE().toVector()).toVector().toBlockVector();
|
||||||
|
s.setUnsigned(bv.getBlockX(), bv.getBlockY(), bv.getBlockZ(), b, legacy);
|
||||||
|
} finally {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void completeWork() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTotalWork() {
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWorkCompleted() {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}.execute(new VolmitSender(p), true, () -> {});
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Iris.reportError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an Iris Object from the 2 coordinates selected with a wand
|
||||||
|
*
|
||||||
|
* @return The new object
|
||||||
|
*/
|
||||||
|
public static Matter createMatterSchem(Player p) {
|
||||||
|
if (!isHoldingWand(p)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Location[] f = getCuboid(p);
|
||||||
|
|
||||||
|
return WorldMatter.createMatter(p.getName(), f[0], f[1]);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Iris.reportError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a user friendly location string to an actual Location
|
||||||
|
*
|
||||||
|
* @param s The string
|
||||||
|
* @return The location
|
||||||
|
*/
|
||||||
|
public static Location stringToLocation(String s) {
|
||||||
|
try {
|
||||||
|
String[] f = s.split("\\Q in \\E");
|
||||||
|
if (f.length != 2) return null;
|
||||||
|
String[] g = f[0].split("\\Q,\\E");
|
||||||
|
if (g.length != 3) return null;
|
||||||
|
World world = WorldIdentity.resolve(f[1]).orElse(null);
|
||||||
|
if (world == null) return null;
|
||||||
|
return new Location(world, Integer.parseInt(g[0]), Integer.parseInt(g[1]), Integer.parseInt(g[2]));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a user friendly string of a location
|
||||||
|
*
|
||||||
|
* @param loc The location
|
||||||
|
* @return The string
|
||||||
|
*/
|
||||||
|
public static String locationToString(Location loc) {
|
||||||
|
if (loc == null) {
|
||||||
|
return "<#>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return loc.getBlockX() + "," + loc.getBlockY() + "," + loc.getBlockZ() + " in " + WorldIdentity.serialize(loc.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new blank Iris wand
|
||||||
|
*
|
||||||
|
* @return The wand itemstack
|
||||||
|
*/
|
||||||
|
public static ItemStack createWand() {
|
||||||
|
return createWand(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new dust itemstack
|
||||||
|
*
|
||||||
|
* @return The stack
|
||||||
|
*/
|
||||||
|
public static ItemStack createDust() {
|
||||||
|
ItemStack is = new ItemStack(Material.GLOWSTONE_DUST);
|
||||||
|
is.addUnsafeEnchantment(Enchantment.FIRE_ASPECT, 1);
|
||||||
|
ItemMeta im = is.getItemMeta();
|
||||||
|
im.setDisplayName(C.BOLD + "" + C.YELLOW + "Dust of Revealing");
|
||||||
|
im.setUnbreakable(true);
|
||||||
|
im.addItemFlags(ItemFlag.values());
|
||||||
|
im.setLore(new KList<String>().qadd("Right click on a block to reveal it's placement structure!"));
|
||||||
|
is.setItemMeta(im);
|
||||||
|
|
||||||
|
return is;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds an existing wand in a users inventory
|
||||||
|
*
|
||||||
|
* @param inventory The inventory to search
|
||||||
|
* @return The slot number the wand is in. Or -1 if none are found
|
||||||
|
*/
|
||||||
|
public static int findWand(Inventory inventory) {
|
||||||
|
ItemStack wand = createWand(); //Create blank wand
|
||||||
|
ItemMeta meta = wand.getItemMeta();
|
||||||
|
meta.setLore(new ArrayList<>()); //We are resetting the lore as the lore differs between wands
|
||||||
|
wand.setItemMeta(meta);
|
||||||
|
|
||||||
|
for (int s = 0; s < inventory.getSize(); s++) {
|
||||||
|
ItemStack stack = inventory.getItem(s);
|
||||||
|
if (stack == null) continue;
|
||||||
|
meta = stack.getItemMeta();
|
||||||
|
meta.setLore(new ArrayList<>()); //Reset the lore on this too so we can compare them
|
||||||
|
stack.setItemMeta(meta); //We dont need to clone the item as items from .get are cloned
|
||||||
|
|
||||||
|
if (wand.isSimilar(stack)) return s; //If the name, material and NBT is the same
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an Iris wand. The locations should be the currently selected locations, or null
|
||||||
|
*
|
||||||
|
* @param a Location A
|
||||||
|
* @param b Location B
|
||||||
|
* @return A new wand
|
||||||
|
*/
|
||||||
|
public static ItemStack createWand(Location a, Location b) {
|
||||||
|
ItemStack is = new ItemStack(Material.BLAZE_ROD);
|
||||||
|
is.addUnsafeEnchantment(Enchantment.FIRE_ASPECT, 1);
|
||||||
|
ItemMeta im = is.getItemMeta();
|
||||||
|
im.setDisplayName(C.BOLD + "" + C.GOLD + "Wand of Iris");
|
||||||
|
im.setUnbreakable(true);
|
||||||
|
im.addItemFlags(ItemFlag.values());
|
||||||
|
im.setLore(new KList<String>().add(locationToString(a), locationToString(b)));
|
||||||
|
is.setItemMeta(im);
|
||||||
|
|
||||||
|
return is;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location[] getCuboidFromItem(ItemStack is) {
|
||||||
|
ItemMeta im = is.getItemMeta();
|
||||||
|
return new Location[]{stringToLocation(im.getLore().get(0)), stringToLocation(im.getLore().get(1))};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location[] getCuboid(Player p) {
|
||||||
|
if (isHoldingIrisWand(p)) {
|
||||||
|
return getCuboidFromItem(p.getInventory().getItemInMainHand());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IrisSettings.get().getWorld().worldEditWandCUI) {
|
||||||
|
Cuboid c = WorldEditLink.getSelection(p);
|
||||||
|
|
||||||
|
if (c != null) {
|
||||||
|
return new Location[]{c.getLowerNE(), c.getUpperSW()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isHoldingWand(Player p) {
|
||||||
|
return isHoldingIrisWand(p) || (IrisSettings.get().getWorld().worldEditWandCUI && WorldEditLink.getSelection(p) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isHoldingIrisWand(Player p) {
|
||||||
|
ItemStack is = p.getInventory().getItemInMainHand();
|
||||||
|
return is != null && isWand(is);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the itemstack passed an Iris wand
|
||||||
|
*
|
||||||
|
* @param is The itemstack
|
||||||
|
* @return True if it is
|
||||||
|
*/
|
||||||
|
public static boolean isWand(ItemStack is) {
|
||||||
|
if (is.getItemMeta() == null) return false;
|
||||||
|
return is.getType().equals(wand.getType()) &&
|
||||||
|
is.getItemMeta().getDisplayName().equals(wand.getItemMeta().getDisplayName()) &&
|
||||||
|
is.getItemMeta().getEnchants().equals(wand.getItemMeta().getEnchants()) &&
|
||||||
|
is.getItemMeta().getItemFlags().equals(wand.getItemMeta().getItemFlags());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
wand = createWand();
|
||||||
|
dust = createDust();
|
||||||
|
|
||||||
|
J.ar(() -> {
|
||||||
|
for (Player i : Bukkit.getOnlinePlayers()) {
|
||||||
|
tick(i);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tick(Player p) {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
if ((IrisSettings.get().getWorld().worldEditWandCUI && isHoldingWand(p)) || isWand(p.getInventory().getItemInMainHand())) {
|
||||||
|
Location[] d = getCuboid(p);
|
||||||
|
if (d == null || d[0] == null || d[1] == null) return;
|
||||||
|
new WandSelection(new Cuboid(d[0], d[1]), p).draw();
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the outline of a selected region
|
||||||
|
*
|
||||||
|
* @param d The cuboid
|
||||||
|
* @param p The player to show it to
|
||||||
|
*/
|
||||||
|
public void draw(Cuboid d, Player p) {
|
||||||
|
draw(new Location[]{d.getLowerNE(), d.getUpperSW()}, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the outline of a selected region
|
||||||
|
*
|
||||||
|
* @param d A pair of locations
|
||||||
|
* @param p The player to show them to
|
||||||
|
*/
|
||||||
|
public void draw(Location[] d, Player p) {
|
||||||
|
Vector gx = Vector.getRandom().subtract(Vector.getRandom()).normalize().clone().multiply(0.65);
|
||||||
|
d[0].getWorld().spawnParticle(CRIT_MAGIC, d[0], 1, 0.5 + gx.getX(), 0.5 + gx.getY(), 0.5 + gx.getZ(), 0, null, false);
|
||||||
|
Vector gxx = Vector.getRandom().subtract(Vector.getRandom()).normalize().clone().multiply(0.65);
|
||||||
|
d[1].getWorld().spawnParticle(CRIT_MAGIC, d[1], 1, 0.5 + gxx.getX(), 0.5 + gxx.getY(), 0.5 + gxx.getZ(), 0, null, false);
|
||||||
|
|
||||||
|
if (!d[0].getWorld().equals(d[1].getWorld())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d[0].distanceSquared(d[1]) > 64 * 64) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minx = Math.min(d[0].getBlockX(), d[1].getBlockX());
|
||||||
|
int miny = Math.min(d[0].getBlockY(), d[1].getBlockY());
|
||||||
|
int minz = Math.min(d[0].getBlockZ(), d[1].getBlockZ());
|
||||||
|
int maxx = Math.max(d[0].getBlockX(), d[1].getBlockX());
|
||||||
|
int maxy = Math.max(d[0].getBlockY(), d[1].getBlockY());
|
||||||
|
int maxz = Math.max(d[0].getBlockZ(), d[1].getBlockZ());
|
||||||
|
|
||||||
|
for (double j = minx - 1; j < maxx + 1; j += 0.25) {
|
||||||
|
for (double k = miny - 1; k < maxy + 1; k += 0.25) {
|
||||||
|
for (double l = minz - 1; l < maxz + 1; l += 0.25) {
|
||||||
|
if (M.r(0.2)) {
|
||||||
|
boolean jj = j == minx || j == maxx;
|
||||||
|
boolean kk = k == miny || k == maxy;
|
||||||
|
boolean ll = l == minz || l == maxz;
|
||||||
|
|
||||||
|
if ((jj && kk) || (jj && ll) || (ll && kk)) {
|
||||||
|
Vector push = new Vector(0, 0, 0);
|
||||||
|
|
||||||
|
if (j == minx) {
|
||||||
|
push.add(new Vector(-0.55, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k == miny) {
|
||||||
|
push.add(new Vector(0, -0.55, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l == minz) {
|
||||||
|
push.add(new Vector(0, 0, -0.55));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j == maxx) {
|
||||||
|
push.add(new Vector(0.55, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k == maxy) {
|
||||||
|
push.add(new Vector(0, 0.55, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l == maxz) {
|
||||||
|
push.add(new Vector(0, 0, 0.55));
|
||||||
|
}
|
||||||
|
|
||||||
|
Location lv = new Location(d[0].getWorld(), j, k, l).clone().add(0.5, 0.5, 0.5).clone().add(push);
|
||||||
|
Color color = Color.getHSBColor((float) (0.5f + (Math.sin((j + k + l + (p.getTicksLived() / 2f)) / 20f) / 2)), 1, 1);
|
||||||
|
int r = color.getRed();
|
||||||
|
int g = color.getGreen();
|
||||||
|
int b = color.getBlue();
|
||||||
|
p.spawnParticle(REDSTONE, lv.getX(), lv.getY(), lv.getZ(), 1, 0, 0, 0, 0, new Particle.DustOptions(org.bukkit.Color.fromRGB(r, g, b), 0.75f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void on(PlayerInteractEvent e) {
|
||||||
|
if (e.getHand() != EquipmentSlot.HAND)
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
if (isHoldingIrisWand(e.getPlayer())) {
|
||||||
|
if (e.getAction().equals(Action.LEFT_CLICK_BLOCK)) {
|
||||||
|
e.setCancelled(true);
|
||||||
|
e.getPlayer().getInventory().setItemInMainHand(update(true, Objects.requireNonNull(e.getClickedBlock()).getLocation(), e.getPlayer().getInventory().getItemInMainHand()));
|
||||||
|
e.getPlayer().playSound(e.getClickedBlock().getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1f, 0.67f);
|
||||||
|
e.getPlayer().updateInventory();
|
||||||
|
} else if (e.getAction().equals(Action.RIGHT_CLICK_BLOCK)) {
|
||||||
|
e.setCancelled(true);
|
||||||
|
e.getPlayer().getInventory().setItemInMainHand(update(false, Objects.requireNonNull(e.getClickedBlock()).getLocation(), e.getPlayer().getInventory().getItemInMainHand()));
|
||||||
|
e.getPlayer().playSound(e.getClickedBlock().getLocation(), Sound.BLOCK_END_PORTAL_FRAME_FILL, 1f, 1.17f);
|
||||||
|
e.getPlayer().updateInventory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHoldingDust(e.getPlayer())) {
|
||||||
|
if (e.getAction().equals(Action.RIGHT_CLICK_BLOCK)) {
|
||||||
|
e.setCancelled(true);
|
||||||
|
e.getPlayer().playSound(Objects.requireNonNull(e.getClickedBlock()).getLocation(), Sound.ENTITY_ENDER_EYE_DEATH, 2f, 1.97f);
|
||||||
|
DustRevealer.spawn(e.getClickedBlock(), new VolmitSender(e.getPlayer(), Iris.instance.getTag()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable xx) {
|
||||||
|
Iris.reportError(xx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the player holding Dust?
|
||||||
|
*
|
||||||
|
* @param p The player
|
||||||
|
* @return True if they are
|
||||||
|
*/
|
||||||
|
public boolean isHoldingDust(Player p) {
|
||||||
|
ItemStack is = p.getInventory().getItemInMainHand();
|
||||||
|
return is != null && isDust(is);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the itemstack passed Iris dust?
|
||||||
|
*
|
||||||
|
* @param is The itemstack
|
||||||
|
* @return True if it is
|
||||||
|
*/
|
||||||
|
public boolean isDust(ItemStack is) {
|
||||||
|
return is.isSimilar(dust);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the location on an Iris wand
|
||||||
|
*
|
||||||
|
* @param left True for first location, false for second
|
||||||
|
* @param a The location
|
||||||
|
* @param item The wand
|
||||||
|
* @return The updated wand
|
||||||
|
*/
|
||||||
|
public ItemStack update(boolean left, Location a, ItemStack item) {
|
||||||
|
if (!isWand(item)) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location[] f = getCuboidFromItem(item);
|
||||||
|
Location other = left ? f[1] : f[0];
|
||||||
|
|
||||||
|
if (other != null && !WorldIdentity.key(other.getWorld()).equals(WorldIdentity.key(a.getWorld()))) {
|
||||||
|
other = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return createWand(left ? a : other, left ? other : a);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* 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.wand;
|
||||||
|
|
||||||
|
import art.arcane.volmlib.util.data.Cuboid;
|
||||||
|
import art.arcane.volmlib.util.math.M;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
|
||||||
|
import static art.arcane.iris.util.common.data.registry.Particles.REDSTONE;
|
||||||
|
|
||||||
|
public class WandSelection {
|
||||||
|
private final Cuboid c;
|
||||||
|
private final Player p;
|
||||||
|
private static final double STEP = 0.10;
|
||||||
|
|
||||||
|
public WandSelection(Cuboid c, Player p) {
|
||||||
|
this.c = c;
|
||||||
|
this.p = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw() {
|
||||||
|
Location playerLoc = p.getLocation();
|
||||||
|
double maxDistanceSquared = 256 * 256;
|
||||||
|
int particleCount = 0;
|
||||||
|
|
||||||
|
// cube!
|
||||||
|
Location[][] edges = {
|
||||||
|
{c.getLowerNE(), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ())},
|
||||||
|
{c.getLowerNE(), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ())},
|
||||||
|
{c.getLowerNE(), new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1)},
|
||||||
|
{new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ())},
|
||||||
|
{new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1)},
|
||||||
|
{new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ())},
|
||||||
|
{new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1)},
|
||||||
|
{new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1)},
|
||||||
|
{new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1)},
|
||||||
|
{new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)},
|
||||||
|
{new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)},
|
||||||
|
{new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (Location[] edge : edges) {
|
||||||
|
Vector direction = edge[1].toVector().subtract(edge[0].toVector());
|
||||||
|
double length = direction.length();
|
||||||
|
direction.normalize();
|
||||||
|
|
||||||
|
for (double d = 0; d <= length; d += STEP) {
|
||||||
|
Location particleLoc = edge[0].clone().add(direction.clone().multiply(d));
|
||||||
|
|
||||||
|
if (playerLoc.distanceSquared(particleLoc) > maxDistanceSquared) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
spawnParticle(particleLoc, playerLoc);
|
||||||
|
particleCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void spawnParticle(Location particleLoc, Location playerLoc) {
|
||||||
|
double accuracy = M.lerpInverse(0, 64 * 64, playerLoc.distanceSquared(particleLoc));
|
||||||
|
double dist = M.lerp(0.125, 3.5, accuracy);
|
||||||
|
|
||||||
|
if (M.r(Math.min(dist * 5, 0.9D) * 0.995)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float hue = (float) (0.5f + (Math.sin((particleLoc.getX() + particleLoc.getY() + particleLoc.getZ() + (p.getTicksLived() / 2f)) / 20f) / 2));
|
||||||
|
Color color = Color.getHSBColor(hue, 1, 1);
|
||||||
|
|
||||||
|
p.spawnParticle(REDSTONE, particleLoc,
|
||||||
|
0, 0, 0, 0, 1,
|
||||||
|
new Particle.DustOptions(org.bukkit.Color.fromRGB(color.getRed(), color.getGreen(), color.getBlue()),
|
||||||
|
(float) dist * 3f));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
name: ${name}
|
||||||
|
version: ${version}
|
||||||
|
main: ${main}
|
||||||
|
bootstrapper: ${bootstrapper}
|
||||||
|
folia-supported: true
|
||||||
|
api-version: '${apiVersion}'
|
||||||
|
load: STARTUP
|
||||||
|
authors: [ cyberpwn, NextdoorPsycho, Vatuu ]
|
||||||
|
website: volmit.com
|
||||||
|
description: More than a Dimension!
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
name: ${name}
|
||||||
|
version: ${version}
|
||||||
|
main: ${main}
|
||||||
|
folia-supported: true
|
||||||
|
load: STARTUP
|
||||||
|
authors: [ cyberpwn, NextdoorPsycho, Vatuu ]
|
||||||
|
website: volmit.com
|
||||||
|
description: More than a Dimension!
|
||||||
|
commands:
|
||||||
|
iris:
|
||||||
|
aliases: [ ir, irs ]
|
||||||
|
api-version: '${apiVersion}'
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
package art.arcane.iris;
|
||||||
|
|
||||||
|
import io.papermc.paper.datapack.Datapack;
|
||||||
|
import io.papermc.paper.datapack.DatapackRegistrar;
|
||||||
|
import io.papermc.paper.datapack.DatapackSource;
|
||||||
|
import io.papermc.paper.datapack.DiscoveredDatapack;
|
||||||
|
import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import org.bukkit.FeatureFlag;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class IrisBootstrapTest {
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void discoverPackAutoEnablesAtFixedTop() throws Exception {
|
||||||
|
File datapackDirectory = temporaryFolder.newFolder("datapack");
|
||||||
|
RecordingRegistrar registrar = new RecordingRegistrar(DiscoveryBehavior.ACCEPT);
|
||||||
|
|
||||||
|
DiscoveredDatapack discovered = IrisBootstrap.discoverPack(registrar, datapackDirectory.toPath());
|
||||||
|
|
||||||
|
assertSame(registrar.discoveredDatapack, discovered);
|
||||||
|
assertEquals(datapackDirectory.toPath(), registrar.discoveredPath);
|
||||||
|
assertEquals(IrisBootstrap.PACK_ID, registrar.discoveredId);
|
||||||
|
assertTrue(registrar.configurer.autoEnableOnServerStart);
|
||||||
|
assertTrue(registrar.configurer.fixedPosition);
|
||||||
|
assertEquals(Datapack.Position.TOP, registrar.configurer.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rejectedDiscoveryReportsDatapackPath() throws Exception {
|
||||||
|
File datapackDirectory = temporaryFolder.newFolder("datapack");
|
||||||
|
RecordingRegistrar registrar = new RecordingRegistrar(DiscoveryBehavior.REJECT);
|
||||||
|
|
||||||
|
IllegalStateException failure = assertThrows(
|
||||||
|
IllegalStateException.class,
|
||||||
|
() -> IrisBootstrap.discoverPack(registrar, datapackDirectory.toPath())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertTrue(failure.getMessage().contains(datapackDirectory.toPath().toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void discoveryIoFailurePropagates() throws Exception {
|
||||||
|
File datapackDirectory = temporaryFolder.newFolder("datapack");
|
||||||
|
RecordingRegistrar registrar = new RecordingRegistrar(DiscoveryBehavior.FAIL);
|
||||||
|
|
||||||
|
IOException failure = assertThrows(
|
||||||
|
IOException.class,
|
||||||
|
() -> IrisBootstrap.discoverPack(registrar, datapackDirectory.toPath())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals("discovery failed", failure.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum DiscoveryBehavior {
|
||||||
|
ACCEPT,
|
||||||
|
REJECT,
|
||||||
|
FAIL
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RecordingConfigurer implements DatapackRegistrar.Configurer {
|
||||||
|
private boolean autoEnableOnServerStart;
|
||||||
|
private boolean fixedPosition;
|
||||||
|
private Datapack.Position position;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatapackRegistrar.Configurer title(Component title) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatapackRegistrar.Configurer autoEnableOnServerStart(boolean autoEnableOnServerStart) {
|
||||||
|
this.autoEnableOnServerStart = autoEnableOnServerStart;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatapackRegistrar.Configurer position(boolean fixed, Datapack.Position position) {
|
||||||
|
this.fixedPosition = fixed;
|
||||||
|
this.position = position;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RecordingRegistrar implements DatapackRegistrar {
|
||||||
|
private final DiscoveryBehavior behavior;
|
||||||
|
private final RecordingConfigurer configurer;
|
||||||
|
private final DiscoveredDatapack discoveredDatapack;
|
||||||
|
private Path discoveredPath;
|
||||||
|
private String discoveredId;
|
||||||
|
|
||||||
|
private RecordingRegistrar(DiscoveryBehavior behavior) {
|
||||||
|
this.behavior = behavior;
|
||||||
|
this.configurer = new RecordingConfigurer();
|
||||||
|
this.discoveredDatapack = new StubDiscoveredDatapack();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPackDiscovered(String name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DiscoveredDatapack getDiscoveredPack(String name) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeDiscoveredPack(String name) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, DiscoveredDatapack> getDiscoveredPacks() {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DiscoveredDatapack discoverPack(URI uri, String id, Consumer<Configurer> configurer) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DiscoveredDatapack discoverPack(Path path, String id, Consumer<Configurer> configurer) throws IOException {
|
||||||
|
this.discoveredPath = path;
|
||||||
|
this.discoveredId = id;
|
||||||
|
if (behavior == DiscoveryBehavior.FAIL) {
|
||||||
|
throw new IOException("discovery failed");
|
||||||
|
}
|
||||||
|
configurer.accept(this.configurer);
|
||||||
|
return behavior == DiscoveryBehavior.ACCEPT ? discoveredDatapack : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DiscoveredDatapack discoverPack(PluginMeta pluginMeta, URI uri, String id, Consumer<Configurer> configurer) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DiscoveredDatapack discoverPack(PluginMeta pluginMeta, Path path, String id, Consumer<Configurer> configurer) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StubDiscoveredDatapack implements DiscoveredDatapack {
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return IrisBootstrap.PACK_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTitle() {
|
||||||
|
return Component.text(IrisBootstrap.PACK_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getDescription() {
|
||||||
|
return Component.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRequired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Datapack.Compatibility getCompatibility() {
|
||||||
|
return Datapack.Compatibility.COMPATIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<FeatureFlag> getRequiredFeatures() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatapackSource getSource() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package art.arcane.iris;
|
||||||
|
|
||||||
|
import art.arcane.iris.core.splash.IrisSplashPackScanner;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class IrisDiagnosticsTest {
|
||||||
|
@Test
|
||||||
|
public void reportErrorWithContextPrintsFullStacktrace() {
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
PrintStream originalErr = System.err;
|
||||||
|
System.setErr(new PrintStream(output, true, StandardCharsets.UTF_8));
|
||||||
|
try {
|
||||||
|
Iris.reportError("Runtime world creation failed.", new IllegalStateException("outer", new IllegalArgumentException("inner")));
|
||||||
|
} finally {
|
||||||
|
System.setErr(originalErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
String text = output.toString(StandardCharsets.UTF_8);
|
||||||
|
assertTrue(text.contains("Runtime world creation failed."));
|
||||||
|
assertTrue(text.contains("IllegalStateException"));
|
||||||
|
assertTrue(text.contains("IllegalArgumentException"));
|
||||||
|
assertTrue(text.contains("inner"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void splashPackScanSkipsInternalAndInvalidFolders() throws Exception {
|
||||||
|
Path root = Files.createTempDirectory("iris-splash");
|
||||||
|
try {
|
||||||
|
Path validPack = root.resolve("overworld");
|
||||||
|
Files.createDirectories(validPack.resolve("dimensions"));
|
||||||
|
Files.writeString(validPack.resolve("dimensions").resolve("overworld.json"), "{\"version\":\"4000\"}");
|
||||||
|
|
||||||
|
Files.createDirectories(root.resolve("datapack-imports"));
|
||||||
|
|
||||||
|
Path brokenPack = root.resolve("broken");
|
||||||
|
Files.createDirectories(brokenPack.resolve("dimensions"));
|
||||||
|
Files.writeString(brokenPack.resolve("dimensions").resolve("broken.json"), "{");
|
||||||
|
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
PrintStream originalErr = System.err;
|
||||||
|
System.setErr(new PrintStream(output, true, StandardCharsets.UTF_8));
|
||||||
|
List<IrisSplashPackScanner.SplashPackMetadata> packs;
|
||||||
|
try {
|
||||||
|
packs = IrisSplashPackScanner.collect(root.toFile(), Iris::reportError);
|
||||||
|
} finally {
|
||||||
|
System.setErr(originalErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(1, packs.size());
|
||||||
|
assertEquals("overworld", packs.get(0).name());
|
||||||
|
assertEquals("4000", packs.get(0).version());
|
||||||
|
|
||||||
|
String text = output.toString(StandardCharsets.UTF_8);
|
||||||
|
assertTrue(text.contains("Failed to read splash metadata for dimension pack \"broken\"."));
|
||||||
|
assertTrue(text.contains("Json"));
|
||||||
|
} finally {
|
||||||
|
Files.walk(root)
|
||||||
|
.sorted(Comparator.reverseOrder())
|
||||||
|
.map(Path::toFile)
|
||||||
|
.forEach(File::delete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package art.arcane.iris;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.PluginDescriptionFile;
|
||||||
|
import org.bukkit.plugin.PluginLoadOrder;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class PaperPluginMetadataTest {
|
||||||
|
@Test
|
||||||
|
public void paperMetadataDeclaresBootstrapAndFoliaSupport() throws Exception {
|
||||||
|
String metadata;
|
||||||
|
try (InputStream stream = PaperPluginMetadataTest.class.getResourceAsStream("/paper-plugin.yml")) {
|
||||||
|
assertNotNull(stream);
|
||||||
|
metadata = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(metadata.contains("bootstrapper: " + IrisBootstrap.class.getName()));
|
||||||
|
assertTrue(metadata.contains("folia-supported: true"));
|
||||||
|
assertTrue(metadata.contains("load: STARTUP"));
|
||||||
|
assertFalse(metadata.contains("commands:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void bukkitMetadataRetainsStartupCommandAndAliases() throws Exception {
|
||||||
|
PluginDescriptionFile metadata;
|
||||||
|
try (InputStream stream = PaperPluginMetadataTest.class.getResourceAsStream("/plugin.yml")) {
|
||||||
|
assertNotNull(stream);
|
||||||
|
metadata = new PluginDescriptionFile(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(Iris.class.getName(), metadata.getMain());
|
||||||
|
assertEquals(PluginLoadOrder.STARTUP, metadata.getLoad());
|
||||||
|
Map<String, Map<String, Object>> commands = metadata.getCommands();
|
||||||
|
assertTrue(commands.containsKey("iris"));
|
||||||
|
assertEquals(List.of("ir", "irs"), commands.get("iris").get("aliases"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processedResourcesContainBothMetadataFormats() throws Exception {
|
||||||
|
URL paperMetadata = PaperPluginMetadataTest.class.getResource("/paper-plugin.yml");
|
||||||
|
assertNotNull(paperMetadata);
|
||||||
|
assertEquals("file", paperMetadata.getProtocol());
|
||||||
|
Path bukkitMetadata = Path.of(paperMetadata.toURI()).resolveSibling("plugin.yml");
|
||||||
|
assertTrue(Files.isRegularFile(bukkitMetadata));
|
||||||
|
assertFalse(Files.readString(bukkitMetadata, StandardCharsets.UTF_8).contains("${"));
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package art.arcane.iris.core;
|
||||||
|
|
||||||
|
import art.arcane.iris.core.commands.CommandStudio;
|
||||||
|
import art.arcane.iris.core.tools.IrisCreator;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
|
public class StudioRuntimeCleanupTest {
|
||||||
|
@Test
|
||||||
|
public void pregenSettingsNoLongerExposeStartupNoisemapPrebake() {
|
||||||
|
boolean found = Arrays.stream(IrisSettings.IrisSettingsPregen.class.getDeclaredFields())
|
||||||
|
.anyMatch(field -> field.getName().equals("startupNoisemapPrebake"));
|
||||||
|
|
||||||
|
assertFalse(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void studioCommandNoLongerExposesProfilecache() {
|
||||||
|
boolean found = Arrays.stream(CommandStudio.class.getDeclaredMethods())
|
||||||
|
.anyMatch(method -> method.getName().equals("profilecache"));
|
||||||
|
|
||||||
|
assertFalse(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void studioCreatorNoLongerContainsPrewarmOrPrebakeHelpers() {
|
||||||
|
boolean found = Arrays.stream(IrisCreator.class.getDeclaredMethods())
|
||||||
|
.map(method -> method.getName().toLowerCase())
|
||||||
|
.anyMatch(name -> name.contains("prewarm") || name.contains("prebake"));
|
||||||
|
|
||||||
|
assertFalse(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void noisemapPrebakePipelineClassIsRemoved() {
|
||||||
|
try {
|
||||||
|
Class.forName("art.arcane.iris.engine.IrisNoisemapPrebakePipeline");
|
||||||
|
} catch (ClassNotFoundException ignored) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AssertionError("IrisNoisemapPrebakePipeline should not exist.");
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package art.arcane.iris.core.service;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class IrisEngineSVCTest {
|
||||||
|
@Test
|
||||||
|
public void maintenanceSkipsReductionWhenPregenDoesNotTargetWorld() {
|
||||||
|
assertTrue(IrisEngineSVC.shouldSkipMantleReductionForMaintenance(true, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void maintenanceDoesNotSkipReductionForActivePregenWorld() {
|
||||||
|
assertFalse(IrisEngineSVC.shouldSkipMantleReductionForMaintenance(true, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void noMaintenanceNeverSkipsReduction() {
|
||||||
|
assertFalse(IrisEngineSVC.shouldSkipMantleReductionForMaintenance(false, false));
|
||||||
|
assertFalse(IrisEngineSVC.shouldSkipMantleReductionForMaintenance(false, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
package art.arcane.iris.core.service;
|
||||||
|
|
||||||
|
import io.papermc.paper.command.brigadier.BasicCommand;
|
||||||
|
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class PaperCommandRegistrarTest {
|
||||||
|
@Test
|
||||||
|
public void paperCommandDelegatesExecutionAndSuggestions() {
|
||||||
|
RecordingCommandService service = new RecordingCommandService();
|
||||||
|
BasicCommand command = PaperCommandRegistrar.command(service);
|
||||||
|
CommandSender sender = sender();
|
||||||
|
CommandSourceStack source = new TestCommandSourceStack(sender);
|
||||||
|
String[] arguments = {"create", "world"};
|
||||||
|
|
||||||
|
command.execute(source, arguments);
|
||||||
|
assertSame(sender, service.sender);
|
||||||
|
assertEquals("iris", service.label);
|
||||||
|
assertArrayEquals(arguments, service.arguments);
|
||||||
|
assertEquals(List.of("first", "second"), command.suggest(source, new String[]{"cr"}));
|
||||||
|
assertTrue(command.canUse(sender));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CommandSender sender() {
|
||||||
|
return (CommandSender) Proxy.newProxyInstance(
|
||||||
|
CommandSender.class.getClassLoader(),
|
||||||
|
new Class<?>[]{CommandSender.class},
|
||||||
|
(proxy, method, arguments) -> primitiveDefault(method.getReturnType())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object primitiveDefault(Class<?> type) {
|
||||||
|
if (type == boolean.class) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (type == byte.class) {
|
||||||
|
return (byte) 0;
|
||||||
|
}
|
||||||
|
if (type == short.class) {
|
||||||
|
return (short) 0;
|
||||||
|
}
|
||||||
|
if (type == int.class) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (type == long.class) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
if (type == float.class) {
|
||||||
|
return 0F;
|
||||||
|
}
|
||||||
|
if (type == double.class) {
|
||||||
|
return 0D;
|
||||||
|
}
|
||||||
|
if (type == char.class) {
|
||||||
|
return '\0';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RecordingCommandService extends CommandSVC {
|
||||||
|
private CommandSender sender;
|
||||||
|
private String label;
|
||||||
|
private String[] arguments;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void executeRoot(CommandSender sender, String label, String[] args) {
|
||||||
|
this.sender = sender;
|
||||||
|
this.label = label;
|
||||||
|
this.arguments = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
List<String> tabCompleteRoot(CommandSender sender, String alias, String[] args) {
|
||||||
|
return List.of("first", "second");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record TestCommandSourceStack(CommandSender sender) implements CommandSourceStack {
|
||||||
|
@Override
|
||||||
|
public Location getLocation() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandSender getSender() {
|
||||||
|
return sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Entity getExecutor() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandSourceStack withLocation(Location location) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandSourceStack withExecutor(Entity executor) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ClientPacketSink {
|
||||||
|
void send(byte[] frame);
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessageCodec;
|
||||||
|
import art.arcane.iris.spi.protocol.IrisProtocol;
|
||||||
|
import art.arcane.iris.spi.protocol.ProtocolException;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
public final class IrisClient {
|
||||||
|
public static final Identifier HUD_ELEMENT_ID = Identifier.fromNamespaceAndPath("irisworldgen", "pregen_hud");
|
||||||
|
public static final Identifier KEYBIND_CATEGORY_ID = Identifier.fromNamespaceAndPath("irisworldgen", "iris");
|
||||||
|
public static final String KEYBIND_TOGGLE_HUD = "key.irisworldgen.toggle_pregen_hud";
|
||||||
|
public static final String KEYBIND_OPEN_MAP = "key.irisworldgen.open_vision_map";
|
||||||
|
public static final String KEYBIND_TOGGLE_WHAT = "key.irisworldgen.toggle_what_overlay";
|
||||||
|
|
||||||
|
private static final IrisClientSession SESSION = new IrisClientSession();
|
||||||
|
private static final IrisClientPregenState PREGEN = new IrisClientPregenState();
|
||||||
|
private static final IrisClientDimension DIMENSION = new IrisClientDimension();
|
||||||
|
private static final IrisClientTileCache TILES = new IrisClientTileCache(IrisClient::sendFrame, System::currentTimeMillis);
|
||||||
|
private static final IrisClientCursor CURSOR = new IrisClientCursor(IrisClient::sendFrame, System::currentTimeMillis);
|
||||||
|
private static final IrisClientMarkers MARKERS = new IrisClientMarkers();
|
||||||
|
private static final IrisClientRegionMap REGIONS = new IrisClientRegionMap();
|
||||||
|
private static final IrisClientToasts TOASTS = new IrisClientToasts();
|
||||||
|
private static volatile ClientPacketSink sink;
|
||||||
|
private static volatile boolean hudVisible = true;
|
||||||
|
private static volatile boolean whatVisible = false;
|
||||||
|
|
||||||
|
private IrisClient() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IrisClientSession session() {
|
||||||
|
return SESSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IrisClientPregenState pregen() {
|
||||||
|
return PREGEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IrisClientDimension dimension() {
|
||||||
|
return DIMENSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IrisClientTileCache tiles() {
|
||||||
|
return TILES;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IrisClientCursor cursor() {
|
||||||
|
return CURSOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IrisClientMarkers markers() {
|
||||||
|
return MARKERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IrisClientRegionMap regions() {
|
||||||
|
return REGIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IrisClientToasts toasts() {
|
||||||
|
return TOASTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hudVisible() {
|
||||||
|
return hudVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void toggleHud() {
|
||||||
|
hudVisible = !hudVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean whatVisible() {
|
||||||
|
return whatVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void toggleWhat() {
|
||||||
|
whatVisible = !whatVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean visionAvailable() {
|
||||||
|
return SESSION.isReady() && DIMENSION.irisWorld() && (SESSION.serverCapabilities() & IrisProtocol.CAPABILITY_VISION) != 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean cursorAvailable() {
|
||||||
|
return SESSION.isReady() && DIMENSION.irisWorld() && (SESSION.serverCapabilities() & IrisProtocol.CAPABILITY_CURSOR) != 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void bindSender(ClientPacketSink boundSink) {
|
||||||
|
sink = boundSink;
|
||||||
|
SESSION.bind(boundSink);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendFrame(byte[] frame) {
|
||||||
|
ClientPacketSink activeSink = sink;
|
||||||
|
if (activeSink != null) {
|
||||||
|
activeSink.send(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onWorldJoin() {
|
||||||
|
SESSION.sendHello();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onDisconnect() {
|
||||||
|
SESSION.reset();
|
||||||
|
PREGEN.clear();
|
||||||
|
DIMENSION.clear();
|
||||||
|
TILES.clear();
|
||||||
|
CURSOR.clear();
|
||||||
|
MARKERS.clear();
|
||||||
|
REGIONS.clear();
|
||||||
|
TOASTS.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onInbound(byte[] frame) {
|
||||||
|
if (frame == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IrisMessage message;
|
||||||
|
try {
|
||||||
|
message = IrisMessageCodec.decode(frame);
|
||||||
|
} catch (ProtocolException rejected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
route(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void route(IrisMessage message) {
|
||||||
|
switch (message) {
|
||||||
|
case IrisMessage.ServerHello serverHello -> SESSION.onServerHello(serverHello);
|
||||||
|
case IrisMessage.PregenProgress progress -> PREGEN.onProgress(progress);
|
||||||
|
case IrisMessage.PregenEnd end -> onPregenEnd(end.jobId());
|
||||||
|
case IrisMessage.DimensionStatus status -> onDimensionStatus(status);
|
||||||
|
case IrisMessage.CursorInfo cursorInfo -> CURSOR.onCursorInfo(cursorInfo);
|
||||||
|
case IrisMessage.VisionTile visionTile -> TILES.onVisionTile(visionTile);
|
||||||
|
case IrisMessage.VisionMarkers visionMarkers -> MARKERS.onMarkers(visionMarkers);
|
||||||
|
case IrisMessage.PregenRegionDelta delta -> REGIONS.onDelta(delta, PREGEN.activeJobId());
|
||||||
|
case IrisMessage.StudioHotload hotload -> TOASTS.enqueueHotload(hotload.packKey(), hotload.changedFiles(), hotload.failed(), hotload.message());
|
||||||
|
case IrisMessage.Toast toast -> TOASTS.enqueue(toast.kind(), toast.title(), toast.body());
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onPregenEnd(long jobId) {
|
||||||
|
PREGEN.onEnd(jobId);
|
||||||
|
REGIONS.onEnd(jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onDimensionStatus(IrisMessage.DimensionStatus status) {
|
||||||
|
boolean changed = DIMENSION.onDimensionStatus(status);
|
||||||
|
if (changed) {
|
||||||
|
TILES.clear();
|
||||||
|
MARKERS.clear();
|
||||||
|
CURSOR.clear();
|
||||||
|
REGIONS.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessageCodec;
|
||||||
|
|
||||||
|
import java.util.function.LongSupplier;
|
||||||
|
|
||||||
|
public final class IrisClientCursor {
|
||||||
|
private static final long MIN_REQUEST_INTERVAL_MILLIS = 500L;
|
||||||
|
|
||||||
|
private final ClientPacketSink sink;
|
||||||
|
private final LongSupplier clock;
|
||||||
|
private volatile IrisMessage.CursorInfo latest;
|
||||||
|
private int lastRequestedX;
|
||||||
|
private int lastRequestedZ;
|
||||||
|
private boolean requested;
|
||||||
|
private long lastRequestMillis;
|
||||||
|
|
||||||
|
public IrisClientCursor(ClientPacketSink sink, LongSupplier clock) {
|
||||||
|
this.sink = sink;
|
||||||
|
this.clock = clock;
|
||||||
|
this.latest = null;
|
||||||
|
this.lastRequestedX = 0;
|
||||||
|
this.lastRequestedZ = 0;
|
||||||
|
this.requested = false;
|
||||||
|
this.lastRequestMillis = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void requestFor(int blockX, int blockZ) {
|
||||||
|
if (requested && blockX == lastRequestedX && blockZ == lastRequestedZ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long now = clock.getAsLong();
|
||||||
|
if (now - lastRequestMillis < MIN_REQUEST_INTERVAL_MILLIS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sink.send(IrisMessageCodec.encode(new IrisMessage.CursorInfoRequest(blockX, blockZ)));
|
||||||
|
lastRequestedX = blockX;
|
||||||
|
lastRequestedZ = blockZ;
|
||||||
|
requested = true;
|
||||||
|
lastRequestMillis = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCursorInfo(IrisMessage.CursorInfo info) {
|
||||||
|
latest = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IrisMessage.CursorInfo latest() {
|
||||||
|
return latest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void clear() {
|
||||||
|
latest = null;
|
||||||
|
requested = false;
|
||||||
|
lastRequestMillis = 0L;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
|
||||||
|
public final class IrisClientDimension {
|
||||||
|
private volatile IrisMessage.DimensionStatus status;
|
||||||
|
|
||||||
|
public boolean onDimensionStatus(IrisMessage.DimensionStatus incoming) {
|
||||||
|
IrisMessage.DimensionStatus previous = status;
|
||||||
|
status = incoming;
|
||||||
|
if (previous == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return previous.irisWorld() != incoming.irisWorld() || !previous.dimensionKey().equals(incoming.dimensionKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public IrisMessage.DimensionStatus status() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean irisWorld() {
|
||||||
|
IrisMessage.DimensionStatus current = status;
|
||||||
|
return current != null && current.irisWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
status = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||||
|
|
||||||
|
public final class IrisClientHud {
|
||||||
|
private IrisClientHud() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void render(GuiGraphicsExtractor graphics) {
|
||||||
|
IrisToastPresenter.pump();
|
||||||
|
IrisPregenHud.render(graphics);
|
||||||
|
IrisWhatOverlay.render(graphics);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import net.minecraft.client.KeyMapping;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
public final class IrisClientKeybinds {
|
||||||
|
private static final KeyMapping.Category CATEGORY = KeyMapping.Category.register(IrisClient.KEYBIND_CATEGORY_ID);
|
||||||
|
public static final KeyMapping TOGGLE_HUD = new KeyMapping(IrisClient.KEYBIND_TOGGLE_HUD, GLFW.GLFW_KEY_H, CATEGORY);
|
||||||
|
public static final KeyMapping OPEN_MAP = new KeyMapping(IrisClient.KEYBIND_OPEN_MAP, GLFW.GLFW_KEY_M, CATEGORY);
|
||||||
|
public static final KeyMapping TOGGLE_WHAT = new KeyMapping(IrisClient.KEYBIND_TOGGLE_WHAT, GLFW.GLFW_KEY_J, CATEGORY);
|
||||||
|
|
||||||
|
private IrisClientKeybinds() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyMapping.Category category() {
|
||||||
|
return CATEGORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void pollToggle() {
|
||||||
|
while (TOGGLE_HUD.consumeClick()) {
|
||||||
|
IrisClient.toggleHud();
|
||||||
|
}
|
||||||
|
while (TOGGLE_WHAT.consumeClick()) {
|
||||||
|
IrisClient.toggleWhat();
|
||||||
|
}
|
||||||
|
while (OPEN_MAP.consumeClick()) {
|
||||||
|
openVisionScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void openVisionScreen() {
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
if (minecraft == null || minecraft.level == null || minecraft.player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
minecraft.setScreenAndShow(new IrisVisionScreen());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public final class IrisClientMarkers {
|
||||||
|
private final Map<IrisTileKey, List<IrisMessage.VisionMarkers.Marker>> byTile;
|
||||||
|
|
||||||
|
public IrisClientMarkers() {
|
||||||
|
this.byTile = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMarkers(IrisMessage.VisionMarkers markers) {
|
||||||
|
byTile.put(new IrisTileKey(markers.tileX(), markers.tileZ(), markers.zoomLevel()), List.copyOf(markers.markers()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IrisMessage.VisionMarkers.Marker> forTile(IrisTileKey key) {
|
||||||
|
return byTile.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
byTile.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public final class IrisClientPregenState {
|
||||||
|
private final ConcurrentHashMap<Long, IrisMessage.PregenProgress> jobs;
|
||||||
|
private volatile Long activeJobId;
|
||||||
|
|
||||||
|
public IrisClientPregenState() {
|
||||||
|
this.jobs = new ConcurrentHashMap<>();
|
||||||
|
this.activeJobId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onProgress(IrisMessage.PregenProgress progress) {
|
||||||
|
jobs.put(progress.jobId(), progress);
|
||||||
|
activeJobId = progress.jobId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEnd(long jobId) {
|
||||||
|
jobs.remove(jobId);
|
||||||
|
Long current = activeJobId;
|
||||||
|
if (current != null && current == jobId) {
|
||||||
|
activeJobId = jobs.keySet().stream().findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IrisMessage.PregenProgress active() {
|
||||||
|
Long current = activeJobId;
|
||||||
|
if (current == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return jobs.get(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long activeJobId() {
|
||||||
|
return activeJobId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
jobs.clear();
|
||||||
|
activeJobId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public final class IrisClientRegionMap {
|
||||||
|
private static final int MAX_REGIONS = 4096;
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<Long, Integer> states;
|
||||||
|
private volatile boolean tracking;
|
||||||
|
private volatile long jobId;
|
||||||
|
private volatile Bounds bounds;
|
||||||
|
|
||||||
|
public IrisClientRegionMap() {
|
||||||
|
this.states = new ConcurrentHashMap<>();
|
||||||
|
this.tracking = false;
|
||||||
|
this.jobId = 0L;
|
||||||
|
this.bounds = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDelta(IrisMessage.PregenRegionDelta delta, Long activeJobId) {
|
||||||
|
if (activeJobId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long active = activeJobId;
|
||||||
|
if (delta.jobId() != active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!tracking || jobId != delta.jobId()) {
|
||||||
|
resetTo(delta.jobId());
|
||||||
|
}
|
||||||
|
record(delta.regionX(), delta.regionZ(), delta.state());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEnd(long endedJobId) {
|
||||||
|
if (tracking && jobId == endedJobId) {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasData() {
|
||||||
|
return tracking && !states.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bounds bounds() {
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forEachCell(CellConsumer consumer) {
|
||||||
|
for (Map.Entry<Long, Integer> entry : states.entrySet()) {
|
||||||
|
long packed = entry.getKey();
|
||||||
|
consumer.accept(regionX(packed), regionZ(packed), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
states.clear();
|
||||||
|
bounds = null;
|
||||||
|
tracking = false;
|
||||||
|
jobId = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetTo(long newJobId) {
|
||||||
|
states.clear();
|
||||||
|
bounds = null;
|
||||||
|
jobId = newJobId;
|
||||||
|
tracking = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void record(int regionX, int regionZ, int state) {
|
||||||
|
long packed = pack(regionX, regionZ);
|
||||||
|
if (!states.containsKey(packed)) {
|
||||||
|
if (states.size() >= MAX_REGIONS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Bounds current = bounds;
|
||||||
|
bounds = current == null ? new Bounds(regionX, regionZ, regionX, regionZ) : current.union(regionX, regionZ);
|
||||||
|
}
|
||||||
|
states.put(packed, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long pack(int regionX, int regionZ) {
|
||||||
|
return ((long) regionX << 32) | (regionZ & 0xFFFFFFFFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int regionX(long packed) {
|
||||||
|
return (int) (packed >> 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int regionZ(long packed) {
|
||||||
|
return (int) packed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface CellConsumer {
|
||||||
|
void accept(int regionX, int regionZ, int state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Bounds(int minRegionX, int minRegionZ, int maxRegionX, int maxRegionZ) {
|
||||||
|
public Bounds union(int regionX, int regionZ) {
|
||||||
|
return new Bounds(
|
||||||
|
Math.min(minRegionX, regionX),
|
||||||
|
Math.min(minRegionZ, regionZ),
|
||||||
|
Math.max(maxRegionX, regionX),
|
||||||
|
Math.max(maxRegionZ, regionZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int regionsWide() {
|
||||||
|
return maxRegionX - minRegionX + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int regionsTall() {
|
||||||
|
return maxRegionZ - minRegionZ + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessageCodec;
|
||||||
|
import art.arcane.iris.spi.protocol.IrisProtocol;
|
||||||
|
|
||||||
|
public final class IrisClientSession {
|
||||||
|
private static final long CLIENT_CAPABILITIES = IrisProtocol.CAPABILITY_PREGEN | IrisProtocol.CAPABILITY_VISION | IrisProtocol.CAPABILITY_CURSOR | IrisProtocol.CAPABILITY_STUDIO;
|
||||||
|
|
||||||
|
private volatile State state;
|
||||||
|
private volatile long serverCapabilities;
|
||||||
|
private volatile boolean irisActive;
|
||||||
|
private volatile String serverBrand;
|
||||||
|
private volatile ClientPacketSink sink;
|
||||||
|
|
||||||
|
public IrisClientSession() {
|
||||||
|
this.state = State.IDLE;
|
||||||
|
this.serverCapabilities = 0L;
|
||||||
|
this.irisActive = false;
|
||||||
|
this.serverBrand = "";
|
||||||
|
this.sink = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(ClientPacketSink boundSink) {
|
||||||
|
this.sink = boundSink;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State state() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isReady() {
|
||||||
|
return state == State.READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean irisActive() {
|
||||||
|
return irisActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long serverCapabilities() {
|
||||||
|
return serverCapabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String serverBrand() {
|
||||||
|
return serverBrand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendHello() {
|
||||||
|
ClientPacketSink activeSink = sink;
|
||||||
|
if (activeSink == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
byte[] frame = IrisMessageCodec.encode(new IrisMessage.ClientHello(IrisProtocol.PROTOCOL_VERSION, CLIENT_CAPABILITIES));
|
||||||
|
state = State.AWAITING_HELLO;
|
||||||
|
activeSink.send(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onServerHello(IrisMessage.ServerHello hello) {
|
||||||
|
this.serverCapabilities = hello.capabilities();
|
||||||
|
this.irisActive = hello.irisActive();
|
||||||
|
this.serverBrand = hello.serverBrand();
|
||||||
|
this.state = State.READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
this.state = State.IDLE;
|
||||||
|
this.serverCapabilities = 0L;
|
||||||
|
this.irisActive = false;
|
||||||
|
this.serverBrand = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum State {
|
||||||
|
IDLE,
|
||||||
|
AWAITING_HELLO,
|
||||||
|
READY
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessageCodec;
|
||||||
|
import art.arcane.iris.spi.protocol.IrisProtocol;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.LongSupplier;
|
||||||
|
|
||||||
|
public final class IrisClientTileCache {
|
||||||
|
private static final int MAX_CACHED_TILES = 256;
|
||||||
|
private static final long REQUEST_RETRY_MILLIS = 3000L;
|
||||||
|
private static final int REQUESTS_PER_SECOND = IrisProtocol.MAX_VISION_TILE_REQUESTS_PER_SECOND;
|
||||||
|
|
||||||
|
private final ClientPacketSink sink;
|
||||||
|
private final LongSupplier clock;
|
||||||
|
private final IrisTileAssembler assembler;
|
||||||
|
private final LinkedHashMap<IrisTileKey, IrisTileImage> cache;
|
||||||
|
private final Map<IrisTileKey, Long> pending;
|
||||||
|
private final Deque<IrisTileKey> queue;
|
||||||
|
private final Set<IrisTileKey> queued;
|
||||||
|
private long windowStartMillis;
|
||||||
|
private int sentInWindow;
|
||||||
|
|
||||||
|
public IrisClientTileCache(ClientPacketSink sink, LongSupplier clock) {
|
||||||
|
this.sink = sink;
|
||||||
|
this.clock = clock;
|
||||||
|
this.assembler = new IrisTileAssembler();
|
||||||
|
this.cache = new LinkedHashMap<>(64, 0.75f, true) {
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<IrisTileKey, IrisTileImage> eldest) {
|
||||||
|
return size() > MAX_CACHED_TILES;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.pending = new HashMap<>();
|
||||||
|
this.queue = new ArrayDeque<>();
|
||||||
|
this.queued = new HashSet<>();
|
||||||
|
this.windowStartMillis = 0L;
|
||||||
|
this.sentInWindow = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void onVisionTile(IrisMessage.VisionTile tile) {
|
||||||
|
IrisTileImage image = assembler.add(tile);
|
||||||
|
if (image == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IrisTileKey key = new IrisTileKey(tile.tileX(), tile.tileZ(), tile.zoomLevel());
|
||||||
|
cache.put(key, image);
|
||||||
|
pending.remove(key);
|
||||||
|
queued.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized IrisTileImage get(IrisTileKey key) {
|
||||||
|
return cache.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void resetRequestQueue() {
|
||||||
|
queue.clear();
|
||||||
|
queued.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void request(IrisTileKey key) {
|
||||||
|
if (cache.containsKey(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long now = clock.getAsLong();
|
||||||
|
Long lastRequest = pending.get(key);
|
||||||
|
if (lastRequest != null && now - lastRequest < REQUEST_RETRY_MILLIS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (queued.add(key)) {
|
||||||
|
queue.addLast(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void pump() {
|
||||||
|
long now = clock.getAsLong();
|
||||||
|
if (now - windowStartMillis >= 1000L) {
|
||||||
|
windowStartMillis = now;
|
||||||
|
sentInWindow = 0;
|
||||||
|
}
|
||||||
|
while (sentInWindow < REQUESTS_PER_SECOND && !queue.isEmpty()) {
|
||||||
|
IrisTileKey key = queue.pollFirst();
|
||||||
|
queued.remove(key);
|
||||||
|
if (cache.containsKey(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Long lastRequest = pending.get(key);
|
||||||
|
if (lastRequest != null && now - lastRequest < REQUEST_RETRY_MILLIS) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sink.send(IrisMessageCodec.encode(new IrisMessage.VisionTileRequest(key.tileX(), key.tileZ(), key.zoom())));
|
||||||
|
pending.put(key, now);
|
||||||
|
sentInWindow++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void clear() {
|
||||||
|
assembler.clear();
|
||||||
|
cache.clear();
|
||||||
|
pending.clear();
|
||||||
|
queue.clear();
|
||||||
|
queued.clear();
|
||||||
|
sentInWindow = 0;
|
||||||
|
windowStartMillis = 0L;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
|
||||||
|
public final class IrisClientToasts {
|
||||||
|
private static final int MAX_PENDING = 4;
|
||||||
|
|
||||||
|
private final ArrayDeque<Pending> pending;
|
||||||
|
|
||||||
|
public IrisClientToasts() {
|
||||||
|
this.pending = new ArrayDeque<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enqueue(int kind, String title, String body) {
|
||||||
|
Pending entry = new Pending(kind, normalize(title), normalize(body));
|
||||||
|
synchronized (pending) {
|
||||||
|
while (pending.size() >= MAX_PENDING) {
|
||||||
|
pending.pollFirst();
|
||||||
|
}
|
||||||
|
pending.addLast(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enqueueHotload(String packKey, int changedFiles, boolean failed, String message) {
|
||||||
|
int kind = failed ? IrisMessage.Toast.KIND_ERROR : IrisMessage.Toast.KIND_SUCCESS;
|
||||||
|
enqueue(kind, "Studio Hotload", hotloadBody(packKey, changedFiles, failed, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pending poll() {
|
||||||
|
synchronized (pending) {
|
||||||
|
return pending.pollFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
synchronized (pending) {
|
||||||
|
pending.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalize(String value) {
|
||||||
|
return value == null ? "" : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String hotloadBody(String packKey, int changedFiles, boolean failed, String message) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
String pack = normalize(packKey);
|
||||||
|
if (!pack.isEmpty()) {
|
||||||
|
builder.append(pack);
|
||||||
|
}
|
||||||
|
if (changedFiles != 0) {
|
||||||
|
append(builder, changedFiles + (changedFiles == 1 ? " file" : " files"));
|
||||||
|
}
|
||||||
|
String text = normalize(message);
|
||||||
|
if (!text.isEmpty()) {
|
||||||
|
append(builder, text);
|
||||||
|
}
|
||||||
|
if (builder.isEmpty()) {
|
||||||
|
return failed ? "reload failed" : "reloaded";
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void append(StringBuilder builder, String part) {
|
||||||
|
if (!builder.isEmpty()) {
|
||||||
|
builder.append(" ");
|
||||||
|
}
|
||||||
|
builder.append(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Pending(int kind, String title, String body) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.Font;
|
||||||
|
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||||
|
|
||||||
|
public final class IrisPregenHud {
|
||||||
|
private static final int PANEL_COLOR = 0xC0101010;
|
||||||
|
private static final int TITLE_COLOR = 0xFF66BB6A;
|
||||||
|
private static final int TEXT_COLOR = 0xFFFFFFFF;
|
||||||
|
private static final int MUTED_COLOR = 0xFFD7D7D7;
|
||||||
|
private static final int BAR_BACK_COLOR = 0xFF2B2B2B;
|
||||||
|
private static final int BAR_RUNNING_COLOR = 0xFF66BB6A;
|
||||||
|
private static final int PAUSED_COLOR = 0xFFFFD54F;
|
||||||
|
private static final int GRID_BACK_COLOR = 0xFF161616;
|
||||||
|
private static final int CELL_PENDING_COLOR = 0xFF3A3A3A;
|
||||||
|
private static final int CELL_GENERATING_COLOR = 0xFFFFD54F;
|
||||||
|
private static final int CELL_DONE_COLOR = 0xFF66BB6A;
|
||||||
|
private static final int ORIGIN_X = 6;
|
||||||
|
private static final int ORIGIN_Y = 6;
|
||||||
|
private static final int PADDING = 4;
|
||||||
|
private static final int ROW_GAP = 2;
|
||||||
|
private static final int BAR_HEIGHT = 6;
|
||||||
|
private static final int MIN_WIDTH = 130;
|
||||||
|
private static final int MINIMAP_MAX_PX = 64;
|
||||||
|
private static final int MINIMAP_MAX_CELL = 6;
|
||||||
|
private static final int MINIMAP_GAP = 4;
|
||||||
|
|
||||||
|
private IrisPregenHud() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void render(GuiGraphicsExtractor graphics) {
|
||||||
|
if (!IrisClient.hudVisible()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IrisMessage.PregenProgress progress = IrisClient.pregen().active();
|
||||||
|
if (progress == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
if (minecraft == null || minecraft.player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Font font = minecraft.font;
|
||||||
|
boolean paused = progress.state() == IrisMessage.PregenProgress.STATE_PAUSED;
|
||||||
|
double percent = progress.chunksTotal() > 0L
|
||||||
|
? clampPercent((double) progress.chunksDone() / (double) progress.chunksTotal() * 100.0D)
|
||||||
|
: 0.0D;
|
||||||
|
String title = "Iris Pregen";
|
||||||
|
String stats = String.format("%,d / %,d (%.1f%%)", progress.chunksDone(), progress.chunksTotal(), percent);
|
||||||
|
String tail = paused ? "PAUSED" : rateAndEta(progress);
|
||||||
|
int accent = paused ? PAUSED_COLOR : BAR_RUNNING_COLOR;
|
||||||
|
|
||||||
|
int lineHeight = font.lineHeight;
|
||||||
|
int contentWidth = Math.max(MIN_WIDTH, Math.max(font.width(title), Math.max(font.width(stats), font.width(tail))));
|
||||||
|
int contentHeight = lineHeight * 3 + ROW_GAP * 3 + BAR_HEIGHT;
|
||||||
|
|
||||||
|
IrisClientRegionMap regionMap = IrisClient.regions();
|
||||||
|
IrisClientRegionMap.Bounds bounds = regionMap.hasData() ? regionMap.bounds() : null;
|
||||||
|
boolean showMap = bounds != null;
|
||||||
|
int cellPx = 0;
|
||||||
|
int gridWidth = 0;
|
||||||
|
int gridHeight = 0;
|
||||||
|
if (showMap) {
|
||||||
|
int span = Math.max(bounds.regionsWide(), bounds.regionsTall());
|
||||||
|
cellPx = Math.max(1, Math.min(MINIMAP_MAX_CELL, MINIMAP_MAX_PX / span));
|
||||||
|
gridWidth = Math.min(MINIMAP_MAX_PX, bounds.regionsWide() * cellPx);
|
||||||
|
gridHeight = Math.min(MINIMAP_MAX_PX, bounds.regionsTall() * cellPx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int panelWidth = showMap ? Math.max(contentWidth, gridWidth) : contentWidth;
|
||||||
|
int panelHeight = showMap ? contentHeight + MINIMAP_GAP + gridHeight : contentHeight;
|
||||||
|
|
||||||
|
graphics.fill(ORIGIN_X - PADDING, ORIGIN_Y - PADDING, ORIGIN_X + panelWidth + PADDING, ORIGIN_Y + panelHeight + PADDING, PANEL_COLOR);
|
||||||
|
|
||||||
|
int cursorY = ORIGIN_Y;
|
||||||
|
graphics.text(font, title, ORIGIN_X, cursorY, TITLE_COLOR);
|
||||||
|
cursorY += lineHeight + ROW_GAP;
|
||||||
|
graphics.text(font, stats, ORIGIN_X, cursorY, TEXT_COLOR);
|
||||||
|
cursorY += lineHeight + ROW_GAP;
|
||||||
|
|
||||||
|
int fillWidth = (int) Math.round(contentWidth * (percent / 100.0D));
|
||||||
|
graphics.fill(ORIGIN_X, cursorY, ORIGIN_X + contentWidth, cursorY + BAR_HEIGHT, BAR_BACK_COLOR);
|
||||||
|
if (fillWidth > 0) {
|
||||||
|
graphics.fill(ORIGIN_X, cursorY, ORIGIN_X + fillWidth, cursorY + BAR_HEIGHT, accent);
|
||||||
|
}
|
||||||
|
cursorY += BAR_HEIGHT + ROW_GAP;
|
||||||
|
|
||||||
|
graphics.text(font, tail, ORIGIN_X, cursorY, paused ? PAUSED_COLOR : MUTED_COLOR);
|
||||||
|
|
||||||
|
if (showMap) {
|
||||||
|
renderMinimap(graphics, regionMap, bounds, cellPx, gridWidth, gridHeight, ORIGIN_Y + contentHeight + MINIMAP_GAP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void renderMinimap(GuiGraphicsExtractor graphics, IrisClientRegionMap regionMap, IrisClientRegionMap.Bounds bounds, int cellPx, int gridWidth, int gridHeight, int gridTop) {
|
||||||
|
int mapLeft = ORIGIN_X;
|
||||||
|
int mapRight = mapLeft + gridWidth;
|
||||||
|
int mapBottom = gridTop + gridHeight;
|
||||||
|
graphics.fill(mapLeft, gridTop, mapRight, mapBottom, GRID_BACK_COLOR);
|
||||||
|
int minRegionX = bounds.minRegionX();
|
||||||
|
int minRegionZ = bounds.minRegionZ();
|
||||||
|
regionMap.forEachCell((regionX, regionZ, state) -> {
|
||||||
|
int px = mapLeft + (regionX - minRegionX) * cellPx;
|
||||||
|
int py = gridTop + (regionZ - minRegionZ) * cellPx;
|
||||||
|
if (px + cellPx > mapRight || py + cellPx > mapBottom) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
graphics.fill(px, py, px + cellPx, py + cellPx, cellColor(state));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int cellColor(int state) {
|
||||||
|
return switch (state) {
|
||||||
|
case IrisMessage.PregenRegionDelta.STATE_DONE -> CELL_DONE_COLOR;
|
||||||
|
case IrisMessage.PregenRegionDelta.STATE_GENERATING -> CELL_GENERATING_COLOR;
|
||||||
|
default -> CELL_PENDING_COLOR;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String rateAndEta(IrisMessage.PregenProgress progress) {
|
||||||
|
String rate = String.format("%,.0f/s", progress.chunksPerSecond());
|
||||||
|
if (progress.etaMillis() > 0L) {
|
||||||
|
return rate + " ETA " + formatDuration(progress.etaMillis());
|
||||||
|
}
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatDuration(long etaMillis) {
|
||||||
|
long totalSeconds = etaMillis / 1000L;
|
||||||
|
long hours = totalSeconds / 3600L;
|
||||||
|
long minutes = (totalSeconds % 3600L) / 60L;
|
||||||
|
long seconds = totalSeconds % 60L;
|
||||||
|
if (hours > 0L) {
|
||||||
|
return hours + "h " + minutes + "m";
|
||||||
|
}
|
||||||
|
if (minutes > 0L) {
|
||||||
|
return minutes + "m " + seconds + "s";
|
||||||
|
}
|
||||||
|
return seconds + "s";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double clampPercent(double value) {
|
||||||
|
if (value < 0.0D) {
|
||||||
|
return 0.0D;
|
||||||
|
}
|
||||||
|
if (value > 100.0D) {
|
||||||
|
return 100.0D;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
final class IrisTileAssembler {
|
||||||
|
private final Map<IrisTileKey, Partial> partials;
|
||||||
|
|
||||||
|
IrisTileAssembler() {
|
||||||
|
this.partials = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisTileImage add(IrisMessage.VisionTile tile) {
|
||||||
|
int chunkCount = tile.chunkCount();
|
||||||
|
int chunkIndex = tile.chunkIndex();
|
||||||
|
if (chunkCount <= 0 || chunkIndex < 0 || chunkIndex >= chunkCount || tile.data() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
IrisTileKey key = new IrisTileKey(tile.tileX(), tile.tileZ(), tile.zoomLevel());
|
||||||
|
Partial partial = partials.get(key);
|
||||||
|
if (partial == null || tile.sequence() > partial.sequence() || partial.chunkCount() != chunkCount) {
|
||||||
|
if (partial != null && tile.sequence() < partial.sequence()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
partial = new Partial(tile.sequence(), chunkCount);
|
||||||
|
partials.put(key, partial);
|
||||||
|
} else if (tile.sequence() < partial.sequence()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!partial.accept(chunkIndex, tile.data())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
partials.remove(key);
|
||||||
|
return IrisTileCodec.decode(partial.concat());
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
partials.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Partial {
|
||||||
|
private final int sequence;
|
||||||
|
private final int chunkCount;
|
||||||
|
private final byte[][] chunks;
|
||||||
|
private int received;
|
||||||
|
|
||||||
|
Partial(int sequence, int chunkCount) {
|
||||||
|
this.sequence = sequence;
|
||||||
|
this.chunkCount = chunkCount;
|
||||||
|
this.chunks = new byte[chunkCount][];
|
||||||
|
this.received = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sequence() {
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
int chunkCount() {
|
||||||
|
return chunkCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean accept(int chunkIndex, byte[] data) {
|
||||||
|
if (chunks[chunkIndex] == null) {
|
||||||
|
chunks[chunkIndex] = data;
|
||||||
|
received++;
|
||||||
|
}
|
||||||
|
return received >= chunkCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] concat() {
|
||||||
|
int total = 0;
|
||||||
|
for (byte[] chunk : chunks) {
|
||||||
|
total += chunk.length;
|
||||||
|
}
|
||||||
|
byte[] blob = new byte[total];
|
||||||
|
int position = 0;
|
||||||
|
for (byte[] chunk : chunks) {
|
||||||
|
System.arraycopy(chunk, 0, blob, position, chunk.length);
|
||||||
|
position += chunk.length;
|
||||||
|
}
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.zip.DataFormatException;
|
||||||
|
import java.util.zip.Inflater;
|
||||||
|
|
||||||
|
public final class IrisTileCodec {
|
||||||
|
public static final int MODE_RAW_RGB = 0;
|
||||||
|
public static final int MODE_PALETTE = 1;
|
||||||
|
private static final int MAX_DIMENSION = 512;
|
||||||
|
private static final int OPAQUE = 0xFF000000;
|
||||||
|
|
||||||
|
private IrisTileCodec() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IrisTileImage decode(byte[] deflatedBlob) {
|
||||||
|
if (deflatedBlob == null || deflatedBlob.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] raw = inflate(deflatedBlob);
|
||||||
|
if (raw == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(raw))) {
|
||||||
|
int width = in.readInt();
|
||||||
|
int height = in.readInt();
|
||||||
|
if (width <= 0 || height <= 0 || width > MAX_DIMENSION || height > MAX_DIMENSION) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int mode = in.readUnsignedByte();
|
||||||
|
int[] argb = new int[width * height];
|
||||||
|
return switch (mode) {
|
||||||
|
case MODE_PALETTE -> decodePalette(in, width, height, argb);
|
||||||
|
case MODE_RAW_RGB -> decodeRaw(in, width, height, argb);
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
} catch (IOException failure) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisTileImage decodePalette(DataInputStream in, int width, int height, int[] argb) throws IOException {
|
||||||
|
int paletteSize = in.readInt();
|
||||||
|
if (paletteSize <= 0 || paletteSize > 256) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int[] palette = new int[paletteSize];
|
||||||
|
for (int index = 0; index < paletteSize; index++) {
|
||||||
|
int red = in.readUnsignedByte();
|
||||||
|
int green = in.readUnsignedByte();
|
||||||
|
int blue = in.readUnsignedByte();
|
||||||
|
palette[index] = OPAQUE | red << 16 | green << 8 | blue;
|
||||||
|
}
|
||||||
|
for (int pixel = 0; pixel < argb.length; pixel++) {
|
||||||
|
int paletteIndex = in.readUnsignedByte();
|
||||||
|
if (paletteIndex >= paletteSize) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
argb[pixel] = palette[paletteIndex];
|
||||||
|
}
|
||||||
|
return new IrisTileImage(width, height, argb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisTileImage decodeRaw(DataInputStream in, int width, int height, int[] argb) throws IOException {
|
||||||
|
for (int pixel = 0; pixel < argb.length; pixel++) {
|
||||||
|
int red = in.readUnsignedByte();
|
||||||
|
int green = in.readUnsignedByte();
|
||||||
|
int blue = in.readUnsignedByte();
|
||||||
|
argb[pixel] = OPAQUE | red << 16 | green << 8 | blue;
|
||||||
|
}
|
||||||
|
return new IrisTileImage(width, height, argb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] inflate(byte[] input) {
|
||||||
|
Inflater inflater = new Inflater();
|
||||||
|
inflater.setInput(input);
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(64, input.length * 2));
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
try {
|
||||||
|
while (!inflater.finished()) {
|
||||||
|
int produced = inflater.inflate(buffer);
|
||||||
|
if (produced == 0) {
|
||||||
|
if (inflater.finished() || inflater.needsInput() || inflater.needsDictionary()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.write(buffer, 0, produced);
|
||||||
|
}
|
||||||
|
} catch (DataFormatException malformed) {
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
inflater.end();
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
public record IrisTileImage(int width, int height, int[] argb) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
public record IrisTileKey(int tileX, int tileZ, int zoom) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.components.toasts.SystemToast;
|
||||||
|
import net.minecraft.client.gui.components.toasts.ToastManager;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
public final class IrisToastPresenter {
|
||||||
|
private IrisToastPresenter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void pump() {
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
if (minecraft == null || minecraft.gui == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ToastManager manager = minecraft.gui.toastManager();
|
||||||
|
IrisClientToasts toasts = IrisClient.toasts();
|
||||||
|
IrisClientToasts.Pending next = toasts.poll();
|
||||||
|
while (next != null) {
|
||||||
|
SystemToast.addOrUpdate(manager, tokenFor(next.kind()), Component.literal(next.title()), Component.literal(next.body()));
|
||||||
|
next = toasts.poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SystemToast.SystemToastId tokenFor(int kind) {
|
||||||
|
return switch (kind) {
|
||||||
|
case IrisMessage.Toast.KIND_SUCCESS -> SystemToast.SystemToastId.WORLD_BACKUP;
|
||||||
|
case IrisMessage.Toast.KIND_WARNING -> SystemToast.SystemToastId.UNSECURE_SERVER_WARNING;
|
||||||
|
case IrisMessage.Toast.KIND_ERROR -> SystemToast.SystemToastId.PACK_LOAD_FAILURE;
|
||||||
|
default -> SystemToast.SystemToastId.PERIODIC_NOTIFICATION;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,328 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
import com.mojang.blaze3d.platform.NativeImage;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.client.input.MouseButtonEvent;
|
||||||
|
import net.minecraft.client.player.LocalPlayer;
|
||||||
|
import net.minecraft.client.renderer.RenderPipelines;
|
||||||
|
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||||
|
import net.minecraft.client.renderer.texture.TextureManager;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class IrisVisionScreen extends Screen {
|
||||||
|
private static final int TILE_PIXELS = 128;
|
||||||
|
private static final int MIN_ZOOM = 0;
|
||||||
|
private static final int MAX_ZOOM = 8;
|
||||||
|
private static final int DEFAULT_ZOOM = 2;
|
||||||
|
private static final int MAX_TEXTURES = 220;
|
||||||
|
private static final int BACKGROUND_COLOR = 0xF00B0E14;
|
||||||
|
private static final int PLACEHOLDER_COLOR = 0xFF161A22;
|
||||||
|
private static final int GRID_COLOR = 0x33FFFFFF;
|
||||||
|
private static final int HEADER_COLOR = 0xC0101010;
|
||||||
|
private static final int TITLE_COLOR = 0xFF66BB6A;
|
||||||
|
private static final int TEXT_COLOR = 0xFFFFFFFF;
|
||||||
|
private static final int MUTED_COLOR = 0xFFB9B9B9;
|
||||||
|
private static final int PLAYER_FILL_COLOR = 0xFFFFDD33;
|
||||||
|
private static final int PLAYER_BORDER_COLOR = 0xFF101010;
|
||||||
|
private static final int MARKER_COLOR = 0xFF4FC3F7;
|
||||||
|
private static final int MARKER_LABEL_BG = 0xE0101010;
|
||||||
|
|
||||||
|
private final Map<IrisTileKey, TileTexture> textures;
|
||||||
|
private double centerBlockX;
|
||||||
|
private double centerBlockZ;
|
||||||
|
private int zoom;
|
||||||
|
private boolean initialized;
|
||||||
|
private String renderedDimensionKey;
|
||||||
|
|
||||||
|
public IrisVisionScreen() {
|
||||||
|
super(Component.literal("Iris Vision"));
|
||||||
|
this.textures = new LinkedHashMap<>(64, 0.75f, true);
|
||||||
|
this.centerBlockX = 0.0D;
|
||||||
|
this.centerBlockZ = 0.0D;
|
||||||
|
this.zoom = DEFAULT_ZOOM;
|
||||||
|
this.initialized = false;
|
||||||
|
this.renderedDimensionKey = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void init() {
|
||||||
|
if (!initialized) {
|
||||||
|
centerOnPlayer();
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPauseScreen() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float partialTick) {
|
||||||
|
graphics.fill(0, 0, width, height, BACKGROUND_COLOR);
|
||||||
|
IrisClientSession session = IrisClient.session();
|
||||||
|
if (!session.isReady()) {
|
||||||
|
drawCentered(graphics, "Connecting to Iris server...", MUTED_COLOR);
|
||||||
|
drawHeader(graphics, "Iris Vision", "not connected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IrisMessage.DimensionStatus status = IrisClient.dimension().status();
|
||||||
|
if (!IrisClient.visionAvailable() || status == null) {
|
||||||
|
drawCentered(graphics, "Not an Iris world", MUTED_COLOR);
|
||||||
|
drawHeader(graphics, "Iris Vision", status == null ? "no dimension data" : label(status));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
syncWorld(status);
|
||||||
|
renderTiles(graphics, mouseX, mouseY, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseDragged(MouseButtonEvent event, double dragX, double dragY) {
|
||||||
|
int blocksPerPixel = 1 << zoom;
|
||||||
|
centerBlockX -= dragX * blocksPerPixel;
|
||||||
|
centerBlockZ -= dragY * blocksPerPixel;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
|
||||||
|
if (scrollY == 0.0D) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int previousBlocksPerPixel = 1 << zoom;
|
||||||
|
double anchorBlockX = centerBlockX + (mouseX - width / 2.0D) * previousBlocksPerPixel;
|
||||||
|
double anchorBlockZ = centerBlockZ + (mouseY - height / 2.0D) * previousBlocksPerPixel;
|
||||||
|
int updated = zoom + (scrollY > 0.0D ? -1 : 1);
|
||||||
|
zoom = Math.max(MIN_ZOOM, Math.min(updated, MAX_ZOOM));
|
||||||
|
int newBlocksPerPixel = 1 << zoom;
|
||||||
|
centerBlockX = anchorBlockX - (mouseX - width / 2.0D) * newBlocksPerPixel;
|
||||||
|
centerBlockZ = anchorBlockZ - (mouseY - height / 2.0D) * newBlocksPerPixel;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removed() {
|
||||||
|
releaseTextures();
|
||||||
|
super.removed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderTiles(GuiGraphicsExtractor graphics, int mouseX, int mouseY, IrisMessage.DimensionStatus status) {
|
||||||
|
int blocksPerPixel = 1 << zoom;
|
||||||
|
int tileSpanBlocks = TILE_PIXELS * blocksPerPixel;
|
||||||
|
int originX = (int) Math.floor(width / 2.0D - centerBlockX / blocksPerPixel);
|
||||||
|
int originY = (int) Math.floor(height / 2.0D - centerBlockZ / blocksPerPixel);
|
||||||
|
int minTileX = (int) Math.floor((0.0D - originX) / TILE_PIXELS) - 1;
|
||||||
|
int maxTileX = (int) Math.floor((width - originX) / (double) TILE_PIXELS) + 1;
|
||||||
|
int minTileZ = (int) Math.floor((0.0D - originY) / TILE_PIXELS) - 1;
|
||||||
|
int maxTileZ = (int) Math.floor((height - originY) / (double) TILE_PIXELS) + 1;
|
||||||
|
int centerTileX = Math.floorDiv((int) Math.floor(centerBlockX), tileSpanBlocks);
|
||||||
|
int centerTileZ = Math.floorDiv((int) Math.floor(centerBlockZ), tileSpanBlocks);
|
||||||
|
|
||||||
|
IrisClientTileCache cache = IrisClient.tiles();
|
||||||
|
cache.resetRequestQueue();
|
||||||
|
List<IrisTileKey> missing = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int tileX = minTileX; tileX <= maxTileX; tileX++) {
|
||||||
|
for (int tileZ = minTileZ; tileZ <= maxTileZ; tileZ++) {
|
||||||
|
int screenX = originX + tileX * TILE_PIXELS;
|
||||||
|
int screenY = originY + tileZ * TILE_PIXELS;
|
||||||
|
IrisTileKey key = new IrisTileKey(tileX, tileZ, zoom);
|
||||||
|
IrisTileImage image = cache.get(key);
|
||||||
|
if (image == null) {
|
||||||
|
graphics.fill(screenX, screenY, screenX + TILE_PIXELS, screenY + TILE_PIXELS, PLACEHOLDER_COLOR);
|
||||||
|
missing.add(key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Identifier texture = ensureTexture(key, image);
|
||||||
|
graphics.blit(RenderPipelines.GUI_TEXTURED, texture, screenX, screenY, 0.0F, 0.0F, TILE_PIXELS, TILE_PIXELS, TILE_PIXELS, TILE_PIXELS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestMissing(cache, missing, centerTileX, centerTileZ);
|
||||||
|
cache.pump();
|
||||||
|
evictTextures();
|
||||||
|
|
||||||
|
drawMarkers(graphics, mouseX, mouseY, minTileX, maxTileX, minTileZ, maxTileZ, originX, originY, blocksPerPixel);
|
||||||
|
drawPlayer(graphics, blocksPerPixel);
|
||||||
|
drawHeader(graphics, "Iris Vision", label(status) + " zoom " + zoom + " x" + (long) centerBlockX + " z" + (long) centerBlockZ);
|
||||||
|
drawFooter(graphics, "Drag to pan Scroll to zoom Esc to close");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestMissing(IrisClientTileCache cache, List<IrisTileKey> missing, int centerTileX, int centerTileZ) {
|
||||||
|
missing.sort((left, right) -> Long.compare(distanceSquared(left, centerTileX, centerTileZ), distanceSquared(right, centerTileX, centerTileZ)));
|
||||||
|
for (IrisTileKey key : missing) {
|
||||||
|
cache.request(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawMarkers(GuiGraphicsExtractor graphics, int mouseX, int mouseY, int minTileX, int maxTileX, int minTileZ, int maxTileZ, int originX, int originY, int blocksPerPixel) {
|
||||||
|
IrisClientMarkers markers = IrisClient.markers();
|
||||||
|
String hoverLabel = null;
|
||||||
|
int hoverX = 0;
|
||||||
|
int hoverY = 0;
|
||||||
|
for (int tileX = minTileX; tileX <= maxTileX; tileX++) {
|
||||||
|
for (int tileZ = minTileZ; tileZ <= maxTileZ; tileZ++) {
|
||||||
|
List<IrisMessage.VisionMarkers.Marker> tileMarkers = markers.forTile(new IrisTileKey(tileX, tileZ, zoom));
|
||||||
|
if (tileMarkers == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (IrisMessage.VisionMarkers.Marker marker : tileMarkers) {
|
||||||
|
int screenX = originX + (int) Math.floor((double) marker.blockX() / blocksPerPixel);
|
||||||
|
int screenY = originY + (int) Math.floor((double) marker.blockZ() / blocksPerPixel);
|
||||||
|
graphics.fill(screenX - 2, screenY - 2, screenX + 2, screenY + 2, MARKER_COLOR);
|
||||||
|
if (Math.abs(mouseX - screenX) <= 4 && Math.abs(mouseY - screenY) <= 4 && marker.label() != null && !marker.label().isEmpty()) {
|
||||||
|
hoverLabel = marker.label();
|
||||||
|
hoverX = screenX;
|
||||||
|
hoverY = screenY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hoverLabel != null) {
|
||||||
|
int labelWidth = font.width(hoverLabel);
|
||||||
|
graphics.fill(hoverX + 6, hoverY - 6, hoverX + 12 + labelWidth, hoverY + font.lineHeight, MARKER_LABEL_BG);
|
||||||
|
graphics.text(font, hoverLabel, hoverX + 9, hoverY - 4, TEXT_COLOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawPlayer(GuiGraphicsExtractor graphics, int blocksPerPixel) {
|
||||||
|
LocalPlayer player = Minecraft.getInstance().player;
|
||||||
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int screenX = (int) Math.round(width / 2.0D + (player.getBlockX() - centerBlockX) / blocksPerPixel);
|
||||||
|
int screenY = (int) Math.round(height / 2.0D + (player.getBlockZ() - centerBlockZ) / blocksPerPixel);
|
||||||
|
graphics.fill(screenX - 4, screenY - 4, screenX + 4, screenY + 4, PLAYER_BORDER_COLOR);
|
||||||
|
graphics.fill(screenX - 3, screenY - 3, screenX + 3, screenY + 3, PLAYER_FILL_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawHeader(GuiGraphicsExtractor graphics, String title, String detail) {
|
||||||
|
int lineHeight = font.lineHeight;
|
||||||
|
int headerHeight = lineHeight + 8;
|
||||||
|
graphics.fill(0, 0, width, headerHeight, HEADER_COLOR);
|
||||||
|
graphics.text(font, title, 8, 4, TITLE_COLOR);
|
||||||
|
graphics.text(font, detail, 12 + font.width(title), 4, MUTED_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawFooter(GuiGraphicsExtractor graphics, String hint) {
|
||||||
|
int lineHeight = font.lineHeight;
|
||||||
|
int footerTop = height - lineHeight - 8;
|
||||||
|
graphics.fill(0, footerTop, width, height, HEADER_COLOR);
|
||||||
|
graphics.text(font, hint, 8, footerTop + 4, MUTED_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawCentered(GuiGraphicsExtractor graphics, String text, int color) {
|
||||||
|
graphics.text(font, text, (width - font.width(text)) / 2, height / 2 - font.lineHeight / 2, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Identifier ensureTexture(IrisTileKey key, IrisTileImage image) {
|
||||||
|
TileTexture existing = textures.get(key);
|
||||||
|
if (existing != null) {
|
||||||
|
return existing.id();
|
||||||
|
}
|
||||||
|
int tileWidth = image.width();
|
||||||
|
int tileHeight = image.height();
|
||||||
|
int[] argb = image.argb();
|
||||||
|
NativeImage nativeImage = new NativeImage(NativeImage.Format.RGBA, tileWidth, tileHeight, false);
|
||||||
|
for (int y = 0; y < tileHeight; y++) {
|
||||||
|
int row = y * tileWidth;
|
||||||
|
for (int x = 0; x < tileWidth; x++) {
|
||||||
|
nativeImage.setPixelABGR(x, y, toAbgr(argb[row + x]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DynamicTexture texture = new DynamicTexture(() -> "iris_vision_tile", nativeImage);
|
||||||
|
Identifier id = Identifier.fromNamespaceAndPath("irisworldgen", texturePath(key));
|
||||||
|
Minecraft.getInstance().getTextureManager().register(id, texture);
|
||||||
|
textures.put(key, new TileTexture(id, texture));
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void evictTextures() {
|
||||||
|
if (textures.size() <= MAX_TEXTURES) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TextureManager manager = Minecraft.getInstance().getTextureManager();
|
||||||
|
Iterator<Map.Entry<IrisTileKey, TileTexture>> iterator = textures.entrySet().iterator();
|
||||||
|
while (textures.size() > MAX_TEXTURES && iterator.hasNext()) {
|
||||||
|
Map.Entry<IrisTileKey, TileTexture> entry = iterator.next();
|
||||||
|
manager.release(entry.getValue().id());
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseTextures() {
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
if (minecraft != null) {
|
||||||
|
TextureManager manager = minecraft.getTextureManager();
|
||||||
|
for (TileTexture texture : textures.values()) {
|
||||||
|
manager.release(texture.id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textures.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncWorld(IrisMessage.DimensionStatus status) {
|
||||||
|
if (renderedDimensionKey == null) {
|
||||||
|
renderedDimensionKey = status.dimensionKey();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!renderedDimensionKey.equals(status.dimensionKey())) {
|
||||||
|
renderedDimensionKey = status.dimensionKey();
|
||||||
|
releaseTextures();
|
||||||
|
centerOnPlayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void centerOnPlayer() {
|
||||||
|
LocalPlayer player = Minecraft.getInstance().player;
|
||||||
|
if (player != null) {
|
||||||
|
centerBlockX = player.getBlockX();
|
||||||
|
centerBlockZ = player.getBlockZ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long distanceSquared(IrisTileKey key, int centerTileX, int centerTileZ) {
|
||||||
|
long deltaX = (long) key.tileX() - centerTileX;
|
||||||
|
long deltaZ = (long) key.tileZ() - centerTileZ;
|
||||||
|
return deltaX * deltaX + deltaZ * deltaZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int toAbgr(int argb) {
|
||||||
|
int alpha = argb >>> 24 & 0xFF;
|
||||||
|
int red = argb >> 16 & 0xFF;
|
||||||
|
int green = argb >> 8 & 0xFF;
|
||||||
|
int blue = argb & 0xFF;
|
||||||
|
return alpha << 24 | blue << 16 | green << 8 | red;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String texturePath(IrisTileKey key) {
|
||||||
|
return "vision/t_" + safe(key.tileX()) + "_" + safe(key.tileZ()) + "_" + key.zoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String safe(int value) {
|
||||||
|
return value < 0 ? "m" + (-(long) value) : Integer.toString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String label(IrisMessage.DimensionStatus status) {
|
||||||
|
String pack = status.packKey() == null || status.packKey().isEmpty() ? "" : " pack " + status.packKey();
|
||||||
|
return status.dimensionKey() + pack;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record TileTexture(Identifier id, DynamicTexture texture) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package art.arcane.iris.client;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.Font;
|
||||||
|
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||||
|
import net.minecraft.client.player.LocalPlayer;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
|
import net.minecraft.world.phys.HitResult;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class IrisWhatOverlay {
|
||||||
|
private static final int PANEL_COLOR = 0xC0101010;
|
||||||
|
private static final int TITLE_COLOR = 0xFF66BB6A;
|
||||||
|
private static final int TEXT_COLOR = 0xFFFFFFFF;
|
||||||
|
private static final int MUTED_COLOR = 0xFFC7C7C7;
|
||||||
|
private static final int PADDING = 4;
|
||||||
|
private static final int ROW_GAP = 2;
|
||||||
|
private static final int CURSOR_OFFSET = 12;
|
||||||
|
|
||||||
|
private IrisWhatOverlay() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void render(GuiGraphicsExtractor graphics) {
|
||||||
|
if (!IrisClient.whatVisible() || !IrisClient.cursorAvailable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
if (minecraft == null || minecraft.player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LocalPlayer player = minecraft.player;
|
||||||
|
int blockX = player.getBlockX();
|
||||||
|
int blockZ = player.getBlockZ();
|
||||||
|
HitResult hit = minecraft.hitResult;
|
||||||
|
if (hit != null && hit.getType() == HitResult.Type.BLOCK && hit instanceof BlockHitResult blockHit) {
|
||||||
|
BlockPos pos = blockHit.getBlockPos();
|
||||||
|
blockX = pos.getX();
|
||||||
|
blockZ = pos.getZ();
|
||||||
|
}
|
||||||
|
IrisClient.cursor().requestFor(blockX, blockZ);
|
||||||
|
|
||||||
|
IrisMessage.CursorInfo info = IrisClient.cursor().latest();
|
||||||
|
List<String> lines = new ArrayList<>();
|
||||||
|
if (info == null) {
|
||||||
|
lines.add("querying " + blockX + ", " + blockZ + "...");
|
||||||
|
} else {
|
||||||
|
lines.add("Biome: " + display(info.biomeKey()));
|
||||||
|
lines.add("Region: " + display(info.regionKey()));
|
||||||
|
if (info.caveBiomeKey() != null && !info.caveBiomeKey().isEmpty()) {
|
||||||
|
lines.add("Cave: " + display(info.caveBiomeKey()));
|
||||||
|
}
|
||||||
|
lines.add("Height: " + info.height() + " (" + info.blockX() + ", " + info.blockZ() + ")");
|
||||||
|
}
|
||||||
|
draw(graphics, minecraft.font, lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void draw(GuiGraphicsExtractor graphics, Font font, List<String> lines) {
|
||||||
|
int lineHeight = font.lineHeight;
|
||||||
|
int contentWidth = font.width("Iris What");
|
||||||
|
for (String line : lines) {
|
||||||
|
contentWidth = Math.max(contentWidth, font.width(line));
|
||||||
|
}
|
||||||
|
int originX = graphics.guiWidth() / 2 + CURSOR_OFFSET;
|
||||||
|
int originY = graphics.guiHeight() / 2 + CURSOR_OFFSET;
|
||||||
|
int rows = lines.size() + 1;
|
||||||
|
int contentHeight = lineHeight * rows + ROW_GAP * rows;
|
||||||
|
graphics.fill(originX - PADDING, originY - PADDING, originX + contentWidth + PADDING, originY + contentHeight + PADDING, PANEL_COLOR);
|
||||||
|
|
||||||
|
int cursorY = originY;
|
||||||
|
graphics.text(font, "Iris What", originX, cursorY, TITLE_COLOR);
|
||||||
|
cursorY += lineHeight + ROW_GAP;
|
||||||
|
for (String line : lines) {
|
||||||
|
graphics.text(font, line, originX, cursorY, line.startsWith("Height") ? MUTED_COLOR : TEXT_COLOR);
|
||||||
|
cursorY += lineHeight + ROW_GAP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String display(String key) {
|
||||||
|
return key == null || key.isEmpty() ? "-" : key;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||||
|
import org.gradle.api.tasks.compile.JavaCompile
|
||||||
|
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'net.fabricmc.fabric-loom' version '1.17.11'
|
||||||
|
alias(libs.plugins.shadow)
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties rootProperties = new Properties()
|
||||||
|
file('../../gradle.properties').withInputStream { InputStream stream -> rootProperties.load(stream) }
|
||||||
|
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.2'))
|
||||||
|
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2'))
|
||||||
|
String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse(rootProperties.getProperty('fabricLoaderVersion', '0.19.3'))
|
||||||
|
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:cdf7e8bb880199734dacd386a6616bda799fcde1'))
|
||||||
|
Closure<String> irisArtifactName = { String platform, String targetVersion ->
|
||||||
|
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'art.arcane'
|
||||||
|
version = irisVersion
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(25)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java {
|
||||||
|
srcDir '../modded-common/src/main/java'
|
||||||
|
srcDir '../client-common/src/main/java'
|
||||||
|
}
|
||||||
|
resources {
|
||||||
|
srcDir '../modded-common/src/main/resources'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test {
|
||||||
|
java {
|
||||||
|
srcDir '../modded-common/src/test/java'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven {
|
||||||
|
name = 'FabricMC'
|
||||||
|
url = uri('https://maven.fabricmc.net/')
|
||||||
|
}
|
||||||
|
maven { url = uri('https://repo.codemc.org/repository/maven-public/') }
|
||||||
|
maven { url = uri('https://jitpack.io') }
|
||||||
|
maven { url = uri('https://hub.spigotmc.org/nexus/content/repositories/snapshots/') }
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
bundle {
|
||||||
|
canBeConsumed = false
|
||||||
|
canBeResolved = true
|
||||||
|
}
|
||||||
|
devBundle {
|
||||||
|
canBeConsumed = false
|
||||||
|
canBeResolved = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations.named('bundle').configure {
|
||||||
|
exclude(group: 'com.google.code.gson')
|
||||||
|
exclude(group: 'com.google.guava')
|
||||||
|
exclude(group: 'commons-io')
|
||||||
|
exclude(group: 'org.apache.commons', module: 'commons-lang3')
|
||||||
|
exclude(group: 'it.unimi.dsi', module: 'fastutil')
|
||||||
|
exclude(group: 'org.slf4j')
|
||||||
|
exclude(group: 'org.apache.logging.log4j')
|
||||||
|
exclude(group: 'de.crazydev22.slimjar.helper')
|
||||||
|
exclude(group: 'de.crazydev22.slimjar')
|
||||||
|
exclude(group: 'io.github.slimjar')
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations.compileClasspath.extendsFrom(configurations.devBundle)
|
||||||
|
configurations.runtimeClasspath.extendsFrom(configurations.devBundle)
|
||||||
|
|
||||||
|
configurations.create('jij') {
|
||||||
|
transitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
minecraft("com.mojang:minecraft:${minecraftVersion}")
|
||||||
|
implementation("net.fabricmc:fabric-loader:${fabricLoaderVersion}")
|
||||||
|
testImplementation('junit:junit:4.13.2')
|
||||||
|
List<Object> fabricApi = [
|
||||||
|
libs.fabricApi.base,
|
||||||
|
libs.fabricApi.registrySync,
|
||||||
|
libs.fabricApi.resourceLoader,
|
||||||
|
libs.fabricApi.lifecycleEvents,
|
||||||
|
libs.fabricApi.commandApi,
|
||||||
|
libs.fabricApi.eventsInteraction,
|
||||||
|
libs.fabricApi.networking,
|
||||||
|
libs.fabricApi.rendering,
|
||||||
|
libs.fabricApi.keyMapping
|
||||||
|
]
|
||||||
|
fabricApi.each { Object notation ->
|
||||||
|
add('jij', notation)
|
||||||
|
add('implementation', notation)
|
||||||
|
}
|
||||||
|
compileOnly('org.slf4j:slf4j-api:2.0.17')
|
||||||
|
compileOnly(libs.spigot) {
|
||||||
|
transitive = false
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Object> shared = [
|
||||||
|
"art.arcane:core:${irisVersion}".toString(),
|
||||||
|
"art.arcane:spi:${irisVersion}".toString(),
|
||||||
|
volmLibCoordinate,
|
||||||
|
libs.paralithic,
|
||||||
|
libs.lru,
|
||||||
|
libs.kotlin.stdlib,
|
||||||
|
libs.kotlin.coroutines,
|
||||||
|
libs.commons.lang,
|
||||||
|
libs.commons.math3,
|
||||||
|
libs.caffeine,
|
||||||
|
libs.lz4,
|
||||||
|
libs.zip,
|
||||||
|
libs.sentry,
|
||||||
|
libs.oshi,
|
||||||
|
libs.byteBuddy.core,
|
||||||
|
libs.byteBuddy.agent
|
||||||
|
]
|
||||||
|
shared.each { Object notation ->
|
||||||
|
add('bundle', notation)
|
||||||
|
add('devBundle', notation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('compileJava', JavaCompile).configure {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
options.release.set(25)
|
||||||
|
}
|
||||||
|
|
||||||
|
loom {
|
||||||
|
accessWidenerPath = file('src/main/resources/irisworldgen.accesswidener')
|
||||||
|
runs {
|
||||||
|
server {
|
||||||
|
String parity = providers.gradleProperty('irisParity').getOrNull()
|
||||||
|
if (parity != null) {
|
||||||
|
property('iris.parity', parity)
|
||||||
|
}
|
||||||
|
String parityGolden = providers.gradleProperty('irisParityGolden').getOrNull()
|
||||||
|
if (parityGolden != null) {
|
||||||
|
property('iris.parity.golden', parityGolden)
|
||||||
|
}
|
||||||
|
String parityDeep = providers.gradleProperty('irisParityDeep').getOrNull()
|
||||||
|
if (parityDeep != null) {
|
||||||
|
property('iris.parity.deep', parityDeep)
|
||||||
|
}
|
||||||
|
String worldCheck = providers.gradleProperty('irisWorldCheck').getOrNull()
|
||||||
|
if (worldCheck != null) {
|
||||||
|
property('iris.worldcheck', worldCheck)
|
||||||
|
}
|
||||||
|
vmArg('-Xmx8G')
|
||||||
|
programArgs('nogui')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
inputs.property('version', project.version)
|
||||||
|
inputs.property('minecraftVersion', minecraftVersion)
|
||||||
|
filesMatching('fabric.mod.json') {
|
||||||
|
expand('version': project.version, 'minecraftVersion': minecraftVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('shadowJar', ShadowJar).configure {
|
||||||
|
doFirst {
|
||||||
|
delete(layout.buildDirectory.file("libs/Iris-${project.version}+mc${minecraftVersion}-fabric.jar"))
|
||||||
|
}
|
||||||
|
archiveFileName.set(irisArtifactName('Fabric', "${minecraftVersion}+${fabricLoaderVersion}"))
|
||||||
|
configurations = [project.configurations.named('bundle').get()]
|
||||||
|
mergeServiceFiles()
|
||||||
|
exclude('META-INF/maven/**')
|
||||||
|
exclude('META-INF/proguard/**')
|
||||||
|
exclude('META-INF/*.SF')
|
||||||
|
exclude('META-INF/*.DSA')
|
||||||
|
exclude('META-INF/*.RSA')
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
relocate('org.objectweb.asm', 'art.arcane.iris.shadow.asm')
|
||||||
|
relocate('io.sentry', 'art.arcane.iris.shadow.sentry')
|
||||||
|
relocate('com.sun.jna', 'art.arcane.iris.shadow.jna')
|
||||||
|
relocate('oshi', 'art.arcane.iris.shadow.oshi')
|
||||||
|
relocate('net.jpountz', 'art.arcane.iris.shadow.jpountz')
|
||||||
|
exclude('oshi.properties')
|
||||||
|
from(project.configurations.named('jij')) {
|
||||||
|
into('META-INF/jars')
|
||||||
|
rename { String fileName -> fileName.replaceAll(/-[0-9][^-]*\.jar$/, '.jar') }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('assemble').configure {
|
||||||
|
dependsOn(tasks.named('shadowJar'))
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
org.gradle.daemon=true
|
||||||
|
org.gradle.parallel=true
|
||||||
|
org.gradle.jvmargs=-Xmx3072m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||||
|
org.gradle.caching=true
|
||||||
|
org.gradle.configuration-cache=false
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
name = 'FabricMC'
|
||||||
|
url = uri('https://maven.fabricmc.net/')
|
||||||
|
}
|
||||||
|
gradlePluginPortal()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = 'iris-fabric'
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
versionCatalogs {
|
||||||
|
libs {
|
||||||
|
from(files('../../gradle/libs.versions.toml'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasVolmLibSettings(File directory) {
|
||||||
|
new File(directory, 'settings.gradle.kts').exists() || new File(directory, 'settings.gradle').exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
File resolveLocalVolmLibDirectory() {
|
||||||
|
String configuredPath = providers.gradleProperty('localVolmLibDirectory')
|
||||||
|
.orElse(providers.environmentVariable('VOLMLIB_DIR'))
|
||||||
|
.orNull
|
||||||
|
if (configuredPath != null && !configuredPath.isBlank()) {
|
||||||
|
File configuredDirectory = file(configuredPath)
|
||||||
|
if (hasVolmLibSettings(configuredDirectory)) {
|
||||||
|
return configuredDirectory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File currentDirectory = settingsDir
|
||||||
|
while (currentDirectory != null) {
|
||||||
|
File candidate = new File(currentDirectory, 'VolmLib')
|
||||||
|
if (hasVolmLibSettings(candidate)) {
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDirectory = currentDirectory.parentFile
|
||||||
|
}
|
||||||
|
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean useLocalVolmLib = providers.gradleProperty('useLocalVolmLib')
|
||||||
|
.orElse('true')
|
||||||
|
.map { String value -> value.equalsIgnoreCase('true') }
|
||||||
|
.get()
|
||||||
|
File localVolmLibDirectory = resolveLocalVolmLibDirectory()
|
||||||
|
|
||||||
|
if (useLocalVolmLib && localVolmLibDirectory != null) {
|
||||||
|
includeBuild(localVolmLibDirectory) {
|
||||||
|
dependencySubstitution {
|
||||||
|
substitute(module('com.github.VolmitSoftware:VolmLib')).using(project(':shared'))
|
||||||
|
substitute(module('com.github.VolmitSoftware.VolmLib:shared')).using(project(':shared'))
|
||||||
|
substitute(module('com.github.VolmitSoftware.VolmLib:volmlib-shared')).using(project(':shared'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
includeBuild('../..') {
|
||||||
|
dependencySubstitution {
|
||||||
|
substitute(module('art.arcane:core')).using(project(':core'))
|
||||||
|
substitute(module('art.arcane:spi')).using(project(':spi'))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.fabric;
|
||||||
|
|
||||||
|
import art.arcane.iris.modded.ModdedForcedDatapack;
|
||||||
|
import net.minecraft.server.packs.repository.PackRepository;
|
||||||
|
import net.minecraft.server.packs.repository.RepositorySource;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class FabricForcedDatapackSources {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
|
||||||
|
|
||||||
|
private FabricForcedDatapackSources() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void attach(PackRepository repository) {
|
||||||
|
if (repository == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Set<RepositorySource> merged = new LinkedHashSet<>(repository.sources);
|
||||||
|
merged.add(ModdedForcedDatapack.repositorySource());
|
||||||
|
repository.sources = merged;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOGGER.error("Iris failed to attach the forced startup datapack source to the server data pack repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.fabric;
|
||||||
|
|
||||||
|
import art.arcane.iris.modded.ModdedLoader;
|
||||||
|
import net.fabricmc.api.EnvType;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import net.fabricmc.loader.api.ModContainer;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public final class FabricModdedLoader implements ModdedLoader {
|
||||||
|
@Override
|
||||||
|
public String platformName() {
|
||||||
|
return "fabric";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String minecraftVersion() {
|
||||||
|
return FabricLoader.getInstance().getModContainer("minecraft")
|
||||||
|
.map((ModContainer container) -> container.getMetadata().getVersion().getFriendlyString())
|
||||||
|
.orElse("unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String modVersion() {
|
||||||
|
return FabricLoader.getInstance().getModContainer("irisworldgen")
|
||||||
|
.map((ModContainer container) -> container.getMetadata().getVersion().getFriendlyString())
|
||||||
|
.orElse("unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MinecraftServer currentServer() {
|
||||||
|
Object instance = FabricLoader.getInstance().getGameInstance();
|
||||||
|
return instance instanceof MinecraftServer server ? server : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean clientEnvironment() {
|
||||||
|
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path configDir() {
|
||||||
|
return FabricLoader.getInstance().getConfigDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File modJar() {
|
||||||
|
return FabricLoader.getInstance().getModContainer("irisworldgen")
|
||||||
|
.flatMap((ModContainer container) -> container.getOrigin().getPaths().stream().findFirst())
|
||||||
|
.map((Path p) -> p.toFile())
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.fabric;
|
||||||
|
|
||||||
|
import art.arcane.iris.modded.ModdedIrisPayload;
|
||||||
|
import art.arcane.iris.modded.ModdedProtocolChannel;
|
||||||
|
import art.arcane.iris.modded.ModdedProtocolHandler;
|
||||||
|
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
|
||||||
|
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||||
|
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
|
||||||
|
public final class FabricProtocolNetworking {
|
||||||
|
private FabricProtocolNetworking() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void install() {
|
||||||
|
PayloadTypeRegistry.clientboundPlay().register(ModdedIrisPayload.TYPE, ModdedIrisPayload.STREAM_CODEC);
|
||||||
|
PayloadTypeRegistry.serverboundPlay().register(ModdedIrisPayload.TYPE, ModdedIrisPayload.STREAM_CODEC);
|
||||||
|
ServerPlayNetworking.registerGlobalReceiver(ModdedIrisPayload.TYPE,
|
||||||
|
(payload, context) -> ModdedProtocolHandler.onInbound(context.player(), payload.data()));
|
||||||
|
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> ModdedProtocolHandler.onPlayerJoin(handler.player));
|
||||||
|
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> ModdedProtocolHandler.onPlayerDisconnect(handler.player));
|
||||||
|
ModdedProtocolHandler.bindChannel(new FabricProtocolChannel());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class FabricProtocolChannel implements ModdedProtocolChannel {
|
||||||
|
@Override
|
||||||
|
public boolean canReceive(ServerPlayer player) {
|
||||||
|
return ServerPlayNetworking.canSend(player, ModdedIrisPayload.TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(ServerPlayer player, ModdedIrisPayload payload) {
|
||||||
|
ServerPlayNetworking.send(player, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.fabric;
|
||||||
|
|
||||||
|
import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
||||||
|
import art.arcane.iris.modded.ModdedEngineBootstrap;
|
||||||
|
import art.arcane.iris.modded.command.IrisModdedCommands;
|
||||||
|
import art.arcane.iris.modded.command.ModdedWandService;
|
||||||
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
|
import net.fabricmc.api.ModInitializer;
|
||||||
|
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||||
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||||
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||||
|
import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
|
||||||
|
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
|
||||||
|
import net.minecraft.commands.CommandBuildContext;
|
||||||
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
|
import net.minecraft.commands.Commands;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.InteractionResult;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
|
|
||||||
|
public final class IrisFabricBootstrap implements ModInitializer {
|
||||||
|
@Override
|
||||||
|
public void onInitialize() {
|
||||||
|
ModdedEngineBootstrap.bootCommon(new FabricModdedLoader(), "Fabric",
|
||||||
|
() -> Registry.register(BuiltInRegistries.CHUNK_GENERATOR, Identifier.fromNamespaceAndPath("irisworldgen", "iris"), IrisModdedChunkGenerator.CODEC));
|
||||||
|
FabricProtocolNetworking.install();
|
||||||
|
ServerLifecycleEvents.SERVER_STARTING.register((MinecraftServer server) -> ModdedEngineBootstrap.start(server));
|
||||||
|
ServerLifecycleEvents.SERVER_STOPPING.register((MinecraftServer server) -> ModdedEngineBootstrap.stop());
|
||||||
|
CommandRegistrationCallback.EVENT.register((CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext buildContext, Commands.CommandSelection selection) -> IrisModdedCommands.register(dispatcher));
|
||||||
|
AttackBlockCallback.EVENT.register((Player player, Level level, InteractionHand hand, BlockPos pos, Direction direction) ->
|
||||||
|
ModdedWandService.attackBlock(player, level, hand, pos) ? InteractionResult.SUCCESS : InteractionResult.PASS);
|
||||||
|
UseBlockCallback.EVENT.register((Player player, Level level, InteractionHand hand, BlockHitResult hit) ->
|
||||||
|
ModdedWandService.useBlock(player, level, hand, hit.getBlockPos()) ? InteractionResult.SUCCESS : InteractionResult.PASS);
|
||||||
|
ServerTickEvents.END_SERVER_TICK.register((MinecraftServer server) -> {
|
||||||
|
ModdedEngineBootstrap.tick(server);
|
||||||
|
ModdedWandService.serverTick(server);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.fabric;
|
||||||
|
|
||||||
|
import art.arcane.iris.client.IrisClient;
|
||||||
|
import art.arcane.iris.client.IrisClientHud;
|
||||||
|
import art.arcane.iris.client.IrisClientKeybinds;
|
||||||
|
import art.arcane.iris.modded.ModdedIrisPayload;
|
||||||
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
|
import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper;
|
||||||
|
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
||||||
|
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||||
|
import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry;
|
||||||
|
|
||||||
|
public final class IrisFabricClient implements ClientModInitializer {
|
||||||
|
@Override
|
||||||
|
public void onInitializeClient() {
|
||||||
|
ClientPlayNetworking.registerGlobalReceiver(ModdedIrisPayload.TYPE,
|
||||||
|
(payload, context) -> IrisClient.onInbound(payload.data()));
|
||||||
|
IrisClient.bindSender(frame -> {
|
||||||
|
if (ClientPlayNetworking.canSend(ModdedIrisPayload.TYPE)) {
|
||||||
|
ClientPlayNetworking.send(new ModdedIrisPayload(frame));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> IrisClient.onWorldJoin());
|
||||||
|
ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> IrisClient.onDisconnect());
|
||||||
|
KeyMappingHelper.registerKeyMapping(IrisClientKeybinds.TOGGLE_HUD);
|
||||||
|
KeyMappingHelper.registerKeyMapping(IrisClientKeybinds.OPEN_MAP);
|
||||||
|
KeyMappingHelper.registerKeyMapping(IrisClientKeybinds.TOGGLE_WHAT);
|
||||||
|
HudElementRegistry.addLast(IrisClient.HUD_ELEMENT_ID, (graphics, delta) -> IrisClientHud.render(graphics));
|
||||||
|
ClientTickEvents.END_CLIENT_TICK.register(client -> IrisClientKeybinds.pollToggle());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.fabric.mixin;
|
||||||
|
|
||||||
|
import art.arcane.iris.modded.ModdedDeathLoot;
|
||||||
|
import net.minecraft.world.damagesource.DamageSource;
|
||||||
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(LivingEntity.class)
|
||||||
|
public class LivingEntityMixin {
|
||||||
|
@Inject(method = "die", at = @At("TAIL"))
|
||||||
|
private void iris$dropIrisDeathLoot(DamageSource damageSource, CallbackInfo info) {
|
||||||
|
ModdedDeathLoot.handle((LivingEntity) (Object) this);
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.fabric.mixin;
|
||||||
|
|
||||||
|
import art.arcane.iris.fabric.FabricForcedDatapackSources;
|
||||||
|
import net.minecraft.server.packs.repository.PackRepository;
|
||||||
|
import net.minecraft.server.packs.repository.ServerPacksSource;
|
||||||
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(ServerPacksSource.class)
|
||||||
|
public class ServerPacksSourceMixin {
|
||||||
|
@Inject(method = "createPackRepository(Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;)Lnet/minecraft/server/packs/repository/PackRepository;", at = @At("RETURN"))
|
||||||
|
private static void iris$addForcedDatapackSource(LevelStorageSource.LevelStorageAccess storage, CallbackInfoReturnable<PackRepository> info) {
|
||||||
|
FabricForcedDatapackSources.attach(info.getReturnValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"id": "irisworldgen",
|
||||||
|
"version": "${version}",
|
||||||
|
"name": "Iris",
|
||||||
|
"description": "Iris World Generation Engine (Fabric adapter - native chunk generator, explicit Iris world datapack workflow, engine lifecycle)",
|
||||||
|
"authors": ["Arcane Arts (Volmit Software)"],
|
||||||
|
"contact": {
|
||||||
|
"sources": "https://github.com/VolmitSoftware/Iris"
|
||||||
|
},
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"environment": "*",
|
||||||
|
"accessWidener": "irisworldgen.accesswidener",
|
||||||
|
"mixins": ["irisworldgen.mixins.json"],
|
||||||
|
"entrypoints": {
|
||||||
|
"main": ["art.arcane.iris.fabric.IrisFabricBootstrap"],
|
||||||
|
"client": ["art.arcane.iris.fabric.IrisFabricClient"]
|
||||||
|
},
|
||||||
|
"jars": [
|
||||||
|
{ "file": "META-INF/jars/fabric-api-base.jar" },
|
||||||
|
{ "file": "META-INF/jars/fabric-registry-sync-v0.jar" },
|
||||||
|
{ "file": "META-INF/jars/fabric-resource-loader-v1.jar" },
|
||||||
|
{ "file": "META-INF/jars/fabric-lifecycle-events-v1.jar" },
|
||||||
|
{ "file": "META-INF/jars/fabric-networking-api-v1.jar" },
|
||||||
|
{ "file": "META-INF/jars/fabric-command-api-v2.jar" },
|
||||||
|
{ "file": "META-INF/jars/fabric-events-interaction-v0.jar" },
|
||||||
|
{ "file": "META-INF/jars/fabric-rendering-v1.jar" },
|
||||||
|
{ "file": "META-INF/jars/fabric-transitive-access-wideners-v1.jar" },
|
||||||
|
{ "file": "META-INF/jars/fabric-key-mapping-api-v1.jar" }
|
||||||
|
],
|
||||||
|
"depends": {
|
||||||
|
"fabricloader": ">=0.19.3",
|
||||||
|
"minecraft": "~${minecraftVersion}",
|
||||||
|
"java": ">=25"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
accessWidener v2 official
|
||||||
|
accessible field net/minecraft/server/MinecraftServer levels Ljava/util/Map;
|
||||||
|
accessible field net/minecraft/server/MinecraftServer executor Ljava/util/concurrent/Executor;
|
||||||
|
accessible field net/minecraft/server/MinecraftServer storageSource Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;
|
||||||
|
accessible field net/minecraft/server/packs/repository/PackRepository sources Ljava/util/Set;
|
||||||
|
mutable field net/minecraft/server/packs/repository/PackRepository sources Ljava/util/Set;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"minVersion": "0.8",
|
||||||
|
"package": "art.arcane.iris.fabric.mixin",
|
||||||
|
"compatibilityLevel": "JAVA_25",
|
||||||
|
"mixins": [
|
||||||
|
"LivingEntityMixin",
|
||||||
|
"ServerPacksSourceMixin"
|
||||||
|
],
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||||
|
import org.gradle.api.tasks.compile.JavaCompile
|
||||||
|
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'net.minecraftforge.gradle' version '7.0.27'
|
||||||
|
alias(libs.plugins.shadow)
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties rootProperties = new Properties()
|
||||||
|
file('../../gradle.properties').withInputStream { InputStream stream -> rootProperties.load(stream) }
|
||||||
|
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.2'))
|
||||||
|
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2'))
|
||||||
|
String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse(rootProperties.getProperty('forgeVersion', '26.2-65.0.0'))
|
||||||
|
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:cdf7e8bb880199734dacd386a6616bda799fcde1'))
|
||||||
|
Closure<String> loaderDisplayVersion = { String loaderVersion ->
|
||||||
|
String coordinatePrefix = "${minecraftVersion}-"
|
||||||
|
if (loaderVersion.startsWith(coordinatePrefix)) {
|
||||||
|
return loaderVersion.substring(coordinatePrefix.length())
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaderVersion
|
||||||
|
}
|
||||||
|
Closure<String> irisArtifactName = { String platform, String targetVersion ->
|
||||||
|
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'art.arcane'
|
||||||
|
version = irisVersion
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(25)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java {
|
||||||
|
srcDir '../modded-common/src/main/java'
|
||||||
|
srcDir '../client-common/src/main/java'
|
||||||
|
}
|
||||||
|
resources {
|
||||||
|
srcDir '../modded-common/src/main/resources'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
minecraft.mavenizer(it)
|
||||||
|
maven fg.forgeMaven
|
||||||
|
maven fg.minecraftLibsMaven
|
||||||
|
mavenCentral()
|
||||||
|
maven { url = uri('https://repo.codemc.org/repository/maven-public/') }
|
||||||
|
maven { url = uri('https://jitpack.io') }
|
||||||
|
maven { url = uri('https://hub.spigotmc.org/nexus/content/repositories/snapshots/') }
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
bundle {
|
||||||
|
canBeConsumed = false
|
||||||
|
canBeResolved = true
|
||||||
|
}
|
||||||
|
devBundle {
|
||||||
|
canBeConsumed = false
|
||||||
|
canBeResolved = true
|
||||||
|
}
|
||||||
|
runBundle {
|
||||||
|
canBeConsumed = false
|
||||||
|
canBeResolved = true
|
||||||
|
}
|
||||||
|
commonsLangRaw {
|
||||||
|
canBeConsumed = false
|
||||||
|
canBeResolved = true
|
||||||
|
transitive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations.named('bundle').configure {
|
||||||
|
exclude(group: 'com.google.code.gson')
|
||||||
|
exclude(group: 'com.google.guava')
|
||||||
|
exclude(group: 'commons-io')
|
||||||
|
exclude(group: 'org.apache.commons', module: 'commons-lang3')
|
||||||
|
exclude(group: 'it.unimi.dsi', module: 'fastutil')
|
||||||
|
exclude(group: 'org.slf4j')
|
||||||
|
exclude(group: 'org.apache.logging.log4j')
|
||||||
|
exclude(group: 'de.crazydev22.slimjar.helper')
|
||||||
|
exclude(group: 'de.crazydev22.slimjar')
|
||||||
|
exclude(group: 'io.github.slimjar')
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations.named('runBundle').configure {
|
||||||
|
exclude(group: 'de.crazydev22.slimjar.helper')
|
||||||
|
exclude(group: 'de.crazydev22.slimjar')
|
||||||
|
exclude(group: 'io.github.slimjar')
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations.compileClasspath.extendsFrom(configurations.devBundle)
|
||||||
|
configurations.runtimeClasspath.extendsFrom(configurations.runBundle)
|
||||||
|
|
||||||
|
['compileClasspath', 'runtimeClasspath'].each { String name ->
|
||||||
|
configurations.named(name).configure {
|
||||||
|
resolutionStrategy.capabilitiesResolution.withCapability('org.lz4:lz4-java') {
|
||||||
|
selectHighestVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation minecraft.dependency("net.minecraftforge:forge:${forgeVersion}")
|
||||||
|
compileOnly('org.slf4j:slf4j-api:2.0.17')
|
||||||
|
compileOnly(libs.spigot) {
|
||||||
|
transitive = false
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Object> shared = [
|
||||||
|
"art.arcane:core:${irisVersion}".toString(),
|
||||||
|
"art.arcane:spi:${irisVersion}".toString(),
|
||||||
|
volmLibCoordinate,
|
||||||
|
libs.paralithic,
|
||||||
|
libs.lru,
|
||||||
|
libs.kotlin.stdlib,
|
||||||
|
libs.kotlin.coroutines,
|
||||||
|
libs.commons.math3,
|
||||||
|
libs.caffeine,
|
||||||
|
libs.lz4,
|
||||||
|
libs.zip,
|
||||||
|
libs.sentry,
|
||||||
|
libs.oshi,
|
||||||
|
libs.byteBuddy.core,
|
||||||
|
libs.byteBuddy.agent
|
||||||
|
]
|
||||||
|
shared.each { Object notation ->
|
||||||
|
add('bundle', notation)
|
||||||
|
add('devBundle', notation)
|
||||||
|
add('runBundle', notation)
|
||||||
|
}
|
||||||
|
add('bundle', libs.commons.lang)
|
||||||
|
add('devBundle', libs.commons.lang)
|
||||||
|
add('commonsLangRaw', libs.commons.lang)
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskProvider<Jar> sanitizedCommonsLang = tasks.register('sanitizedCommonsLang', Jar) {
|
||||||
|
archiveBaseName.set('commons-lang-sanitized')
|
||||||
|
destinationDirectory.set(layout.buildDirectory.dir('sanitized'))
|
||||||
|
from({ zipTree(configurations.named('commonsLangRaw').get().singleFile) })
|
||||||
|
exclude('org/apache/commons/lang/enum/**')
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
add('runBundle', files(sanitizedCommonsLang))
|
||||||
|
}
|
||||||
|
|
||||||
|
minecraft {
|
||||||
|
accessTransformer.from(file('src/main/resources/META-INF/accesstransformer.cfg'))
|
||||||
|
runs {
|
||||||
|
register('server') {
|
||||||
|
workingDir = layout.projectDirectory.dir('run')
|
||||||
|
String parity = providers.gradleProperty('irisParity').getOrNull()
|
||||||
|
if (parity != null) {
|
||||||
|
systemProperty('iris.parity', parity)
|
||||||
|
}
|
||||||
|
String parityGolden = providers.gradleProperty('irisParityGolden').getOrNull()
|
||||||
|
if (parityGolden != null) {
|
||||||
|
systemProperty('iris.parity.golden', parityGolden)
|
||||||
|
}
|
||||||
|
String parityDeep = providers.gradleProperty('irisParityDeep').getOrNull()
|
||||||
|
if (parityDeep != null) {
|
||||||
|
systemProperty('iris.parity.deep', parityDeep)
|
||||||
|
}
|
||||||
|
String worldCheck = providers.gradleProperty('irisWorldCheck').getOrNull()
|
||||||
|
if (worldCheck != null) {
|
||||||
|
systemProperty('iris.worldcheck', worldCheck)
|
||||||
|
}
|
||||||
|
jvmArgs('-Xmx8G')
|
||||||
|
args('--nogui')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('compileJava', JavaCompile).configure {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
options.release.set(25)
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
inputs.property('version', project.version)
|
||||||
|
inputs.property('minecraftVersion', minecraftVersion)
|
||||||
|
filesMatching('META-INF/mods.toml') {
|
||||||
|
expand('version': project.version, 'minecraftVersion': minecraftVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('shadowJar', ShadowJar).configure {
|
||||||
|
doFirst {
|
||||||
|
delete(layout.buildDirectory.file("libs/Iris-${project.version}+mc${minecraftVersion}-forge.jar"))
|
||||||
|
}
|
||||||
|
archiveFileName.set(irisArtifactName('Forge', "${minecraftVersion}+${loaderDisplayVersion(forgeVersion)}"))
|
||||||
|
configurations = [project.configurations.named('bundle').get()]
|
||||||
|
from(sourceSets.main.output)
|
||||||
|
mergeServiceFiles()
|
||||||
|
exclude('META-INF/maven/**')
|
||||||
|
exclude('META-INF/proguard/**')
|
||||||
|
exclude('META-INF/*.SF')
|
||||||
|
exclude('META-INF/*.DSA')
|
||||||
|
exclude('META-INF/*.RSA')
|
||||||
|
exclude('org/apache/commons/lang/enum/**')
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
relocate('org.objectweb.asm', 'art.arcane.iris.shadow.asm')
|
||||||
|
relocate('io.sentry', 'art.arcane.iris.shadow.sentry')
|
||||||
|
relocate('com.sun.jna', 'art.arcane.iris.shadow.jna')
|
||||||
|
relocate('oshi', 'art.arcane.iris.shadow.oshi')
|
||||||
|
relocate('net.jpountz', 'art.arcane.iris.shadow.jpountz')
|
||||||
|
exclude('oshi.properties')
|
||||||
|
exclude('org/jspecify/**')
|
||||||
|
exclude('com/google/errorprone/**')
|
||||||
|
exclude('com/google/j2objc/**')
|
||||||
|
exclude('org/checkerframework/**')
|
||||||
|
exclude('org/jetbrains/annotations/**')
|
||||||
|
exclude('org/intellij/lang/**')
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('assemble').configure {
|
||||||
|
dependsOn(tasks.named('shadowJar'))
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
org.gradle.daemon=true
|
||||||
|
org.gradle.parallel=true
|
||||||
|
org.gradle.jvmargs=-Xmx3072m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||||
|
org.gradle.caching=true
|
||||||
|
org.gradle.configuration-cache=false
|
||||||
|
net.minecraftforge.gradle.merge-source-sets=true
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
maven {
|
||||||
|
name = 'MinecraftForge'
|
||||||
|
url = uri('https://maven.minecraftforge.net/')
|
||||||
|
}
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = 'iris-forge'
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
versionCatalogs {
|
||||||
|
libs {
|
||||||
|
from(files('../../gradle/libs.versions.toml'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasVolmLibSettings(File directory) {
|
||||||
|
new File(directory, 'settings.gradle.kts').exists() || new File(directory, 'settings.gradle').exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
File resolveLocalVolmLibDirectory() {
|
||||||
|
String configuredPath = providers.gradleProperty('localVolmLibDirectory')
|
||||||
|
.orElse(providers.environmentVariable('VOLMLIB_DIR'))
|
||||||
|
.orNull
|
||||||
|
if (configuredPath != null && !configuredPath.isBlank()) {
|
||||||
|
File configuredDirectory = file(configuredPath)
|
||||||
|
if (hasVolmLibSettings(configuredDirectory)) {
|
||||||
|
return configuredDirectory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File currentDirectory = settingsDir
|
||||||
|
while (currentDirectory != null) {
|
||||||
|
File candidate = new File(currentDirectory, 'VolmLib')
|
||||||
|
if (hasVolmLibSettings(candidate)) {
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDirectory = currentDirectory.parentFile
|
||||||
|
}
|
||||||
|
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean useLocalVolmLib = providers.gradleProperty('useLocalVolmLib')
|
||||||
|
.orElse('true')
|
||||||
|
.map { String value -> value.equalsIgnoreCase('true') }
|
||||||
|
.get()
|
||||||
|
File localVolmLibDirectory = resolveLocalVolmLibDirectory()
|
||||||
|
|
||||||
|
if (useLocalVolmLib && localVolmLibDirectory != null) {
|
||||||
|
includeBuild(localVolmLibDirectory) {
|
||||||
|
dependencySubstitution {
|
||||||
|
substitute(module('com.github.VolmitSoftware:VolmLib')).using(project(':shared'))
|
||||||
|
substitute(module('com.github.VolmitSoftware.VolmLib:shared')).using(project(':shared'))
|
||||||
|
substitute(module('com.github.VolmitSoftware.VolmLib:volmlib-shared')).using(project(':shared'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
includeBuild('../..') {
|
||||||
|
dependencySubstitution {
|
||||||
|
substitute(module('art.arcane:core')).using(project(':core'))
|
||||||
|
substitute(module('art.arcane:spi')).using(project(':spi'))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.forge;
|
||||||
|
|
||||||
|
import art.arcane.iris.client.IrisClient;
|
||||||
|
import net.minecraftforge.event.network.CustomPayloadEvent;
|
||||||
|
|
||||||
|
public final class ForgeClientProtocol {
|
||||||
|
private ForgeClientProtocol() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handleInbound(CustomPayloadEvent.Context context, byte[] data) {
|
||||||
|
context.enqueueWork(() -> IrisClient.onInbound(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.forge;
|
||||||
|
|
||||||
|
import art.arcane.iris.modded.ModdedLoader;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraftforge.fml.ModList;
|
||||||
|
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||||
|
import net.minecraftforge.fml.loading.FMLLoader;
|
||||||
|
import net.minecraftforge.fml.loading.FMLPaths;
|
||||||
|
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||||
|
import net.minecraftforge.server.ServerLifecycleHooks;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public final class ForgeModdedLoader implements ModdedLoader {
|
||||||
|
@Override
|
||||||
|
public String platformName() {
|
||||||
|
return "forge";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String minecraftVersion() {
|
||||||
|
return FMLLoader.versionInfo().mcVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String modVersion() {
|
||||||
|
return ModList.getModContainerById("irisworldgen")
|
||||||
|
.map((net.minecraftforge.fml.ModContainer container) -> container.getModInfo().getVersion().toString())
|
||||||
|
.orElse("unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MinecraftServer currentServer() {
|
||||||
|
return ServerLifecycleHooks.getCurrentServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean clientEnvironment() {
|
||||||
|
return FMLEnvironment.dist.isClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path configDir() {
|
||||||
|
return FMLPaths.CONFIGDIR.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File modJar() {
|
||||||
|
IModFileInfo info = ModList.getModFileById("irisworldgen");
|
||||||
|
return info == null ? null : info.getFile().getFilePath().toFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.forge;
|
||||||
|
|
||||||
|
import art.arcane.iris.modded.ModdedIrisPayload;
|
||||||
|
import art.arcane.iris.modded.ModdedProtocolChannel;
|
||||||
|
import art.arcane.iris.modded.ModdedProtocolHandler;
|
||||||
|
import art.arcane.iris.spi.protocol.IrisProtocol;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraftforge.event.network.CustomPayloadEvent;
|
||||||
|
import net.minecraftforge.network.Channel;
|
||||||
|
import net.minecraftforge.network.ChannelBuilder;
|
||||||
|
import net.minecraftforge.network.NetworkProtocol;
|
||||||
|
import net.minecraftforge.network.PacketDistributor;
|
||||||
|
|
||||||
|
public final class ForgeProtocolNetworking {
|
||||||
|
private static volatile Channel<CustomPacketPayload> channel;
|
||||||
|
|
||||||
|
private ForgeProtocolNetworking() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void register() {
|
||||||
|
Channel<CustomPacketPayload> boundChannel = ChannelBuilder.named(Identifier.parse(IrisProtocol.CHANNEL))
|
||||||
|
.optional()
|
||||||
|
.payloadChannel()
|
||||||
|
.protocol(NetworkProtocol.PLAY)
|
||||||
|
.bidirectional()
|
||||||
|
.add(ModdedIrisPayload.TYPE, ModdedIrisPayload.STREAM_CODEC, ForgeProtocolNetworking::onPayload)
|
||||||
|
.build();
|
||||||
|
channel = boundChannel;
|
||||||
|
ModdedProtocolHandler.bindChannel(new ForgeProtocolChannel(boundChannel));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Channel<CustomPacketPayload> channel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onPayload(ModdedIrisPayload payload, CustomPayloadEvent.Context context) {
|
||||||
|
context.setPacketHandled(true);
|
||||||
|
if (context.isClientSide()) {
|
||||||
|
ForgeClientProtocol.handleInbound(context, payload.data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ServerPlayer player = context.getSender();
|
||||||
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ModdedProtocolHandler.onInbound(player, payload.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ForgeProtocolChannel implements ModdedProtocolChannel {
|
||||||
|
private final Channel<CustomPacketPayload> channel;
|
||||||
|
|
||||||
|
private ForgeProtocolChannel(Channel<CustomPacketPayload> channel) {
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReceive(ServerPlayer player) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(ServerPlayer player, ModdedIrisPayload payload) {
|
||||||
|
channel.send(payload, PacketDistributor.PLAYER.with(player));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.forge;
|
||||||
|
|
||||||
|
import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
||||||
|
import art.arcane.iris.modded.ModdedDeathLoot;
|
||||||
|
import art.arcane.iris.modded.ModdedEngineBootstrap;
|
||||||
|
import art.arcane.iris.modded.ModdedForcedDatapack;
|
||||||
|
import art.arcane.iris.modded.ModdedProtocolHandler;
|
||||||
|
import art.arcane.iris.modded.command.IrisModdedCommands;
|
||||||
|
import art.arcane.iris.modded.command.ModdedWandService;
|
||||||
|
import com.mojang.serialization.MapCodec;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.server.packs.PackType;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||||
|
import net.minecraftforge.event.AddPackFindersEvent;
|
||||||
|
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||||
|
import net.minecraftforge.event.TickEvent;
|
||||||
|
import net.minecraftforge.event.entity.living.LivingDropsEvent;
|
||||||
|
import net.minecraftforge.event.entity.player.PlayerEvent;
|
||||||
|
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
|
||||||
|
import net.minecraftforge.event.server.ServerStartingEvent;
|
||||||
|
import net.minecraftforge.event.server.ServerStoppingEvent;
|
||||||
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||||
|
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||||
|
import net.minecraftforge.fml.loading.FMLLoader;
|
||||||
|
import net.minecraftforge.registries.DeferredRegister;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
@Mod("irisworldgen")
|
||||||
|
public final class IrisForgeBootstrap {
|
||||||
|
public IrisForgeBootstrap(FMLJavaModLoadingContext context) {
|
||||||
|
ModdedEngineBootstrap.bootCommon(new ForgeModdedLoader(), "Forge " + FMLLoader.versionInfo().forgeVersion(), () -> {
|
||||||
|
DeferredRegister<MapCodec<? extends ChunkGenerator>> chunkGenerators = DeferredRegister.create(Registries.CHUNK_GENERATOR, "irisworldgen");
|
||||||
|
chunkGenerators.register("iris", () -> IrisModdedChunkGenerator.CODEC);
|
||||||
|
chunkGenerators.register(context.getModBusGroup());
|
||||||
|
});
|
||||||
|
|
||||||
|
ForgeProtocolNetworking.register();
|
||||||
|
|
||||||
|
if (FMLEnvironment.dist == Dist.CLIENT) {
|
||||||
|
IrisForgeClient.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerStartingEvent.BUS.addListener((ServerStartingEvent event) -> ModdedEngineBootstrap.start(event.getServer()));
|
||||||
|
ServerStoppingEvent.BUS.addListener((ServerStoppingEvent event) -> ModdedEngineBootstrap.stop());
|
||||||
|
PlayerEvent.PlayerLoggedInEvent.BUS.addListener((PlayerEvent.PlayerLoggedInEvent event) -> {
|
||||||
|
if (event.getEntity() instanceof ServerPlayer player) {
|
||||||
|
ModdedProtocolHandler.onPlayerJoin(player);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
PlayerEvent.PlayerLoggedOutEvent.BUS.addListener((PlayerEvent.PlayerLoggedOutEvent event) -> {
|
||||||
|
if (event.getEntity() instanceof ServerPlayer player) {
|
||||||
|
ModdedProtocolHandler.onPlayerDisconnect(player);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AddPackFindersEvent.BUS.addListener((AddPackFindersEvent event) -> {
|
||||||
|
if (event.getPackType() == PackType.SERVER_DATA) {
|
||||||
|
event.addRepositorySource(ModdedForcedDatapack.repositorySource());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
RegisterCommandsEvent.BUS.addListener((RegisterCommandsEvent event) -> IrisModdedCommands.register(event.getDispatcher()));
|
||||||
|
LivingDropsEvent.BUS.addListener((LivingDropsEvent event) -> ModdedDeathLoot.handle(event.getEntity()));
|
||||||
|
PlayerInteractEvent.LeftClickBlock.BUS.addListener((Predicate<PlayerInteractEvent.LeftClickBlock>) (PlayerInteractEvent.LeftClickBlock event) ->
|
||||||
|
ModdedWandService.attackBlock(event.getEntity(), event.getLevel(), event.getHand(), event.getPos()));
|
||||||
|
PlayerInteractEvent.RightClickBlock.BUS.addListener((Predicate<PlayerInteractEvent.RightClickBlock>) (PlayerInteractEvent.RightClickBlock event) ->
|
||||||
|
ModdedWandService.useBlock(event.getEntity(), event.getLevel(), event.getHand(), event.getPos()));
|
||||||
|
TickEvent.ServerTickEvent.Post.BUS.addListener((TickEvent.ServerTickEvent.Post event) -> {
|
||||||
|
ModdedEngineBootstrap.tick(event.server());
|
||||||
|
ModdedWandService.serverTick(event.server());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.forge;
|
||||||
|
|
||||||
|
import art.arcane.iris.client.IrisClient;
|
||||||
|
import art.arcane.iris.client.IrisClientHud;
|
||||||
|
import art.arcane.iris.client.IrisClientKeybinds;
|
||||||
|
import art.arcane.iris.modded.ModdedIrisPayload;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraftforge.client.event.AddGuiOverlayLayersEvent;
|
||||||
|
import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
|
||||||
|
import net.minecraftforge.client.event.InputEvent;
|
||||||
|
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
|
||||||
|
import net.minecraftforge.network.Channel;
|
||||||
|
import net.minecraftforge.network.PacketDistributor;
|
||||||
|
|
||||||
|
public final class IrisForgeClient {
|
||||||
|
private IrisForgeClient() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
IrisClient.bindSender(IrisForgeClient::sendToServer);
|
||||||
|
AddGuiOverlayLayersEvent.BUS.addListener((AddGuiOverlayLayersEvent event) ->
|
||||||
|
event.getLayeredDraw().add(IrisClient.HUD_ELEMENT_ID, (graphics, delta) -> IrisClientHud.render(graphics)));
|
||||||
|
RegisterKeyMappingsEvent.BUS.addListener((RegisterKeyMappingsEvent event) -> {
|
||||||
|
event.register(IrisClientKeybinds.TOGGLE_HUD);
|
||||||
|
event.register(IrisClientKeybinds.OPEN_MAP);
|
||||||
|
event.register(IrisClientKeybinds.TOGGLE_WHAT);
|
||||||
|
});
|
||||||
|
ClientPlayerNetworkEvent.LoggingIn.BUS.addListener((ClientPlayerNetworkEvent.LoggingIn event) -> IrisClient.onWorldJoin());
|
||||||
|
ClientPlayerNetworkEvent.LoggingOut.BUS.addListener((ClientPlayerNetworkEvent.LoggingOut event) -> IrisClient.onDisconnect());
|
||||||
|
InputEvent.Key.BUS.addListener((InputEvent.Key event) -> IrisClientKeybinds.pollToggle());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sendToServer(byte[] frame) {
|
||||||
|
Channel<CustomPacketPayload> channel = ForgeProtocolNetworking.channel();
|
||||||
|
if (channel == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
channel.send(new ModdedIrisPayload(frame), PacketDistributor.SERVER.noArg());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
public net.minecraft.server.MinecraftServer levels
|
||||||
|
public net.minecraft.server.MinecraftServer executor
|
||||||
|
public net.minecraft.server.MinecraftServer storageSource
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
modLoader = "javafml"
|
||||||
|
loaderVersion = "[65,)"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[[mods]]
|
||||||
|
modId = "irisworldgen"
|
||||||
|
version = "${version}"
|
||||||
|
displayName = "Iris"
|
||||||
|
description = "Iris World Generation Engine (Forge adapter - native chunk generator, explicit Iris world datapack workflow, engine lifecycle)"
|
||||||
|
authors = "Arcane Arts (Volmit Software)"
|
||||||
|
|
||||||
|
[[dependencies.irisworldgen]]
|
||||||
|
modId = "forge"
|
||||||
|
mandatory = true
|
||||||
|
versionRange = "[65,)"
|
||||||
|
ordering = "NONE"
|
||||||
|
side = "BOTH"
|
||||||
|
|
||||||
|
[[dependencies.irisworldgen]]
|
||||||
|
modId = "minecraft"
|
||||||
|
mandatory = true
|
||||||
|
versionRange = "[${minecraftVersion}]"
|
||||||
|
ordering = "NONE"
|
||||||
|
side = "BOTH"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"pack": {
|
||||||
|
"description": "Iris World Generation Engine resources",
|
||||||
|
"max_format": 101,
|
||||||
|
"min_format": [
|
||||||
|
101,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.modded;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.LongSupplier;
|
||||||
|
|
||||||
|
final class InitialSpawnQueue {
|
||||||
|
private static final long DEFAULT_MAX_AGE_NANOS = TimeUnit.MINUTES.toNanos(5);
|
||||||
|
|
||||||
|
private final int capacity;
|
||||||
|
private final long maxAgeNanos;
|
||||||
|
private final LongSupplier nanoTime;
|
||||||
|
private final ArrayDeque<Long> queue;
|
||||||
|
private final Map<Long, Long> pending;
|
||||||
|
private final Set<Long> queued;
|
||||||
|
private boolean closed;
|
||||||
|
|
||||||
|
InitialSpawnQueue(int capacity) {
|
||||||
|
this(capacity, DEFAULT_MAX_AGE_NANOS, System::nanoTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
InitialSpawnQueue(int capacity, long maxAgeNanos, LongSupplier nanoTime) {
|
||||||
|
if (capacity < 1) {
|
||||||
|
throw new IllegalArgumentException("capacity must be positive");
|
||||||
|
}
|
||||||
|
if (maxAgeNanos < 1L) {
|
||||||
|
throw new IllegalArgumentException("maxAgeNanos must be positive");
|
||||||
|
}
|
||||||
|
this.capacity = capacity;
|
||||||
|
this.maxAgeNanos = maxAgeNanos;
|
||||||
|
this.nanoTime = nanoTime;
|
||||||
|
this.queue = new ArrayDeque<>(Math.min(capacity, 256));
|
||||||
|
this.pending = new HashMap<>();
|
||||||
|
this.queued = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized boolean offer(long key) {
|
||||||
|
if (closed || pending.containsKey(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pending.size() >= capacity) {
|
||||||
|
expire(nanoTime.getAsLong());
|
||||||
|
}
|
||||||
|
if (pending.size() >= capacity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pending.put(key, nanoTime.getAsLong());
|
||||||
|
queued.add(key);
|
||||||
|
queue.addLast(key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized int batchSize(int limit) {
|
||||||
|
if (closed || limit <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.min(limit, queue.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized Long poll() {
|
||||||
|
long now = nanoTime.getAsLong();
|
||||||
|
Long key;
|
||||||
|
while ((key = queue.pollFirst()) != null) {
|
||||||
|
queued.remove(key);
|
||||||
|
Long offeredAt = pending.get(key);
|
||||||
|
if (offeredAt == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (expired(offeredAt, now)) {
|
||||||
|
pending.remove(key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void retry(long key) {
|
||||||
|
Long offeredAt = pending.get(key);
|
||||||
|
if (closed || offeredAt == null || expired(offeredAt, nanoTime.getAsLong())) {
|
||||||
|
pending.remove(key);
|
||||||
|
queued.remove(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!queued.add(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
queue.addLast(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void complete(long key) {
|
||||||
|
pending.remove(key);
|
||||||
|
if (queued.remove(key)) {
|
||||||
|
queue.removeFirstOccurrence(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized boolean isEmpty() {
|
||||||
|
return queue.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized int size() {
|
||||||
|
return pending.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void clear() {
|
||||||
|
queue.clear();
|
||||||
|
pending.clear();
|
||||||
|
queued.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void close() {
|
||||||
|
closed = true;
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expire(long now) {
|
||||||
|
Set<Long> expired = new HashSet<>();
|
||||||
|
for (Map.Entry<Long, Long> entry : pending.entrySet()) {
|
||||||
|
if (expired(entry.getValue(), now)) {
|
||||||
|
expired.add(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (expired.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Long key : expired) {
|
||||||
|
pending.remove(key);
|
||||||
|
queued.remove(key);
|
||||||
|
}
|
||||||
|
queue.removeIf(expired::contains);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean expired(long offeredAt, long now) {
|
||||||
|
return now - offeredAt >= maxAgeNanos;
|
||||||
|
}
|
||||||
|
}
|
||||||
+584
@@ -0,0 +1,584 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.modded;
|
||||||
|
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.framework.GenerationSessionException;
|
||||||
|
import art.arcane.iris.engine.object.IrisBiome;
|
||||||
|
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||||
|
import art.arcane.iris.spi.IrisPlatforms;
|
||||||
|
import art.arcane.iris.spi.PlatformBiome;
|
||||||
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
|
import art.arcane.iris.util.project.hunk.Hunk;
|
||||||
|
import com.mojang.serialization.Codec;
|
||||||
|
import com.mojang.serialization.MapCodec;
|
||||||
|
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Holder;
|
||||||
|
import net.minecraft.core.QuartPos;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.core.RegistryAccess;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.WorldGenRegion;
|
||||||
|
import net.minecraft.util.random.WeightedList;
|
||||||
|
import net.minecraft.world.entity.MobCategory;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.LevelHeightAccessor;
|
||||||
|
import net.minecraft.world.level.NoiseColumn;
|
||||||
|
import net.minecraft.world.level.StructureManager;
|
||||||
|
import net.minecraft.world.level.WorldGenLevel;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import net.minecraft.world.level.biome.BiomeManager;
|
||||||
|
import net.minecraft.world.level.biome.BiomeResolver;
|
||||||
|
import net.minecraft.world.level.biome.BiomeSource;
|
||||||
|
import net.minecraft.world.level.biome.Climate;
|
||||||
|
import net.minecraft.world.level.biome.MobSpawnSettings;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||||
|
import net.minecraft.world.level.levelgen.Heightmap;
|
||||||
|
import net.minecraft.world.level.levelgen.RandomState;
|
||||||
|
import net.minecraft.world.level.levelgen.blending.Blender;
|
||||||
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
|
||||||
|
public static final MapCodec<IrisModdedChunkGenerator> CODEC = RecordCodecBuilder.mapCodec((RecordCodecBuilder.Instance<IrisModdedChunkGenerator> instance) -> instance.group(
|
||||||
|
BiomeSource.CODEC.fieldOf("biome_source").forGetter((IrisModdedChunkGenerator generator) -> generator.biomeSource),
|
||||||
|
Codec.STRING.fieldOf("dimension").forGetter((IrisModdedChunkGenerator generator) -> generator.dimensionKey)
|
||||||
|
).apply(instance, IrisModdedChunkGenerator::new));
|
||||||
|
|
||||||
|
private static final AtomicInteger GEN_THREAD_SEQ = new AtomicInteger();
|
||||||
|
private static final boolean PARALLEL_CHUNK_SYSTEM = detectParallelChunkSystem();
|
||||||
|
private static volatile ExecutorService genPool = createGenPool();
|
||||||
|
|
||||||
|
public static void startGenPool() {
|
||||||
|
ExecutorService pool = genPool;
|
||||||
|
if (pool == null || pool.isShutdown()) {
|
||||||
|
genPool = createGenPool();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void shutdownGenPool() {
|
||||||
|
ExecutorService pool = genPool;
|
||||||
|
if (pool != null) {
|
||||||
|
pool.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean detectParallelChunkSystem() {
|
||||||
|
String[] markers = {
|
||||||
|
"com.ishland.c2me.base.ModProperties",
|
||||||
|
"com.ishland.c2me.base.common.config.C2MEConfig",
|
||||||
|
"com.ishland.c2me.opts.chunkio.ModProperties",
|
||||||
|
"ca.spottedleaf.moonrise.common.util.MoonriseCommon"
|
||||||
|
};
|
||||||
|
for (String marker : markers) {
|
||||||
|
try {
|
||||||
|
Class.forName(marker, false, IrisModdedChunkGenerator.class.getClassLoader());
|
||||||
|
return true;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ExecutorService createGenPool() {
|
||||||
|
int threads = Math.max(2, Runtime.getRuntime().availableProcessors());
|
||||||
|
ThreadPoolExecutor pool = new ThreadPoolExecutor(
|
||||||
|
threads, threads, 30L, TimeUnit.SECONDS,
|
||||||
|
new LinkedBlockingQueue<>(),
|
||||||
|
runnable -> {
|
||||||
|
Thread thread = new Thread(runnable, "Iris ModGen-" + GEN_THREAD_SEQ.incrementAndGet());
|
||||||
|
thread.setDaemon(true);
|
||||||
|
return thread;
|
||||||
|
});
|
||||||
|
pool.allowCoreThreadTimeOut(true);
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String dimensionKey;
|
||||||
|
private final String defaultPack;
|
||||||
|
private final String defaultDimensionKey;
|
||||||
|
private final ConcurrentHashMap<String, Holder<Biome>> biomeHolders = new ConcurrentHashMap<>();
|
||||||
|
private final ConcurrentHashMap<Biome, Holder<Biome>> vanillaSpawnBiomes = new ConcurrentHashMap<>();
|
||||||
|
private final ConcurrentHashMap<SpawnTableKey, WeightedList<MobSpawnSettings.SpawnerData>> mergedSpawnTables = new ConcurrentHashMap<>();
|
||||||
|
private final Set<String> missingBiomeWarnings = ConcurrentHashMap.newKeySet();
|
||||||
|
private final AtomicBoolean announced = new AtomicBoolean(false);
|
||||||
|
private volatile boolean vanillaSpawnBiomesInitialized;
|
||||||
|
private volatile Engine engine;
|
||||||
|
private volatile String activePack;
|
||||||
|
private volatile String activeDimensionKey;
|
||||||
|
private volatile long seedOverride = Long.MIN_VALUE;
|
||||||
|
private volatile long lastChunkGenAt = 0L;
|
||||||
|
|
||||||
|
public IrisModdedChunkGenerator(BiomeSource biomeSource, String dimensionKey) {
|
||||||
|
super(biomeSource);
|
||||||
|
this.dimensionKey = dimensionKey;
|
||||||
|
int colon = dimensionKey.indexOf(':');
|
||||||
|
this.defaultPack = colon >= 0 ? dimensionKey.substring(0, colon) : dimensionKey;
|
||||||
|
this.defaultDimensionKey = colon >= 0 ? dimensionKey.substring(colon + 1) : dimensionKey;
|
||||||
|
this.activePack = defaultPack;
|
||||||
|
this.activeDimensionKey = defaultDimensionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void repoint(String pack, String packDimensionKey, long seed) {
|
||||||
|
ServerLevel level = boundLevel();
|
||||||
|
if (level != null) {
|
||||||
|
ModdedWorldEngines.evict(level);
|
||||||
|
}
|
||||||
|
this.activePack = pack;
|
||||||
|
this.activeDimensionKey = packDimensionKey;
|
||||||
|
this.seedOverride = seed;
|
||||||
|
this.engine = null;
|
||||||
|
this.announced.set(false);
|
||||||
|
this.biomeHolders.clear();
|
||||||
|
this.missingBiomeWarnings.clear();
|
||||||
|
resetVanillaSpawnBiomes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void unbindEngine() {
|
||||||
|
ServerLevel level = boundLevel();
|
||||||
|
if (level != null) {
|
||||||
|
ModdedWorldEngines.evict(level);
|
||||||
|
}
|
||||||
|
this.engine = null;
|
||||||
|
this.announced.set(false);
|
||||||
|
this.biomeHolders.clear();
|
||||||
|
this.missingBiomeWarnings.clear();
|
||||||
|
resetVanillaSpawnBiomes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void resetToDefault() {
|
||||||
|
repoint(defaultPack, defaultDimensionKey, Long.MIN_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String activePack() {
|
||||||
|
return activePack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String activeDimensionKey() {
|
||||||
|
return activeDimensionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MapCodec<? extends ChunkGenerator> codec() {
|
||||||
|
return CODEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerLevel boundLevel() {
|
||||||
|
MinecraftServer server = ModdedEngineBootstrap.currentServer();
|
||||||
|
if (server == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (ServerLevel level : server.getAllLevels()) {
|
||||||
|
if (level.getChunkSource().getGenerator() == this) {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Engine engine() {
|
||||||
|
Engine cached = engine;
|
||||||
|
if (cached != null) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (engine != null) {
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
ServerLevel level = boundLevel();
|
||||||
|
if (level == null) {
|
||||||
|
throw new IllegalStateException("Iris generator '" + dimensionKey + "' has no bound ServerLevel yet");
|
||||||
|
}
|
||||||
|
Engine created = ModdedWorldEngines.get(level, activePack, activeDimensionKey, seedOverride);
|
||||||
|
engine = created;
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Engine engineOrNull() {
|
||||||
|
Engine cached = engine;
|
||||||
|
if (cached != null) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return engine();
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String dimensionKey() {
|
||||||
|
return dimensionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Engine engineIfBound() {
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Engine commandEngine() {
|
||||||
|
return engine();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long lastChunkGenAt() {
|
||||||
|
return lastChunkGenAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onHotload() {
|
||||||
|
biomeHolders.clear();
|
||||||
|
missingBiomeWarnings.clear();
|
||||||
|
resetVanillaSpawnBiomes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WeightedList<MobSpawnSettings.SpawnerData> getMobsAt(
|
||||||
|
Holder<Biome> biome, StructureManager structureManager, MobCategory category, BlockPos pos) {
|
||||||
|
WeightedList<MobSpawnSettings.SpawnerData> explicitSpawns = biome.value().getMobSettings().getMobs(category);
|
||||||
|
WeightedList<MobSpawnSettings.SpawnerData> resolvedSpawns = super.getMobsAt(
|
||||||
|
biome, structureManager, category, pos);
|
||||||
|
if (resolvedSpawns != explicitSpawns) {
|
||||||
|
return resolvedSpawns;
|
||||||
|
}
|
||||||
|
|
||||||
|
Registry<Biome> registry = structureManager.registryAccess().lookupOrThrow(Registries.BIOME);
|
||||||
|
initializeVanillaSpawnBiomes(registry);
|
||||||
|
Holder<Biome> vanillaSpawnBiome = vanillaSpawnBiomes.get(biome.value());
|
||||||
|
if (vanillaSpawnBiome == null) {
|
||||||
|
return explicitSpawns;
|
||||||
|
}
|
||||||
|
|
||||||
|
WeightedList<MobSpawnSettings.SpawnerData> vanillaSpawns = vanillaSpawnBiome.value().getMobSettings().getMobs(category);
|
||||||
|
if (explicitSpawns.isEmpty()) {
|
||||||
|
return vanillaSpawns;
|
||||||
|
}
|
||||||
|
if (vanillaSpawns.isEmpty()) {
|
||||||
|
return explicitSpawns;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpawnTableKey key = new SpawnTableKey(biome.value(), category);
|
||||||
|
return mergedSpawnTables.computeIfAbsent(key, ignored -> NativeSpawnTableMerger.merge(vanillaSpawns, explicitSpawns));
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void initializeVanillaSpawnBiomes(Registry<Biome> registry) {
|
||||||
|
if (vanillaSpawnBiomesInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Engine current = engineOrNull();
|
||||||
|
if (current == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String namespace = current.getDimension().getLoadKey().toLowerCase(Locale.ROOT);
|
||||||
|
for (IrisBiome irisBiome : current.getDimension().getAllBiomes(current)) {
|
||||||
|
if (irisBiome == null || !irisBiome.isCustom()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Holder<Biome> vanillaHolder = resolveBiomeHolder(registry, irisBiome.getVanillaDerivativeKey());
|
||||||
|
if (vanillaHolder == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
|
||||||
|
Holder<Biome> customHolder = resolveBiomeHolder(registry, namespace + ":" + customBiome.getId());
|
||||||
|
if (customHolder != null) {
|
||||||
|
vanillaSpawnBiomes.putIfAbsent(customHolder.value(), vanillaHolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vanillaSpawnBiomesInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Holder<Biome> resolveBiomeHolder(Registry<Biome> registry, String key) {
|
||||||
|
if (key == null || key.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Identifier identifier = Identifier.tryParse(key);
|
||||||
|
if (identifier == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Optional<Holder.Reference<Biome>> reference = registry.get(identifier);
|
||||||
|
return reference.<Holder<Biome>>map((Holder.Reference<Biome> value) -> value).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void resetVanillaSpawnBiomes() {
|
||||||
|
vanillaSpawnBiomes.clear();
|
||||||
|
mergedSpawnTables.clear();
|
||||||
|
vanillaSpawnBiomesInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<ChunkAccess> fillFromNoise(Blender blender, RandomState randomState, StructureManager structureManager, ChunkAccess chunk) {
|
||||||
|
Engine generationEngine = engine();
|
||||||
|
ChunkPos pos = chunk.getPos();
|
||||||
|
lastChunkGenAt = System.currentTimeMillis();
|
||||||
|
if (announced.compareAndSet(false, true)) {
|
||||||
|
LOGGER.info("Iris generating {} through IrisModdedChunkGenerator (dim={} first chunk {},{})",
|
||||||
|
dimensionKey, generationEngine.getDimension().getLoadKey(), pos.x(), pos.z());
|
||||||
|
}
|
||||||
|
LOGGER.debug("Iris generating chunk {},{}", pos.x(), pos.z());
|
||||||
|
|
||||||
|
int dimMinY = generationEngine.getMinHeight();
|
||||||
|
int dimMaxY = generationEngine.getMaxHeight();
|
||||||
|
int height = dimMaxY - dimMinY;
|
||||||
|
PlatformBlockState air = IrisPlatforms.get().registries().air();
|
||||||
|
Registry<Biome> biomeRegistry = structureManager.registryAccess().lookupOrThrow(Registries.BIOME);
|
||||||
|
|
||||||
|
if (PARALLEL_CHUNK_SYSTEM) {
|
||||||
|
return CompletableFuture.completedFuture(
|
||||||
|
generateTerrain(chunk, generationEngine, pos, dimMinY, height, air, biomeRegistry, randomState));
|
||||||
|
}
|
||||||
|
return CompletableFuture.supplyAsync(
|
||||||
|
() -> generateTerrain(chunk, generationEngine, pos, dimMinY, height, air, biomeRegistry, randomState),
|
||||||
|
genPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChunkAccess generateTerrain(ChunkAccess chunk, Engine generationEngine, ChunkPos pos,
|
||||||
|
int dimMinY, int height, PlatformBlockState air,
|
||||||
|
Registry<Biome> biomeRegistry, RandomState randomState) {
|
||||||
|
ModdedBlockBuffer blocks = new ModdedBlockBuffer(height, air);
|
||||||
|
Hunk<PlatformBiome> biomes = Hunk.newArrayHunk(16, height, 16);
|
||||||
|
try {
|
||||||
|
generationEngine.generate(pos.getMinBlockX(), pos.getMinBlockZ(), blocks, biomes, false);
|
||||||
|
} catch (GenerationSessionException e) {
|
||||||
|
if (e.isExpectedTeardown()) {
|
||||||
|
LOGGER.debug("Iris chunk {},{} skipped: engine sealed for hotload/teardown", pos.x(), pos.z());
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
LOGGER.error("Iris failed to generate chunk {},{}", pos.x(), pos.z(), e);
|
||||||
|
throw new IllegalStateException("Iris generation failed for chunk " + pos.x() + "," + pos.z(), e);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOGGER.error("Iris failed to generate chunk {},{}", pos.x(), pos.z(), e);
|
||||||
|
throw new IllegalStateException("Iris generation failed for chunk " + pos.x() + "," + pos.z(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeBlocks(chunk, blocks, dimMinY, height);
|
||||||
|
Heightmap.primeHeightmaps(chunk, EnumSet.of(Heightmap.Types.WORLD_SURFACE_WG, Heightmap.Types.OCEAN_FLOOR_WG));
|
||||||
|
chunk.fillBiomesFromNoise(new HunkBiomeResolver(this, biomes, biomeRegistry, pos, dimMinY, height), randomState.sampler());
|
||||||
|
ModdedWorldManager.enqueueGenerated(generationEngine, pos.x(), pos.z());
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Holder<Biome> fallbackBiome(Registry<Biome> registry) {
|
||||||
|
Holder<Biome> existing = biomeHolders.get("minecraft:plains");
|
||||||
|
if (existing != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
Holder<Biome> resolved = registry.get(Identifier.fromNamespaceAndPath("minecraft", "plains"))
|
||||||
|
.<Holder<Biome>>map((Holder.Reference<Biome> reference) -> reference)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("minecraft:plains missing from biome registry"));
|
||||||
|
Holder<Biome> raced = biomeHolders.putIfAbsent("minecraft:plains", resolved);
|
||||||
|
return raced != null ? raced : resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Holder<Biome> holderFor(Registry<Biome> registry, PlatformBiome biome) {
|
||||||
|
if (biome == null) {
|
||||||
|
return fallbackBiome(registry);
|
||||||
|
}
|
||||||
|
String key = biome.key();
|
||||||
|
Holder<Biome> existing = biomeHolders.get(key);
|
||||||
|
if (existing != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
Identifier identifier = Identifier.tryParse(key);
|
||||||
|
Optional<Holder.Reference<Biome>> reference = identifier == null ? Optional.empty() : registry.get(identifier);
|
||||||
|
Holder<Biome> resolved = reference.<Holder<Biome>>map((Holder.Reference<Biome> value) -> value).orElseGet(() -> {
|
||||||
|
if (missingBiomeWarnings.size() < 256 && missingBiomeWarnings.add(key)) {
|
||||||
|
LOGGER.warn("Iris biome '{}' (pack {}) is not in the biome registry; writing minecraft:plains. Restart so the forced datapack registers the pack biomes.", key, activePack);
|
||||||
|
}
|
||||||
|
return fallbackBiome(registry);
|
||||||
|
});
|
||||||
|
Holder<Biome> raced = biomeHolders.putIfAbsent(key, resolved);
|
||||||
|
return raced != null ? raced : resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BiomeResolver regenBiomeResolver(Registry<Biome> registry, Hunk<PlatformBiome> biomes, ChunkPos pos) {
|
||||||
|
Engine current = engine();
|
||||||
|
int dimMinY = current.getMinHeight();
|
||||||
|
int height = current.getMaxHeight() - dimMinY;
|
||||||
|
return new HunkBiomeResolver(this, biomes, registry, pos, dimMinY, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeBlocks(ChunkAccess chunk, ModdedBlockBuffer blocks, int dimMinY, int height) {
|
||||||
|
int chunkMinY = chunk.getMinY();
|
||||||
|
int chunkMaxY = chunkMinY + chunk.getHeight();
|
||||||
|
int from = Math.max(dimMinY, chunkMinY);
|
||||||
|
int to = Math.min(dimMinY + height, chunkMaxY);
|
||||||
|
|
||||||
|
for (int y = from; y < to; ) {
|
||||||
|
int sectionIndex = chunk.getSectionIndex(y);
|
||||||
|
LevelChunkSection section = chunk.getSection(sectionIndex);
|
||||||
|
int sectionMinY = chunk.getSectionYFromSectionIndex(sectionIndex) << 4;
|
||||||
|
int sectionEnd = Math.min(sectionMinY + 16, to);
|
||||||
|
section.acquire();
|
||||||
|
try {
|
||||||
|
for (int blockY = y; blockY < sectionEnd; blockY++) {
|
||||||
|
int bufferY = blockY - dimMinY;
|
||||||
|
int localY = blockY & 15;
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
if (blocks.isAir(x, bufferY, z)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
PlatformBlockState state = blocks.getRaw(x, bufferY, z);
|
||||||
|
section.setBlockState(x, localY, z, (BlockState) state.nativeHandle(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
section.release();
|
||||||
|
}
|
||||||
|
y = sectionEnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class HunkBiomeResolver implements BiomeResolver {
|
||||||
|
private final IrisModdedChunkGenerator generator;
|
||||||
|
private final Hunk<PlatformBiome> biomes;
|
||||||
|
private final Registry<Biome> registry;
|
||||||
|
private final ChunkPos pos;
|
||||||
|
private final int dimMinY;
|
||||||
|
private final int height;
|
||||||
|
|
||||||
|
private HunkBiomeResolver(IrisModdedChunkGenerator generator, Hunk<PlatformBiome> biomes, Registry<Biome> registry, ChunkPos pos, int dimMinY, int height) {
|
||||||
|
this.generator = generator;
|
||||||
|
this.biomes = biomes;
|
||||||
|
this.registry = registry;
|
||||||
|
this.pos = pos;
|
||||||
|
this.dimMinY = dimMinY;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Holder<Biome> getNoiseBiome(int quartX, int quartY, int quartZ, Climate.Sampler sampler) {
|
||||||
|
int localX = QuartPos.toBlock(quartX) - pos.getMinBlockX();
|
||||||
|
int localZ = QuartPos.toBlock(quartZ) - pos.getMinBlockZ();
|
||||||
|
int bufferY = Math.max(0, Math.min(height - 1, QuartPos.toBlock(quartY) - dimMinY));
|
||||||
|
PlatformBiome biome = biomes.get(localX, bufferY, localZ);
|
||||||
|
return generator.holderFor(registry, biome);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyCarvers(WorldGenRegion region, long seed, RandomState randomState, BiomeManager biomeManager, StructureManager structureManager, ChunkAccess chunk) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void buildSurface(WorldGenRegion region, StructureManager structureManager, RandomState randomState, ChunkAccess chunk) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyBiomeDecoration(WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess chunk, StructureTemplateManager templateManager, ResourceKey<Level> levelKey) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createReferences(WorldGenLevel level, StructureManager structureManager, ChunkAccess chunk) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void spawnOriginalMobs(WorldGenRegion region) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getGenDepth() {
|
||||||
|
Engine current = engineOrNull();
|
||||||
|
return current == null ? 384 : current.getMaxHeight() - current.getMinHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSeaLevel() {
|
||||||
|
Engine current = engineOrNull();
|
||||||
|
return current == null ? 63 : current.getMinHeight() + current.getDimension().getFluidHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinY() {
|
||||||
|
Engine current = engineOrNull();
|
||||||
|
return current == null ? -64 : current.getMinHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBaseHeight(int x, int z, Heightmap.Types type, LevelHeightAccessor heightAccessor, RandomState randomState) {
|
||||||
|
Engine current = engineOrNull();
|
||||||
|
if (current == null) {
|
||||||
|
return heightAccessor.getMinY() + Math.min(heightAccessor.getHeight(), 64);
|
||||||
|
}
|
||||||
|
boolean ignoreFluid = type == Heightmap.Types.OCEAN_FLOOR || type == Heightmap.Types.OCEAN_FLOOR_WG;
|
||||||
|
return current.getMinHeight() + current.getHeight(x, z, ignoreFluid) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NoiseColumn getBaseColumn(int x, int z, LevelHeightAccessor heightAccessor, RandomState randomState) {
|
||||||
|
int minY = heightAccessor.getMinY();
|
||||||
|
BlockState[] states = new BlockState[heightAccessor.getHeight()];
|
||||||
|
Engine current = engineOrNull();
|
||||||
|
BlockState airState = Blocks.AIR.defaultBlockState();
|
||||||
|
if (current == null) {
|
||||||
|
Arrays.fill(states, airState);
|
||||||
|
return new NoiseColumn(minY, states);
|
||||||
|
}
|
||||||
|
int surface = current.getMinHeight() + current.getHeight(x, z, true);
|
||||||
|
int fluid = current.getMinHeight() + current.getDimension().getFluidHeight();
|
||||||
|
BlockState stone = Blocks.STONE.defaultBlockState();
|
||||||
|
BlockState water = Blocks.WATER.defaultBlockState();
|
||||||
|
int solidEnd = Math.max(0, Math.min(states.length, surface - minY + 1));
|
||||||
|
int fluidEnd = Math.max(solidEnd, Math.max(0, Math.min(states.length, fluid - minY + 1)));
|
||||||
|
Arrays.fill(states, 0, solidEnd, stone);
|
||||||
|
Arrays.fill(states, solidEnd, fluidEnd, water);
|
||||||
|
Arrays.fill(states, fluidEnd, states.length, airState);
|
||||||
|
return new NoiseColumn(minY, states);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addDebugScreenInfo(List<String> info, RandomState randomState, BlockPos pos) {
|
||||||
|
info.add("Iris dimension: " + dimensionKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record SpawnTableKey(Biome biome, MobCategory category) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.modded;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public final class MainWorldService {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
|
||||||
|
private static final String PRESET_NAMESPACE = "irisworldgen";
|
||||||
|
private static final String MARKER_NAME = "mainworld.pending";
|
||||||
|
private static final String[] VANILLA_DIMENSION_FOLDERS = {
|
||||||
|
"region",
|
||||||
|
"entities",
|
||||||
|
"poi",
|
||||||
|
"mantle",
|
||||||
|
"dimensions/minecraft/overworld",
|
||||||
|
"dimensions/minecraft/the_nether",
|
||||||
|
"dimensions/minecraft/the_end",
|
||||||
|
"DIM-1",
|
||||||
|
"DIM1"
|
||||||
|
};
|
||||||
|
|
||||||
|
private MainWorldService() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String presetIdFor(String packRef) {
|
||||||
|
String value = packRef.trim();
|
||||||
|
int colon = value.indexOf(':');
|
||||||
|
String pack = colon >= 0 ? value.substring(0, colon) : value;
|
||||||
|
String dimension = colon >= 0 ? value.substring(colon + 1) : value;
|
||||||
|
String presetKey = dimension.equals(pack) ? pack : pack + "_" + dimension;
|
||||||
|
return PRESET_NAMESPACE + ":" + presetKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void reconcileEarly() {
|
||||||
|
try {
|
||||||
|
ModdedModConfig config = ModdedModConfig.get();
|
||||||
|
String pack = config.mainWorldPack();
|
||||||
|
if (pack == null || pack.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Path properties = instanceRoot().resolve("server.properties");
|
||||||
|
String target = presetIdFor(pack);
|
||||||
|
String currentType = readProperty(properties, "level-type");
|
||||||
|
if (!target.equals(currentType)) {
|
||||||
|
writeLevelProperties(properties, target, config.mainWorldSeed());
|
||||||
|
markPending();
|
||||||
|
LOGGER.warn("Iris main world '{}' staged: server.properties level-type set to {}. Restart again to generate it (this boot still uses the previous overworld; player data is kept).", pack, target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isPending()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String levelName = firstNonBlank(readProperty(properties, "level-name"), "world");
|
||||||
|
wipeVanillaDimensions(instanceRoot().resolve(levelName));
|
||||||
|
clearPending();
|
||||||
|
LOGGER.warn("Iris main world '{}' generated fresh: cleared the previous overworld/nether/end so this boot regenerates them as {} (player data kept).", pack, target);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOGGER.error("Iris main world reconciliation failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean stage(String packRef, long seed) {
|
||||||
|
try {
|
||||||
|
Path properties = instanceRoot().resolve("server.properties");
|
||||||
|
writeLevelProperties(properties, presetIdFor(packRef), seed);
|
||||||
|
markPending();
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Iris failed to stage the main world in server.properties", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearOverride() {
|
||||||
|
try {
|
||||||
|
clearPending();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Iris failed to clear the pending main world marker", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path instanceRoot() {
|
||||||
|
return ModdedEngineBootstrap.loader().configDir().getParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path markerFile() {
|
||||||
|
return ModdedEngineBootstrap.loader().configDir().resolve("irisworldgen").resolve(MARKER_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPending() {
|
||||||
|
return Files.isRegularFile(markerFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void markPending() throws IOException {
|
||||||
|
Path marker = markerFile();
|
||||||
|
Files.createDirectories(marker.getParent());
|
||||||
|
Files.writeString(marker, "pending", StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void clearPending() throws IOException {
|
||||||
|
Files.deleteIfExists(markerFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readProperty(Path properties, String key) throws IOException {
|
||||||
|
if (!Files.isRegularFile(properties)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<String> lines = Files.readAllLines(properties, StandardCharsets.UTF_8);
|
||||||
|
String prefix = key + "=";
|
||||||
|
for (String line : lines) {
|
||||||
|
if (line.startsWith(prefix)) {
|
||||||
|
return unescape(line.substring(prefix.length()).trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeLevelProperties(Path properties, String target, long seed) throws IOException {
|
||||||
|
List<String> lines = Files.isRegularFile(properties)
|
||||||
|
? new ArrayList<>(Files.readAllLines(properties, StandardCharsets.UTF_8))
|
||||||
|
: new ArrayList<>();
|
||||||
|
setProperty(lines, "level-type", escape(target));
|
||||||
|
if (seed != 0L) {
|
||||||
|
setProperty(lines, "level-seed", Long.toString(seed));
|
||||||
|
}
|
||||||
|
Files.write(properties, lines, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setProperty(List<String> lines, String key, String value) {
|
||||||
|
String prefix = key + "=";
|
||||||
|
for (int i = 0; i < lines.size(); i++) {
|
||||||
|
if (lines.get(i).startsWith(prefix)) {
|
||||||
|
lines.set(i, prefix + value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines.add(prefix + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void wipeVanillaDimensions(Path worldRoot) throws IOException {
|
||||||
|
Files.deleteIfExists(worldRoot.resolve("level.dat"));
|
||||||
|
Files.deleteIfExists(worldRoot.resolve("level.dat_old"));
|
||||||
|
for (String folder : VANILLA_DIMENSION_FOLDERS) {
|
||||||
|
deleteRecursively(worldRoot.resolve(folder));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deleteRecursively(Path path) throws IOException {
|
||||||
|
if (!Files.exists(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<Path> entries = new ArrayList<>();
|
||||||
|
try (Stream<Path> walk = Files.walk(path)) {
|
||||||
|
walk.sorted(Comparator.comparingInt(Path::getNameCount).reversed()).forEach(entries::add);
|
||||||
|
}
|
||||||
|
for (Path entry : entries) {
|
||||||
|
Files.deleteIfExists(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String escape(String value) {
|
||||||
|
return value.replace(":", "\\:");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String unescape(String value) {
|
||||||
|
return value.replace("\\:", ":").replace("\\=", "=");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String firstNonBlank(String value, String fallback) {
|
||||||
|
return value == null || value.isBlank() ? fallback : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.modded;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.PlatformBiome;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public final class ModdedBiome implements PlatformBiome {
|
||||||
|
private static final ConcurrentHashMap<String, ModdedBiome> CACHE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final Biome biome;
|
||||||
|
private final String key;
|
||||||
|
private final String namespace;
|
||||||
|
|
||||||
|
private ModdedBiome(Biome biome, String key) {
|
||||||
|
this.biome = biome;
|
||||||
|
this.key = key;
|
||||||
|
int colon = key.indexOf(':');
|
||||||
|
this.namespace = colon >= 0 ? key.substring(0, colon) : "minecraft";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModdedBiome of(Biome biome, String key) {
|
||||||
|
return CACHE.computeIfAbsent(key, (String k) -> new ModdedBiome(biome, k));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String key() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String namespace() {
|
||||||
|
return namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object nativeHandle() {
|
||||||
|
return biome;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.modded;
|
||||||
|
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.object.IrisBiome;
|
||||||
|
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||||
|
import art.arcane.iris.spi.PlatformBiome;
|
||||||
|
import art.arcane.iris.spi.PlatformBiomeWriter;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public final class ModdedBiomeWriter implements PlatformBiomeWriter {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
|
||||||
|
private static final String VANILLA_FALLBACK_KEY = "minecraft:plains";
|
||||||
|
|
||||||
|
private final Supplier<MinecraftServer> server;
|
||||||
|
|
||||||
|
public ModdedBiomeWriter(Supplier<MinecraftServer> server) {
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int biomeIdFor(String key) {
|
||||||
|
Registry<Biome> registry = biomeRegistry();
|
||||||
|
if (registry == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int direct = idForKey(registry, key);
|
||||||
|
if (direct >= 0) {
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
int derivative = idForDerivative(registry, key);
|
||||||
|
if (derivative >= 0) {
|
||||||
|
return derivative;
|
||||||
|
}
|
||||||
|
return fallbackId(registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PlatformBiome> allBiomes() {
|
||||||
|
Registry<Biome> registry = biomeRegistry();
|
||||||
|
List<PlatformBiome> biomes = new ArrayList<>();
|
||||||
|
if (registry == null) {
|
||||||
|
return biomes;
|
||||||
|
}
|
||||||
|
for (Identifier identifier : registry.keySet()) {
|
||||||
|
Biome biome = registry.getValue(identifier);
|
||||||
|
if (biome != null) {
|
||||||
|
biomes.add(ModdedBiome.of(biome, identifier.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return biomes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int idForKey(Registry<Biome> registry, String key) {
|
||||||
|
Identifier identifier = Identifier.tryParse(key);
|
||||||
|
if (identifier == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
Biome biome = registry.getValue(identifier);
|
||||||
|
return biome == null ? -1 : registry.getId(biome);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int idForDerivative(Registry<Biome> registry, String key) {
|
||||||
|
int colon = key.indexOf(':');
|
||||||
|
if (colon <= 0 || colon >= key.length() - 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
String dimensionLoadKey = key.substring(0, colon);
|
||||||
|
String customBiomeId = key.substring(colon + 1);
|
||||||
|
IrisBiome owner = findCustomBiomeOwner(dimensionLoadKey, customBiomeId);
|
||||||
|
if (owner == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
String derivativeKey = owner.getVanillaDerivativeKey();
|
||||||
|
if (derivativeKey == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return idForKey(registry, derivativeKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisBiome findCustomBiomeOwner(String dimensionLoadKey, String customBiomeId) {
|
||||||
|
for (Engine engine : ModdedWorldEngines.activeEngines()) {
|
||||||
|
if (engine == null || engine.isClosed()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!dimensionLoadKey.equalsIgnoreCase(engine.getDimension().getLoadKey())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (IrisBiome biome : engine.getDimension().getAllBiomes(engine)) {
|
||||||
|
if (!biome.isCustom()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (IrisBiomeCustom custom : biome.getCustomDerivitives()) {
|
||||||
|
if (customBiomeId.equals(custom.getId())) {
|
||||||
|
return biome;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int fallbackId(Registry<Biome> registry) {
|
||||||
|
int plains = idForKey(registry, VANILLA_FALLBACK_KEY);
|
||||||
|
return plains >= 0 ? plains : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Registry<Biome> biomeRegistry() {
|
||||||
|
MinecraftServer instance = server.get();
|
||||||
|
if (instance == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return instance.registryAccess().lookupOrThrow(Registries.BIOME);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Servers
|
||||||
|
* Copyright (c) 2026 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.modded;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
|
import art.arcane.iris.util.project.hunk.Hunk;
|
||||||
|
|
||||||
|
public final class ModdedBlockBuffer implements Hunk<PlatformBlockState> {
|
||||||
|
private final PlatformBlockState[] data;
|
||||||
|
private final PlatformBlockState air;
|
||||||
|
private final int height;
|
||||||
|
|
||||||
|
public ModdedBlockBuffer(int height, PlatformBlockState air) {
|
||||||
|
this.data = new PlatformBlockState[16 * height * 16];
|
||||||
|
this.air = air;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int index(int x, int y, int z) {
|
||||||
|
return (y * 16 + z) * 16 + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAir(int x, int y, int z) {
|
||||||
|
return data[index(x, y, z)] == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWidth() {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDepth() {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(int x, int y, int z, PlatformBlockState t) {
|
||||||
|
if (t == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (x < 0 || y < 0 || z < 0 || x >= 16 || y >= height || z >= 16) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data[index(x, y, z)] = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRaw(int x, int y, int z, PlatformBlockState t) {
|
||||||
|
if (t == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data[index(x, y, z)] = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PlatformBlockState getRaw(int x, int y, int z) {
|
||||||
|
PlatformBlockState state = data[index(x, y, z)];
|
||||||
|
return state == null ? air : state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PlatformBlockState get(int x, int y, int z) {
|
||||||
|
if (x < 0 || y < 0 || z < 0 || x >= 16 || y >= height || z >= 16) {
|
||||||
|
return air;
|
||||||
|
}
|
||||||
|
return getRaw(x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user