260 Commits
Author SHA1 Message Date
NotAlexNoyle 9f8bf11660 Exclude CODEOWNERS from language statistics 2026-10-29 16:18:54 -04:00
NotAlexNoyle f8dcf33fcb Add CODEOWNERS (one staff approval) 2026-10-29 16:18:54 -04:00
NotAlexNoyle 116976d8e8 Merge branch 'master' of github.com:true-og/Chunky 2026-06-17 06:47:56 -04:00
NotAlexNoyle 800a4fa743 Update config 2026-06-17 06:47:36 -04:00
NotAlexNoyle 3124d52e3b Merge pull request #2 from true-og/codex/fix-runtime-error-in-gradle-jar-build
Fix Bukkit plugin main class relocation in shaded JAR
2026-03-13 13:42:30 -04:00
NotAlexNoyle b973781649 Fix Bukkit shadowJar relocation for plugin main class 2026-03-13 13:42:19 -04:00
NotAlexNoyle 107bb79474 Merge pull request #1 from true-og/codex/fix-jar-build-for-minecraft-plugin
Produce shaded Bukkit plugin jar for Purpur 1.19.4 and bundle/relocate dependencies
2026-03-13 09:42:12 -04:00
NotAlexNoyle f35cb20714 Fix Bukkit plugin packaging and Purpur target 2026-03-13 09:40:54 -04:00
NotAlexNoyle aab04df0b6 Update gradle to 8.14.3 2025-09-17 10:24:14 -04:00
NotAlexNoyle eda3874ba7 Remove unneeded subprojects 2025-06-11 15:31:36 -04:00
NotAlexNoyle 609fea09f6 Fix build 2024-12-08 14:07:26 -05:00
pop4959 d3956d688b New Crowdin updates (#330) 2024-04-13 23:49:49 -07:00
citylion e46c1c3abe Add Hexagon shape (#329) 2024-04-13 23:22:44 -07:00
pop4959 ca668d2434 New Crowdin updates (#328) 2024-04-13 21:37:03 -07:00
pop4959 af621921e9 New Crowdin updates (#326) 2024-03-19 23:34:46 -07:00
Christopher Bohn 9f165e647d Don't wait for server thread when dispatching tasks (Forge) 2024-02-24 11:58:35 -08:00
ishland 4cff7a4711 Don't wait for server thread when dispatching tasks (#322) 2024-02-24 11:00:53 -08:00
Christopher Bohn a60f083e08 Send trim updates to console 2024-02-20 13:07:17 -08:00
Christopher Bohn 74f6ab4866 Improve error handling of trim side 2024-02-20 13:01:05 -08:00
pop4959 ea68abc3cc New Crowdin updates (#318) 2024-02-16 18:14:16 -08:00
Christopher Bohn 73813786c0 Default to op when no permissions are set 2024-02-16 18:11:33 -08:00
pop4959 7b5594c196 New Crowdin updates (#312) 2024-02-12 23:55:32 -08:00
Christopher Bohn 87a13cf729 Add border command to Forge 2024-02-12 23:34:11 -08:00
Christopher Bohn 324f042f77 Update Github Actions 2024-02-07 19:45:26 -08:00
Christopher Bohn 83a9e8b8c3 Bump archloom 2024-02-05 22:02:15 -08:00
Christopher Bohn 2a58fdaf80 Implement chunk filters 2024-02-04 02:31:28 -08:00
Christopher Bohn 62c437dcc8 Support tag search 2024-02-03 22:33:55 -08:00
Christopher Bohn a2e8736c14 Handle interrupts while processing world 2024-02-03 12:10:10 -08:00
Christopher Bohn b93468e9f6 Add world iterator region progress 2024-02-03 11:45:34 -08:00
pop4959 9947f37a26 Implement new world pattern (#315) 2024-02-01 04:17:14 -08:00
Christopher Bohn ce93af7c81 Buffer decompressed chunk data 2024-01-31 02:59:04 -08:00
Christopher Bohn f132397ae6 Default to key when no translation 2024-01-31 02:56:52 -08:00
Christopher Bohn c15c600b45 Fix saving of CSV tasks 2024-01-25 23:10:46 -08:00
Christopher Bohn f59d7c4759 Revert "Adjust max working count default scaling"
This reverts commit 9e447d4a2a.
2024-01-25 23:07:56 -08:00
Christopher Bohn 76b678aec4 Add warning for world border beyond max world size 2024-01-21 18:43:16 -08:00
Christopher Bohn e51f093ce8 Adjust sample interval default values 2024-01-21 02:29:36 -08:00
Christopher Bohn 10f1478474 Make trim cancellable and add progress messages 2024-01-17 23:11:57 -08:00
Christopher Bohn 29c92a8dc8 Scan for generated chunk status 2024-01-17 21:24:54 -08:00
Christopher Bohn 9e447d4a2a Adjust max working count default scaling 2024-01-17 02:35:18 -08:00
Christopher Bohn 1726479648 Don't load region where location table is missing 2024-01-17 01:36:43 -08:00
Christopher Bohn 117c03fb46 Use server boss bars instead of command boss bars 2024-01-17 01:19:50 -08:00
Christopher Bohn 4d562aa62c Optional world argument for worldborder command 2024-01-17 00:11:12 -08:00
Christopher Bohn e398cc2d8e Add system property to await ticket removal 2024-01-16 23:19:51 -08:00
Christopher Bohn e6137925f3 Mention NeoForge in readme 2024-01-15 23:30:35 -08:00
Christopher Bohn f08fb59aa3 Add NeoForge artifact to Github Actions 2024-01-15 23:12:52 -08:00
Christopher Bohn f9429cd717 Apparently the loader version got reset 2024-01-15 23:11:09 -08:00
Christopher Bohn fc7e1b0bae NeoForge implementation 2024-01-15 22:50:51 -08:00
Christopher Bohn c833f27a92 Update platform specific gradle plugins 2024-01-15 20:36:11 -08:00
Christopher Bohn 5975a9f5f3 Find world by namespaced id path as a fallback 2024-01-15 16:37:05 -08:00
Christopher Bohn 0f65dbc19b Don't require permissions in Singleplayer mode 2024-01-14 02:26:42 -08:00
Christopher Bohn 646043cdf0 Replace magic values for tag type 2024-01-14 01:56:34 -08:00
Christopher Bohn caef3640d1 Make Bukkit world getDirectory more strict 2024-01-13 18:20:27 -08:00
Christopher Bohn fe130e8b8e Update Github Actions 2023-12-29 02:37:56 -08:00
Christopher Bohn 586a14e409 Revert Folia to latest published version 2023-12-29 01:25:17 -08:00
Christopher Bohn 213c5ce763 Update build scripts 2023-12-29 01:23:06 -08:00
Christopher Bohn cadf998c32 Add system property for sample interval 2023-11-22 18:13:53 -08:00
Christopher Bohn 886be957cd Support command permission on Fabric 2023-11-22 00:19:03 -08:00
Metriximor 580f0072e3 Improve vehicle teleporting logic (#301) 2023-11-21 23:28:53 -08:00
Emily 5079beb68e Implement isChunkGenerated on Spigot (#299) 2023-11-18 15:27:44 -08:00
Christopher Bohn f609ab4a34 Fix getting elevation on Folia 2023-11-05 20:03:42 -08:00
Christopher Bohn ee02cec5ac Remount vehicle when teleporting 2023-11-05 19:15:42 -08:00
pop4959 e2d8f7b89b New Crowdin updates (#292) 2023-11-05 16:35:26 -08:00
pop4959 f12a5a9258 New Crowdin updates (#291) 2023-09-16 15:40:25 -07:00
pop4959 709f2a5376 New Crowdin updates (#290) 2023-09-03 16:49:09 -07:00
pop4959 329937a7f7 New Crowdin updates (#277) 2023-08-13 18:04:53 -07:00
pop4959 61ca4e7bbf New Crowdin updates (#275) 2023-06-16 13:10:09 -07:00
pop4959 6edaeaa0b0 Handle POIs and entities properly when trimming with inhabited time 2023-06-16 13:06:31 -07:00
Christopher Bohn 176928cff2 Update Forge to 1.20 2023-06-09 22:35:46 -07:00
pop4959 1cdd07b2d5 New Crowdin updates (#273) 2023-06-09 18:37:49 -07:00
Christopher Bohn ebd263954a Update Fabric to 1.20 2023-06-07 18:33:04 -07:00
pop4959 b487702986 New Crowdin updates (#270) 2023-06-07 18:16:38 -07:00
Christopher Bohn 3597ec3465 Update build scripts 2023-05-29 20:51:37 -07:00
Christopher Bohn 33b6080ccd Update bStats 2023-05-29 17:27:53 -07:00
Christopher Bohn bfb30e58cc update test strings 2023-05-29 16:09:56 -07:00
Christopher Bohn 4a329e8029 add notice about inhabited time trim 2023-05-29 16:01:02 -07:00
Christopher Bohn f435293368 Register new trim arguments in Fabric/Forge 2023-05-29 14:01:46 -07:00
Christopher Bohn 3666d58e88 add another folia method 2023-05-28 16:22:54 -07:00
Christopher Bohn 38e99a8703 add some folia methods 2023-05-28 15:55:51 -07:00
Christopher Bohn 2ff6a5dddf Add missing tests 2023-05-21 17:34:44 -07:00
pop4959 b1d088bef6 New Crowdin updates (#268) 2023-05-21 17:31:16 -07:00
pop4959 c5f15db2b8 New Crowdin updates (#266) 2023-05-09 23:23:31 -07:00
Christopher Bohn 98baadfe32 Move NBT to its own module 2023-05-02 22:22:53 -07:00
pop4959 d4c0af18aa New Crowdin updates (#262) 2023-04-29 16:06:46 -07:00
Christopher Bohn 6df5aa1e1e Bump supported version ranges 2023-04-15 01:09:22 -07:00
Christopher Bohn 410ddd106f Fix usage of BlockPos on 1.19.4 2023-04-15 00:59:25 -07:00
pop4959 5d83912c3b New Crowdin updates (#259) 2023-04-14 21:29:33 -07:00
pop4959 953196facb New Crowdin updates (#257) 2023-04-06 22:23:38 -07:00
pop4959 9474b9144c New Crowdin updates (#248) 2023-03-29 19:54:08 -07:00
Christopher Bohn b347572f84 Update default iterator to regionized 2023-03-29 19:32:39 -07:00
pop4959 2a3f089861 Folia support (#256) 2023-03-29 19:18:20 -07:00
Christopher Bohn f8b7d0fba5 Fix reading chunks from non-regions 2023-03-18 01:38:01 -07:00
pop4959 96bb1ea7e2 Implement new region pattern (#253) 2023-03-14 01:26:16 -07:00
Christopher Bohn 9ee34d3983 Ensure tickets placed on main after gen 2023-03-11 21:31:42 -08:00
Christopher Bohn dbc51fbedf Improve generation pipeline 2023-03-08 23:38:52 -08:00
pop4959 0f914e461f New Crowdin updates (#247) 2023-03-04 01:15:15 -08:00
Christopher Bohn 66179d4a45 Extra trim options 2023-03-04 00:50:11 -08:00
Christopher Bohn 75640077b4 Update Paper repo URL 2023-03-02 00:45:29 -08:00
Christopher Bohn b7cbd6775b Register extended border arguments on Fabric 2023-03-01 20:52:34 -08:00
Christopher Bohn f912195a26 Add system property for ticking load duration 2023-02-01 01:15:27 -08:00
pop4959 02923bee53 New Crowdin updates (#236) 2023-02-01 01:11:58 -08:00
pop4959 bda5898a35 New Crowdin updates (#235) 2022-12-31 14:06:26 -08:00
pop4959 5eecf8b671 Use release version of PaperLib 2022-12-31 14:02:28 -08:00
pop4959 e2b76451ab Use RegisterCommandsEvent on Forge 2022-12-28 14:11:20 -08:00
pop4959 51c2dae5f9 New Crowdin updates (#233) 2022-12-28 13:23:39 -08:00
pop4959 c17ad230ca Bump required Minecraft versions 2022-12-15 17:25:53 -08:00
pop4959 b4d2748f94 New Crowdin updates (#225) 2022-12-15 01:34:23 -08:00
pop4959 6922cd49f5 Fabric/Forge updates for 1.19.3 2022-12-09 00:40:18 -08:00
pop4959 469dd57931 New Crowdin updates (#223) 2022-10-27 19:07:21 -07:00
pop4959 447c3daca0 Add Chinese Traditional to crowdin.yml 2022-10-26 22:17:45 -07:00
pop4959 cdd1d92fcb Fix error loading permissions before server start 2022-10-02 18:17:08 -07:00
Dylan Xaldin 2776307e51 Remove unused imports 2022-10-01 01:34:01 -07:00
pop4959 c25ea97301 New Crowdin updates (#217) 2022-09-25 14:08:16 -07:00
pop4959 eeaf3cccac New Crowdin updates (#216) 2022-09-22 08:16:28 -07:00
pop4959 0e0d5395b2 New Crowdin updates (#215) 2022-09-20 02:05:54 -07:00
pop4959 4e70b40e26 New translations en.json (French) (#214) 2022-09-18 18:24:29 -07:00
pop4959 0cc7d22190 Internally support custom shapes 2022-09-18 17:42:23 -07:00
pop4959 8473fee636 Create workflow for javadoc 2022-09-11 22:41:49 -07:00
pop4959 47a228f14a Add API documentation 2022-09-11 21:55:35 -07:00
pop4959 304b29b762 New Crowdin updates (#213) 2022-09-09 20:13:24 -07:00
pop4959 a6ac1dc4f7 New Crowdin updates (#212) 2022-09-06 02:09:49 -07:00
pop4959 b86fb33cbb Preview API 2022-09-05 23:45:17 -07:00
pop4959 c727a8b9f6 New Crowdin updates (#211) 2022-09-05 21:56:03 -07:00
pop4959 9a6ddb3b00 Update wrap metadata 2022-09-05 21:02:18 -07:00
pop4959 57fbe2c3eb Register dimension arguments for border command 2022-09-05 17:27:51 -07:00
pop4959 1ac89047f5 Permission support on Fabric 2022-09-03 21:01:51 -07:00
pop4959 18c3a53db5 Safer air check 2022-09-03 19:52:35 -07:00
pop4959 21a9bd35cc New Crowdin updates (#210) 2022-09-03 03:26:57 -07:00
pop4959 423daa0e84 Improve elevation calculation 2022-09-03 03:10:58 -07:00
pop4959 4dac670207 Register instance with the service manager 2022-08-28 02:50:05 -07:00
pop4959 7c28e1b17c Include border command in help when present 2022-08-27 22:47:33 -07:00
pop4959 142af0c2c8 Update Gradle to 7.5.1 2022-08-27 21:51:03 -07:00
pop4959 c356a9e008 Fix circle returning incorrect radii 2022-08-27 19:46:30 -07:00
pop4959 9ba9e407b0 Update default permissions 2022-08-20 22:33:51 -07:00
pop4959 7464d503e6 Add missing test translations 2022-08-20 22:26:26 -07:00
pop4959 e076408af9 Add Korean to crowdin.yml 2022-08-20 11:32:53 -07:00
pop4959 00478faa4e Fix errors in Hebrew translation 2022-08-14 02:50:52 -07:00
pop4959 0983ca3a8e Add test for Hebrew 2022-08-14 02:41:39 -07:00
pop4959 8f49d24569 New Crowdin updates (#204) 2022-08-14 02:36:59 -07:00
pop4959 0d17535530 Add Hebrew to crowdin.yml 2022-08-12 10:49:59 -07:00
pop4959 a605fa9e1a Refactor methods into ShapeUtil 2022-08-09 21:33:53 -07:00
pop4959 6a010c5c57 Option to force load chunks 2022-08-06 22:31:56 -07:00
pop4959 062a556a70 Fire event when running reload 2022-08-06 21:21:02 -07:00
pop4959 c0c03a16a1 Add command to display selection
Closes #177
2022-08-06 21:03:41 -07:00
pop4959 d813a08b62 Add more specific reload 2022-08-06 19:45:17 -07:00
pop4959 8783e099b1 Bump config version 2022-08-02 01:38:34 -07:00
pop4959 f7669ca67f Create task directories 2022-08-02 01:18:45 -07:00
pop4959 3dce0ff86f Add missing config directories 2022-08-02 01:06:54 -07:00
pop4959 cd4ee39655 Simple NBT 2022-07-29 23:40:28 -07:00
pop4959 309d3e185f Move task loading out of config 2022-07-24 17:49:14 -07:00
pop4959 20c749ed61 Update Gradle to 7.5 2022-07-19 19:30:30 -07:00
pop4959 d85e81c936 Update WorldBorder dependency 2022-07-18 20:17:51 -07:00
pop4959 064a0bbb25 Use player sender on Forge 2022-07-18 20:01:21 -07:00
pop4959 8b643a9f2d Fix no-arg execution 2022-07-18 19:37:03 -07:00
pop4959 a18d68b919 Select player's world by default 2022-07-17 20:14:43 -07:00
pop4959 d77595ce07 Rename suggestion method 2022-07-17 19:51:43 -07:00
pop4959 95825514b8 Wrap command arguments 2022-07-17 19:38:50 -07:00
pop4959 2811dd732b Misc refactor 2022-07-09 19:07:50 -07:00
pop4959 9a61676c4f Use Java 17 everywhere 2022-07-09 16:54:09 -07:00
pop4959 91427b80a7 New Crowdin updates (#200) 2022-07-08 19:28:14 -07:00
pop4959 dda4c845d4 Add test for Hindi 2022-07-07 02:18:49 -07:00
pop4959 ff39433836 Add Hindi translation (#199) 2022-07-07 02:16:01 -07:00
pop4959 ffb35e9059 Add Hindi to crowdin.yml 2022-07-06 22:46:41 -07:00
pop4959 2a4db272ea Minor style fixes 2022-07-03 00:47:34 -07:00
pop4959 a3ad11188c Add test for Finnish 2022-07-02 19:19:55 -07:00
pop4959 a780ed5c8a New Crowdin updates (#196) 2022-07-02 19:14:25 -07:00
pop4959 1feaea45e4 Add Finnish to crowdin.yml 2022-06-26 00:59:32 -07:00
pop4959 0e6a974173 Remove boss bars when paused or cancelled 2022-06-25 17:42:16 -07:00
pop4959 6b0d09bf46 Add tests for Bosnian and Croatian 2022-06-25 16:57:10 -07:00
pop4959 41a7749afc Add Bosnian and Croatian translation (#194) 2022-06-25 16:45:07 -07:00
pop4959 e5e377176c Add Bosnian and Croatian to crowdin.yml 2022-06-22 16:48:58 -07:00
pop4959 1544a30715 New Crowdin updates (#193) 2022-06-22 16:35:40 -07:00
pop4959 79888db1f6 Fix failing translation in Serbian (Latin) (#192) 2022-06-21 22:58:38 -07:00
pop4959 e34a49aee1 Add test for Serbian (Latin) 2022-06-21 22:48:49 -07:00
pop4959 4708dbd47b Add Serbian (Latin) translation (#191) 2022-06-21 22:44:23 -07:00
pop4959 a2951bed7b Add Serbian (Latin) to crowdin.yml 2022-06-21 22:00:06 -07:00
dfsek c84435efc1 Fix Fabric invoker mixin names (#190) 2022-06-18 02:57:13 -07:00
pop4959 ba7d2cb57f Add test for Brazilian Portuguese 2022-06-18 02:53:49 -07:00
pop4959 75e227b7dc Add Brazilian Portuguese translation (#188) 2022-06-18 02:45:25 -07:00
pop4959 b4c361f4f5 Add Brazilian Portuguese to crowdin.yml 2022-06-11 00:32:06 -07:00
pop4959 71e8c11fca New Crowdin updates (#187) 2022-06-11 00:24:52 -07:00
pop4959 b09a9d6e53 Update Forge to 1.19 2022-06-09 02:46:18 -07:00
pop4959 0db4a98c6d Update Fabric to 1.19 2022-06-07 19:26:28 -07:00
pop4959 73c8628d39 Update README.md 2022-06-07 19:26:28 -07:00
pop4959 fe1dac6977 Migrate options to config 2022-06-07 19:26:28 -07:00
pop4959 a986ef17b0 Forgot to remove old Forge boss bar 2022-06-07 19:26:28 -07:00
pop4959 91c9d9d335 Reimplement boss bar progress 2022-06-07 19:26:28 -07:00
pop4959 bcdfe9918e Replace deprecated sound registry method 2022-06-07 19:26:27 -07:00
pop4959 807f24afbc Fix issue with broken header copy 2022-06-07 19:26:27 -07:00
pop4959 41c4df3ca5 Replace deprecated defaultWorld method 2022-06-07 19:26:27 -07:00
pop4959 5af88bb328 Bump sponge API / gradle plugin versions 2022-06-07 19:26:27 -07:00
pop4959 67d4667057 Update Fabric to 1.19-pre 2022-06-07 19:26:27 -07:00
pop4959 7ccb15c834 Ensure both pattern args are terminal 2022-06-07 19:26:27 -07:00
pop4959 9c1ee4601c Add brig args for pattern 2022-06-07 19:26:27 -07:00
pop4959 39b04da630 consolidate directory methods 2022-05-29 00:29:46 -07:00
pop4959 1508169353 Improve csv iterator 2022-05-28 19:00:37 -07:00
pop4959 f73aba5d0d New Crowdin updates (#180) 2022-05-28 03:41:14 -07:00
pop4959 69655a4530 Add CSV pattern 2022-05-28 03:34:37 -07:00
pop4959 9a63393ec5 Update shadow to 7.1.2 2022-05-25 11:58:34 -07:00
pop4959 1fc2e036f5 Bump loom version 2022-05-24 22:31:44 -07:00
pop4959 867e3c2b2c Set mixin compatibility to Java 17 2022-05-24 22:09:06 -07:00
pop4959 7b92f22201 Bump fabric loader version 2022-05-24 21:59:39 -07:00
pop4959 517be4692e No tasks to continue when all cancelled 2022-05-21 00:10:34 -07:00
pop4959 8b38275da5 Load cancelled tasks 2022-05-21 00:02:47 -07:00
pop4959 5abcdc3bbf Refactor for ChunkyBorder Fabric support (#179) 2022-05-19 21:30:45 -07:00
pop4959 150c7f2ee5 Update loom version 2022-04-16 15:33:19 -07:00
pop4959 199d4faa2b New Crowdin updates (#167) 2022-04-10 18:22:22 -07:00
pop4959 4c71c18b12 Update Gradle to 7.4.2 2022-04-10 17:53:48 -07:00
pop4959 c696d61fbe Remove unnecessary dependencies in common 2022-04-10 17:48:38 -07:00
pop4959 7b1254c6b6 Dependency cleanup 2022-04-10 17:17:45 -07:00
pop4959 3c680f4bbe Update SpongeAPI and SpongeGradle 2022-04-10 16:00:15 -07:00
pop4959 b758dc0d16 Add test for Italian 2022-01-03 13:33:18 -08:00
pop4959 e1a0c5c4f3 Add Italian translation (#163) 2022-01-03 13:29:43 -08:00
pop4959 27d8d445ad Fix error for server stopping before initialization 2022-01-03 13:08:12 -08:00
pop4959 93b2ffe082 Improve Brigadier command registration 2021-12-31 21:30:17 -08:00
pop4959 ab592e7dc3 Add Italian to crowdin.yml 2021-12-31 17:48:24 -08:00
pop4959 4b722138dd Add test for Ukrainian 2021-12-31 13:23:43 -08:00
pop4959 59a209f20e Add Ukrainian translation (#162) 2021-12-31 11:06:02 -08:00
pop4959 31b9889e67 Add Ukrainian to crowdin.yml 2021-12-28 09:59:34 -08:00
pop4959 99141b6cfd New Crowdin updates (#160) 2021-12-26 23:55:04 -08:00
pop4959 8a81e62c85 Handle CompletionException from isChunkGenerated 2021-12-24 21:19:09 -08:00
pop4959 5613f94e01 Clear region cache on world init 2021-12-24 16:52:59 -08:00
pop4959 3ee15e5cad Fix regression with squaremap polygon markers 2021-12-20 22:19:19 -08:00
pop4959 6c5f8b59d8 New Crowdin updates (#155) 2021-12-20 22:06:04 -08:00
Dylan Xaldin 10a508495b Add Squaremap Integration (#156) 2021-12-20 21:03:17 -08:00
pop4959 fdc69bab13 Test Czech 2021-12-12 12:43:28 -08:00
pop4959 4391b21153 New Crowdin updates (#146) 2021-12-12 12:38:31 -08:00
pop4959 e73bb16451 Fix RGB support check 2021-12-05 22:11:12 -08:00
pop4959 5b17f2d979 Release permits with exceptional completion 2021-12-05 18:47:03 -08:00
pop4959 650127c068 Add Czech to crowdin.yml 2021-12-02 00:23:21 -08:00
Dylan Xaldin 32072f3161 [Forge] Update to 1.18 (#145) 2021-12-01 23:09:55 -08:00
pop4959 f534c46fb4 Update Fabric to 1.18 2021-11-30 12:10:36 -08:00
pop4959 8e00001d21 New Crowdin updates (#144) 2021-11-30 12:05:29 -08:00
pop4959 a225c1251b Translate pattern in command 2021-11-30 02:42:24 -08:00
pop4959 53181d90e7 Add system property for max working count 2021-11-28 04:16:04 -08:00
pop4959 4b4f6249bf Use Java 17 in Github Actions 2021-11-28 03:46:49 -08:00
pop4959 cb4f905ed9 Update Fabric to 1.18-rc3 2021-11-28 03:41:40 -08:00
pop4959 0191428555 Update Gradle to 7.3 2021-11-28 03:12:04 -08:00
pop4959 83731b0c33 New Crowdin updates (#143) 2021-11-28 02:43:57 -08:00
pop4959 c7f0e12418 Update PaperLib version for release candidate 2021-11-27 11:01:03 -08:00
pop4959 eb9d3413f4 New Crowdin updates (#140) 2021-11-26 13:32:51 -08:00
pop4959 61c3383246 Add missing commands to chunky border help 2021-11-21 21:58:42 -08:00
pop4959 7cdf34bcab Add missing commands to help menu 2021-11-21 21:53:12 -08:00
pop4959 4cd8ac5f12 Add translation strings for patterns 2021-11-21 21:40:41 -08:00
pop4959 3574cdd9bc Update translation tests 2021-10-29 01:46:19 -07:00
pop4959 0ed5de2fb1 New Crowdin updates (#137) 2021-10-29 01:32:26 -07:00
pop4959 e19b46f282 Refactor for ChunkyBorder 2021-10-29 00:25:52 -07:00
pop4959 d8aa40af23 Make Player distinct from Sender 2021-10-29 00:25:36 -07:00
pop4959 0ed37bebea Update crowdin.yml 2021-10-29 00:25:20 -07:00
pop4959 9fbe8ef271 Add method to get server players 2021-10-14 01:36:21 -07:00
pop4959 66b38acce0 Add setter methods to border 2021-10-14 01:15:45 -07:00
pop4959 eb83576310 Move version util to common 2021-10-13 00:18:38 -07:00
pop4959 71da25cc68 Add method to server for player 2021-10-13 00:12:53 -07:00
pop4959 8b60a8e13f Add method to sender for UUID 2021-10-12 23:38:28 -07:00
pop4959 2615e89817 Change border help message 2021-10-12 23:21:42 -07:00
pop4959 76f378de2a New Crowdin updates (#136) 2021-10-02 17:59:29 -07:00
pop4959 bc5bc30b54 Add border addon translations 2021-10-02 17:23:42 -07:00
pop4959 1fcd160666 Update to gradle 7.2 2021-10-02 16:52:07 -07:00
pop4959 e1b41d7005 Code cleanup 2021-10-02 16:44:26 -07:00
pop4959 a9cf9b5fa7 Update to new Sponge metadata format 2021-10-02 16:35:59 -07:00
Dylan Xaldin c55e573e17 [Forge] Update to 1.17.1 (#127) 2021-08-15 20:57:17 -07:00
219 changed files with 8207 additions and 4439 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="output" path="bin/default"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17/"/>
</classpath>
+1
View File
@@ -0,0 +1 @@
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
+17 -17
View File
@@ -6,42 +6,42 @@ jobs:
if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gradle/wrapper-validation-action@v1
- name: Gradle wrapper validation
uses: gradle/wrapper-validation-action@v2
- name: Set up JDK
uses: actions/setup-java@v2
uses: actions/setup-java@v4
with:
distribution: 'adopt'
java-version: '16'
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- name: Build
run: ./gradlew build
- name: Upload Bukkit
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: Bukkit
path: "bukkit/build/libs/Chunky-*.jar"
- name: Upload Fabric
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: Fabric
path: "fabric/build/libs/Chunky-*.jar"
- name: Upload Forge
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: Forge
path: "forge/build/libs/Chunky-*.jar"
- name: Upload NeoForge
uses: actions/upload-artifact@v4
with:
name: NeoForge
path: "neoforge/build/libs/Chunky-*.jar"
- name: Upload Sponge
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: Sponge
path: "sponge/build/libs/Chunky-*.jar"
+39
View File
@@ -0,0 +1,39 @@
name: Deploy Javadoc to Pages
on:
push:
branches: ["javadoc"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
# Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Pages
uses: actions/configure-pages@v2
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
path: './docs/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Chunky</name>
<comment>Pre-generates chunks, quickly, efficiently, and safely</comment>
<projects/>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments/>
</buildCommand>
</buildSpec>
<linkedResources/>
<filteredResources/>
</projectDescription>
+23 -4
View File
@@ -1,7 +1,26 @@
## Chunky
# Chunky
Pre-generates chunks, quickly, efficiently, and safely
![chunkylogo](https://user-images.githubusercontent.com/17698576/171119146-122497e4-7527-438b-8f8e-c1c04c2841b9.jpg)
[Spigot Resource Page](https://www.spigotmc.org/resources/chunky.81534/)
Minecraft chunk pre-generator, supporting a wide variety of server implementations including Spigot, Paper, Fabric, Forge, NeoForge, and Sponge.
[Wiki](https://github.com/pop4959/Chunky/wiki/Commands)
Pre-generate chunks, quickly, efficiently, and safely!
## Getting Started
- [Installing](https://github.com/pop4959/Chunky/wiki/Installing)
- [Pre-generating Chunks](https://github.com/pop4959/Chunky/wiki/Pregeneration)
- [Command Reference](https://github.com/pop4959/Chunky/wiki/Commands)
## Support
For questions about Chunky, first try the [Wiki](https://github.com/pop4959/Chunky/wiki) to see if your question is already answered there.
If you can't find what you're looking for, visit us in the #chunky channel on our [Discord server](https://discord.gg/ZwVJukcNQG).
## Special Thanks
[![yklogo](https://www.yourkit.com/images/yklogo.png)](https://www.yourkit.com/)
YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET applications. YourKit is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/), [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/),
and [YourKit YouMonitor](https://www.yourkit.com/youmonitor/). We thank them for granting us open source project licenses so that we can continue to deliver the best performance possible.
+54 -25
View File
@@ -1,73 +1,102 @@
import java.io.ByteArrayOutputStream
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.api.tasks.Sync
import org.gradle.api.tasks.bundling.Jar
plugins {
eclipse
id("java-library")
id("maven-publish")
id("com.github.johnrengelman.shadow") version "7.0.0"
id("com.gradleup.shadow") version "8.3.9" apply false
}
subprojects {
plugins.apply("java-library")
plugins.apply("maven-publish")
plugins.apply("com.github.johnrengelman.shadow")
plugins.apply("com.gradleup.shadow")
group = "${project.property("group")}"
group = project.property("group") as String
version = "${project.property("version")}.${commitsSinceLastTag()}"
repositories {
mavenCentral()
maven("https://oss.sonatype.org/content/repositories/snapshots/")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(8))
}
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
withSourcesJar()
}
tasks {
withType<JavaCompile> {
options.encoding = "UTF-8"
options.release = 17
options.compilerArgs.add("-Xlint:none")
}
jar {
withType<AbstractArchiveTask> {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}
named<Jar>("jar") {
archiveClassifier.set("noshade")
}
shadowJar {
named<ShadowJar>("shadowJar") {
archiveClassifier.set("")
archiveFileName.set("${rootProject.name.capitalize()}-${project.version}.jar")
archiveFileName.set("${project.property("artifactName")}-${project.version}.jar")
isEnableRelocation = true
relocationPrefix = "${project.group}.${rootProject.name.lowercase()}.shadow"
}
build {
dependsOn(shadowJar)
named("build") {
dependsOn("shadowJar")
}
}
publishing {
repositories {
if (project.hasProperty("mavenUsername") && project.hasProperty("mavenPassword")) {
if (hasProperty("mavenUsername") && hasProperty("mavenPassword")) {
maven {
credentials {
username = "${project.property("mavenUsername")}"
password = "${project.property("mavenPassword")}"
username = project.property("mavenUsername") as String
password = project.property("mavenPassword") as String
}
url = uri("https://repo.codemc.io/repository/maven-releases/")
}
}
}
publications {
create<MavenPublication>("maven") {
groupId = "${project.group}"
artifactId = project.name
version = "${project.version}"
from(components["java"])
}
publications.create<MavenPublication>("maven") {
groupId = project.group.toString()
artifactId = project.name
version = project.version.toString()
from(components["java"])
}
}
}
tasks.named<Jar>("jar") {
enabled = false
}
val pluginArtifact by tasks.registering(Sync::class) {
dependsOn(":Chunky-bukkit:shadowJar")
from(project(":Chunky-bukkit").tasks.named("shadowJar"))
into(layout.buildDirectory.dir("libs"))
}
tasks.named("build") {
dependsOn(pluginArtifact)
}
fun commitsSinceLastTag(): String {
val tagDescription = ByteArrayOutputStream()
exec {
commandLine("git", "describe", "--tags")
standardOutput = tagDescription
}
if (tagDescription.toString().indexOf('-') < 0) {
return "0"
}
return tagDescription.toString().split('-')[1]
val desc = tagDescription.toString().trim()
return if ('-' !in desc) "0" else desc.split('-')[1]
}
+23 -9
View File
@@ -1,19 +1,30 @@
plugins {
id("com.gradleup.shadow")
}
repositories {
maven("https://papermc.io/repo/repository/maven-public")
maven("https://repo.purpurmc.org/snapshots")
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://jitpack.io")
}
dependencies {
compileOnly(group = "org.spigotmc", name = "spigot-api", version = "1.17.1-R0.1-SNAPSHOT")
implementation(group = "io.papermc", name = "paperlib", version = "1.0.6")
implementation(group = "org.bstats", name = "bstats-bukkit", version = "2.2.1")
implementation(project(":chunky-common"))
compileOnly(group = "org.purpurmc.purpur", name = "purpur-api", version = "1.19.4-R0.1-SNAPSHOT")
compileOnly(group = "com.github.Puremin0rez", name = "WorldBorder", version = "1.19") {
isTransitive = false
}
implementation(group = "org.bstats", name = "bstats-bukkit", version = "3.0.2")
implementation(project(":Chunky-common"))
implementation(project(":Chunky-paper"))
implementation(project(":Chunky-folia"))
}
tasks {
processResources {
filesMatching("plugin.yml") {
expand(
"name" to rootProject.name.capitalize(),
"name" to project.property("artifactName"),
"version" to project.version,
"group" to project.group,
"author" to project.property("author"),
@@ -22,10 +33,13 @@ tasks {
}
}
shadowJar {
archiveClassifier.set("")
isEnableRelocation = false
minimize {
exclude(project(":chunky-common"))
exclude(project(":Chunky-common"))
exclude(project(":Chunky-paper"))
exclude(project(":Chunky-folia"))
}
relocate("io.papermc.lib", "${project.group}.${rootProject.name}.lib.paperlib")
relocate("org.bstats", "${project.group}.${rootProject.name}.lib.bstats")
relocate("org.bstats", "${project.group}.${rootProject.name.lowercase()}.lib.bstats")
}
}
@@ -1,95 +1,123 @@
package org.popcraft.chunky;
import io.papermc.lib.PaperLib;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldInitEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
import org.popcraft.chunky.api.ChunkyAPI;
import org.popcraft.chunky.command.ChunkyCommand;
import org.popcraft.chunky.command.CommandArguments;
import org.popcraft.chunky.command.CommandLiteral;
import org.popcraft.chunky.integration.WorldBorderIntegration;
import org.popcraft.chunky.platform.BukkitConfig;
import org.popcraft.chunky.platform.BukkitPlayer;
import org.popcraft.chunky.platform.BukkitSender;
import org.popcraft.chunky.platform.BukkitServer;
import org.popcraft.chunky.platform.Folia;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.util.TranslationKey;
import org.popcraft.chunky.util.Version;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.popcraft.chunky.util.Translator.translate;
public final class ChunkyBukkit extends JavaPlugin {
public final class ChunkyBukkit extends JavaPlugin implements Listener {
private static final String COMMAND_PERMISSION_KEY = "chunky.command.";
private Chunky chunky;
@Override
public void onEnable() {
this.chunky = new Chunky(new BukkitServer(this), new BukkitConfig(this));
final Version currentVersion = Version.getCurrentMinecraftVersion();
if (Version.v1_13_2.isEqualTo(currentVersion) && !PaperLib.isPaper()) {
getLogger().severe(() -> translate("error_version_spigot"));
getServer().getPluginManager().disablePlugin(this);
} else if (currentVersion.isValid() && Version.v1_13_2.isHigherThan(currentVersion)) {
getLogger().severe(() -> translate("error_version"));
final Version currentVersion = new Version(Bukkit.getBukkitVersion(), true);
if (currentVersion.isValid() && Version.MINECRAFT_1_13_2.isHigherThan(currentVersion)) {
getLogger().severe(() -> translate(TranslationKey.ERROR_VERSION));
getServer().getPluginManager().disablePlugin(this);
}
if (!isEnabled()) {
return;
}
getServer().getServicesManager().register(Chunky.class, chunky, this, ServicePriority.Normal);
getServer().getServicesManager().register(ChunkyAPI.class, chunky.getApi(), this, ServicePriority.Normal);
if (chunky.getConfig().getContinueOnRestart()) {
getServer().getScheduler().scheduleSyncDelayedTask(this, () -> chunky.getCommands().get("continue").execute(chunky.getServer().getConsoleSender(), new String[]{}));
final Runnable continueTask = () -> chunky.getCommands().get(CommandLiteral.CONTINUE).execute(chunky.getServer().getConsole(), CommandArguments.empty());
if (Folia.isFolia()) {
Folia.onServerInit(this, continueTask);
} else {
getServer().getScheduler().scheduleSyncDelayedTask(this, continueTask);
}
}
if (getServer().getPluginManager().getPlugin("WorldBorder") != null) {
chunky.getServer().getIntegrations().put("border", new WorldBorderIntegration());
}
final Metrics metrics = new Metrics(this, 8211);
metrics.addCustomChart(new SimplePie("language", () -> chunky.getConfig().getLanguage()));
getServer().getPluginManager().registerEvents(this, this);
}
@Override
public void onDisable() {
chunky.disable();
HandlerList.unregisterAll((Plugin) this);
if (chunky != null) {
chunky.disable();
}
}
@SuppressWarnings("NullableProblems")
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
Sender bukkitSender = new BukkitSender(sender);
Map<String, ChunkyCommand> commands = chunky.getCommands();
public boolean onCommand(final CommandSender sender, final Command command, final String label, final String[] args) {
final Sender bukkitSender = sender instanceof final Player player ? new BukkitPlayer(player) : new BukkitSender(sender);
final Map<String, ChunkyCommand> commands = chunky.getCommands();
final CommandArguments arguments = CommandArguments.of(Arrays.copyOfRange(args, Math.min(1, args.length), args.length));
if (args.length > 0 && commands.containsKey(args[0].toLowerCase())) {
if (sender.hasPermission("chunky.command." + args[0].toLowerCase())) {
commands.get(args[0].toLowerCase()).execute(bukkitSender, args);
if (sender.hasPermission(COMMAND_PERMISSION_KEY + args[0].toLowerCase())) {
commands.get(args[0].toLowerCase()).execute(bukkitSender, arguments);
} else {
bukkitSender.sendMessage("command_no_permission");
bukkitSender.sendMessage(TranslationKey.COMMAND_NO_PERMISSION);
}
} else {
commands.get("help").execute(bukkitSender, new String[]{});
commands.get(CommandLiteral.HELP).execute(bukkitSender, arguments);
}
return true;
}
@SuppressWarnings("NullableProblems")
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
public List<String> onTabComplete(final CommandSender sender, final Command command, final String alias, final String[] args) {
if (args.length < 1) {
return Collections.emptyList();
return List.of();
}
final List<String> suggestions = new ArrayList<>();
Map<String, ChunkyCommand> commands = chunky.getCommands();
final Map<String, ChunkyCommand> commands = chunky.getCommands();
if (args.length == 1) {
commands.keySet().stream().filter(name -> sender.hasPermission("chunky.command." + name)).forEach(suggestions::add);
} else if (commands.containsKey(args[0].toLowerCase()) && sender.hasPermission("chunky.command." + args[0].toLowerCase())) {
suggestions.addAll(commands.get(args[0].toLowerCase()).tabSuggestions(new BukkitSender(sender), args));
commands.keySet().stream().filter(name -> sender.hasPermission(COMMAND_PERMISSION_KEY + name)).forEach(suggestions::add);
} else if (commands.containsKey(args[0].toLowerCase()) && sender.hasPermission(COMMAND_PERMISSION_KEY + args[0].toLowerCase())) {
final CommandArguments arguments = CommandArguments.of(Arrays.copyOfRange(args, 1, args.length));
suggestions.addAll(commands.get(args[0].toLowerCase()).suggestions(arguments));
}
return suggestions.stream()
.filter(s -> s.toLowerCase().contains(args[args.length - 1].toLowerCase()))
.collect(Collectors.toList());
.toList();
}
public Chunky getChunky() {
return chunky;
}
@EventHandler
public void onWorldInit(final WorldInitEvent event) {
chunky.getRegionCache().clear(event.getWorld().getName());
}
}
@@ -3,21 +3,22 @@ package org.popcraft.chunky.integration;
import com.wimbli.WorldBorder.BorderData;
import com.wimbli.WorldBorder.Config;
import org.popcraft.chunky.platform.Border;
import org.popcraft.chunky.util.Coordinate;
import org.popcraft.chunky.platform.util.Vector2;
import org.popcraft.chunky.shape.ShapeType;
public class WorldBorderIntegration implements BorderIntegration {
@Override
public boolean hasBorder(String world) {
public boolean hasBorder(final String world) {
return Config.Border(world) != null;
}
@Override
public Border getBorder(String world) {
public Border getBorder(final String world) {
return new Border() {
@Override
public Coordinate getCenter() {
BorderData borderData = Config.Border(world);
return new Coordinate(borderData.getX(), borderData.getZ());
public Vector2 getCenter() {
final BorderData borderData = Config.Border(world);
return Vector2.of(borderData.getX(), borderData.getZ());
}
@Override
@@ -32,15 +33,15 @@ public class WorldBorderIntegration implements BorderIntegration {
@Override
public String getShape() {
BorderData borderData = Config.Border(world);
double radiusX = getRadiusX();
double radiusZ = getRadiusZ();
boolean round = borderData.getShape() == null ? Config.ShapeRound() : borderData.getShape();
final BorderData borderData = Config.Border(world);
final double radiusX = getRadiusX();
final double radiusZ = getRadiusZ();
final boolean round = borderData.getShape() == null ? Config.ShapeRound() : borderData.getShape();
final String shape;
if (radiusX == radiusZ) {
shape = round ? "circle" : "square";
shape = round ? ShapeType.CIRCLE : ShapeType.SQUARE;
} else {
shape = round ? "ellipse" : "rectangle";
shape = round ? ShapeType.ELLIPSE : ShapeType.RECTANGLE;
}
return shape;
}
@@ -1,18 +1,19 @@
package org.popcraft.chunky.platform;
import org.bukkit.WorldBorder;
import org.popcraft.chunky.util.Coordinate;
import org.popcraft.chunky.platform.util.Vector2;
import org.popcraft.chunky.shape.ShapeType;
public class BukkitBorder implements Border {
final WorldBorder worldBorder;
public BukkitBorder(WorldBorder worldBorder) {
public BukkitBorder(final WorldBorder worldBorder) {
this.worldBorder = worldBorder;
}
@Override
public Coordinate getCenter() {
return new Coordinate(worldBorder.getCenter().getX(), worldBorder.getCenter().getZ());
public Vector2 getCenter() {
return Vector2.of(worldBorder.getCenter().getX(), worldBorder.getCenter().getZ());
}
@Override
@@ -27,6 +28,6 @@ public class BukkitBorder implements Border {
@Override
public String getShape() {
return "square";
return ShapeType.SQUARE;
}
}
@@ -1,25 +1,28 @@
package org.popcraft.chunky.platform;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.FileConfigurationOptions;
import org.popcraft.chunky.ChunkyBukkit;
import org.popcraft.chunky.GenerationTask;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.Translator;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class BukkitConfig implements Config {
private static final String TASKS_KEY = "tasks.";
private static final List<String> HEADER = Arrays.asList("Chunky Configuration", "https://github.com/pop4959/Chunky/wiki/Configuration");
private final ChunkyBukkit plugin;
public BukkitConfig(ChunkyBukkit plugin) {
public BukkitConfig(final ChunkyBukkit plugin) {
this.plugin = plugin;
plugin.getConfig().options().copyDefaults(true);
plugin.getConfig().options().copyHeader(true);
final FileConfigurationOptions options = plugin.getConfig().options();
options.copyDefaults(true);
try {
FileConfigurationOptions.class.getMethod("header", String.class).invoke(options, String.join("\n", HEADER));
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
options.setHeader(HEADER);
}
plugin.saveConfig();
Translator.setLanguage(getLanguage());
}
@@ -29,78 +32,6 @@ public class BukkitConfig implements Config {
return plugin.getDataFolder().toPath();
}
@Override
public synchronized Optional<GenerationTask> loadTask(World world) {
FileConfiguration config = plugin.getConfig();
if (config.getConfigurationSection(TASKS_KEY + world.getName()) == null) {
return Optional.empty();
}
String worldKey = TASKS_KEY + world.getName() + ".";
if (config.getBoolean(worldKey + "cancelled", false)) {
return Optional.empty();
}
double radiusX = config.getDouble(worldKey + "radius", 500);
double radiusZ = config.getDouble(worldKey + "z-radius", radiusX);
Selection.Builder selection = Selection.builder(world)
.centerX(config.getDouble(worldKey + "x-center", 0))
.centerZ(config.getDouble(worldKey + "z-center", 0))
.radiusX(radiusX)
.radiusZ(radiusZ)
.pattern(config.getString(worldKey + "iterator", "loop"))
.shape(config.getString(worldKey + "shape", "square"));
long count = config.getLong(worldKey + "count", 0);
long time = config.getLong(worldKey + "time", 0);
return Optional.of(new GenerationTask(plugin.getChunky(), selection.build(), count, time));
}
@Override
public synchronized List<GenerationTask> loadTasks() {
List<GenerationTask> generationTasks = new ArrayList<>();
plugin.getChunky().getServer().getWorlds().forEach(world -> loadTask(world).ifPresent(generationTasks::add));
return generationTasks;
}
@Override
public synchronized void saveTask(GenerationTask generationTask) {
FileConfiguration config = plugin.getConfig();
Selection selection = generationTask.getSelection();
String worldKey = TASKS_KEY + selection.world().getName() + ".";
String shape = generationTask.getShape().name();
config.set(worldKey + "cancelled", generationTask.isCancelled());
config.set(worldKey + "radius", selection.radiusX());
if ("rectangle".equals(shape) || "ellipse".equals(shape)) {
config.set(worldKey + "z-radius", selection.radiusZ());
}
config.set(worldKey + "x-center", selection.centerX());
config.set(worldKey + "z-center", selection.centerZ());
config.set(worldKey + "iterator", generationTask.getChunkIterator().name());
config.set(worldKey + "shape", shape);
config.set(worldKey + "count", generationTask.getCount());
config.set(worldKey + "time", generationTask.getTotalTime());
plugin.saveConfig();
}
@Override
public synchronized void saveTasks() {
plugin.getChunky().getGenerationTasks().values().forEach(this::saveTask);
}
@Override
public void cancelTask(World world) {
loadTask(world).ifPresent(generationTask -> {
generationTask.stop(true);
saveTask(generationTask);
});
}
@Override
public synchronized void cancelTasks() {
loadTasks().forEach(generationTask -> {
generationTask.stop(true);
saveTask(generationTask);
});
}
@Override
public int getVersion() {
return plugin.getConfig().getInt("version", 0);
@@ -116,6 +47,31 @@ public class BukkitConfig implements Config {
return plugin.getConfig().getBoolean("continue-on-restart", false);
}
@Override
public boolean isForceLoadExistingChunks() {
return plugin.getConfig().getBoolean("force-load-existing-chunks", false);
}
@Override
public boolean isSilent() {
return plugin.getConfig().getBoolean("silent", false);
}
@Override
public void setSilent(final boolean silent) {
plugin.getConfig().set("silent", silent);
}
@Override
public int getUpdateInterval() {
return plugin.getConfig().getInt("update-interval", 1);
}
@Override
public void setUpdateInterval(final int updateInterval) {
plugin.getConfig().set("update-interval", updateInterval);
}
@Override
public void reload() {
plugin.reloadConfig();
@@ -0,0 +1,108 @@
package org.popcraft.chunky.platform;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.java.JavaPlugin;
import org.popcraft.chunky.ChunkyBukkit;
import org.popcraft.chunky.platform.util.Location;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import static org.popcraft.chunky.util.Translator.translateKey;
public class BukkitPlayer extends BukkitSender implements Player {
private static final boolean ACTION_BAR_SUPPORTED;
private final JavaPlugin plugin = JavaPlugin.getPlugin(ChunkyBukkit.class);
static {
boolean barSupported;
try {
org.bukkit.entity.Player.class.getMethod("spigot");
barSupported = true;
} catch (NoSuchMethodException e) {
barSupported = false;
}
ACTION_BAR_SUPPORTED = barSupported;
}
final org.bukkit.entity.Player player;
public BukkitPlayer(final org.bukkit.entity.Player player) {
super(player);
this.player = player;
}
@Override
public boolean isPlayer() {
return true;
}
@Override
public String getName() {
return player.getName();
}
@Override
public World getWorld() {
return new BukkitWorld(player.getWorld());
}
@Override
public Location getLocation() {
final org.bukkit.Location location = player.getLocation();
return new Location(getWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
}
@Override
public UUID getUUID() {
return player.getUniqueId();
}
@SuppressWarnings("UnstableApiUsage")
@Override
public void teleport(final Location location) {
final org.bukkit.World world = Bukkit.getWorld(location.getWorld().getName());
final org.bukkit.Location loc = new org.bukkit.Location(world, location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
final Entity vehicle = player.getVehicle();
if (vehicle == null) {
teleportAsync(player, loc);
} else {
final List<Entity> passengers = new ArrayList<>(vehicle.getPassengers());
vehicle.eject();
teleportAsync(player, loc).thenAcceptAsync(ignored -> {
vehicle.teleport(player);
for (final Entity passenger : passengers) {
passenger.teleport(player);
if (passenger instanceof final org.bukkit.entity.Player playerPassenger) {
playerPassenger.hideEntity(plugin, vehicle);
playerPassenger.showEntity(plugin, vehicle);
}
vehicle.addPassenger(passenger);
}
}, command -> plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, command, 1));
}
}
private CompletableFuture<Boolean> teleportAsync(final Entity entity, final org.bukkit.Location location) {
if (Paper.isPaper()) {
return Paper.teleportAsync(entity, location);
} else {
return CompletableFuture.completedFuture(entity.teleport(location));
}
}
@Override
public void sendActionBar(final String key) {
if (ACTION_BAR_SUPPORTED) {
final String message = formatColored(translateKey(key, false));
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(message));
} else {
this.sendMessage(key);
}
}
}
@@ -1,17 +1,35 @@
package org.popcraft.chunky.platform;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.popcraft.chunky.util.Coordinate;
import org.popcraft.chunky.platform.util.Location;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.popcraft.chunky.util.Translator.translateKey;
public class BukkitSender implements Sender {
final CommandSender sender;
private static final Pattern RGB_PATTERN = Pattern.compile("&#[0-9a-fA-F]{6}");
private static final boolean RGB_COLORS_SUPPORTED;
public BukkitSender(CommandSender sender) {
static {
boolean rgbSupported;
try {
Class.forName("net.md_5.bungee.api.ChatColor");
ChatColor.class.getMethod("of", String.class);
rgbSupported = true;
} catch (ClassNotFoundException | NoSuchMethodException e) {
rgbSupported = false;
}
RGB_COLORS_SUPPORTED = rgbSupported;
}
private final CommandSender sender;
public BukkitSender(final CommandSender sender) {
this.sender = sender;
}
@@ -26,17 +44,37 @@ public class BukkitSender implements Sender {
}
@Override
public Coordinate getCoordinate() {
if (sender instanceof Player) {
Location location = ((Player) sender).getLocation();
return new Coordinate(location.getBlockX(), location.getBlockZ());
} else {
return new Coordinate(0, 0);
}
public World getWorld() {
return new BukkitWorld(Bukkit.getWorlds().get(0));
}
@Override
public void sendMessage(String key, boolean prefixed, Object... args) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', translateKey(key, prefixed, args)));
public Location getLocation() {
return new Location(getWorld(), 0, 0, 0, 0, 0);
}
@Override
public boolean hasPermission(final String permission) {
return sender.hasPermission(permission);
}
@Override
public void sendMessage(final String key, final boolean prefixed, final Object... args) {
sender.sendMessage(formatColored(translateKey(key, prefixed, args)));
}
protected String formatColored(final String message) {
String coloredMessage = message;
if (RGB_COLORS_SUPPORTED) {
Matcher rgbMatcher = RGB_PATTERN.matcher(message);
while (rgbMatcher.find()) {
final ChatColor rgbColor = ChatColor.of(rgbMatcher.group().substring(1));
final String messageStart = coloredMessage.substring(0, rgbMatcher.start());
final String messageEnd = coloredMessage.substring(rgbMatcher.end());
coloredMessage = messageStart + rgbColor + messageEnd;
rgbMatcher = RGB_PATTERN.matcher(coloredMessage);
}
}
return org.bukkit.ChatColor.translateAlternateColorCodes('&', coloredMessage);
}
}
@@ -1,10 +1,10 @@
package org.popcraft.chunky.platform;
import org.bukkit.Bukkit;
import org.popcraft.chunky.ChunkyBukkit;
import org.popcraft.chunky.integration.Integration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -14,7 +14,7 @@ public class BukkitServer implements Server {
private final ChunkyBukkit plugin;
private final Map<String, Integration> integrations;
public BukkitServer(ChunkyBukkit plugin) {
public BukkitServer(final ChunkyBukkit plugin) {
this.plugin = plugin;
this.integrations = new HashMap<>();
}
@@ -25,21 +25,38 @@ public class BukkitServer implements Server {
}
@Override
public Optional<World> getWorld(String name) {
org.bukkit.World world = Bukkit.getWorld(name);
public Optional<World> getWorld(final String name) {
final org.bukkit.World world = plugin.getServer().getWorld(name);
return Optional.ofNullable(world == null ? null : new BukkitWorld(world));
}
@Override
public List<World> getWorlds() {
List<World> worlds = new ArrayList<>();
Bukkit.getWorlds().forEach(world -> worlds.add(new BukkitWorld(world)));
final List<World> worlds = new ArrayList<>();
plugin.getServer().getWorlds().forEach(world -> worlds.add(new BukkitWorld(world)));
return worlds;
}
@Override
public Sender getConsoleSender() {
return new BukkitSender(Bukkit.getConsoleSender());
public int getMaxWorldSize() {
return plugin.getServer().getMaxWorldSize();
}
@Override
public Sender getConsole() {
return new BukkitSender(plugin.getServer().getConsoleSender());
}
@Override
public Collection<Player> getPlayers() {
final Collection<Player> players = new ArrayList<>();
plugin.getServer().getOnlinePlayers().forEach(player -> players.add(new BukkitPlayer(player)));
return players;
}
@Override
public Optional<Player> getPlayer(final String name) {
return Optional.ofNullable(plugin.getServer().getPlayer(name)).map(BukkitPlayer::new);
}
@Override
@@ -1,22 +1,44 @@
package org.popcraft.chunky.platform;
import io.papermc.lib.PaperLib;
import org.bukkit.Location;
import org.popcraft.chunky.util.Coordinate;
import org.bukkit.Chunk;
import org.bukkit.Effect;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.plugin.java.JavaPlugin;
import org.popcraft.chunky.ChunkyBukkit;
import org.popcraft.chunky.platform.util.Location;
import org.popcraft.chunky.util.Input;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;
public class BukkitWorld implements World {
private static final boolean IS_GENERATED_SUPPORTED;
private static final int TICKING_LOAD_DURATION = Input.tryInteger(System.getProperty("chunky.tickingLoadDuration")).orElse(0);
private static final boolean AWAIT_TICKET_REMOVAL = Boolean.getBoolean("chunky.awaitTicketRemoval");
private final JavaPlugin plugin = JavaPlugin.getPlugin(ChunkyBukkit.class);
private final org.bukkit.World world;
private final Border worldBorder;
public BukkitWorld(org.bukkit.World world) {
static {
boolean isGeneratedSupported;
try {
Chunk.class.getMethod("isGenerated");
isGeneratedSupported = true;
} catch (NoSuchMethodException e) {
isGeneratedSupported = false;
}
IS_GENERATED_SUPPORTED = isGeneratedSupported;
}
public BukkitWorld(final org.bukkit.World world) {
this.world = world;
this.worldBorder = new BukkitBorder(world.getWorldBorder());
}
@@ -27,15 +49,59 @@ public class BukkitWorld implements World {
}
@Override
public boolean isChunkGenerated(int x, int z) {
return PaperLib.isPaper() && PaperLib.isChunkGenerated(world, x, z);
public String getKey() {
return world.getKey().toString();
}
@Override
public CompletableFuture<Void> getChunkAtAsync(int x, int z) {
CompletableFuture<Void> future = new CompletableFuture<>();
PaperLib.getChunkAtAsync(world, x, z).thenRun(() -> future.complete(null));
return future;
public CompletableFuture<Boolean> isChunkGenerated(final int x, final int z) {
if (Paper.isPaper()) {
return CompletableFuture.supplyAsync(() -> {
try {
return world.isChunkGenerated(x, z);
} catch (CompletionException e) {
return false;
}
});
} else {
if (IS_GENERATED_SUPPORTED) {
return CompletableFuture.completedFuture(world.getChunkAt(x, z, false).isGenerated());
} else {
return CompletableFuture.completedFuture(false);
}
}
}
@Override
public CompletableFuture<Void> getChunkAtAsync(final int x, final int z) {
final CompletableFuture<Void> chunkFuture;
if (Paper.isPaper()) {
chunkFuture = CompletableFuture.allOf(Paper.getChunkAtAsync(world, x, z));
} else {
chunkFuture = CompletableFuture.allOf(CompletableFuture.completedFuture(world.getChunkAt(x, z)));
}
if (TICKING_LOAD_DURATION > 0) {
final CompletableFuture<Void> removeTicketFuture = new CompletableFuture<>();
chunkFuture.thenAccept(ignored -> {
final Runnable addTicketTask = () -> world.addPluginChunkTicket(x, z, plugin);
final Runnable removeTicketTask = () -> {
world.removePluginChunkTicket(x, z, plugin);
removeTicketFuture.complete(null);
};
if (Folia.isFolia()) {
final org.bukkit.Location location = new org.bukkit.Location(world, x << 4, 0, z << 4);
Folia.schedule(plugin, location, addTicketTask);
CompletableFuture.runAsync(() -> Folia.schedule(plugin, location, removeTicketTask), CompletableFuture.delayedExecutor(TICKING_LOAD_DURATION, TimeUnit.SECONDS));
} else {
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, addTicketTask);
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, removeTicketTask, TICKING_LOAD_DURATION * 20L);
}
});
if (AWAIT_TICKET_REMOVAL) {
return removeTicketFuture;
}
}
return chunkFuture;
}
@Override
@@ -49,9 +115,9 @@ public class BukkitWorld implements World {
}
@Override
public Coordinate getSpawnCoordinate() {
Location spawnLocation = world.getSpawnLocation();
return new Coordinate(spawnLocation.getX(), spawnLocation.getZ());
public Location getSpawn() {
final org.bukkit.Location spawnLocation = world.getSpawnLocation();
return new Location(this, spawnLocation.getX(), spawnLocation.getY(), spawnLocation.getZ(), spawnLocation.getYaw(), spawnLocation.getPitch());
}
@Override
@@ -60,42 +126,81 @@ public class BukkitWorld implements World {
}
@Override
public Optional<Path> getEntitiesDirectory() {
return getDirectory("entities");
public int getElevation(final int x, final int z) {
if (Folia.isFolia()) {
final org.bukkit.Location location = new org.bukkit.Location(world, x, 0, z);
return CompletableFuture
.supplyAsync(() -> getElevationForLocation(x, z), command -> Folia.schedule(plugin, location, command))
.join();
} else {
return getElevationForLocation(x, z);
}
}
@Override
public Optional<Path> getPOIDirectory() {
return getDirectory("poi");
}
@Override
public Optional<Path> getRegionDirectory() {
return getDirectory("region");
}
private Optional<Path> getDirectory(final String name) {
if (name != null) {
try (Stream<Path> paths = Files.walk(world.getWorldFolder().toPath())) {
return paths.filter(Files::isDirectory)
.filter(path -> name.equals(path.getFileName().toString()))
.findFirst();
} catch (IOException e) {
e.printStackTrace();
private int getElevationForLocation(final int x, final int z) {
final int height = world.getHighestBlockYAt(x, z) + 1;
final int logicalHeight = world.getLogicalHeight();
if (height >= logicalHeight) {
Block block = world.getBlockAt(x, logicalHeight, z);
int air = 0;
while (block.getY() > world.getMinHeight()) {
block = block.getRelative(BlockFace.DOWN);
final Material type = block.getType();
if (type.isSolid() && air > 1) {
return block.getY() + 1;
}
air = type.isAir() ? air + 1 : 0;
}
}
return Optional.empty();
return height;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return world.equals(((BukkitWorld) o).world);
public int getMaxElevation() {
return world.getLogicalHeight();
}
@Override
public int hashCode() {
return world.hashCode();
public void playEffect(final Player player, final String effect) {
final Effect effectType;
try {
effectType = Effect.valueOf(effect.toUpperCase());
} catch (IllegalArgumentException e) {
return;
}
final Location location = player.getLocation();
final org.bukkit.Location bukkitLocation = new org.bukkit.Location(world, location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
world.playEffect(bukkitLocation, effectType, 0);
}
@Override
public void playSound(final Player player, final String sound) {
final Sound soundType;
try {
soundType = Sound.valueOf(sound.toUpperCase());
} catch (IllegalArgumentException e) {
return;
}
final Location location = player.getLocation();
final org.bukkit.Location bukkitLocation = new org.bukkit.Location(world, location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
world.playSound(bukkitLocation, soundType, 2f, 1f);
}
@Override
public Optional<Path> getDirectory(final String name) {
if (name == null) {
return Optional.empty();
}
final org.bukkit.World.Environment environment = world.getEnvironment();
final String parent;
if (org.bukkit.World.Environment.NETHER.equals(environment)) {
parent = "DIM-1";
} else if (org.bukkit.World.Environment.THE_END.equals(environment)) {
parent = "DIM1";
} else {
parent = "";
}
final Path directory = world.getWorldFolder().toPath().resolve(parent).normalize().resolve(name);
return Files.isDirectory(directory) ? Optional.of(directory) : Optional.empty();
}
}
+6 -2
View File
@@ -1,5 +1,9 @@
# Chunky Configuration
# https://github.com/pop4959/Chunky/wiki/Configuration
version: 1
version: 2
language: en
continue-on-restart: false
continue-on-restart: true
force-load-existing-chunks: false
silent: false
update-interval: 1
+12 -4
View File
@@ -2,7 +2,8 @@ name: ${name}
version: ${version}
main: ${group}.chunky.ChunkyBukkit
api-version: 1.13
softdepend: [dynmap, BlueMap, Pl3xMap, WorldBorder]
folia-supported: true
softdepend: [dynmap, BlueMap, squaremap, WorldBorder]
load: POSTWORLD
authors: [${author}]
description: ${description}
@@ -16,6 +17,9 @@ commands:
permissions:
chunky.command:
default: op
chunky.command.border:
default: op
children: [chunky.command]
chunky.command.cancel:
default: op
children: [chunky.command]
@@ -31,9 +35,6 @@ permissions:
chunky.command.corners:
default: op
children: [chunky.command]
chunky.command.trim:
default: op
children: [chunky.command]
chunky.command.help:
default: op
children: [chunky.command]
@@ -55,6 +56,9 @@ permissions:
chunky.command.reload:
default: op
children: [chunky.command]
chunky.command.selection:
default: op
children: [chunky.command]
chunky.command.shape:
default: op
children: [chunky.command]
@@ -67,6 +71,9 @@ permissions:
chunky.command.start:
default: op
children: [chunky.command]
chunky.command.trim:
default: op
children: [chunky.command]
chunky.command.worldborder:
default: op
children: [chunky.command]
@@ -89,6 +96,7 @@ permissions:
- chunky.command.quiet
- chunky.command.radius
- chunky.command.reload
- chunky.command.selection
- chunky.command.shape
- chunky.command.silent
- chunky.command.spawn
+23 -13
View File
@@ -1,16 +1,26 @@
repositories {
mavenCentral()
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://repo.mikeprimm.com")
maven("https://repo.pl3x.net")
maven("https://jitpack.io")
dependencies {
compileOnly(group = "com.google.code.gson", name = "gson", version = "2.8.9")
testImplementation(group = "junit", name = "junit", version = "4.13.2")
testImplementation(group = "com.google.code.gson", name = "gson", version = "2.8.9")
implementation(project(":Chunky-nbt"))
}
dependencies {
compileOnly(group = "us.dynmap", name = "dynmap-api", version = "3.1")
compileOnly(group = "com.github.BlueMap-Minecraft", name = "BlueMapAPI", version = "v1.6.0")
compileOnly(group = "net.pl3x.map", name = "pl3xmap-api", version = "1.0.0-SNAPSHOT")
compileOnly(group = "com.github.Brettflan", name = "WorldBorder", version = "c0d1772418")
testImplementation(group = "junit", name = "junit", version = "4.13.2")
testImplementation(group = "com.google.code.gson", name = "gson", version = "2.8.7")
tasks {
processResources {
filesMatching("version.properties") {
expand(
"version" to project.version
)
}
}
javadoc {
sourceSets {
main {
allJava
}
}
setDestinationDir(rootProject.projectDir.resolve("docs/chunky/javadoc"))
include("org/popcraft/chunky/api/**")
exclude("org/popcraft/chunky/api/ChunkyAPIImpl.java")
}
}
@@ -1,15 +1,40 @@
package org.popcraft.chunky;
import org.popcraft.chunky.command.*;
import org.popcraft.chunky.api.ChunkyAPI;
import org.popcraft.chunky.api.ChunkyAPIImpl;
import org.popcraft.chunky.command.CancelCommand;
import org.popcraft.chunky.command.CenterCommand;
import org.popcraft.chunky.command.ChunkyCommand;
import org.popcraft.chunky.command.CommandLiteral;
import org.popcraft.chunky.command.ConfirmCommand;
import org.popcraft.chunky.command.ContinueCommand;
import org.popcraft.chunky.command.CornersCommand;
import org.popcraft.chunky.command.HelpCommand;
import org.popcraft.chunky.command.PatternCommand;
import org.popcraft.chunky.command.PauseCommand;
import org.popcraft.chunky.command.ProgressCommand;
import org.popcraft.chunky.command.QuietCommand;
import org.popcraft.chunky.command.RadiusCommand;
import org.popcraft.chunky.command.ReloadCommand;
import org.popcraft.chunky.command.SelectionCommand;
import org.popcraft.chunky.command.ShapeCommand;
import org.popcraft.chunky.command.SilentCommand;
import org.popcraft.chunky.command.SpawnCommand;
import org.popcraft.chunky.command.StartCommand;
import org.popcraft.chunky.command.TrimCommand;
import org.popcraft.chunky.command.WorldBorderCommand;
import org.popcraft.chunky.command.WorldCommand;
import org.popcraft.chunky.event.EventBus;
import org.popcraft.chunky.platform.Config;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.platform.Server;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.PendingAction;
import org.popcraft.chunky.util.RegionCache;
import org.popcraft.chunky.util.TaskLoader;
import org.popcraft.chunky.util.TaskScheduler;
import org.popcraft.chunky.util.Translator;
import org.popcraft.chunky.util.Version;
import java.io.IOException;
import java.io.InputStream;
@@ -24,45 +49,37 @@ import java.util.concurrent.ConcurrentHashMap;
public class Chunky {
private final Server server;
private final Config config;
private final TaskLoader taskLoader;
private final EventBus eventBus;
private final Selection.Builder selection;
private final TaskScheduler scheduler = new TaskScheduler();
private final Map<World, GenerationTask> generationTasks = new ConcurrentHashMap<>();
private final Options options = new Options();
private final Map<String, GenerationTask> generationTasks = new ConcurrentHashMap<>();
private final Map<String, TrimCommand.Task> trimTasks = new ConcurrentHashMap<>();
private final Map<String, PendingAction> pendingActions = new HashMap<>();
private final RegionCache regionCache = new RegionCache();
private final double limit;
private final Version version;
private final Map<String, ChunkyCommand> commands;
private final ChunkyAPI api;
public Chunky(Server server, Config config) {
public Chunky(final Server server, final Config config) {
this.server = server;
this.config = config;
this.selection = Selection.builder(server.getWorlds().get(0));
this.taskLoader = new TaskLoader(this);
this.eventBus = new EventBus();
this.selection = Selection.builder(this, server.getWorlds().get(0));
this.limit = loadLimit().orElse(Double.MAX_VALUE);
this.version = loadVersion();
this.commands = loadCommands();
this.api = new ChunkyAPIImpl(this);
ChunkyProvider.register(this);
}
private Map<String, ChunkyCommand> loadCommands() {
final Map<String, ChunkyCommand> commandMap = new HashMap<>();
commandMap.put("cancel", new CancelCommand(this));
commandMap.put("center", new CenterCommand(this));
commandMap.put("confirm", new ConfirmCommand(this));
commandMap.put("continue", new ContinueCommand(this));
commandMap.put("corners", new CornersCommand(this));
commandMap.put("help", new HelpCommand(this));
commandMap.put("pattern", new PatternCommand(this));
commandMap.put("pause", new PauseCommand(this));
commandMap.put("progress", new ProgressCommand(this));
commandMap.put("quiet", new QuietCommand(this));
commandMap.put("radius", new RadiusCommand(this));
commandMap.put("reload", new ReloadCommand(this));
commandMap.put("shape", new ShapeCommand(this));
commandMap.put("silent", new SilentCommand(this));
commandMap.put("spawn", new SpawnCommand(this));
commandMap.put("start", new StartCommand(this));
commandMap.put("trim", new TrimCommand(this));
commandMap.put("worldborder", new WorldBorderCommand(this));
commandMap.put("world", new WorldCommand(this));
return commandMap;
public void disable() {
taskLoader.saveTasks();
getGenerationTasks().values().forEach(generationTask -> generationTask.stop(false));
getScheduler().cancelTasks();
ChunkyProvider.unregister();
}
private Optional<Double> loadLimit() {
@@ -76,10 +93,39 @@ public class Chunky {
}
}
public void disable() {
getConfig().saveTasks();
getGenerationTasks().values().forEach(generationTask -> generationTask.stop(false));
getScheduler().cancelTasks();
private Version loadVersion() {
try (final InputStream input = getClass().getClassLoader().getResourceAsStream("version.properties")) {
final Properties properties = new Properties();
properties.load(input);
return new Version(properties.getProperty("version"));
} catch (IOException e) {
return Version.INVALID;
}
}
private Map<String, ChunkyCommand> loadCommands() {
final Map<String, ChunkyCommand> commandMap = new HashMap<>();
commandMap.put(CommandLiteral.CANCEL, new CancelCommand(this));
commandMap.put(CommandLiteral.CENTER, new CenterCommand(this));
commandMap.put(CommandLiteral.CONFIRM, new ConfirmCommand(this));
commandMap.put(CommandLiteral.CONTINUE, new ContinueCommand(this));
commandMap.put(CommandLiteral.CORNERS, new CornersCommand(this));
commandMap.put(CommandLiteral.HELP, new HelpCommand(this));
commandMap.put(CommandLiteral.PATTERN, new PatternCommand(this));
commandMap.put(CommandLiteral.PAUSE, new PauseCommand(this));
commandMap.put(CommandLiteral.PROGRESS, new ProgressCommand(this));
commandMap.put(CommandLiteral.QUIET, new QuietCommand(this));
commandMap.put(CommandLiteral.RADIUS, new RadiusCommand(this));
commandMap.put(CommandLiteral.RELOAD, new ReloadCommand(this));
commandMap.put(CommandLiteral.SELECTION, new SelectionCommand(this));
commandMap.put(CommandLiteral.SHAPE, new ShapeCommand(this));
commandMap.put(CommandLiteral.SILENT, new SilentCommand(this));
commandMap.put(CommandLiteral.SPAWN, new SpawnCommand(this));
commandMap.put(CommandLiteral.START, new StartCommand(this));
commandMap.put(CommandLiteral.TRIM, new TrimCommand(this));
commandMap.put(CommandLiteral.WORLDBORDER, new WorldBorderCommand(this));
commandMap.put(CommandLiteral.WORLD, new WorldCommand(this));
return commandMap;
}
public TaskScheduler getScheduler() {
@@ -94,10 +140,22 @@ public class Chunky {
return config;
}
public Map<World, GenerationTask> getGenerationTasks() {
public TaskLoader getTaskLoader() {
return taskLoader;
}
public EventBus getEventBus() {
return eventBus;
}
public Map<String, GenerationTask> getGenerationTasks() {
return generationTasks;
}
public Map<String, TrimCommand.Task> getTrimTasks() {
return trimTasks;
}
public Map<String, ChunkyCommand> getCommands() {
return commands;
}
@@ -106,21 +164,17 @@ public class Chunky {
return selection;
}
public Options getOptions() {
return options;
}
public Optional<Runnable> getPendingAction(Sender sender) {
public Optional<Runnable> getPendingAction(final Sender sender) {
pendingActions.values().removeIf(PendingAction::hasExpired);
PendingAction pendingAction = pendingActions.remove(sender.getName());
final PendingAction pendingAction = pendingActions.remove(sender.getName());
return Optional.ofNullable(pendingAction).map(PendingAction::getAction);
}
public void setPendingAction(Sender sender, Runnable action) {
public void setPendingAction(final Sender sender, final Runnable action) {
pendingActions.put(sender.getName(), new PendingAction(action));
}
public void setLanguage(String language) {
public void setLanguage(final String language) {
Translator.setLanguage(language);
}
@@ -131,4 +185,12 @@ public class Chunky {
public double getLimit() {
return limit;
}
public Version getVersion() {
return version;
}
public ChunkyAPI getApi() {
return api;
}
}
@@ -0,0 +1,24 @@
package org.popcraft.chunky;
public final class ChunkyProvider {
private static Chunky instance;
private ChunkyProvider() {
throw new UnsupportedOperationException("This class cannot be instantiated.");
}
public static Chunky get() {
if (instance == null) {
throw new IllegalStateException("Chunky is not loaded.");
}
return instance;
}
static void register(final Chunky instance) {
ChunkyProvider.instance = instance;
}
static void unregister() {
ChunkyProvider.instance = null;
}
}
@@ -1,28 +1,36 @@
package org.popcraft.chunky;
import org.popcraft.chunky.api.event.task.GenerationCompleteEvent;
import org.popcraft.chunky.api.event.task.GenerationProgressEvent;
import org.popcraft.chunky.event.task.GenerationTaskFinishEvent;
import org.popcraft.chunky.event.task.GenerationTaskUpdateEvent;
import org.popcraft.chunky.iterator.ChunkIterator;
import org.popcraft.chunky.iterator.ChunkIteratorFactory;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.shape.Shape;
import org.popcraft.chunky.shape.ShapeFactory;
import org.popcraft.chunky.util.ChunkCoordinate;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.Pair;
import org.popcraft.chunky.util.RegionCache;
import org.popcraft.chunky.util.TranslationKey;
import java.util.Deque;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;
public class GenerationTask implements Runnable {
private static final int MAX_WORKING = 50;
private static final int MAX_WORKING_COUNT = Input.tryInteger(System.getProperty("chunky.maxWorkingCount")).orElse(50);
private static final double SAMPLE_INTERVAL = 1000d * Math.max(Input.tryInteger(System.getProperty("chunky.sampleInterval")).orElse(30), 30);
private static final double SAMPLE_SUB_INTERVAL = SAMPLE_INTERVAL / 30;
private final Chunky chunky;
private final Selection selection;
private final Shape shape;
private final AtomicLong startTime = new AtomicLong();
private final AtomicLong printTime = new AtomicLong();
private final AtomicLong updateTime = new AtomicLong();
private final AtomicLong finishedChunks = new AtomicLong();
private final AtomicLong totalChunks = new AtomicLong();
private final Deque<Pair<Long, AtomicLong>> updateSamples = new ConcurrentLinkedDeque<>();
private final Progress progress;
private final RegionCache.WorldState worldState;
@@ -30,44 +38,44 @@ public class GenerationTask implements Runnable {
private boolean stopped, cancelled;
private long prevTime;
public GenerationTask(Chunky chunky, Selection selection, long count, long time) {
public GenerationTask(final Chunky chunky, final Selection selection, final long count, final long time, final boolean cancelled) {
this(chunky, selection);
this.chunkIterator = ChunkIteratorFactory.getChunkIterator(selection, count);
this.finishedChunks.set(count);
this.cancelled = cancelled;
this.prevTime = time;
}
public GenerationTask(Chunky chunky, Selection selection) {
public GenerationTask(final Chunky chunky, final Selection selection) {
this.chunky = chunky;
this.selection = selection;
this.chunkIterator = ChunkIteratorFactory.getChunkIterator(selection);
this.shape = ShapeFactory.getShape(selection);
this.totalChunks.set(chunkIterator.total());
this.progress = new Progress(selection.world().getName());
this.worldState = chunky.getRegionCache().getWorld(selection.world().getName());
}
private void update(int chunkX, int chunkZ, boolean loaded) {
private synchronized void update(final int chunkX, final int chunkZ, final boolean loaded) {
if (stopped) {
return;
}
progress.chunkCount = finishedChunks.addAndGet(1);
progress.percentComplete = 100f * progress.chunkCount / totalChunks.get();
progress.percentComplete = 100f * progress.chunkCount / chunkIterator.total();
final long currentTime = System.currentTimeMillis();
final Pair<Long, AtomicLong> bin = updateSamples.peekLast();
if (loaded) {
worldState.setGenerated(chunkX, chunkZ);
if (bin != null && currentTime - bin.left() < 2e3) {
if (bin != null && currentTime - bin.left() < SAMPLE_SUB_INTERVAL) {
bin.right().addAndGet(1);
} else if (updateSamples.add(Pair.of(currentTime, new AtomicLong(1)))) {
while (!updateSamples.isEmpty() && currentTime - updateSamples.peek().left() > 1e4) {
while (!updateSamples.isEmpty() && currentTime - updateSamples.peek().left() > SAMPLE_INTERVAL) {
updateSamples.poll();
}
}
}
final Pair<Long, AtomicLong> oldest = updateSamples.peek();
final long oldestTime = oldest == null ? currentTime : oldest.left();
final long chunksLeft = totalChunks.get() - finishedChunks.get();
final long chunksLeft = chunkIterator.total() - finishedChunks.get();
final double timeDiff = (currentTime - oldestTime) / 1e3;
if (chunksLeft > 0 && timeDiff < 1e-1) {
return;
@@ -89,29 +97,43 @@ public class GenerationTask implements Runnable {
progress.seconds = time - progress.hours * 3600 - progress.minutes * 60;
progress.chunkX = chunkX;
progress.chunkZ = chunkZ;
if (chunksLeft > 0 && (chunky.getOptions().isSilent() || ((currentTime - printTime.get()) / 1e3) < chunky.getOptions().getQuietInterval())) {
chunky.getEventBus().call(new GenerationProgressEvent(progress.world, progress.chunkCount, progress.complete, progress.percentComplete, progress.hours, progress.minutes, progress.seconds, progress.rate, progress.chunkX, progress.chunkZ));
if (progress.complete) {
progress.sendUpdate(chunky.getServer().getConsole());
chunky.getEventBus().call(new GenerationTaskUpdateEvent(this));
return;
}
printTime.set(currentTime);
progress.sendUpdate(chunky.getServer().getConsoleSender());
final boolean silentMode = chunky.getConfig().isSilent();
final boolean updateIntervalElapsed = ((currentTime - updateTime.get()) / 1e3) > chunky.getConfig().getUpdateInterval();
if (updateIntervalElapsed) {
if (!silentMode) {
progress.sendUpdate(chunky.getServer().getConsole());
}
chunky.getEventBus().call(new GenerationTaskUpdateEvent(this));
updateTime.set(currentTime);
}
}
@Override
public void run() {
final String poolThreadName = Thread.currentThread().getName();
Thread.currentThread().setName(String.format("Chunky-%s Thread", selection.world().getName()));
final Semaphore working = new Semaphore(MAX_WORKING);
if (!chunkIterator.process()) {
stop(true);
}
final Semaphore working = new Semaphore(MAX_WORKING_COUNT);
final boolean forceLoadExistingChunks = chunky.getConfig().isForceLoadExistingChunks();
startTime.set(System.currentTimeMillis());
while (!stopped && chunkIterator.hasNext()) {
final ChunkCoordinate chunk = chunkIterator.next();
final int chunkCenterX = (chunk.x << 4) + 8;
final int chunkCenterZ = (chunk.z << 4) + 8;
if (!shape.isBounding(chunkCenterX, chunkCenterZ) || worldState.isGenerated(chunk.x, chunk.z)) {
update(chunk.x, chunk.z, false);
final int chunkCenterX = (chunk.x() << 4) + 8;
final int chunkCenterZ = (chunk.z() << 4) + 8;
if (!shape.isBounding(chunkCenterX, chunkCenterZ)) {
update(chunk.x(), chunk.z(), false);
continue;
}
if (selection.world().isChunkGenerated(chunk.x, chunk.z)) {
update(chunk.x, chunk.z, true);
if (!forceLoadExistingChunks && worldState.isGenerated(chunk.x(), chunk.z())) {
update(chunk.x(), chunk.z(), false);
continue;
}
try {
@@ -121,19 +143,31 @@ public class GenerationTask implements Runnable {
stop(cancelled);
break;
}
selection.world().getChunkAtAsync(chunk.x, chunk.z).thenRun(() -> {
working.release();
update(chunk.x, chunk.z, true);
});
final CompletableFuture<Boolean> isChunkGenerated = forceLoadExistingChunks ?
CompletableFuture.completedFuture(false) :
selection.world().isChunkGenerated(chunk.x(), chunk.z());
isChunkGenerated
.thenCompose(generated -> {
if (Boolean.TRUE.equals(generated)) {
return CompletableFuture.completedFuture(null);
} else {
return selection.world().getChunkAtAsync(chunk.x(), chunk.z());
}
}).whenComplete((ignored, throwable) -> {
working.release();
update(chunk.x(), chunk.z(), true);
});
}
if (stopped) {
chunky.getServer().getConsoleSender().sendMessagePrefixed("task_stopped", selection.world().getName());
chunky.getServer().getConsole().sendMessagePrefixed(TranslationKey.TASK_STOPPED, selection.world().getName());
} else {
cancelled = true;
}
chunky.getConfig().saveTask(this);
chunky.getGenerationTasks().remove(selection.world());
chunky.getTaskLoader().saveTask(this);
chunky.getGenerationTasks().remove(selection.world().getName());
Thread.currentThread().setName(poolThreadName);
chunky.getEventBus().call(new GenerationTaskFinishEvent(this));
chunky.getEventBus().call(new GenerationCompleteEvent(selection.world().getName()));
}
public void stop(final boolean cancelled) {
@@ -141,6 +175,10 @@ public class GenerationTask implements Runnable {
this.cancelled = cancelled;
}
public Chunky getChunky() {
return chunky;
}
public Selection getSelection() {
return selection;
}
@@ -169,7 +207,8 @@ public class GenerationTask implements Runnable {
return progress;
}
public static class Progress {
@SuppressWarnings("unused")
public static final class Progress {
private final String world;
private long chunkCount;
private boolean complete;
@@ -224,9 +263,9 @@ public class GenerationTask implements Runnable {
public void sendUpdate(final Sender sender) {
if (complete) {
sender.sendMessagePrefixed("task_done", world, chunkCount, String.format("%.2f", percentComplete), String.format("%01d", hours), String.format("%02d", minutes), String.format("%02d", seconds));
sender.sendMessagePrefixed(TranslationKey.TASK_DONE, world, chunkCount, String.format("%.2f", percentComplete), String.format("%01d", hours), String.format("%02d", minutes), String.format("%02d", seconds));
} else {
sender.sendMessagePrefixed("task_update", world, chunkCount, String.format("%.2f", percentComplete), String.format("%01d", hours), String.format("%02d", minutes), String.format("%02d", seconds), String.format("%.1f", rate), chunkX, chunkZ);
sender.sendMessagePrefixed(TranslationKey.TASK_UPDATE, world, chunkCount, String.format("%.2f", percentComplete), String.format("%01d", hours), String.format("%02d", minutes), String.format("%02d", seconds), String.format("%.1f", rate), chunkX, chunkZ);
}
}
}
@@ -1,22 +0,0 @@
package org.popcraft.chunky;
public class Options {
private boolean isSilent;
private int quietInterval = 1;
public boolean isSilent() {
return this.isSilent;
}
public void setSilent(boolean silent) {
this.isSilent = silent;
}
public int getQuietInterval() {
return this.quietInterval;
}
public void setQuietInterval(int quietInterval) {
this.quietInterval = quietInterval;
}
}
@@ -1,16 +1,25 @@
package org.popcraft.chunky;
import org.popcraft.chunky.iterator.PatternType;
import org.popcraft.chunky.platform.Border;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.util.Coordinate;
import org.popcraft.chunky.platform.util.Location;
import org.popcraft.chunky.platform.util.Vector2;
import org.popcraft.chunky.shape.ShapeType;
import org.popcraft.chunky.util.Parameter;
public class Selection {
@SuppressWarnings("unused")
public final class Selection {
public static final double DEFAULT_CENTER_X = 0d;
public static final double DEFAULT_CENTER_Z = 0d;
public static final double DEFAULT_RADIUS = 500d;
private final Chunky chunky;
private final World world;
private final double centerX;
private final double centerZ;
private final double radiusX;
private final double radiusZ;
private final String pattern;
private final Parameter pattern;
private final String shape;
private final int centerChunkX;
private final int centerChunkZ;
@@ -18,8 +27,15 @@ public class Selection {
private final int radiusChunksZ;
private final int diameterChunksX;
private final int diameterChunksZ;
private final int centerRegionX;
private final int centerRegionZ;
private final int radiusRegionsX;
private final int radiusRegionsZ;
private final int diameterRegionsX;
private final int diameterRegionsZ;
private Selection(World world, double centerX, double centerZ, double radiusX, double radiusZ, String pattern, String shape) {
private Selection(final Chunky chunky, final World world, final double centerX, final double centerZ, final double radiusX, final double radiusZ, final Parameter pattern, final String shape) {
this.chunky = chunky;
this.world = world;
this.centerX = centerX;
this.centerZ = centerZ;
@@ -33,6 +49,20 @@ public class Selection {
this.radiusChunksZ = (int) Math.ceil(radiusZ / 16f);
this.diameterChunksX = 2 * radiusChunksX + 1;
this.diameterChunksZ = 2 * radiusChunksZ + 1;
this.centerRegionX = centerChunkX >> 5;
this.centerRegionZ = centerChunkZ >> 5;
this.radiusRegionsX = (int) Math.ceil(radiusChunksX / 32f);
this.radiusRegionsZ = (int) Math.ceil(radiusChunksZ / 32f);
this.diameterRegionsX = 2 * radiusRegionsX + 1;
this.diameterRegionsZ = 2 * radiusRegionsZ + 1;
}
public static Builder builder(final Chunky chunky, final World world) {
return new Builder(chunky, world);
}
public Chunky chunky() {
return chunky;
}
public World world() {
@@ -55,7 +85,7 @@ public class Selection {
return this.radiusZ;
}
public String pattern() {
public Parameter pattern() {
return this.pattern;
}
@@ -87,80 +117,102 @@ public class Selection {
return this.diameterChunksZ;
}
public static Builder builder(World world) {
return new Builder(world);
public int centerRegionX() {
return this.centerRegionX;
}
public int centerRegionZ() {
return this.centerRegionZ;
}
public int radiusRegionsX() {
return this.radiusRegionsX;
}
public int radiusRegionsZ() {
return this.radiusRegionsZ;
}
public int diameterRegionsX() {
return this.diameterRegionsX;
}
public int diameterRegionsZ() {
return this.diameterRegionsZ;
}
public static final class Builder {
private final Chunky chunky;
private World world;
private double centerX = 0;
private double centerZ = 0;
private double radiusX = 500;
private double radiusZ = 500;
private String pattern = "concentric";
private String shape = "square";
private double centerX = DEFAULT_CENTER_X;
private double centerZ = DEFAULT_CENTER_Z;
private double radiusX = DEFAULT_RADIUS;
private double radiusZ = DEFAULT_RADIUS;
private Parameter pattern = Parameter.of(PatternType.REGION);
private String shape = ShapeType.SQUARE;
private Builder(World world) {
private Builder(final Chunky chunky, final World world) {
this.chunky = chunky;
this.world = world;
}
public Builder world(World world) {
public Builder world(final World world) {
this.world = world;
return this;
}
public Builder center(double centerX, double centerZ) {
public Builder center(final double centerX, final double centerZ) {
this.centerX = centerX;
this.centerZ = centerZ;
return this;
}
public Builder centerX(double centerX) {
public Builder centerX(final double centerX) {
this.centerX = centerX;
return this;
}
public Builder centerZ(double centerZ) {
public Builder centerZ(final double centerZ) {
this.centerZ = centerZ;
return this;
}
public Builder radius(double radius) {
public Builder radius(final double radius) {
this.radiusX = radius;
this.radiusZ = radius;
return this;
}
public Builder radiusX(double radiusX) {
public Builder radiusX(final double radiusX) {
this.radiusX = radiusX;
return this;
}
public Builder radiusZ(double radiusZ) {
public Builder radiusZ(final double radiusZ) {
this.radiusZ = radiusZ;
return this;
}
public Builder pattern(String pattern) {
public Builder pattern(final Parameter pattern) {
this.pattern = pattern;
return this;
}
public Builder shape(String shape) {
public Builder shape(final String shape) {
this.shape = shape;
return this;
}
public Builder spawn() {
Coordinate spawn = world.getSpawnCoordinate();
final Location spawn = world.getSpawn();
this.centerX = spawn.getX();
this.centerZ = spawn.getZ();
return this;
}
public Builder worldborder() {
Border border = world.getWorldBorder();
Coordinate center = border.getCenter();
final Border border = world.getWorldBorder();
final Vector2 center = border.getCenter();
this.centerX = center.getX();
this.centerZ = center.getZ();
this.radiusX = border.getRadiusX();
@@ -169,7 +221,7 @@ public class Selection {
}
public Selection build() {
return new Selection(world, centerX, centerZ, radiusX, radiusZ, pattern, shape);
return new Selection(chunky, world, centerX, centerZ, radiusX, radiusZ, pattern, shape);
}
}
}
@@ -0,0 +1,79 @@
package org.popcraft.chunky.api;
import org.popcraft.chunky.api.event.task.GenerationCompleteEvent;
import org.popcraft.chunky.api.event.task.GenerationProgressEvent;
import java.util.function.Consumer;
/**
* The Chunky API
*/
@SuppressWarnings("unused")
public interface ChunkyAPI {
/**
* Gets the current API version.
*
* @return The api version
*/
int version();
/**
* Gets whether a generation task is currently running for a world.
*
* @param world The world identifier
* @return If a task is running in that world
*/
boolean isRunning(final String world);
/**
* Starts a generation task with a given selection in a world.
*
* @param world The world identifier
* @param shape The selection shape
* @param centerX The center x coordinate
* @param centerZ The center z coordinate
* @param radiusX The primary radius (x-axis)
* @param radiusZ The secondary radius (z-axis) (only used for certain shapes)
* @param pattern The generation pattern
* @return If the task was created and started successfully
*/
boolean startTask(final String world, final String shape, final double centerX, final double centerZ, final double radiusX, final double radiusZ, final String pattern);
/**
* Pauses a generation task in a world.
*
* @param world The world identifier
* @return If the task was paused
*/
boolean pauseTask(final String world);
/**
* Continues a generation task in a world.
*
* @param world The world identifier
* @return If the task was continued
*/
boolean continueTask(final String world);
/**
* Cancels a generation task in a world.
*
* @param world The world identifier
* @return If the task was cancelled
*/
boolean cancelTask(final String world);
/**
* Register a listener for generation progress events.
*
* @param listener The listener
*/
void onGenerationProgress(final Consumer<GenerationProgressEvent> listener);
/**
* Register a listener for generation complete events.
*
* @param listener The listener
*/
void onGenerationComplete(final Consumer<GenerationCompleteEvent> listener);
}
@@ -0,0 +1,102 @@
package org.popcraft.chunky.api;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.GenerationTask;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.api.event.task.GenerationCompleteEvent;
import org.popcraft.chunky.api.event.task.GenerationProgressEvent;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.Parameter;
import java.util.function.Consumer;
@SuppressWarnings("ClassCanBeRecord")
public class ChunkyAPIImpl implements ChunkyAPI {
private final Chunky chunky;
public ChunkyAPIImpl(final Chunky chunky) {
this.chunky = chunky;
}
@Override
public int version() {
return 0;
}
@Override
public boolean isRunning(final String world) {
return chunky.getGenerationTasks().containsKey(world);
}
@Override
public boolean startTask(final String world, final String shape, final double centerX, final double centerZ, final double radiusX, final double radiusZ, final String pattern) {
final World implWorld = Input.tryWorld(chunky, world).orElse(null);
if (implWorld == null) {
return false;
}
if (chunky.getGenerationTasks().containsKey(world)) {
return false;
}
final Selection selection = Selection.builder(chunky, implWorld)
.shape(shape).center(centerX, centerZ)
.radiusX(radiusX).radiusZ(radiusZ)
.pattern(Parameter.of(pattern)).build();
final GenerationTask task = new GenerationTask(chunky, selection);
chunky.getGenerationTasks().put(world, task);
chunky.getScheduler().runTask(task);
return true;
}
@Override
public boolean pauseTask(final String world) {
final GenerationTask task = chunky.getGenerationTasks().get(world);
if (task == null) {
return false;
}
task.stop(false);
return true;
}
@Override
public boolean continueTask(final String world) {
final World implWorld = Input.tryWorld(chunky, world).orElse(null);
if (implWorld == null) {
return false;
}
final GenerationTask task = chunky.getTaskLoader().loadTask(implWorld).orElse(null);
if (task == null || task.isCancelled()) {
return false;
}
if (chunky.getGenerationTasks().containsKey(world)) {
return false;
}
chunky.getGenerationTasks().put(world, task);
chunky.getScheduler().runTask(task);
return true;
}
@Override
public boolean cancelTask(final String world) {
final World implWorld = Input.tryWorld(chunky, world).orElse(null);
if (implWorld == null) {
return false;
}
if (!chunky.getGenerationTasks().containsKey(world)) {
return false;
}
chunky.getGenerationTasks().remove(world).stop(true);
chunky.getTaskLoader().cancelTask(implWorld);
return true;
}
@Override
public void onGenerationProgress(final Consumer<GenerationProgressEvent> event) {
chunky.getEventBus().subscribe(GenerationProgressEvent.class, event);
}
@Override
public void onGenerationComplete(final Consumer<GenerationCompleteEvent> event) {
chunky.getEventBus().subscribe(GenerationCompleteEvent.class, event);
}
}
@@ -0,0 +1,11 @@
package org.popcraft.chunky.api.event.task;
import org.popcraft.chunky.event.Event;
/**
* Event which is fired when a generation task completes.
*
* @param world The world identifier associated with the completed task
*/
public record GenerationCompleteEvent(String world) implements Event {
}
@@ -0,0 +1,21 @@
package org.popcraft.chunky.api.event.task;
import org.popcraft.chunky.event.Event;
/**
* Event which is fired when a generation task calculates progress.
*
* @param world The world identifier
* @param chunks The number of chunks generated
* @param complete If the generation task completed
* @param progress The percent progress
* @param hours The number of hours elapsed for this task
* @param minutes The number of minutes elapsed for this task
* @param seconds The number of seconds elapsed for this task
* @param rate The current average generation rate
* @param x The current chunk's x coordinate
* @param z The current chunk's z coordinate
*/
public record GenerationProgressEvent(String world, long chunks, boolean complete, float progress, long hours,
long minutes, long seconds, double rate, long x, long z) implements Event {
}
@@ -5,59 +5,69 @@ import org.popcraft.chunky.GenerationTask;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.TranslationKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class CancelCommand extends ChunkyCommand {
public CancelCommand(Chunky chunky) {
super(chunky);
public class CancelCommand implements ChunkyCommand {
private final Chunky chunky;
public CancelCommand(final Chunky chunky) {
this.chunky = chunky;
}
public void execute(Sender sender, String[] args) {
final Map<World, GenerationTask> generationTasks = chunky.getGenerationTasks();
if (generationTasks.isEmpty() && chunky.getConfig().loadTasks().isEmpty()) {
sender.sendMessagePrefixed("format_cancel_no_tasks");
@Override
public void execute(final Sender sender, final CommandArguments arguments) {
final Map<String, GenerationTask> generationTasks = chunky.getGenerationTasks();
final Map<String, TrimCommand.Task> trimTasks = chunky.getTrimTasks();
if (generationTasks.isEmpty()
&& chunky.getTaskLoader().loadTasks().stream().allMatch(GenerationTask::isCancelled)
&& trimTasks.isEmpty()) {
sender.sendMessagePrefixed(TranslationKey.FORMAT_CANCEL_NO_TASKS);
return;
}
final Runnable cancelAction;
if (args.length > 1) {
final Optional<World> world = Input.tryWorld(chunky, String.join(" ", Arrays.copyOfRange(args, 1, args.length)));
if (!world.isPresent()) {
sender.sendMessage("help_cancel");
if (arguments.size() > 0) {
final Optional<World> world = Input.tryWorld(chunky, arguments.joined());
if (world.isEmpty()) {
sender.sendMessage(TranslationKey.HELP_CANCEL);
return;
}
cancelAction = () -> {
sender.sendMessagePrefixed("format_cancel", world.get().getName());
chunky.getConfig().cancelTask(world.get());
if (chunky.getGenerationTasks().containsKey(world.get())) {
chunky.getGenerationTasks().remove(world.get()).stop(true);
sender.sendMessagePrefixed(TranslationKey.FORMAT_CANCEL, world.get().getName());
chunky.getTaskLoader().cancelTask(world.get());
if (chunky.getGenerationTasks().containsKey(world.get().getName())) {
chunky.getGenerationTasks().remove(world.get().getName()).stop(true);
}
if (chunky.getTrimTasks().containsKey(world.get().getName())) {
chunky.getTrimTasks().remove(world.get().getName()).setCancelled(true);
}
};
} else {
cancelAction = () -> {
sender.sendMessagePrefixed("format_cancel_all");
chunky.getConfig().cancelTasks();
sender.sendMessagePrefixed(TranslationKey.FORMAT_CANCEL_ALL);
chunky.getTaskLoader().cancelTasks();
chunky.getGenerationTasks().values().forEach(generationTask -> generationTask.stop(true));
chunky.getGenerationTasks().clear();
chunky.getScheduler().cancelTasks();
chunky.getTrimTasks().values().forEach(trimTask -> trimTask.setCancelled(true));
chunky.getTrimTasks().clear();
};
}
chunky.setPendingAction(sender, cancelAction);
sender.sendMessagePrefixed("format_cancel_confirm", "/chunky confirm");
sender.sendMessagePrefixed(TranslationKey.FORMAT_CANCEL_CONFIRM, "/chunky confirm");
}
@Override
public List<String> tabSuggestions(Sender sender, String[] args) {
if (args.length == 2) {
List<String> suggestions = new ArrayList<>();
public List<String> suggestions(final CommandArguments arguments) {
if (arguments.size() == 1) {
final List<String> suggestions = new ArrayList<>();
chunky.getServer().getWorlds().forEach(world -> suggestions.add(world.getName()));
return suggestions;
}
return Collections.emptyList();
return List.of();
}
}
@@ -2,44 +2,48 @@ package org.popcraft.chunky.command;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.util.Coordinate;
import org.popcraft.chunky.platform.util.Location;
import org.popcraft.chunky.util.Formatting;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.TranslationKey;
import java.util.List;
import java.util.Optional;
public class CenterCommand extends ChunkyCommand {
public CenterCommand(Chunky chunky) {
super(chunky);
public class CenterCommand implements ChunkyCommand {
private final Chunky chunky;
public CenterCommand(final Chunky chunky) {
this.chunky = chunky;
}
public void execute(Sender sender, String[] args) {
Optional<Double> newX = Optional.empty();
if (args.length > 1) {
newX = Input.tryDoubleSuffixed(args[1]);
}
Optional<Double> newZ = Optional.empty();
if (args.length > 2) {
newZ = Input.tryDoubleSuffixed(args[2]);
}
@Override
public void execute(final Sender sender, final CommandArguments arguments) {
final Optional<Double> newX = arguments.next().flatMap(Input::tryDoubleSuffixed);
final Optional<Double> newZ = arguments.next().flatMap(Input::tryDoubleSuffixed);
final double centerX;
final double centerZ;
if (!newX.isPresent() && !newZ.isPresent()) {
Coordinate coordinate = sender.getCoordinate();
if (newX.isEmpty() && newZ.isEmpty()) {
final Location coordinate = sender.getLocation();
centerX = coordinate.getX();
centerZ = coordinate.getZ();
} else if (newX.isPresent() && newZ.isPresent()) {
centerX = newX.get();
centerZ = newZ.get();
} else {
sender.sendMessage("help_center");
sender.sendMessage(TranslationKey.HELP_CENTER);
return;
}
if (Input.isPastWorldLimit(centerX) || Input.isPastWorldLimit(centerZ)) {
sender.sendMessage("help_center");
sender.sendMessage(TranslationKey.HELP_CENTER);
return;
}
chunky.getSelection().center(centerX, centerZ);
sender.sendMessagePrefixed("format_center", Formatting.number(centerX), Formatting.number(centerZ));
sender.sendMessagePrefixed(TranslationKey.FORMAT_CENTER, Formatting.number(centerX), Formatting.number(centerZ));
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
return List.of();
}
}
@@ -1,21 +1,11 @@
package org.popcraft.chunky.command;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.platform.Sender;
import java.util.Collections;
import java.util.List;
public abstract class ChunkyCommand {
protected final Chunky chunky;
public interface ChunkyCommand {
void execute(Sender sender, CommandArguments arguments);
protected ChunkyCommand(Chunky chunky) {
this.chunky = chunky;
}
public abstract void execute(Sender sender, String[] args);
public List<String> tabSuggestions(Sender sender, String[] args) {
return Collections.emptyList();
}
List<String> suggestions(final CommandArguments arguments);
}
@@ -0,0 +1,47 @@
package org.popcraft.chunky.command;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
public final class CommandArguments {
private final int size;
private final Queue<String> args = new LinkedList<>();
private CommandArguments(final List<String> arguments) {
this.size = arguments.size();
this.args.addAll(arguments);
}
public static CommandArguments of(final List<String> arguments) {
return new CommandArguments(arguments);
}
public static CommandArguments of(final String... arguments) {
return new CommandArguments(List.of(arguments));
}
public static CommandArguments empty() {
return new CommandArguments(List.of());
}
public int size() {
return size;
}
public Optional<String> next() {
return Optional.ofNullable(args.poll());
}
public List<String> remaining() {
final List<String> arguments = new ArrayList<>(args);
args.clear();
return arguments;
}
public String joined() {
return String.join(" ", remaining());
}
}
@@ -0,0 +1,52 @@
package org.popcraft.chunky.command;
public final class CommandLiteral {
public static final String BORDER = "border";
public static final String CANCEL = "cancel";
public static final String CENTER = "center";
public static final String CENTER_X = "centerX";
public static final String CENTER_Z = "centerZ";
public static final String CHUNKY = "chunky";
public static final String CONFIRM = "confirm";
public static final String CONTINUE = "continue";
public static final String CORNERS = "corners";
public static final String HELP = "help";
public static final String INHABITED = "inhabited";
public static final String INTERVAL = "interval";
public static final String PAGE = "page";
public static final String PATTERN = "pattern";
public static final String PAUSE = "pause";
public static final String PROGRESS = "progress";
public static final String QUIET = "quiet";
public static final String RADIUS = "radius";
public static final String RADIUS_X = "radiusX";
public static final String RADIUS_Z = "radiusZ";
public static final String RELOAD = "reload";
public static final String SELECTION = "selection";
public static final String SHAPE = "shape";
public static final String SILENT = "silent";
public static final String SPAWN = "spawn";
public static final String START = "start";
public static final String TRIM = "trim";
public static final String TRIM_MODE = "trimMode";
public static final String WORLDBORDER = "worldborder";
public static final String WORLD = "world";
public static final String X = "x";
public static final String X1 = "x1";
public static final String X2 = "x2";
public static final String Z = "z";
public static final String Z1 = "z1";
public static final String Z2 = "z2";
public static final String ADD = "add";
public static final String BYPASS = "bypass";
public static final String LIST = "list";
public static final String LOAD = "load";
public static final String REMOVE = "remove";
public static final String WRAP = "wrap";
public static final String PLAYER = "player";
public static final String VALUE = "value";
public static final String TYPE = "type";
private CommandLiteral() {
}
}
@@ -2,21 +2,30 @@ package org.popcraft.chunky.command;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.util.TranslationKey;
import java.util.List;
import java.util.Optional;
public class ConfirmCommand extends ChunkyCommand {
public ConfirmCommand(Chunky chunky) {
super(chunky);
public class ConfirmCommand implements ChunkyCommand {
private final Chunky chunky;
public ConfirmCommand(final Chunky chunky) {
this.chunky = chunky;
}
@Override
public void execute(Sender sender, String[] args) {
Optional<Runnable> pendingAction = chunky.getPendingAction(sender);
if (!pendingAction.isPresent()) {
sender.sendMessagePrefixed("format_confirm");
public void execute(final Sender sender, final CommandArguments arguments) {
final Optional<Runnable> pendingAction = chunky.getPendingAction(sender);
if (pendingAction.isEmpty()) {
sender.sendMessagePrefixed(TranslationKey.FORMAT_CONFIRM);
return;
}
pendingAction.get().run();
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
return List.of();
}
}
@@ -5,55 +5,57 @@ import org.popcraft.chunky.GenerationTask;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.TranslationKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class ContinueCommand extends ChunkyCommand {
public ContinueCommand(Chunky chunky) {
super(chunky);
public class ContinueCommand implements ChunkyCommand {
private final Chunky chunky;
public ContinueCommand(final Chunky chunky) {
this.chunky = chunky;
}
public void execute(Sender sender, String[] args) {
@Override
public void execute(final Sender sender, final CommandArguments arguments) {
final List<GenerationTask> loadTasks;
if (args.length > 1) {
final Optional<World> world = Input.tryWorld(chunky, String.join(" ", Arrays.copyOfRange(args, 1, args.length)));
if (!world.isPresent()) {
sender.sendMessage("help_continue");
if (arguments.size() > 0) {
final Optional<World> world = Input.tryWorld(chunky, arguments.joined());
if (world.isEmpty()) {
sender.sendMessage(TranslationKey.HELP_CONTINUE);
return;
}
loadTasks = chunky.getConfig().loadTask(world.get()).map(Collections::singletonList).orElse(Collections.emptyList());
loadTasks = chunky.getTaskLoader().loadTask(world.get()).map(List::of).orElse(List.of());
} else {
loadTasks = chunky.getConfig().loadTasks();
loadTasks = chunky.getTaskLoader().loadTasks();
}
if (loadTasks.isEmpty()) {
sender.sendMessagePrefixed("format_continue_no_tasks");
if (loadTasks.stream().allMatch(GenerationTask::isCancelled)) {
sender.sendMessagePrefixed(TranslationKey.FORMAT_CONTINUE_NO_TASKS);
return;
}
final Map<World, GenerationTask> generationTasks = chunky.getGenerationTasks();
loadTasks.forEach(generationTask -> {
World world = generationTask.getSelection().world();
if (!generationTasks.containsKey(world)) {
generationTasks.put(world, generationTask);
final Map<String, GenerationTask> generationTasks = chunky.getGenerationTasks();
loadTasks.stream().filter(task -> !task.isCancelled()).forEach(generationTask -> {
final World world = generationTask.getSelection().world();
if (!generationTasks.containsKey(world.getName())) {
generationTasks.put(world.getName(), generationTask);
chunky.getScheduler().runTask(generationTask);
sender.sendMessagePrefixed("format_continue", world.getName());
sender.sendMessagePrefixed(TranslationKey.FORMAT_CONTINUE, world.getName());
} else {
sender.sendMessagePrefixed("format_started_already", world.getName());
sender.sendMessagePrefixed(TranslationKey.FORMAT_STARTED_ALREADY, world.getName());
}
});
}
@Override
public List<String> tabSuggestions(Sender sender, String[] args) {
if (args.length == 2) {
List<String> suggestions = new ArrayList<>();
public List<String> suggestions(final CommandArguments arguments) {
if (arguments.size() == 1) {
final List<String> suggestions = new ArrayList<>();
chunky.getServer().getWorlds().forEach(world -> suggestions.add(world.getName()));
return suggestions;
}
return Collections.emptyList();
return List.of();
}
}
@@ -2,49 +2,55 @@ package org.popcraft.chunky.command;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.shape.ShapeType;
import org.popcraft.chunky.util.Formatting;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.TranslationKey;
import java.util.List;
import java.util.Optional;
public class CornersCommand extends ChunkyCommand {
public CornersCommand(Chunky chunky) {
super(chunky);
public class CornersCommand implements ChunkyCommand {
private final Chunky chunky;
public CornersCommand(final Chunky chunky) {
this.chunky = chunky;
}
@Override
public void execute(Sender sender, String[] args) {
if (args.length < 5) {
sender.sendMessage("help_corners");
return;
}
Optional<Double> x1 = Input.tryDoubleSuffixed(args[1]);
Optional<Double> z1 = Input.tryDoubleSuffixed(args[2]);
Optional<Double> x2 = Input.tryDoubleSuffixed(args[3]);
Optional<Double> z2 = Input.tryDoubleSuffixed(args[4]);
if (!x1.isPresent() || !z1.isPresent() || !x2.isPresent() || !z2.isPresent()) {
sender.sendMessage("help_corners");
public void execute(final Sender sender, final CommandArguments arguments) {
final Optional<Double> x1 = arguments.next().flatMap(Input::tryDoubleSuffixed);
final Optional<Double> z1 = arguments.next().flatMap(Input::tryDoubleSuffixed);
final Optional<Double> x2 = arguments.next().flatMap(Input::tryDoubleSuffixed);
final Optional<Double> z2 = arguments.next().flatMap(Input::tryDoubleSuffixed);
if (x1.isEmpty() || z1.isEmpty() || x2.isEmpty() || z2.isEmpty()) {
sender.sendMessage(TranslationKey.HELP_CORNERS);
return;
}
if (Input.isPastWorldLimit(x1.get()) || Input.isPastWorldLimit(z1.get()) || Input.isPastWorldLimit(x2.get()) || Input.isPastWorldLimit(z2.get())) {
sender.sendMessage("help_corners");
sender.sendMessage(TranslationKey.HELP_CORNERS);
return;
}
double centerX = (x1.get() + x2.get()) / 2d;
double centerZ = (z1.get() + z2.get()) / 2d;
double radiusX = Math.abs(x1.get() - x2.get()) / 2d;
double radiusZ = Math.abs(z1.get() - z2.get()) / 2d;
final double centerX = (x1.get() + x2.get()) / 2d;
final double centerZ = (z1.get() + z2.get()) / 2d;
final double radiusX = Math.abs(x1.get() - x2.get()) / 2d;
final double radiusZ = Math.abs(z1.get() - z2.get()) / 2d;
chunky.getSelection().center(centerX, centerZ).radiusX(radiusX).radiusZ(radiusZ);
sender.sendMessagePrefixed("format_center", Formatting.number(centerX), Formatting.number(centerZ));
String shape;
sender.sendMessagePrefixed(TranslationKey.FORMAT_CENTER, Formatting.number(centerX), Formatting.number(centerZ));
final String shape;
if (radiusX == radiusZ) {
sender.sendMessagePrefixed("format_radius", Formatting.number(radiusX));
shape = "square";
sender.sendMessagePrefixed(TranslationKey.FORMAT_RADIUS, Formatting.number(radiusX));
shape = ShapeType.SQUARE;
} else {
sender.sendMessagePrefixed("format_radii", Formatting.number(radiusX), Formatting.number(radiusZ));
shape = "rectangle";
sender.sendMessagePrefixed(TranslationKey.FORMAT_RADII, Formatting.number(radiusX), Formatting.number(radiusZ));
shape = ShapeType.RECTANGLE;
}
chunky.getSelection().shape(shape);
sender.sendMessagePrefixed("format_shape", shape);
sender.sendMessagePrefixed(TranslationKey.FORMAT_SHAPE, shape);
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
return List.of();
}
}
@@ -3,52 +3,73 @@ package org.popcraft.chunky.command;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.TranslationKey;
import java.util.ArrayList;
import java.util.List;
import static org.popcraft.chunky.util.Translator.translate;
public class HelpCommand extends ChunkyCommand {
private final List<String> helpMessages;
public class HelpCommand implements ChunkyCommand {
private final Chunky chunky;
private final List<String> helpCommands = List.of(
CommandLiteral.START,
CommandLiteral.PAUSE,
CommandLiteral.CONTINUE,
CommandLiteral.CANCEL,
CommandLiteral.WORLD,
CommandLiteral.WORLDBORDER,
CommandLiteral.CENTER,
CommandLiteral.SPAWN,
CommandLiteral.RADIUS,
CommandLiteral.CORNERS,
CommandLiteral.SHAPE,
CommandLiteral.PATTERN,
CommandLiteral.SILENT,
CommandLiteral.QUIET,
CommandLiteral.TRIM,
CommandLiteral.SELECTION,
CommandLiteral.PROGRESS,
CommandLiteral.BORDER,
CommandLiteral.RELOAD
);
public HelpCommand(Chunky chunky) {
super(chunky);
this.helpMessages = new ArrayList<>();
helpMessages.add("help_start");
helpMessages.add("help_pause");
helpMessages.add("help_continue");
helpMessages.add("help_cancel");
helpMessages.add("help_world");
helpMessages.add("help_worldborder");
helpMessages.add("help_center");
helpMessages.add("help_spawn");
helpMessages.add("help_radius");
helpMessages.add("help_corners");
helpMessages.add("help_shape");
helpMessages.add("help_pattern");
helpMessages.add("help_silent");
helpMessages.add("help_quiet");
helpMessages.add("help_reload");
public HelpCommand(final Chunky chunky) {
this.chunky = chunky;
}
@Override
public void execute(Sender sender, String[] args) {
final StringBuilder help = new StringBuilder();
if (sender.isPlayer()) {
int pageIndexLast = helpMessages.size() / 8;
int pageIndex = (args.length < 2 ? 0 : Math.max(0, Input.tryInteger(args[1]).orElse(1) - 1)) % (pageIndexLast + 1);
int helpIndexFirst = 8 * pageIndex;
int helpIndexLast = Math.min(helpIndexFirst + 8, helpMessages.size());
for (int i = helpIndexFirst; i < helpIndexLast; ++i) {
help.append('\n').append(translate(helpMessages.get(i)));
public void execute(final Sender sender, final CommandArguments arguments) {
final List<String> visibleCommands = new ArrayList<>();
for (final String command : helpCommands) {
if (chunky.getCommands().containsKey(command)) {
visibleCommands.add(command);
}
if (pageIndex != pageIndexLast) {
help.append('\n').append(translate("help_more", "/chunky help " + (pageIndex + 2)));
}
} else {
helpMessages.forEach(message -> help.append('\n').append(translate(message)));
}
sender.sendMessage("help_menu", help.toString());
final int visibleCommandCount = visibleCommands.size();
final StringBuilder help = new StringBuilder();
final int pageIndexLast = visibleCommandCount / 8;
final int pageIndex = (arguments.size() < 1 ? 0 : Math.max(0, arguments.next().flatMap(Input::tryInteger).orElse(1) - 1)) % (pageIndexLast + 1);
final int helpIndexFirst;
final int helpIndexLast;
if (sender.isPlayer()) {
helpIndexFirst = 8 * pageIndex;
helpIndexLast = Math.min(helpIndexFirst + 8, visibleCommandCount);
} else {
helpIndexFirst = 0;
helpIndexLast = visibleCommandCount;
}
for (int i = helpIndexFirst; i < helpIndexLast; ++i) {
help.append('\n').append(translate("help_" + visibleCommands.get(i)));
}
if (sender.isPlayer() && pageIndex != pageIndexLast) {
help.append('\n').append(translate(TranslationKey.HELP_MORE, "/chunky help " + (pageIndex + 2)));
}
sender.sendMessage(TranslationKey.HELP_MENU, help.toString());
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
return List.of();
}
}
@@ -1,38 +1,47 @@
package org.popcraft.chunky.command;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.iterator.PatternType;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.Parameter;
import org.popcraft.chunky.util.TranslationKey;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
public class PatternCommand extends ChunkyCommand {
private static final List<String> PATTERNS = Arrays.asList("concentric", "loop", "spiral");
import static org.popcraft.chunky.util.Translator.translate;
public PatternCommand(Chunky chunky) {
super(chunky);
}
public class PatternCommand implements ChunkyCommand {
private final Chunky chunky;
public void execute(Sender sender, String[] args) {
if (args.length < 2) {
sender.sendMessage("help_pattern");
return;
}
String pattern = args[1].toLowerCase();
if (!PATTERNS.contains(pattern)) {
sender.sendMessage("help_pattern");
return;
}
chunky.getSelection().pattern(pattern);
sender.sendMessagePrefixed("format_pattern", pattern);
public PatternCommand(final Chunky chunky) {
this.chunky = chunky;
}
@Override
public List<String> tabSuggestions(Sender sender, String[] args) {
if (args.length == 2) {
return PATTERNS;
public void execute(final Sender sender, final CommandArguments arguments) {
final Optional<String> optionalType = arguments.next().flatMap(Input::tryPattern);
if (optionalType.isEmpty()) {
sender.sendMessage(TranslationKey.HELP_PATTERN);
return;
}
return Collections.emptyList();
final String type = optionalType.get();
final Optional<String> value = arguments.next();
if (PatternType.CSV.equals(type) && value.isEmpty()) {
sender.sendMessage(TranslationKey.HELP_PATTERN);
return;
}
final Parameter pattern = Parameter.of(type, value.orElse(null));
chunky.getSelection().pattern(pattern);
sender.sendMessagePrefixed(TranslationKey.FORMAT_PATTERN, translate("pattern_" + pattern.getType()));
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
if (arguments.size() == 1) {
return PatternType.ALL;
}
return List.of();
}
}
@@ -5,48 +5,50 @@ import org.popcraft.chunky.GenerationTask;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.TranslationKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class PauseCommand extends ChunkyCommand {
public PauseCommand(Chunky chunky) {
super(chunky);
public class PauseCommand implements ChunkyCommand {
private final Chunky chunky;
public PauseCommand(final Chunky chunky) {
this.chunky = chunky;
}
public void execute(Sender sender, String[] args) {
final Map<World, GenerationTask> generationTasks = chunky.getGenerationTasks();
@Override
public void execute(final Sender sender, final CommandArguments arguments) {
final Map<String, GenerationTask> generationTasks = chunky.getGenerationTasks();
if (generationTasks.isEmpty()) {
sender.sendMessagePrefixed("format_pause_no_tasks");
sender.sendMessagePrefixed(TranslationKey.FORMAT_PAUSE_NO_TASKS);
return;
}
if (args.length > 1) {
final Optional<World> world = Input.tryWorld(chunky, String.join(" ", Arrays.copyOfRange(args, 1, args.length)));
if (!world.isPresent() || !generationTasks.containsKey(world.get())) {
sender.sendMessage("help_pause");
if (arguments.size() > 0) {
final Optional<World> world = Input.tryWorld(chunky, arguments.joined());
if (world.isEmpty() || !generationTasks.containsKey(world.get().getName())) {
sender.sendMessage(TranslationKey.HELP_PAUSE);
} else {
generationTasks.get(world.get()).stop(false);
sender.sendMessagePrefixed("format_pause", world.get().getName());
generationTasks.get(world.get().getName()).stop(false);
sender.sendMessagePrefixed(TranslationKey.FORMAT_PAUSE, world.get().getName());
}
return;
}
for (GenerationTask generationTask : chunky.getGenerationTasks().values()) {
generationTask.stop(false);
sender.sendMessagePrefixed("format_pause", generationTask.getSelection().world().getName());
sender.sendMessagePrefixed(TranslationKey.FORMAT_PAUSE, generationTask.getSelection().world().getName());
}
}
@Override
public List<String> tabSuggestions(Sender sender, String[] args) {
if (args.length == 2) {
List<String> suggestions = new ArrayList<>();
public List<String> suggestions(final CommandArguments arguments) {
if (arguments.size() == 1) {
final List<String> suggestions = new ArrayList<>();
chunky.getServer().getWorlds().forEach(world -> suggestions.add(world.getName()));
return suggestions;
}
return Collections.emptyList();
return List.of();
}
}
@@ -4,25 +4,34 @@ import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.GenerationTask;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.util.TranslationKey;
import java.util.List;
import java.util.Map;
public class ProgressCommand extends ChunkyCommand {
public ProgressCommand(Chunky chunky) {
super(chunky);
public class ProgressCommand implements ChunkyCommand {
private final Chunky chunky;
public ProgressCommand(final Chunky chunky) {
this.chunky = chunky;
}
@Override
public void execute(Sender sender, String[] args) {
final Map<World, GenerationTask> generationTasks = chunky.getGenerationTasks();
public void execute(final Sender sender, final CommandArguments arguments) {
final Map<String, GenerationTask> generationTasks = chunky.getGenerationTasks();
if (generationTasks.isEmpty()) {
sender.sendMessagePrefixed("format_progress_no_tasks");
sender.sendMessagePrefixed(TranslationKey.FORMAT_PROGRESS_NO_TASKS);
return;
}
for (World world : chunky.getServer().getWorlds()) {
if (generationTasks.containsKey(world)) {
generationTasks.get(world).getProgress().sendUpdate(sender);
if (generationTasks.containsKey(world.getName())) {
generationTasks.get(world.getName()).getProgress().sendUpdate(sender);
}
}
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
return List.of();
}
}
@@ -3,25 +3,32 @@ package org.popcraft.chunky.command;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.TranslationKey;
import java.util.List;
import java.util.Optional;
public class QuietCommand extends ChunkyCommand {
public QuietCommand(Chunky chunky) {
super(chunky);
public class QuietCommand implements ChunkyCommand {
private final Chunky chunky;
public QuietCommand(final Chunky chunky) {
this.chunky = chunky;
}
public void execute(Sender sender, String[] args) {
Optional<Integer> newQuiet = Optional.empty();
if (args.length > 1) {
newQuiet = Input.tryInteger(args[1]);
}
if (!newQuiet.isPresent()) {
sender.sendMessage("help_quiet");
@Override
public void execute(final Sender sender, final CommandArguments arguments) {
final Optional<Integer> newQuiet = arguments.next().flatMap(Input::tryInteger);
if (newQuiet.isEmpty()) {
sender.sendMessage(TranslationKey.HELP_QUIET);
return;
}
int quietInterval = Math.max(0, newQuiet.get());
chunky.getOptions().setQuietInterval(quietInterval);
sender.sendMessagePrefixed("format_quiet", quietInterval);
final int quietInterval = Math.max(0, newQuiet.get());
chunky.getConfig().setUpdateInterval(quietInterval);
sender.sendMessagePrefixed(TranslationKey.FORMAT_QUIET, quietInterval);
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
return List.of();
}
}
@@ -5,48 +5,56 @@ import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.util.Formatting;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.TranslationKey;
import java.util.List;
import java.util.Optional;
public class RadiusCommand extends ChunkyCommand {
public RadiusCommand(Chunky chunky) {
super(chunky);
public class RadiusCommand implements ChunkyCommand {
private final Chunky chunky;
public RadiusCommand(final Chunky chunky) {
this.chunky = chunky;
}
public void execute(Sender sender, String[] args) {
if (args.length < 2) {
sender.sendMessage("help_radius");
return;
}
final Optional<Integer> signX = Input.trySign(args[1]);
final Optional<Double> newRadiusX = Input.tryDoubleSuffixed(signX.isPresent() ? args[1].substring(1) : args[1]);
if (!newRadiusX.isPresent() || newRadiusX.get() < 0 || Input.isPastWorldLimit(newRadiusX.get())) {
sender.sendMessage("help_radius");
@Override
public void execute(final Sender sender, final CommandArguments arguments) {
final Optional<String> newX = arguments.next();
final Optional<Integer> signX = newX.flatMap(Input::trySign);
final Optional<Double> newRadiusX = newX.map(x -> signX.isPresent() ? x.substring(1) : x).flatMap(Input::tryDoubleSuffixed);
if (newRadiusX.isEmpty() || newRadiusX.get() < 0 || Input.isPastWorldLimit(newRadiusX.get())) {
sender.sendMessage(TranslationKey.HELP_RADIUS);
return;
}
final Selection current = chunky.getSelection().build();
double radiusX = signX.map(sign -> current.radiusX() + sign * newRadiusX.get()).orElseGet(newRadiusX::get);
final double radiusX = signX.map(sign -> current.radiusX() + sign * newRadiusX.get()).orElseGet(newRadiusX::get);
if (radiusX < 0 || Input.isPastWorldLimit(radiusX)) {
sender.sendMessage("help_radius");
sender.sendMessage(TranslationKey.HELP_RADIUS);
return;
}
if (args.length > 2) {
Optional<Integer> signZ = Input.trySign(args[2]);
Optional<Double> newRadiusZ = Input.tryDoubleSuffixed(signZ.isPresent() ? args[2].substring(1) : args[2]);
if (!newRadiusZ.isPresent() || newRadiusZ.get() < 0 || Input.isPastWorldLimit(newRadiusZ.get())) {
sender.sendMessage("help_radius");
final Optional<String> newZ = arguments.next();
if (newZ.isPresent()) {
final Optional<Integer> signZ = newZ.flatMap(Input::trySign);
final Optional<Double> newRadiusZ = newZ.map(z -> signZ.isPresent() ? z.substring(1) : z).flatMap(Input::tryDoubleSuffixed);
if (newRadiusZ.isEmpty() || newRadiusZ.get() < 0 || Input.isPastWorldLimit(newRadiusZ.get())) {
sender.sendMessage(TranslationKey.HELP_RADIUS);
return;
}
final double radiusZ = signZ.map(sign -> current.radiusZ() + sign * newRadiusZ.get()).orElseGet(newRadiusZ::get);
if (radiusZ < 0 || Input.isPastWorldLimit(radiusZ)) {
sender.sendMessage("help_radius");
sender.sendMessage(TranslationKey.HELP_RADIUS);
return;
}
chunky.getSelection().radiusX(radiusX).radiusZ(radiusZ);
sender.sendMessagePrefixed("format_radii", Formatting.number(radiusX), Formatting.number(radiusZ));
sender.sendMessagePrefixed(TranslationKey.FORMAT_RADII, Formatting.number(radiusX), Formatting.number(radiusZ));
} else {
chunky.getSelection().radius(radiusX);
sender.sendMessagePrefixed("format_radius", Formatting.number(radiusX));
sender.sendMessagePrefixed(TranslationKey.FORMAT_RADIUS, Formatting.number(radiusX));
}
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
return List.of();
}
}
@@ -1,23 +1,43 @@
package org.popcraft.chunky.command;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.event.command.ReloadCommandEvent;
import org.popcraft.chunky.platform.Config;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.util.TranslationKey;
public class ReloadCommand extends ChunkyCommand {
public ReloadCommand(Chunky chunky) {
super(chunky);
import java.util.List;
public class ReloadCommand implements ChunkyCommand {
private final Chunky chunky;
public ReloadCommand(final Chunky chunky) {
this.chunky = chunky;
}
@Override
public void execute(Sender sender, String[] args) {
if (!chunky.getGenerationTasks().isEmpty()) {
sender.sendMessagePrefixed("format_reload_tasks_running");
return;
public void execute(final Sender sender, final CommandArguments arguments) {
final String type = arguments.next().orElse(null);
if ("tasks".equals(type)) {
if (!chunky.getGenerationTasks().isEmpty()) {
sender.sendMessagePrefixed(TranslationKey.FORMAT_RELOAD_TASKS_RUNNING);
return;
}
chunky.getTaskLoader().reload();
} else {
final Config config = chunky.getServer().getConfig();
config.reload();
chunky.setLanguage(config.getLanguage());
chunky.getEventBus().call(new ReloadCommandEvent());
}
Config config = chunky.getServer().getConfig();
config.reload();
chunky.setLanguage(config.getLanguage());
sender.sendMessagePrefixed("format_reload");
sender.sendMessagePrefixed(TranslationKey.FORMAT_RELOAD);
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
if (arguments.size() == 1) {
return List.of("config", "tasks");
}
return List.of();
}
}
@@ -0,0 +1,40 @@
package org.popcraft.chunky.command;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.util.Formatting;
import org.popcraft.chunky.util.TranslationKey;
import java.util.List;
import static org.popcraft.chunky.util.Translator.translate;
public class SelectionCommand implements ChunkyCommand {
private final Chunky chunky;
public SelectionCommand(final Chunky chunky) {
this.chunky = chunky;
}
@Override
public void execute(final Sender sender, final CommandArguments arguments) {
final Selection current = chunky.getSelection().build();
sender.sendMessagePrefixed(TranslationKey.FORMAT_SELECTION);
sender.sendMessage(TranslationKey.FORMAT_SELECTION_WORLD, current.world().getName());
sender.sendMessage(TranslationKey.FORMAT_SELECTION_SHAPE, translate("shape_" + current.shape()));
sender.sendMessage(TranslationKey.FORMAT_SELECTION_CENTER, Formatting.number(current.centerX()), Formatting.number(current.centerZ()));
final double radiusX = current.radiusX();
final double radiusZ = current.radiusZ();
if (radiusX == radiusZ) {
sender.sendMessage(TranslationKey.FORMAT_SELECTION_RADIUS, Formatting.number(radiusX));
} else {
sender.sendMessage(TranslationKey.FORMAT_SELECTION_RADII, Formatting.number(radiusX), Formatting.number(radiusZ));
}
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
return List.of();
}
}
@@ -2,39 +2,39 @@ package org.popcraft.chunky.command;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.shape.ShapeType;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.TranslationKey;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static org.popcraft.chunky.util.Translator.translate;
public class ShapeCommand extends ChunkyCommand {
public ShapeCommand(Chunky chunky) {
super(chunky);
}
public class ShapeCommand implements ChunkyCommand {
private final Chunky chunky;
public void execute(Sender sender, String[] args) {
if (args.length < 2) {
sender.sendMessage("help_shape");
return;
}
Optional<String> inputShape = Input.tryShape(args[1]);
if (!inputShape.isPresent()) {
sender.sendMessage("help_shape");
return;
}
String shape = inputShape.get();
chunky.getSelection().shape(shape);
sender.sendMessagePrefixed("format_shape", translate("shape_" + shape));
public ShapeCommand(final Chunky chunky) {
this.chunky = chunky;
}
@Override
public List<String> tabSuggestions(Sender sender, String[] args) {
if (args.length == 2) {
return Input.SHAPES;
public void execute(final Sender sender, final CommandArguments arguments) {
final Optional<String> inputShape = arguments.next().flatMap(Input::tryShape);
if (inputShape.isEmpty()) {
sender.sendMessage(TranslationKey.HELP_SHAPE);
return;
}
return Collections.emptyList();
final String shape = inputShape.get();
chunky.getSelection().shape(shape);
sender.sendMessagePrefixed(TranslationKey.FORMAT_SHAPE, translate("shape_" + shape));
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
if (arguments.size() == 1) {
return ShapeType.all();
}
return List.of();
}
}
@@ -2,17 +2,28 @@ package org.popcraft.chunky.command;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.util.TranslationKey;
import java.util.List;
import static org.popcraft.chunky.util.Translator.translate;
public class SilentCommand extends ChunkyCommand {
public SilentCommand(Chunky chunky) {
super(chunky);
public class SilentCommand implements ChunkyCommand {
private final Chunky chunky;
public SilentCommand(final Chunky chunky) {
this.chunky = chunky;
}
public void execute(Sender sender, String[] args) {
chunky.getOptions().setSilent(!chunky.getOptions().isSilent());
String status = translate(chunky.getOptions().isSilent() ? "enabled" : "disabled");
sender.sendMessagePrefixed("format_silent", status);
@Override
public void execute(final Sender sender, final CommandArguments arguments) {
chunky.getConfig().setSilent(!chunky.getConfig().isSilent());
final String status = translate(chunky.getConfig().isSilent() ? TranslationKey.ENABLED : TranslationKey.DISABLED);
sender.sendMessagePrefixed(TranslationKey.FORMAT_SILENT, status);
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
return List.of();
}
}
@@ -4,16 +4,26 @@ import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.util.Formatting;
import org.popcraft.chunky.util.TranslationKey;
public class SpawnCommand extends ChunkyCommand {
public SpawnCommand(Chunky chunky) {
super(chunky);
import java.util.List;
public class SpawnCommand implements ChunkyCommand {
private final Chunky chunky;
public SpawnCommand(final Chunky chunky) {
this.chunky = chunky;
}
@Override
public void execute(Sender sender, String[] args) {
public void execute(final Sender sender, final CommandArguments arguments) {
chunky.getSelection().spawn();
Selection current = chunky.getSelection().build();
sender.sendMessagePrefixed("format_center", Formatting.number(current.centerX()), Formatting.number(current.centerZ()));
final Selection current = chunky.getSelection().build();
sender.sendMessagePrefixed(TranslationKey.FORMAT_CENTER, Formatting.number(current.centerX()), Formatting.number(current.centerZ()));
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
return List.of();
}
}
@@ -5,100 +5,104 @@ import org.popcraft.chunky.GenerationTask;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.shape.ShapeType;
import org.popcraft.chunky.util.Formatting;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.TranslationKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static org.popcraft.chunky.util.Translator.translate;
public class StartCommand extends ChunkyCommand {
public StartCommand(Chunky chunky) {
super(chunky);
public class StartCommand implements ChunkyCommand {
private final Chunky chunky;
public StartCommand(final Chunky chunky) {
this.chunky = chunky;
}
public void execute(Sender sender, String[] args) {
if (args.length > 1) {
Optional<World> world = Input.tryWorld(chunky, args[1]);
@Override
public void execute(final Sender sender, final CommandArguments arguments) {
if (arguments.size() > 0) {
final Optional<World> world = arguments.next().flatMap(arg -> Input.tryWorld(chunky, arg));
if (world.isPresent()) {
chunky.getSelection().world(world.get());
} else {
sender.sendMessage("help_start");
sender.sendMessage(TranslationKey.HELP_START);
return;
}
}
if (args.length > 2) {
Optional<String> shape = Input.tryShape(args[2]);
if (arguments.size() > 1) {
final Optional<String> shape = arguments.next().flatMap(Input::tryShape);
if (shape.isPresent()) {
chunky.getSelection().shape(shape.get());
} else {
sender.sendMessage("help_start");
sender.sendMessage(TranslationKey.HELP_START);
return;
}
}
if (args.length > 3) {
Optional<Double> centerX = Input.tryDoubleSuffixed(args[3]).filter(cx -> !Input.isPastWorldLimit(cx));
Optional<Double> centerZ = Input.tryDoubleSuffixed(args.length > 4 ? args[4] : null).filter(cz -> !Input.isPastWorldLimit(cz));
if (arguments.size() > 2) {
final Optional<Double> centerX = arguments.next().flatMap(Input::tryDoubleSuffixed).filter(c -> !Input.isPastWorldLimit(c));
final Optional<Double> centerZ = arguments.next().flatMap(Input::tryDoubleSuffixed).filter(c -> !Input.isPastWorldLimit(c));
if (centerX.isPresent() && centerZ.isPresent()) {
chunky.getSelection().center(centerX.get(), centerZ.get());
} else {
sender.sendMessage("help_start");
sender.sendMessage(TranslationKey.HELP_START);
return;
}
}
if (args.length > 5) {
Optional<Double> radiusX = Input.tryDoubleSuffixed(args[5]).filter(rx -> rx >= 0 && !Input.isPastWorldLimit(rx));
if (arguments.size() > 4) {
final Optional<Double> radiusX = arguments.next().flatMap(Input::tryDoubleSuffixed).filter(r -> r >= 0 && !Input.isPastWorldLimit(r));
if (radiusX.isPresent()) {
chunky.getSelection().radius(radiusX.get());
} else {
sender.sendMessage("help_start");
sender.sendMessage(TranslationKey.HELP_START);
return;
}
}
if (args.length > 6) {
Optional<Double> radiusZ = Input.tryDoubleSuffixed(args[6]).filter(rz -> rz >= 0 && !Input.isPastWorldLimit(rz));
if (arguments.size() > 5) {
final Optional<Double> radiusZ = arguments.next().flatMap(Input::tryDoubleSuffixed).filter(r -> r >= 0 && !Input.isPastWorldLimit(r));
if (radiusZ.isPresent()) {
chunky.getSelection().radiusZ(radiusZ.get());
} else {
sender.sendMessage("help_start");
sender.sendMessage(TranslationKey.HELP_START);
return;
}
}
final Selection current = chunky.getSelection().build();
if (chunky.getGenerationTasks().containsKey(current.world())) {
sender.sendMessagePrefixed("format_started_already", current.world().getName());
if (chunky.getGenerationTasks().containsKey(current.world().getName())) {
sender.sendMessagePrefixed(TranslationKey.FORMAT_STARTED_ALREADY, current.world().getName());
return;
}
if (current.radiusX() > chunky.getLimit()) {
sender.sendMessagePrefixed("format_start_limit", Formatting.number(chunky.getLimit()));
sender.sendMessagePrefixed(TranslationKey.FORMAT_START_LIMIT, Formatting.number(chunky.getLimit()));
return;
}
final Runnable startAction = () -> {
GenerationTask generationTask = new GenerationTask(chunky, current);
chunky.getGenerationTasks().put(current.world(), generationTask);
final GenerationTask generationTask = new GenerationTask(chunky, current);
chunky.getGenerationTasks().put(current.world().getName(), generationTask);
chunky.getScheduler().runTask(generationTask);
sender.sendMessagePrefixed("format_start", current.world().getName(), translate("shape_" + current.shape()), Formatting.number(current.centerX()), Formatting.number(current.centerZ()), Formatting.radius(current));
sender.sendMessagePrefixed(TranslationKey.FORMAT_START, current.world().getName(), translate("shape_" + current.shape()), Formatting.number(current.centerX()), Formatting.number(current.centerZ()), Formatting.radius(current));
};
if (chunky.getConfig().loadTask(current.world()).isPresent()) {
if (chunky.getTaskLoader().loadTask(current.world()).filter(task -> !task.isCancelled()).isPresent()) {
chunky.setPendingAction(sender, startAction);
sender.sendMessagePrefixed("format_start_confirm", "/chunky continue", "/chunky confirm");
sender.sendMessagePrefixed(TranslationKey.FORMAT_START_CONFIRM, "/chunky continue", "/chunky confirm");
} else {
startAction.run();
}
}
@Override
public List<String> tabSuggestions(Sender sender, String[] args) {
if (args.length == 2) {
List<String> suggestions = new ArrayList<>();
public List<String> suggestions(final CommandArguments arguments) {
if (arguments.size() == 1) {
final List<String> suggestions = new ArrayList<>();
chunky.getServer().getWorlds().forEach(world -> suggestions.add(world.getName()));
return suggestions;
} else if (args.length == 3) {
return Input.SHAPES;
} else if (arguments.size() == 2) {
return ShapeType.all();
}
return Collections.emptyList();
return List.of();
}
}
@@ -2,147 +2,171 @@ package org.popcraft.chunky.command;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.nbt.LongTag;
import org.popcraft.chunky.nbt.Tag;
import org.popcraft.chunky.nbt.TagType;
import org.popcraft.chunky.nbt.util.ChunkFilter;
import org.popcraft.chunky.nbt.util.RegionFile;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.shape.Shape;
import org.popcraft.chunky.shape.ShapeFactory;
import org.popcraft.chunky.shape.ShapeType;
import org.popcraft.chunky.util.ChunkCoordinate;
import org.popcraft.chunky.util.Formatting;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.TranslationKey;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;
import static org.popcraft.chunky.util.Translator.translate;
public class TrimCommand extends ChunkyCommand {
public TrimCommand(Chunky chunky) {
super(chunky);
public class TrimCommand implements ChunkyCommand {
private final Chunky chunky;
public TrimCommand(final Chunky chunky) {
this.chunky = chunky;
}
@Override
public void execute(Sender sender, String[] args) {
if (args.length > 1) {
Optional<World> world = Input.tryWorld(chunky, args[1]);
public void execute(final Sender sender, final CommandArguments arguments) {
if (arguments.size() > 0) {
final Optional<World> world = arguments.next().flatMap(arg -> Input.tryWorld(chunky, arg));
if (world.isPresent()) {
chunky.getSelection().world(world.get());
} else {
sender.sendMessage("help_trim");
sender.sendMessage(TranslationKey.HELP_TRIM);
return;
}
}
if (args.length > 2) {
Optional<String> shape = Input.tryShape(args[2]);
if (arguments.size() > 1) {
final Optional<String> shape = arguments.next().flatMap(Input::tryShape);
if (shape.isPresent()) {
chunky.getSelection().shape(shape.get());
} else {
sender.sendMessage("help_trim");
sender.sendMessage(TranslationKey.HELP_TRIM);
return;
}
}
if (args.length > 3) {
Optional<Double> centerX = Input.tryDoubleSuffixed(args[3]).filter(cx -> !Input.isPastWorldLimit(cx));
Optional<Double> centerZ = Input.tryDoubleSuffixed(args.length > 4 ? args[4] : null).filter(cz -> !Input.isPastWorldLimit(cz));
if (arguments.size() > 2) {
final Optional<Double> centerX = arguments.next().flatMap(Input::tryDoubleSuffixed).filter(c -> !Input.isPastWorldLimit(c));
final Optional<Double> centerZ = arguments.next().flatMap(Input::tryDoubleSuffixed).filter(c -> !Input.isPastWorldLimit(c));
if (centerX.isPresent() && centerZ.isPresent()) {
chunky.getSelection().center(centerX.get(), centerZ.get());
} else {
sender.sendMessage("help_trim");
sender.sendMessage(TranslationKey.HELP_TRIM);
return;
}
}
if (args.length > 5) {
Optional<Double> radiusX = Input.tryDoubleSuffixed(args[5]).filter(rx -> rx >= 0 && !Input.isPastWorldLimit(rx));
if (arguments.size() > 4) {
final Optional<Double> radiusX = arguments.next().flatMap(Input::tryDoubleSuffixed).filter(r -> r >= 0 && !Input.isPastWorldLimit(r));
if (radiusX.isPresent()) {
chunky.getSelection().radius(radiusX.get());
} else {
sender.sendMessage("help_trim");
sender.sendMessage(TranslationKey.HELP_TRIM);
return;
}
}
if (args.length > 6) {
Optional<Double> radiusZ = Input.tryDoubleSuffixed(args[6]).filter(rz -> rz >= 0 && !Input.isPastWorldLimit(rz));
if (arguments.size() > 5) {
final Optional<Double> radiusZ = arguments.next().flatMap(Input::tryDoubleSuffixed).filter(r -> r >= 0 && !Input.isPastWorldLimit(r));
if (radiusZ.isPresent()) {
chunky.getSelection().radiusZ(radiusZ.get());
} else {
sender.sendMessage("help_trim");
sender.sendMessage(TranslationKey.HELP_TRIM);
return;
}
}
final boolean inside;
if (arguments.size() > 6) {
final Optional<String> side = arguments.next().map(String::toLowerCase).filter(s -> "outside".equals(s) || "inside".equals(s));
if (side.isPresent()) {
inside = side.map("inside"::equals).orElse(false);
} else {
sender.sendMessage(TranslationKey.HELP_TRIM);
return;
}
} else {
inside = false;
}
final int inhabitedTime = arguments.next().flatMap(Input::tryIntegerSuffixed).orElse(Integer.MAX_VALUE);
final boolean inhabitedTimeCheck = inhabitedTime < Integer.MAX_VALUE;
final Selection selection = chunky.getSelection().build();
final Shape shape = ShapeFactory.getShape(selection);
final Runnable deletionAction = () -> chunky.getScheduler().runTask(() -> {
sender.sendMessagePrefixed("format_start", selection.world().getName(), translate("shape_" + selection.shape()), Formatting.number(selection.centerX()), Formatting.number(selection.centerZ()), Formatting.radius(selection));
sender.sendMessagePrefixed(TranslationKey.FORMAT_START, selection.world().getName(), translate("shape_" + selection.shape()), Formatting.number(selection.centerX()), Formatting.number(selection.centerZ()), Formatting.radius(selection));
TrimCommand.Task trimTask = new TrimCommand.Task();
chunky.getTrimTasks().put(selection.world().getName(), trimTask);
final Optional<Path> regionPath = selection.world().getRegionDirectory();
final Optional<Path> poiPath = selection.world().getPOIDirectory();
final Optional<Path> entitiesPath = selection.world().getEntitiesDirectory();
final AtomicLong finishedRegions = new AtomicLong();
final AtomicLong deleted = new AtomicLong();
final long startTime = System.currentTimeMillis();
final AtomicLong updateTime = new AtomicLong(startTime);
try {
if (regionPath.isPresent()) {
Files.walk(regionPath.get()).forEach(region -> deleted.getAndAdd(checkRegion(region, shape)));
}
if (poiPath.isPresent()) {
Files.walk(poiPath.get()).forEach(region -> checkRegion(region, shape));
}
if (entitiesPath.isPresent()) {
Files.walk(entitiesPath.get()).forEach(region -> checkRegion(region, shape));
try (final Stream<Path> files = Files.list(regionPath.get())) {
final List<Path> regions = files
.filter(file -> ChunkCoordinate.fromRegionFile(file.getFileName().toString()).isPresent())
.toList();
final long totalRegions = regions.size();
for (final Path region : regions) {
if (trimTask.isCancelled()) {
break;
}
deleted.getAndAdd(checkRegion(selection.world(), region.getFileName().toString(), shape, inside, inhabitedTimeCheck, inhabitedTime));
finishedRegions.getAndIncrement();
if (!trimTask.isCancelled() && !chunky.getConfig().isSilent()) {
final long currentTime = System.currentTimeMillis();
final boolean updateIntervalElapsed = ((currentTime - updateTime.get()) / 1e3) > chunky.getConfig().getUpdateInterval();
if (updateIntervalElapsed || finishedRegions.get() == totalRegions) {
chunky.getServer().getConsole().sendMessagePrefixed(TranslationKey.TASK_TRIM_UPDATE, selection.world().getName(), finishedRegions.get(), String.format("%.2f", 100f * finishedRegions.get() / totalRegions));
updateTime.set(currentTime);
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
final long totalTime = System.currentTimeMillis() - startTime;
sender.sendMessagePrefixed("task_trim", deleted.get(), selection.world().getName(), String.format("%.3f", totalTime / 1e3f));
chunky.getTrimTasks().remove(selection.world().getName());
sender.sendMessagePrefixed(TranslationKey.TASK_TRIM, deleted.get(), selection.world().getName(), String.format("%.3f", totalTime / 1e3f));
});
chunky.setPendingAction(sender, deletionAction);
sender.sendMessagePrefixed("format_trim_confirm", selection.world().getName(), translate("shape_" + selection.shape()), Formatting.number(selection.centerX()), Formatting.number(selection.centerZ()), Formatting.radius(selection), "/chunky confirm");
sender.sendMessagePrefixed(inside ? TranslationKey.FORMAT_TRIM_CONFIRM_INSIDE : TranslationKey.FORMAT_TRIM_CONFIRM, selection.world().getName(), translate("shape_" + selection.shape()), Formatting.number(selection.centerX()), Formatting.number(selection.centerZ()), Formatting.radius(selection), "/chunky confirm");
if (inhabitedTimeCheck) {
sender.sendMessagePrefixed(TranslationKey.FORMAT_TRIM_CONFIRM_INHABITED, Formatting.number(inhabitedTime));
}
}
private int checkRegion(final Path region, final Shape shape) {
Optional<ChunkCoordinate> regionCoordinate = tryRegionCoordinate(region);
if (!regionCoordinate.isPresent()) {
private int checkRegion(final World world, final String regionFileName, final Shape shape, final boolean inside, final boolean inhabitedTimeCheck, final int inhabitedTime) {
final Optional<ChunkCoordinate> regionCoordinate = ChunkCoordinate.fromRegionFile(regionFileName);
if (regionCoordinate.isEmpty()) {
return 0;
}
int chunkX = regionCoordinate.get().x << 5;
int chunkZ = regionCoordinate.get().z << 5;
if (shouldDeleteRegion(shape, chunkX, chunkZ)) {
return deleteRegion(region);
final int chunkX = regionCoordinate.get().x() << 5;
final int chunkZ = regionCoordinate.get().z() << 5;
if (!inhabitedTimeCheck && shouldDeleteRegion(shape, inside, chunkX, chunkZ)) {
return deleteRegion(world, regionFileName);
} else {
return trimRegion(region, shape, chunkX, chunkZ);
return trimRegion(world, regionFileName, shape, inside, chunkX, chunkZ, inhabitedTimeCheck, inhabitedTime);
}
}
private Optional<ChunkCoordinate> tryRegionCoordinate(final Path region) {
final String fileName = region.getFileName().toString();
if (!fileName.startsWith("r.")) {
return Optional.empty();
}
final int extension = fileName.indexOf(".mca");
if (extension < 2) {
return Optional.empty();
}
final String regionCoordinates = fileName.substring(2, extension);
final int separator = regionCoordinates.indexOf('.');
Optional<Integer> regionX = Input.tryInteger(regionCoordinates.substring(0, separator));
Optional<Integer> regionZ = Input.tryInteger(regionCoordinates.substring(separator + 1));
if (regionX.isPresent() && regionZ.isPresent()) {
return Optional.of(new ChunkCoordinate(regionX.get(), regionZ.get()));
}
return Optional.empty();
}
private boolean shouldDeleteRegion(final Shape shape, final int chunkX, final int chunkZ) {
private boolean shouldDeleteRegion(final Shape shape, final boolean inside, final int chunkX, final int chunkZ) {
for (int offsetX = 0; offsetX < 32; ++offsetX) {
for (int offsetZ = 0; offsetZ < 32; ++offsetZ) {
int chunkCenterX = ((chunkX + offsetX) << 4) + 8;
int chunkCenterZ = ((chunkZ + offsetZ) << 4) + 8;
if (shape.isBounding(chunkCenterX, chunkCenterZ)) {
final int chunkCenterX = ((chunkX + offsetX) << 4) + 8;
final int chunkCenterZ = ((chunkZ + offsetZ) << 4) + 8;
if (inside != shape.isBounding(chunkCenterX, chunkCenterZ)) {
return false;
}
}
@@ -150,9 +174,18 @@ public class TrimCommand extends ChunkyCommand {
return true;
}
private int deleteRegion(final Path region) {
private int deleteRegion(final World world, final String regionFileName) {
try {
Files.deleteIfExists(region);
final Path regionPath = world.getRegionDirectory().map(region -> region.resolve(regionFileName)).orElseThrow(IllegalStateException::new);
Files.deleteIfExists(regionPath);
final Path poiPath = world.getPOIDirectory().map(region -> region.resolve(regionFileName)).orElse(null);
if (poiPath != null) {
Files.deleteIfExists(poiPath);
}
final Path entitiesPath = world.getEntitiesDirectory().map(region -> region.resolve(regionFileName)).orElse(null);
if (entitiesPath != null) {
Files.deleteIfExists(entitiesPath);
}
return 1024;
} catch (IOException e) {
e.printStackTrace();
@@ -160,42 +193,98 @@ public class TrimCommand extends ChunkyCommand {
}
}
private int trimRegion(final Path region, final Shape shape, final int chunkX, final int chunkZ) {
private int trimRegion(final World world, final String regionFileName, final Shape shape, final boolean inside, final int chunkX, final int chunkZ, final boolean inhabitedTimeCheck, final int inhabitedTime) {
final Path regionPath = world.getRegionDirectory().map(region -> region.resolve(regionFileName)).orElseThrow(IllegalStateException::new);
final Path poiPath = world.getPOIDirectory().map(region -> region.resolve(regionFileName)).orElse(null);
final Path entitiesPath = world.getEntitiesDirectory().map(region -> region.resolve(regionFileName)).orElse(null);
int marked = 0;
int deleted = 0;
try (RandomAccessFile regionFile = new RandomAccessFile(region.toFile(), "rw")) {
final RegionFile regionData = inhabitedTimeCheck ? new RegionFile(regionPath.toFile(), ChunkFilter.of(TagType.LONG, "InhabitedTime")) : null;
try (final RandomAccessFile regionFile = new RandomAccessFile(regionPath.toFile(), "rw");
final RandomAccessFile poiFile = poiPath == null || Files.notExists(poiPath) ? null : new RandomAccessFile(poiPath.toFile(), "rw");
final RandomAccessFile entitiesFile = entitiesPath == null || Files.notExists(entitiesPath) ? null : new RandomAccessFile(entitiesPath.toFile(), "rw")) {
if (regionFile.length() < 4096) {
return 0;
}
final boolean poiValid = poiFile != null && poiFile.length() >= 4096;
final boolean entitiesValid = entitiesFile != null && entitiesFile.length() >= 4096;
for (int offsetX = 0; offsetX < 32; ++offsetX) {
for (int offsetZ = 0; offsetZ < 32; ++offsetZ) {
int chunkCenterX = ((chunkX + offsetX) << 4) + 8;
int chunkCenterZ = ((chunkZ + offsetZ) << 4) + 8;
if (!shape.isBounding(chunkCenterX, chunkCenterZ)) {
int chunkLocation = ((offsetX % 32) + (offsetZ % 32) * 32) * 4;
final int offsetChunkX = chunkX + offsetX;
final int offsetChunkZ = chunkZ + offsetZ;
final int chunkCenterX = (offsetChunkX << 4) + 8;
final int chunkCenterZ = (offsetChunkZ << 4) + 8;
final boolean trimChunk;
if (inside) {
trimChunk = shape.isBounding(chunkCenterX, chunkCenterZ);
} else {
trimChunk = !shape.isBounding(chunkCenterX, chunkCenterZ);
}
final boolean trimInhabited = regionData == null || regionData.getChunk(offsetChunkX, offsetChunkZ)
.map(chunk -> {
final Tag tag = chunk.getData();
if (!(tag instanceof final LongTag inhabited)) {
return true;
}
return inhabited.value() <= inhabitedTime;
})
.orElse(true);
if (trimChunk && trimInhabited) {
++marked;
final int chunkLocation = ((offsetX % 32) + (offsetZ % 32) * 32) * 4;
regionFile.seek(chunkLocation);
if (regionFile.readInt() != 0) {
regionFile.seek(chunkLocation);
regionFile.writeInt(0);
++deleted;
}
if (poiValid) {
poiFile.seek(chunkLocation);
if (poiFile.readInt() != 0) {
poiFile.seek(chunkLocation);
poiFile.writeInt(0);
}
}
if (entitiesValid) {
entitiesFile.seek(chunkLocation);
if (entitiesFile.readInt() != 0) {
entitiesFile.seek(chunkLocation);
entitiesFile.writeInt(0);
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
if (inhabitedTimeCheck && marked == 1024) {
deleteRegion(world, regionFileName);
}
return deleted;
}
@Override
public List<String> tabSuggestions(Sender sender, String[] args) {
if (args.length == 2) {
List<String> suggestions = new ArrayList<>();
public List<String> suggestions(final CommandArguments arguments) {
if (arguments.size() == 1) {
final List<String> suggestions = new ArrayList<>();
chunky.getServer().getWorlds().forEach(world -> suggestions.add(world.getName()));
return suggestions;
} else if (args.length == 3) {
return Input.SHAPES;
} else if (arguments.size() == 2) {
return ShapeType.all();
}
return List.of();
}
public static final class Task {
private boolean cancelled;
public boolean isCancelled() {
return cancelled;
}
public void setCancelled(final boolean cancelled) {
this.cancelled = cancelled;
}
return Collections.emptyList();
}
}
@@ -7,41 +7,71 @@ import org.popcraft.chunky.integration.Integration;
import org.popcraft.chunky.platform.Border;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.util.Coordinate;
import org.popcraft.chunky.platform.util.Vector2;
import org.popcraft.chunky.util.Formatting;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.TranslationKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class WorldBorderCommand extends ChunkyCommand {
public WorldBorderCommand(Chunky chunky) {
super(chunky);
public class WorldBorderCommand implements ChunkyCommand {
private final Chunky chunky;
public WorldBorderCommand(final Chunky chunky) {
this.chunky = chunky;
}
public void execute(Sender sender, String[] args) {
Selection previous = chunky.getSelection().build();
@Override
public void execute(final Sender sender, final CommandArguments arguments) {
if (arguments.size() > 0) {
final Optional<World> world = arguments.next().flatMap(arg -> Input.tryWorld(chunky, arg));
if (world.isPresent()) {
chunky.getSelection().world(world.get());
} else {
sender.sendMessage(TranslationKey.HELP_WORLDBORDER);
return;
}
}
final Selection previous = chunky.getSelection().build();
if (!setBorderViaIntegration(previous.world())) {
chunky.getSelection().worldborder();
}
Selection current = chunky.getSelection().build();
sender.sendMessagePrefixed("format_center", Formatting.number(current.centerX()), Formatting.number(current.centerZ()));
final Selection current = chunky.getSelection().build();
sender.sendMessagePrefixed(TranslationKey.FORMAT_CENTER, Formatting.number(current.centerX()), Formatting.number(current.centerZ()));
if (current.radiusX() == current.radiusZ()) {
sender.sendMessagePrefixed("format_radius", Formatting.number(current.radiusX()));
sender.sendMessagePrefixed(TranslationKey.FORMAT_RADIUS, Formatting.number(current.radiusX()));
} else {
sender.sendMessagePrefixed("format_radii", Formatting.number(current.radiusX()), Formatting.number(current.radiusZ()));
sender.sendMessagePrefixed(TranslationKey.FORMAT_RADII, Formatting.number(current.radiusX()), Formatting.number(current.radiusZ()));
}
if (!previous.shape().equals(current.shape())) {
sender.sendMessagePrefixed("format_shape", current.shape());
sender.sendMessagePrefixed(TranslationKey.FORMAT_SHAPE, current.shape());
}
if (current.radiusX() > chunky.getServer().getMaxWorldSize()) {
sender.sendMessagePrefixed(TranslationKey.FORMAT_WORLDBORDER_TOO_LARGE, chunky.getServer().getMaxWorldSize());
}
}
boolean setBorderViaIntegration(World world) {
Map<String, Integration> integrations = chunky.getServer().getIntegrations();
@Override
public List<String> suggestions(final CommandArguments arguments) {
if (arguments.size() == 1) {
final List<String> suggestions = new ArrayList<>();
chunky.getServer().getWorlds().forEach(world -> suggestions.add(world.getName()));
return suggestions;
}
return List.of();
}
boolean setBorderViaIntegration(final World world) {
final Map<String, Integration> integrations = chunky.getServer().getIntegrations();
if (integrations.containsKey("border")) {
BorderIntegration worldborder = (BorderIntegration) integrations.get("border");
String worldName = world.getName();
final BorderIntegration worldborder = (BorderIntegration) integrations.get("border");
final String worldName = world.getName();
if (worldborder.hasBorder(worldName)) {
Border border = worldborder.getBorder(worldName);
Coordinate center = border.getCenter();
final Border border = worldborder.getBorder(worldName);
final Vector2 center = border.getCenter();
chunky.getSelection().center(center.getX(), center.getZ())
.radiusX(border.getRadiusX()).radiusZ(border.getRadiusZ())
.shape(border.getShape());
@@ -1,42 +1,45 @@
package org.popcraft.chunky.command;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.platform.Player;
import org.popcraft.chunky.platform.Sender;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.TranslationKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
public class WorldCommand extends ChunkyCommand {
public WorldCommand(Chunky chunky) {
super(chunky);
}
public class WorldCommand implements ChunkyCommand {
private final Chunky chunky;
public void execute(Sender sender, String[] args) {
if (args.length < 2) {
sender.sendMessage("help_world");
return;
}
Optional<World> newWorld = Input.tryWorld(chunky, String.join(" ", Arrays.copyOfRange(args, 1, args.length)));
if (!newWorld.isPresent()) {
sender.sendMessage("help_world");
return;
}
chunky.getSelection().world(newWorld.get());
sender.sendMessagePrefixed("format_world", newWorld.get().getName());
public WorldCommand(final Chunky chunky) {
this.chunky = chunky;
}
@Override
public List<String> tabSuggestions(Sender sender, String[] args) {
if (args.length == 2) {
List<String> suggestions = new ArrayList<>();
public void execute(final Sender sender, final CommandArguments arguments) {
final World world;
if (arguments.size() == 0 && sender instanceof final Player player) {
world = player.getWorld();
} else {
world = Input.tryWorld(chunky, arguments.joined()).orElse(null);
}
if (world == null) {
sender.sendMessage(TranslationKey.HELP_WORLD);
return;
}
chunky.getSelection().world(world);
sender.sendMessagePrefixed(TranslationKey.FORMAT_WORLD, world.getName());
}
@Override
public List<String> suggestions(final CommandArguments arguments) {
if (arguments.size() == 1) {
final List<String> suggestions = new ArrayList<>();
chunky.getServer().getWorlds().forEach(world -> suggestions.add(world.getName()));
return suggestions;
}
return Collections.emptyList();
return List.of();
}
}
@@ -0,0 +1,13 @@
package org.popcraft.chunky.event;
public abstract class Cancellable implements Event {
private boolean cancelled;
public boolean isCancelled() {
return cancelled;
}
public void setCancelled(final boolean cancelled) {
this.cancelled = cancelled;
}
}
@@ -0,0 +1,4 @@
package org.popcraft.chunky.event;
public interface Event {
}
@@ -0,0 +1,58 @@
package org.popcraft.chunky.event;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
public final class EventBus {
private static final MethodHandle accept;
static {
MethodHandle acceptMethodHandle = null;
try {
acceptMethodHandle = MethodHandles.publicLookup().findVirtual(Consumer.class, "accept", MethodType.methodType(void.class, Object.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
e.printStackTrace();
}
accept = acceptMethodHandle;
}
private final Map<Class<?>, Set<Consumer<?>>> subscribers = new HashMap<>();
public <T> void subscribe(final Class<T> eventClass, final Consumer<T> subscriber) {
subscribers.computeIfAbsent(eventClass, x -> new HashSet<>());
subscribers.get(eventClass).add(subscriber);
}
public <T> void unsubscribe(final Class<T> eventClass, final Consumer<T> subscriber) {
subscribers.computeIfAbsent(eventClass, x -> new HashSet<>());
subscribers.get(eventClass).remove(subscriber);
}
public void unsubscribeAll() {
subscribers.clear();
}
public void unsubscribeAll(final Class<?> eventClass) {
subscribers.remove(eventClass);
}
public void call(final Object event) {
final Class<?> eventClass = event.getClass();
if (accept == null || !subscribers.containsKey(eventClass)) {
return;
}
subscribers.get(eventClass).forEach(subscriber -> {
try {
accept.invoke(subscriber, event);
} catch (Throwable e) {
e.printStackTrace();
}
});
}
}
@@ -0,0 +1,6 @@
package org.popcraft.chunky.event.command;
import org.popcraft.chunky.event.Event;
public record ReloadCommandEvent() implements Event {
}
@@ -0,0 +1,7 @@
package org.popcraft.chunky.event.task;
import org.popcraft.chunky.GenerationTask;
import org.popcraft.chunky.event.Event;
public record GenerationTaskFinishEvent(GenerationTask generationTask) implements Event {
}
@@ -0,0 +1,7 @@
package org.popcraft.chunky.event.task;
import org.popcraft.chunky.GenerationTask;
import org.popcraft.chunky.event.Event;
public record GenerationTaskUpdateEvent(GenerationTask generationTask) implements Event {
}
@@ -1,22 +0,0 @@
package org.popcraft.chunky.integration;
public abstract class AbstractMapIntegration implements MapIntegration {
protected String label = "World Border";
protected int color = 0xFF0000;
protected int weight = 3;
@Override
public void setOptions(String label, String color, boolean hideByDefault, int priority, int weight) {
if (label != null && !label.isEmpty()) {
this.label = label;
}
this.weight = Math.max(1, weight);
if (color.length() != 6) {
return;
}
try {
this.color = Integer.parseInt(color, 16);
} catch (NumberFormatException ignored) {
}
}
}
@@ -1,110 +0,0 @@
package org.popcraft.chunky.integration;
import com.flowpowered.math.vector.Vector2d;
import de.bluecolored.bluemap.api.BlueMapAPI;
import de.bluecolored.bluemap.api.marker.MarkerAPI;
import de.bluecolored.bluemap.api.marker.MarkerSet;
import de.bluecolored.bluemap.api.marker.ShapeMarker;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.shape.AbstractEllipse;
import org.popcraft.chunky.shape.AbstractPolygon;
import org.popcraft.chunky.shape.Shape;
import java.awt.Color;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class BlueMapIntegration extends AbstractMapIntegration {
private static final String MARKERSET_ID = "chunky";
private final List<Runnable> pendingMarkers = new ArrayList<>();
private BlueMapAPI blueMapAPI;
public BlueMapIntegration() {
BlueMapAPI.onEnable(blueMap -> {
this.blueMapAPI = blueMap;
pendingMarkers.forEach(Runnable::run);
pendingMarkers.clear();
});
BlueMapAPI.onDisable(blueMap -> this.blueMapAPI = null);
}
@Override
public void addShapeMarker(final World world, final Shape shape) {
if (blueMapAPI == null) {
this.pendingMarkers.add(() -> this.addShapeMarker(world, shape));
return;
}
final MarkerAPI markerAPI;
try {
markerAPI = blueMapAPI.getMarkerAPI();
} catch (IOException e) {
return;
}
final MarkerSet markerSet = markerAPI.createMarkerSet(MARKERSET_ID);
markerSet.setLabel(this.label);
de.bluecolored.bluemap.api.marker.Shape blueShape;
if (shape instanceof AbstractPolygon) {
AbstractPolygon polygon = (AbstractPolygon) shape;
double[] pointsX = polygon.pointsX();
double[] pointsZ = polygon.pointsZ();
if (pointsX.length != pointsZ.length) {
return;
}
Vector2d[] points = new Vector2d[pointsX.length];
for (int i = 0; i < pointsX.length; ++i) {
points[i] = new Vector2d(pointsX[i], pointsZ[i]);
}
blueShape = new de.bluecolored.bluemap.api.marker.Shape(points);
} else if (shape instanceof AbstractEllipse) {
AbstractEllipse ellipse = (AbstractEllipse) shape;
double[] center = ellipse.getCenter();
double[] radii = ellipse.getRadii();
Vector2d centerPos = new Vector2d(center[0], center[1]);
blueShape = de.bluecolored.bluemap.api.marker.Shape.createEllipse(centerPos, radii[0], radii[1], 100);
} else {
return;
}
blueMapAPI.getWorld(world.getUUID()).ifPresent(blueWorld -> blueWorld.getMaps().forEach(map -> {
ShapeMarker marker = markerSet.createShapeMarker(world.getName(), map, blueShape, world.getSeaLevel());
marker.setColors(new Color(this.color), new Color(0, true));
marker.setLabel(this.label);
try {
ShapeMarker.class.getMethod("setLineWidth", int.class).invoke(marker, this.weight);
} catch (Exception ignored) {
}
}));
try {
markerAPI.save();
} catch (IOException ignored) {
}
}
@Override
public void removeShapeMarker(final World world) {
if (blueMapAPI == null) {
return;
}
try {
final MarkerAPI markerAPI = blueMapAPI.getMarkerAPI();
final MarkerSet markerSet = markerAPI.createMarkerSet(MARKERSET_ID);
blueMapAPI.getWorld(world.getUUID()).ifPresent(blueWorld -> blueWorld.getMaps().forEach(map -> markerSet.removeMarker(world.getName())));
markerAPI.save();
} catch (IOException ignored) {
}
}
@Override
public void removeAllShapeMarkers() {
if (blueMapAPI == null) {
return;
}
try {
final MarkerAPI markerAPI = blueMapAPI.getMarkerAPI();
if (markerAPI.removeMarkerSet(MARKERSET_ID)) {
markerAPI.save();
}
} catch (IOException ignored) {
}
}
}
@@ -1,69 +0,0 @@
package org.popcraft.chunky.integration;
import org.dynmap.DynmapAPI;
import org.dynmap.markers.AreaMarker;
import org.dynmap.markers.CircleMarker;
import org.dynmap.markers.MarkerDescription;
import org.dynmap.markers.MarkerSet;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.shape.AbstractEllipse;
import org.popcraft.chunky.shape.AbstractPolygon;
import org.popcraft.chunky.shape.Shape;
import java.util.HashMap;
import java.util.Map;
public class DynmapIntegration extends AbstractMapIntegration {
private final MarkerSet markerSet;
private final Map<World, MarkerDescription> markers;
public DynmapIntegration(DynmapAPI dynmapAPI) {
this.markerSet = dynmapAPI.getMarkerAPI().createMarkerSet("chunky.markerset", this.label, null, false);
this.markers = new HashMap<>();
}
@Override
public void addShapeMarker(World world, Shape shape) {
removeShapeMarker(world);
if (shape instanceof AbstractPolygon) {
AbstractPolygon polygon = (AbstractPolygon) shape;
AreaMarker marker = markerSet.createAreaMarker(null, this.label, false, world.getName(), polygon.pointsX(), polygon.pointsZ(), false);
marker.setLineStyle(this.weight, 1f, color);
marker.setFillStyle(0f, 0x000000);
markers.put(world, marker);
} else if (shape instanceof AbstractEllipse) {
AbstractEllipse ellipse = (AbstractEllipse) shape;
double[] center = ellipse.getCenter();
double[] radii = ellipse.getRadii();
CircleMarker marker = markerSet.createCircleMarker(null, this.label, false, world.getName(), center[0], world.getSeaLevel(), center[1], radii[0], radii[1], false);
marker.setLineStyle(this.weight, 1f, color);
marker.setFillStyle(0f, 0x000000);
markers.put(world, marker);
}
}
@Override
public void removeShapeMarker(World world) {
MarkerDescription marker = markers.remove(world);
if (marker != null) {
marker.deleteMarker();
}
}
@Override
public void removeAllShapeMarkers() {
if (markerSet != null) {
markerSet.deleteMarkerSet();
}
markers.clear();
}
@Override
public void setOptions(String label, String color, boolean hideByDefault, int priority, int weight) {
super.setOptions(label, color, hideByDefault, priority, weight);
if (markerSet != null) {
markerSet.setHideByDefault(hideByDefault);
markerSet.setLayerPriority(priority);
}
}
}
@@ -1,14 +0,0 @@
package org.popcraft.chunky.integration;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.shape.Shape;
public interface MapIntegration extends Integration {
void addShapeMarker(World world, Shape shape);
void removeShapeMarker(World world);
void removeAllShapeMarkers();
void setOptions(String label, String color, boolean hideByDefault, int priority, int weight);
}
@@ -1,134 +0,0 @@
package org.popcraft.chunky.integration;
import net.pl3x.map.api.Key;
import net.pl3x.map.api.LayerProvider;
import net.pl3x.map.api.MapWorld;
import net.pl3x.map.api.Pl3xMap;
import net.pl3x.map.api.Point;
import net.pl3x.map.api.Registry;
import net.pl3x.map.api.SimpleLayerProvider;
import net.pl3x.map.api.marker.Marker;
import net.pl3x.map.api.marker.MarkerOptions;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.shape.AbstractEllipse;
import org.popcraft.chunky.shape.AbstractPolygon;
import org.popcraft.chunky.shape.Circle;
import org.popcraft.chunky.shape.Shape;
import java.awt.Color;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class Pl3xMapIntegration extends AbstractMapIntegration {
private static final Key WORLDBORDER_KEY = Key.of("pl3xmap-worldborder");
private static final Key CHUNKY_KEY = Key.of("chunky");
private final Pl3xMap pl3xMap;
private final Map<UUID, LayerProvider> defaultProviders = new HashMap<>();
private boolean hideByDefault;
private int priority;
public Pl3xMapIntegration(Pl3xMap pl3xMap) {
this.pl3xMap = pl3xMap;
}
@Override
public void addShapeMarker(final World world, final Shape shape) {
pl3xMap.getWorldIfEnabled(world.getUUID()).ifPresent(pl3xWorld -> {
Registry<LayerProvider> layerRegistry = pl3xWorld.layerRegistry();
if (layerRegistry.hasEntry(WORLDBORDER_KEY)) {
defaultProviders.put(pl3xWorld.uuid(), layerRegistry.get(WORLDBORDER_KEY));
layerRegistry.unregister(WORLDBORDER_KEY);
}
if (!layerRegistry.hasEntry(CHUNKY_KEY)) {
layerRegistry.register(CHUNKY_KEY, SimpleLayerProvider.builder(this.label)
.defaultHidden(hideByDefault)
.layerPriority(1)
.zIndex(priority)
.build());
}
SimpleLayerProvider chunkyLayerProvider = (SimpleLayerProvider) layerRegistry.get(CHUNKY_KEY);
chunkyLayerProvider.clearMarkers();
final Marker marker;
if (shape instanceof AbstractPolygon) {
AbstractPolygon polygon = (AbstractPolygon) shape;
double[] pointsX = polygon.pointsX();
double[] pointsZ = polygon.pointsZ();
if (pointsX.length != pointsZ.length) {
return;
}
Point[] points = new Point[pointsX.length + 1];
for (int i = 0; i < pointsX.length; ++i) {
points[i] = Point.of(pointsX[i], pointsZ[i]);
}
points[pointsX.length] = Point.of(pointsX[0], pointsZ[0]);
marker = Marker.polyline(points);
} else if (shape instanceof AbstractEllipse) {
AbstractEllipse ellipse = (AbstractEllipse) shape;
double[] center = ellipse.getCenter();
double[] radii = ellipse.getRadii();
Point centerPoint = Point.of(center[0], center[1]);
if (ellipse instanceof Circle) {
marker = Marker.circle(centerPoint, radii[0]);
} else {
marker = ellipse(centerPoint, radii[0], radii[1]);
}
} else {
return;
}
MarkerOptions markerOptions = MarkerOptions.builder()
.stroke(true)
.strokeColor(new Color(this.color))
.strokeWeight(this.weight)
.fill(false)
.clickTooltip(this.label)
.build();
marker.markerOptions(markerOptions);
chunkyLayerProvider.addMarker(CHUNKY_KEY, marker);
});
}
@Override
public void removeShapeMarker(final World world) {
pl3xMap.getWorldIfEnabled(world.getUUID()).ifPresent(this::unregisterLayer);
}
@Override
public void removeAllShapeMarkers() {
pl3xMap.mapWorlds().forEach(this::unregisterLayer);
}
private void unregisterLayer(MapWorld mapWorld) {
Registry<LayerProvider> layerRegistry = mapWorld.layerRegistry();
if (!layerRegistry.hasEntry(WORLDBORDER_KEY)) {
LayerProvider defaultProvider = defaultProviders.get(mapWorld.uuid());
if (defaultProvider != null) {
layerRegistry.register(WORLDBORDER_KEY, defaultProvider);
}
}
if (layerRegistry.hasEntry(CHUNKY_KEY)) {
((SimpleLayerProvider) layerRegistry.get(CHUNKY_KEY)).clearMarkers();
layerRegistry.unregister(CHUNKY_KEY);
}
}
@Override
public void setOptions(String label, String color, boolean hideByDefault, int priority, int weight) {
super.setOptions(label, color, hideByDefault, priority, weight);
this.hideByDefault = hideByDefault;
this.priority = priority;
}
private Marker ellipse(Point center, double radiusX, double radiusZ) {
int numPoints = 360;
Point[] points = new Point[numPoints + 1];
double segmentAngle = 2 * Math.PI / numPoints;
for (int i = 0; i < numPoints; ++i) {
double pointX = center.x() + Math.sin(segmentAngle * i) * radiusX;
double pointZ = center.z() + Math.cos(segmentAngle * i) * radiusZ;
points[i] = Point.of(pointX, pointZ);
}
points[numPoints] = Point.of(center.x(), center.z() + radiusZ);
return Marker.polyline(points);
}
}
@@ -8,4 +8,8 @@ public interface ChunkIterator extends Iterator<ChunkCoordinate> {
long total();
String name();
default boolean process() {
return true;
}
}
@@ -1,29 +1,30 @@
package org.popcraft.chunky.iterator;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.shape.ShapeType;
public class ChunkIteratorFactory {
public static ChunkIterator getChunkIterator(Selection selection, long count) {
switch (selection.shape()) {
case "rectangle":
case "oval":
case "ellipse":
return new Loop2ChunkIterator(selection, count);
default:
break;
}
switch (selection.pattern()) {
case "loop":
return new Loop2ChunkIterator(selection, count);
case "spiral":
return new SpiralChunkIterator(selection, count);
case "concentric":
default:
return new ConcentricChunkIterator(selection, count);
}
public final class ChunkIteratorFactory {
private ChunkIteratorFactory() {
}
public static ChunkIterator getChunkIterator(Selection selection) {
public static ChunkIterator getChunkIterator(final Selection selection, final long count) {
if (selection.pattern().getType().equals(PatternType.WORLD)) {
return new WorldChunkIterator(selection);
}
final String shape = selection.shape();
if (ShapeType.RECTANGLE.equals(shape) || ShapeType.ELLIPSE.equals(shape) || ShapeType.OVAL.equals(shape)) {
return new Loop2ChunkIterator(selection, count);
}
return switch (selection.pattern().getType()) {
case PatternType.LOOP -> new Loop2ChunkIterator(selection, count);
case PatternType.SPIRAL -> new SpiralChunkIterator(selection, count);
case PatternType.CSV -> new CsvChunkIterator(selection, count);
case PatternType.CONCENTRIC -> new ConcentricChunkIterator(selection, count);
default -> new RegionChunkIterator(selection, count);
};
}
public static ChunkIterator getChunkIterator(final Selection selection) {
return getChunkIterator(selection, 0);
}
}
@@ -3,6 +3,8 @@ package org.popcraft.chunky.iterator;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.util.ChunkCoordinate;
import java.util.NoSuchElementException;
public class ConcentricChunkIterator implements ChunkIterator {
private final int xCenter, zCenter;
private final int radiusChunks;
@@ -12,7 +14,7 @@ public class ConcentricChunkIterator implements ChunkIterator {
private int down, left, up, right;
private boolean hasNext = true;
public ConcentricChunkIterator(Selection selection, long count) {
public ConcentricChunkIterator(final Selection selection, final long count) {
this(selection);
if (count <= 0) {
return;
@@ -40,13 +42,13 @@ public class ConcentricChunkIterator implements ChunkIterator {
}
}
public ConcentricChunkIterator(Selection selection) {
public ConcentricChunkIterator(final Selection selection) {
this.radiusChunks = selection.radiusChunksX();
this.x = selection.centerChunkX();
this.z = selection.centerChunkZ();
this.xCenter = x;
this.zCenter = z;
long diameterChunks = selection.diameterChunksX();
final long diameterChunks = selection.diameterChunksX();
this.total = diameterChunks * diameterChunks;
}
@@ -57,6 +59,9 @@ public class ConcentricChunkIterator implements ChunkIterator {
@Override
public ChunkCoordinate next() {
if (!hasNext) {
throw new NoSuchElementException();
}
final ChunkCoordinate chunkCoord = new ChunkCoordinate(x, z);
if (x == xCenter + annulus && z == zCenter + annulus) {
++annulus;
@@ -91,6 +96,6 @@ public class ConcentricChunkIterator implements ChunkIterator {
@Override
public String name() {
return "concentric";
return PatternType.CONCENTRIC;
}
}
@@ -0,0 +1,78 @@
package org.popcraft.chunky.iterator;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.util.ChunkCoordinate;
import org.popcraft.chunky.util.Input;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;
public class CsvChunkIterator implements ChunkIterator {
private final Queue<ChunkCoordinate> chunks;
private final long total;
private final String name;
public CsvChunkIterator(final Selection selection, final long count) {
this(selection);
for (int i = 0; i < count && hasNext(); ++i) {
chunks.poll();
}
}
public CsvChunkIterator(final Selection selection) {
final Path filePath = selection.pattern().getValue()
.map(value -> selection.chunky().getConfig().getDirectory().resolve(String.format("%s.csv", value)))
.orElse(null);
this.chunks = new LinkedList<>();
final AtomicLong valid = new AtomicLong();
if (filePath != null) {
try (final Stream<String> lines = Files.lines(filePath)) {
lines.forEach(line -> {
final String[] split = line.split(",");
if (split.length > 1) {
final Optional<Integer> x = Input.tryInteger(split[0]);
final Optional<Integer> z = Input.tryInteger(split[1]);
if (x.isPresent() && z.isPresent()) {
chunks.add(new ChunkCoordinate(x.get(), z.get()));
valid.incrementAndGet();
}
}
});
} catch (final IOException e) {
e.printStackTrace();
}
}
this.total = valid.get();
this.name = selection.pattern().toString();
}
@Override
public boolean hasNext() {
return !chunks.isEmpty();
}
@Override
public ChunkCoordinate next() {
if (chunks.isEmpty()) {
throw new NoSuchElementException();
}
return chunks.poll();
}
@Override
public long total() {
return total;
}
@Override
public String name() {
return name;
}
}
@@ -3,6 +3,8 @@ package org.popcraft.chunky.iterator;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.util.ChunkCoordinate;
import java.util.NoSuchElementException;
public class Loop2ChunkIterator implements ChunkIterator {
private final int x1, x2, z1, z2;
private final long diameterChunksZ;
@@ -10,7 +12,7 @@ public class Loop2ChunkIterator implements ChunkIterator {
private int x, z;
private boolean hasNext = true;
public Loop2ChunkIterator(Selection selection, long count) {
public Loop2ChunkIterator(final Selection selection, final long count) {
this(selection);
if (count <= 0) {
return;
@@ -22,18 +24,18 @@ public class Loop2ChunkIterator implements ChunkIterator {
}
}
public Loop2ChunkIterator(Selection selection) {
int radiusChunksX = selection.radiusChunksX();
int radiusChunksZ = selection.radiusChunksZ();
int centerChunkX = selection.centerChunkX();
int centerChunkZ = selection.centerChunkZ();
public Loop2ChunkIterator(final Selection selection) {
final int radiusChunksX = selection.radiusChunksX();
final int radiusChunksZ = selection.radiusChunksZ();
final int centerChunkX = selection.centerChunkX();
final int centerChunkZ = selection.centerChunkZ();
this.x1 = centerChunkX - radiusChunksX;
this.x2 = centerChunkX + radiusChunksX;
this.z1 = centerChunkZ - radiusChunksZ;
this.z2 = centerChunkZ + radiusChunksZ;
this.x = x1;
this.z = z1;
int diameterChunksX = selection.diameterChunksX();
final int diameterChunksX = selection.diameterChunksX();
this.diameterChunksZ = selection.diameterChunksZ();
this.total = diameterChunksX * diameterChunksZ;
}
@@ -45,6 +47,9 @@ public class Loop2ChunkIterator implements ChunkIterator {
@Override
public ChunkCoordinate next() {
if (!hasNext) {
throw new NoSuchElementException();
}
final ChunkCoordinate chunkCoord = new ChunkCoordinate(x, z);
if (++z > z2) {
z = z1;
@@ -62,6 +67,6 @@ public class Loop2ChunkIterator implements ChunkIterator {
@Override
public String name() {
return "loop";
return PatternType.LOOP;
}
}
@@ -0,0 +1,17 @@
package org.popcraft.chunky.iterator;
import java.util.List;
public final class PatternType {
public static final String CONCENTRIC = "concentric";
public static final String LOOP = "loop";
public static final String SPIRAL = "spiral";
public static final String CSV = "csv";
public static final String REGION = "region";
public static final String WORLD = "world";
public static final List<String> ALL = List.of(CONCENTRIC, LOOP, SPIRAL, CSV, REGION, WORLD);
private PatternType() {
}
}
@@ -0,0 +1,241 @@
package org.popcraft.chunky.iterator;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.util.ChunkCoordinate;
import org.popcraft.chunky.util.Hilbert;
import java.util.NoSuchElementException;
public class RegionChunkIterator implements ChunkIterator {
private final int centerRegionX;
private final int centerRegionZ;
private final int radiusRegions;
private final int minChunkX;
private final int minChunkZ;
private final int maxChunkX;
private final int maxChunkZ;
private final long totalChunks;
private RegionChunkProgress currentRegionProgress;
private int regionX;
private int regionZ;
private int annulusRegions;
private int spanRegions;
private int downRegions;
private int leftRegions;
private int upRegions;
private int rightRegions;
private boolean hasNextRegion = true;
private boolean hasNext = true;
public RegionChunkIterator(final Selection selection, final long count) {
this(selection);
if (count <= 0) {
return;
}
this.currentRegionProgress = null;
this.regionX = centerRegionX;
this.regionZ = centerRegionZ;
this.annulusRegions = 0;
this.spanRegions = 0;
this.downRegions = this.leftRegions = this.upRegions = this.rightRegions = 0;
this.hasNextRegion = true;
this.hasNext = true;
long countRemainingChunks = count;
final long estimatedRegionCount = count / 1024;
int estimatedDiameterRegions = (int) Math.floor(Math.sqrt(estimatedRegionCount));
if (estimatedDiameterRegions % 2 == 0) {
--estimatedDiameterRegions;
}
if (estimatedDiameterRegions > 2) {
final int skipDiameterRegions = estimatedDiameterRegions - 2;
this.annulusRegions = skipDiameterRegions / 2 + 1;
this.regionX += annulusRegions;
this.regionZ += annulusRegions - 1;
this.spanRegions = 2 * annulusRegions;
++this.downRegions;
final long skipChunks = ((long) skipDiameterRegions * skipDiameterRegions) * 1024;
countRemainingChunks -= skipChunks;
}
currentRegionProgress = nextRegionChunkProgress(countRemainingChunks);
countRemainingChunks -= Math.min(countRemainingChunks, currentRegionProgress == null ? 0 : currentRegionProgress.total());
while (currentRegionProgress != null && !currentRegionProgress.hasNext()) {
currentRegionProgress = nextRegionChunkProgress(countRemainingChunks);
countRemainingChunks -= Math.min(countRemainingChunks, currentRegionProgress == null ? 0 : currentRegionProgress.total());
}
if (currentRegionProgress == null) {
hasNext = false;
}
}
public RegionChunkIterator(final Selection selection) {
this.centerRegionX = selection.centerRegionX();
this.centerRegionZ = selection.centerRegionZ();
this.radiusRegions = selection.radiusRegionsX();
final int centerChunkX = selection.centerChunkX();
final int centerChunkZ = selection.centerChunkZ();
final int radiusChunks = selection.radiusChunksX();
this.minChunkX = centerChunkX - radiusChunks;
this.minChunkZ = centerChunkZ - radiusChunks;
this.maxChunkX = centerChunkX + radiusChunks;
this.maxChunkZ = centerChunkZ + radiusChunks;
this.regionX = centerRegionX;
this.regionZ = centerRegionZ;
final long diameterChunks = selection.diameterChunksX();
this.totalChunks = diameterChunks * diameterChunks;
this.currentRegionProgress = nextRegionChunkProgress();
}
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public ChunkCoordinate next() {
if (!hasNext) {
throw new NoSuchElementException();
}
final ChunkCoordinate chunkCoord = currentRegionProgress.next();
while (currentRegionProgress != null && !currentRegionProgress.hasNext()) {
currentRegionProgress = nextRegionChunkProgress();
}
if (currentRegionProgress == null) {
hasNext = false;
}
return chunkCoord;
}
private RegionChunkProgress nextRegionChunkProgress() {
return nextRegionChunkProgress(0);
}
private RegionChunkProgress nextRegionChunkProgress(final long count) {
if (!hasNextRegion) {
return null;
}
final RegionChunkProgress regionChunkProgress = new RegionChunkProgress(regionX, regionZ, count);
if (regionX == centerRegionX + annulusRegions && regionZ == centerRegionZ + annulusRegions) {
++annulusRegions;
++regionX;
++regionZ;
if (annulusRegions > radiusRegions) {
hasNextRegion = false;
}
spanRegions = 2 * annulusRegions;
downRegions = leftRegions = upRegions = rightRegions = 0;
}
if (downRegions < spanRegions) {
--regionZ;
++downRegions;
} else if (leftRegions < spanRegions) {
--regionX;
++leftRegions;
} else if (upRegions < spanRegions) {
++regionZ;
++upRegions;
} else if (rightRegions < spanRegions) {
++regionX;
++rightRegions;
}
return regionChunkProgress;
}
@Override
public long total() {
return totalChunks;
}
@Override
public String name() {
return PatternType.REGION;
}
public final class RegionChunkProgress implements ChunkIterator {
private final int minX;
private final int minZ;
private final int sizeZ;
private final int total;
private final boolean full;
private final String name;
private int current;
private int offsetX;
private int offsetZ;
private boolean hasNext = true;
public RegionChunkProgress(final int x, final int z, final long count) {
this(x, z);
if (count <= 0) {
return;
}
this.current = (int) Math.min(count, 1024);
if (!full && sizeZ > 0) {
offsetX = current / sizeZ;
offsetZ = current % sizeZ;
}
if (current >= total) {
this.hasNext = false;
}
}
public RegionChunkProgress(final int x, final int z) {
final int lowEdgeX = x << 5;
final int lowEdgeZ = z << 5;
final int highEdgeX = lowEdgeX + 31;
final int highEdgeZ = lowEdgeZ + 31;
this.minX = Math.max(lowEdgeX, minChunkX);
this.minZ = Math.max(lowEdgeZ, minChunkZ);
final int maxX = Math.min(highEdgeX, maxChunkX);
final int maxZ = Math.min(highEdgeZ, maxChunkZ);
final int sizeX = maxX - minX + 1;
if (minX > highEdgeX || minZ > highEdgeZ || maxX < lowEdgeX || maxZ < lowEdgeZ) {
this.sizeZ = 0;
this.total = 0;
this.full = false;
this.hasNext = false;
} else {
this.sizeZ = maxZ - minZ + 1;
this.total = sizeX * sizeZ;
this.full = total == 1024;
}
this.name = "region_chunk_progress_%d_%d".formatted(x, z);
}
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public ChunkCoordinate next() {
if (!hasNext) {
throw new NoSuchElementException();
}
final ChunkCoordinate chunkCoord;
if (full) {
final ChunkCoordinate offset = Hilbert.regionDistanceToChunkCoordinateOffset(current);
chunkCoord = new ChunkCoordinate(minX + offset.x(), minZ + offset.z());
} else {
chunkCoord = new ChunkCoordinate(minX + offsetX, minZ + offsetZ);
if (++offsetZ >= sizeZ) {
offsetZ = 0;
++offsetX;
}
}
++current;
if (current >= total) {
hasNext = false;
}
return chunkCoord;
}
@Override
public long total() {
return total;
}
@Override
public String name() {
return name;
}
}
}
@@ -3,6 +3,8 @@ package org.popcraft.chunky.iterator;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.util.ChunkCoordinate;
import java.util.NoSuchElementException;
public class SpiralChunkIterator implements ChunkIterator {
private static final int RIGHT = 0, DOWN = 1, LEFT = 2, UP = 3;
private final int stopX, stopZ;
@@ -12,7 +14,7 @@ public class SpiralChunkIterator implements ChunkIterator {
private int direction;
private boolean hasNext = true;
public SpiralChunkIterator(Selection selection, long count) {
public SpiralChunkIterator(final Selection selection, final long count) {
this(selection);
if (count <= 0) {
return;
@@ -24,7 +26,7 @@ public class SpiralChunkIterator implements ChunkIterator {
this.span = diameterFinished;
this.spanCount = 1;
this.direction = DOWN;
int radiusFinished = diameterFinished / 2;
final int radiusFinished = diameterFinished / 2;
this.x += radiusFinished + 1;
this.z += radiusFinished;
long perimeterCount = count - (long) diameterFinished * diameterFinished;
@@ -71,13 +73,13 @@ public class SpiralChunkIterator implements ChunkIterator {
}
}
public SpiralChunkIterator(Selection selection) {
int radiusChunks = selection.radiusChunksX();
public SpiralChunkIterator(final Selection selection) {
final int radiusChunks = selection.radiusChunksX();
this.x = selection.centerChunkX();
this.z = selection.centerChunkZ();
this.stopX = x + radiusChunks;
this.stopZ = z + radiusChunks;
long diameter = selection.diameterChunksX();
final long diameter = selection.diameterChunksX();
this.total = diameter * diameter;
}
@@ -88,6 +90,9 @@ public class SpiralChunkIterator implements ChunkIterator {
@Override
public ChunkCoordinate next() {
if (!hasNext) {
throw new NoSuchElementException();
}
final ChunkCoordinate chunkCoord = new ChunkCoordinate(x, z);
if (x == stopX && z == stopZ) {
hasNext = false;
@@ -96,21 +101,16 @@ public class SpiralChunkIterator implements ChunkIterator {
spanCount = 0;
++span;
}
switch (direction) {
case RIGHT:
x += 1;
break;
case DOWN:
z -= 1;
break;
case LEFT:
x -= 1;
break;
case UP:
z += 1;
break;
default:
throw new IllegalStateException("Invalid direction");
if (direction == RIGHT) {
x += 1;
} else if (direction == DOWN) {
z -= 1;
} else if (direction == LEFT) {
x -= 1;
} else if (direction == UP) {
z += 1;
} else {
throw new IllegalStateException("Invalid direction");
}
++spanProgress;
if (spanProgress == span) {
@@ -128,6 +128,6 @@ public class SpiralChunkIterator implements ChunkIterator {
@Override
public String name() {
return "spiral";
return PatternType.SPIRAL;
}
}
@@ -0,0 +1,156 @@
package org.popcraft.chunky.iterator;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.nbt.StringTag;
import org.popcraft.chunky.nbt.TagType;
import org.popcraft.chunky.nbt.util.ChunkFilter;
import org.popcraft.chunky.nbt.util.RegionFile;
import org.popcraft.chunky.util.ChunkCoordinate;
import org.popcraft.chunky.util.Hilbert;
import org.popcraft.chunky.util.Parameter;
import org.popcraft.chunky.util.TranslationKey;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;
public class WorldChunkIterator implements ChunkIterator {
private final Chunky chunky;
private final int minRegionX;
private final int minRegionZ;
private final int maxRegionX;
private final int maxRegionZ;
private final int minChunkX;
private final int minChunkZ;
private final int maxChunkX;
private final int maxChunkZ;
private final String worldName;
private final Queue<ChunkCoordinate> chunks;
private final AtomicLong total = new AtomicLong();
private final Path savePath;
private final Path regionPath;
private final String name;
public WorldChunkIterator(final Selection selection) {
this.chunky = selection.chunky();
final int centerRegionX = selection.centerRegionX();
final int centerRegionZ = selection.centerRegionZ();
final int radiusRegionsX = selection.radiusRegionsX();
final int radiusRegionsZ = selection.radiusRegionsZ();
this.minRegionX = centerRegionX - radiusRegionsX;
this.minRegionZ = centerRegionZ - radiusRegionsZ;
this.maxRegionX = centerRegionX + radiusRegionsX;
this.maxRegionZ = centerRegionZ + radiusRegionsZ;
final int centerChunkX = selection.centerChunkX();
final int centerChunkZ = selection.centerChunkZ();
final int radiusChunksX = selection.radiusChunksX();
final int radiusChunksZ = selection.radiusChunksZ();
this.minChunkX = centerChunkX - radiusChunksX;
this.minChunkZ = centerChunkZ - radiusChunksZ;
this.maxChunkX = centerChunkX + radiusChunksX;
this.maxChunkZ = centerChunkZ + radiusChunksZ;
this.chunks = new LinkedList<>();
this.worldName = selection.world().getName();
final String saveFile = worldName.substring(worldName.indexOf(':') + 1);
this.savePath = selection.chunky().getConfig().getDirectory().resolve(String.format("%s.csv", saveFile));
this.regionPath = selection.world().getRegionDirectory().orElse(null);
this.name = Parameter.of(PatternType.CSV, saveFile).toString();
}
@Override
public boolean hasNext() {
return !chunks.isEmpty();
}
@Override
public ChunkCoordinate next() {
if (chunks.isEmpty()) {
throw new NoSuchElementException();
}
return chunks.poll();
}
@Override
public long total() {
return total.get();
}
@Override
public String name() {
return name;
}
@Override
public boolean process() {
if (regionPath == null) {
return false;
}
final long startTime = System.currentTimeMillis();
final AtomicLong updateTime = new AtomicLong(startTime);
final StringBuilder saveData = new StringBuilder();
try (final Stream<Path> files = Files.list(regionPath)) {
final List<Path> regions = files
.filter(file -> {
final ChunkCoordinate regionCoordinate = ChunkCoordinate.fromRegionFile(file.getFileName().toString())
.orElse(null);
return regionCoordinate != null && regionCoordinate.x() >= minRegionX && regionCoordinate.x() <= maxRegionX && regionCoordinate.z() >= minRegionZ && regionCoordinate.z() <= maxRegionZ;
})
.toList();
final long totalRegions = regions.size();
final AtomicLong finishedRegions = new AtomicLong();
for (final Path region : regions) {
final ChunkCoordinate regionCoordinate = ChunkCoordinate.fromRegionFile(region.getFileName().toString())
.orElseThrow(IllegalStateException::new);
final int regionX = regionCoordinate.x();
final int regionZ = regionCoordinate.z();
final RegionFile regionFile = new RegionFile(region.toFile(), ChunkFilter.of(TagType.STRING, "Status"));
for (final ChunkCoordinate offset : Hilbert.chunkCoordinateOffsets()) {
final ChunkCoordinate chunkCoordinate = new ChunkCoordinate((regionX << 5) + offset.x(), (regionZ << 5) + offset.z());
if (chunkCoordinate.x() < minChunkX || chunkCoordinate.x() > maxChunkX || chunkCoordinate.z() < minChunkZ || chunkCoordinate.z() > maxChunkZ) {
continue;
}
regionFile.getChunk(chunkCoordinate.x(), chunkCoordinate.z()).ifPresent(chunk -> {
final boolean generated = Optional.ofNullable(chunk.getData())
.filter(StringTag.class::isInstance)
.map(StringTag.class::cast)
.map(StringTag::value)
.map(status -> "minecraft:full".equals(status) || "full".equals(status))
.orElse(false);
if (generated) {
chunks.add(chunkCoordinate);
saveData.append(chunkCoordinate.x()).append(',').append(chunkCoordinate.z()).append('\n');
total.incrementAndGet();
}
});
}
finishedRegions.getAndIncrement();
if (!chunky.getConfig().isSilent()) {
final long currentTime = System.currentTimeMillis();
final boolean updateIntervalElapsed = ((currentTime - updateTime.get()) / 1e3) > chunky.getConfig().getUpdateInterval();
if (updateIntervalElapsed || finishedRegions.get() == totalRegions) {
chunky.getServer().getConsole().sendMessagePrefixed(TranslationKey.TASK_TRIM_UPDATE, worldName, finishedRegions.get(), String.format("%.2f", 100f * finishedRegions.get() / totalRegions));
updateTime.set(currentTime);
}
}
if (Thread.interrupted()) {
Thread.currentThread().interrupt();
return false;
}
}
Files.writeString(savePath, saveData, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}
@@ -1,9 +1,9 @@
package org.popcraft.chunky.platform;
import org.popcraft.chunky.util.Coordinate;
import org.popcraft.chunky.platform.util.Vector2;
public interface Border {
Coordinate getCenter();
Vector2 getCenter();
double getRadiusX();
@@ -1,31 +1,25 @@
package org.popcraft.chunky.platform;
import org.popcraft.chunky.GenerationTask;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
public interface Config {
Path getDirectory();
Optional<GenerationTask> loadTask(World world);
List<GenerationTask> loadTasks();
void saveTask(GenerationTask generationTask);
void saveTasks();
void cancelTask(World world);
void cancelTasks();
int getVersion();
String getLanguage();
boolean getContinueOnRestart();
boolean isForceLoadExistingChunks();
boolean isSilent();
void setSilent(boolean silent);
int getUpdateInterval();
void setUpdateInterval(int updateInterval);
void reload();
}
@@ -0,0 +1,13 @@
package org.popcraft.chunky.platform;
import org.popcraft.chunky.platform.util.Location;
import java.util.UUID;
public interface Player extends Sender {
UUID getUUID();
void teleport(Location location);
void sendActionBar(String key);
}
@@ -1,13 +1,17 @@
package org.popcraft.chunky.platform;
import org.popcraft.chunky.util.Coordinate;
import org.popcraft.chunky.platform.util.Location;
public interface Sender {
boolean isPlayer();
String getName();
Coordinate getCoordinate();
World getWorld();
Location getLocation();
boolean hasPermission(String permission);
void sendMessage(String key, boolean prefixed, Object... args);
@@ -2,6 +2,7 @@ package org.popcraft.chunky.platform;
import org.popcraft.chunky.integration.Integration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -13,7 +14,13 @@ public interface Server {
List<World> getWorlds();
Sender getConsoleSender();
int getMaxWorldSize();
Sender getConsole();
Collection<Player> getPlayers();
Optional<Player> getPlayer(String name);
Config getConfig();
}
@@ -1,6 +1,6 @@
package org.popcraft.chunky.platform;
import org.popcraft.chunky.util.Coordinate;
import org.popcraft.chunky.platform.util.Location;
import java.nio.file.Path;
import java.util.Optional;
@@ -10,7 +10,9 @@ import java.util.concurrent.CompletableFuture;
public interface World {
String getName();
boolean isChunkGenerated(int x, int z);
String getKey();
CompletableFuture<Boolean> isChunkGenerated(int x, int z);
CompletableFuture<Void> getChunkAtAsync(int x, int z);
@@ -18,13 +20,29 @@ public interface World {
int getSeaLevel();
Coordinate getSpawnCoordinate();
Location getSpawn();
Border getWorldBorder();
Optional<Path> getEntitiesDirectory();
int getElevation(int x, int z);
Optional<Path> getPOIDirectory();
int getMaxElevation();
Optional<Path> getRegionDirectory();
void playEffect(Player player, String effect);
void playSound(Player player, String sound);
Optional<Path> getDirectory(String name);
default Optional<Path> getEntitiesDirectory() {
return getDirectory("entities");
}
default Optional<Path> getPOIDirectory() {
return getDirectory("poi");
}
default Optional<Path> getRegionDirectory() {
return getDirectory("region");
}
}
@@ -2,199 +2,249 @@ package org.popcraft.chunky.platform.impl;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import org.popcraft.chunky.Chunky;
import org.popcraft.chunky.GenerationTask;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.Config;
import org.popcraft.chunky.platform.World;
import org.popcraft.chunky.util.Input;
import org.popcraft.chunky.util.Translator;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class GsonConfig implements Config {
private final Supplier<Chunky> chunky;
private final Gson gson;
private final Path configPath;
private ConfigModel configModel;
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private final Path savePath;
private ConfigModel configModel = new ConfigModel();
public GsonConfig(Supplier<Chunky> chunky, File configFile) {
this.chunky = chunky;
this.gson = new GsonBuilder().setPrettyPrinting().create();
this.configPath = configFile.toPath();
if (!configFile.exists()) {
try {
if (configFile.createNewFile()) {
this.configModel = new ConfigModel();
configModel.version = 1;
configModel.language = "en";
configModel.continueOnRestart = false;
saveConfig();
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
public GsonConfig(final Path savePath) {
this.savePath = savePath;
if (Files.exists(this.savePath)) {
reload();
} else {
saveConfig();
}
Translator.setLanguage(getLanguage());
}
@Override
public Path getDirectory() {
return configPath.getParent();
}
@Override
public Optional<GenerationTask> loadTask(World world) {
if (this.configModel == null) {
return Optional.empty();
}
Map<String, TaskModel> tasks = this.configModel.tasks;
if (tasks == null) {
return Optional.empty();
}
TaskModel taskModel = tasks.get(world.getName());
if (taskModel == null || taskModel.cancelled) {
return Optional.empty();
}
Selection.Builder selection = Selection.builder(world)
.centerX(taskModel.centerX)
.centerZ(taskModel.centerZ)
.radiusX(taskModel.radius)
.radiusZ(taskModel.radiusZ == null ? taskModel.radius : taskModel.radiusZ)
.pattern(taskModel.iterator)
.shape(taskModel.shape);
long count = taskModel.count;
long time = taskModel.time;
return Optional.of(new GenerationTask(chunky.get(), selection.build(), count, time));
}
@Override
public List<GenerationTask> loadTasks() {
List<GenerationTask> generationTasks = new ArrayList<>();
chunky.get().getServer().getWorlds().forEach(world -> loadTask(world).ifPresent(generationTasks::add));
return generationTasks;
}
@Override
public void saveTask(GenerationTask generationTask) {
if (this.configModel == null) {
this.configModel = new ConfigModel();
}
if (this.configModel.tasks == null) {
this.configModel.tasks = new HashMap<>();
}
Map<String, TaskModel> tasks = this.configModel.tasks;
Selection selection = generationTask.getSelection();
TaskModel taskModel = tasks.getOrDefault(selection.world().getName(), new TaskModel());
String shape = generationTask.getShape().name();
taskModel.cancelled = generationTask.isCancelled();
taskModel.radius = selection.radiusX();
if ("rectangle".equals(shape) || "ellipse".equals(shape)) {
taskModel.radiusZ = selection.radiusZ();
}
taskModel.centerX = selection.centerX();
taskModel.centerZ = selection.centerZ();
taskModel.iterator = generationTask.getChunkIterator().name();
taskModel.shape = shape;
taskModel.count = generationTask.getCount();
taskModel.time = generationTask.getTotalTime();
tasks.put(selection.world().getName(), taskModel);
saveConfig();
}
@Override
public void saveTasks() {
chunky.get().getGenerationTasks().values().forEach(this::saveTask);
}
@Override
public void cancelTask(World world) {
loadTask(world).ifPresent(generationTask -> {
generationTask.stop(true);
saveTask(generationTask);
});
}
@Override
public void cancelTasks() {
loadTasks().forEach(generationTask -> {
generationTask.stop(true);
saveTask(generationTask);
});
return savePath.getParent();
}
@Override
public int getVersion() {
return getConfigModel().map(model -> model.version).orElse(0);
return Optional.ofNullable(configModel.version).orElse(0);
}
@Override
public String getLanguage() {
return getConfigModel().map(model -> Input.checkLanguage(model.language)).orElse("en");
return Optional.ofNullable(configModel.language).map(Input::checkLanguage).orElse("en");
}
@Override
public boolean getContinueOnRestart() {
return getConfigModel().map(model -> model.continueOnRestart).orElse(false);
return Optional.ofNullable(configModel.continueOnRestart).orElse(false);
}
@Override
public boolean isForceLoadExistingChunks() {
return Optional.ofNullable(configModel.forceLoadExistingChunks).orElse(false);
}
@Override
public boolean isSilent() {
return Optional.ofNullable(configModel.silent).orElse(false);
}
@Override
public void setSilent(final boolean silent) {
configModel.silent = silent;
}
@Override
public int getUpdateInterval() {
return Optional.ofNullable(configModel.updateInterval).orElse(1);
}
@Override
public void setUpdateInterval(final int updateInterval) {
configModel.updateInterval = updateInterval;
}
@Override
public void reload() {
StringBuilder configBuilder = new StringBuilder();
try (Stream<String> input = Files.lines(configPath)) {
input.forEach(configBuilder::append);
} catch (IOException e) {
e.printStackTrace();
}
configModel = gson.fromJson(configBuilder.toString(), new TypeToken<ConfigModel>() {
}.getType());
}
public void saveConfig() {
try (Writer writer = new BufferedWriter(new FileWriter(configPath.toFile()))) {
gson.toJson(configModel, new TypeToken<ConfigModel>() {
}.getType(), writer);
try (final Reader reader = Files.newBufferedReader(savePath)) {
configModel = GSON.fromJson(reader, ConfigModel.class);
} catch (IOException e) {
e.printStackTrace();
}
}
public Optional<ConfigModel> getConfigModel() {
return Optional.ofNullable(configModel);
private void saveConfig() {
try {
Files.createDirectories(savePath.getParent());
} catch (IOException e) {
e.printStackTrace();
}
try (final Writer writer = Files.newBufferedWriter(savePath)) {
GSON.toJson(configModel, writer);
} catch (IOException e) {
e.printStackTrace();
}
}
public static class ConfigModel {
public Integer version;
public String language;
public Boolean continueOnRestart;
public Map<String, TaskModel> tasks;
@SuppressWarnings("unused")
private static class ConfigModel {
private Integer version = 2;
private String language = "en";
private Boolean continueOnRestart = false;
private Boolean forceLoadExistingChunks = false;
private Boolean silent = false;
private Integer updateInterval = 1;
private Map<String, TaskModel> tasks;
public Integer getVersion() {
return version;
}
public void setVersion(final Integer version) {
this.version = version;
}
public String getLanguage() {
return language;
}
public void setLanguage(final String language) {
this.language = language;
}
public Boolean getContinueOnRestart() {
return continueOnRestart;
}
public void setContinueOnRestart(final Boolean continueOnRestart) {
this.continueOnRestart = continueOnRestart;
}
public Boolean getForceLoadExistingChunks() {
return forceLoadExistingChunks;
}
public void setForceLoadExistingChunks(final Boolean forceLoadExistingChunks) {
this.forceLoadExistingChunks = forceLoadExistingChunks;
}
public Map<String, TaskModel> getTasks() {
return tasks;
}
public void setTasks(final Map<String, TaskModel> tasks) {
this.tasks = tasks;
}
public boolean isSilent() {
return silent;
}
public void setSilent(final boolean silent) {
this.silent = silent;
}
public int getUpdateInterval() {
return updateInterval;
}
public void setUpdateInterval(final int updateInterval) {
this.updateInterval = updateInterval;
}
}
public static class TaskModel {
public Boolean cancelled;
public Double radius;
public Double radiusZ;
public Double centerX;
public Double centerZ;
public String iterator;
public String shape;
public Long count;
public Long time;
@SuppressWarnings("unused")
private static class TaskModel {
private Boolean cancelled;
private Double radius;
private Double radiusZ;
private Double centerX;
private Double centerZ;
private String iterator;
private String shape;
private Long count;
private Long time;
public Boolean getCancelled() {
return cancelled;
}
public void setCancelled(final Boolean cancelled) {
this.cancelled = cancelled;
}
public Double getRadius() {
return radius;
}
public void setRadius(final Double radius) {
this.radius = radius;
}
public Double getRadiusZ() {
return radiusZ;
}
public void setRadiusZ(final Double radiusZ) {
this.radiusZ = radiusZ;
}
public Double getCenterX() {
return centerX;
}
public void setCenterX(final Double centerX) {
this.centerX = centerX;
}
public Double getCenterZ() {
return centerZ;
}
public void setCenterZ(final Double centerZ) {
this.centerZ = centerZ;
}
public String getIterator() {
return iterator;
}
public void setIterator(final String iterator) {
this.iterator = iterator;
}
public String getShape() {
return shape;
}
public void setShape(final String shape) {
this.shape = shape;
}
public Long getCount() {
return count;
}
public void setCount(final Long count) {
this.count = count;
}
public Long getTime() {
return time;
}
public void setTime(final Long time) {
this.time = time;
}
}
}
@@ -0,0 +1,104 @@
package org.popcraft.chunky.platform.util;
import org.popcraft.chunky.platform.World;
public class Location {
private World world;
private double x;
private double y;
private double z;
private float yaw;
private float pitch;
public Location(final World world, final double x, final double y, final double z, final float yaw, final float pitch) {
this.world = world;
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
}
public Location(final World world, final double x, final double y, final double z) {
this.world = world;
this.x = x;
this.y = y;
this.z = z;
}
public Vector3 toVector() {
return new Vector3(x, y, z);
}
public Location add(final Vector3 vector) {
x += vector.getX();
y += vector.getY();
z += vector.getZ();
return this;
}
public Location setDirection(final Vector3 direction) {
final double dirX = direction.getX();
final double dirY = direction.getY();
final double dirZ = direction.getZ();
if (dirX == 0 && dirZ == 0) {
if (dirY == 0) {
pitch = 0;
} else {
pitch = dirY < 0 ? 90 : -90;
}
return this;
}
yaw = (float) Math.toDegrees(Math.atan2(-dirX, dirZ));
pitch = (float) Math.toDegrees(-Math.atan(dirY / Math.sqrt(dirX * dirX + dirZ * dirZ)));
return this;
}
public World getWorld() {
return world;
}
public void setWorld(final World world) {
this.world = world;
}
public double getX() {
return x;
}
public void setX(final double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(final double y) {
this.y = y;
}
public double getZ() {
return z;
}
public void setZ(final double z) {
this.z = z;
}
public float getYaw() {
return yaw;
}
public void setYaw(final float yaw) {
this.yaw = yaw;
}
public float getPitch() {
return pitch;
}
public void setPitch(final float pitch) {
this.pitch = pitch;
}
}
@@ -0,0 +1,68 @@
package org.popcraft.chunky.platform.util;
public class Vector2 {
private double x;
private double z;
public Vector2(final double x, final double z) {
this.x = x;
this.z = z;
}
public static Vector2 of(final double x, final double z) {
return new Vector2(x, z);
}
public Vector2 add(final Vector2 other) {
x += other.x;
z += other.z;
return this;
}
public Vector2 multiply(final double value) {
x *= value;
z *= value;
return this;
}
public Vector2 normalize() {
final double length = length();
x /= length;
z /= length;
return this;
}
public double distance(final Vector2 other) {
return Math.sqrt(distanceSquared(other));
}
public double distanceSquared(final Vector2 other) {
final double dx = this.x - other.x;
final double dz = this.z - other.z;
return dx * dx + dz * dz;
}
public double length() {
return Math.sqrt(lengthSquared());
}
public double lengthSquared() {
return x * x + z * z;
}
public double getX() {
return x;
}
public void setX(final double x) {
this.x = x;
}
public double getZ() {
return z;
}
public void setZ(final double z) {
this.z = z;
}
}
@@ -0,0 +1,82 @@
package org.popcraft.chunky.platform.util;
public class Vector3 {
private double x;
private double y;
private double z;
public Vector3(final double x, final double y, final double z) {
this.x = x;
this.y = y;
this.z = z;
}
public static Vector3 of(final double x, final double y, final double z) {
return new Vector3(x, y, z);
}
public Vector3 add(final Vector3 other) {
x += other.x;
y += other.y;
z += other.z;
return this;
}
public Vector3 multiply(final double value) {
x *= value;
y *= value;
z *= value;
return this;
}
public Vector3 normalize() {
final double length = length();
x /= length;
y /= length;
z /= length;
return this;
}
public double distance(final Vector3 other) {
return Math.sqrt(distanceSquared(other));
}
public double distanceSquared(final Vector3 other) {
final double dx = this.x - other.x;
final double dy = this.y - other.y;
final double dz = this.z - other.z;
return dx * dx + dy * dy + dz * dz;
}
public double length() {
return Math.sqrt(lengthSquared());
}
public double lengthSquared() {
return x * x + y * y + z * z;
}
public double getX() {
return x;
}
public void setX(final double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(final double y) {
this.y = y;
}
public double getZ() {
return z;
}
public void setZ(final double z) {
this.z = z;
}
}
@@ -1,13 +1,12 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.util.Vector2;
public abstract class AbstractEllipse extends AbstractShape {
protected AbstractEllipse(Selection selection, boolean chunkAligned) {
protected AbstractEllipse(final Selection selection, final boolean chunkAligned) {
super(selection, chunkAligned);
}
public double[] getRadii() {
return new double[]{radiusX, radiusZ};
}
public abstract Vector2 radii();
}
@@ -1,13 +1,14 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.util.Vector2;
import java.util.List;
public abstract class AbstractPolygon extends AbstractShape {
protected AbstractPolygon(Selection selection, boolean chunkAligned) {
protected AbstractPolygon(final Selection selection, final boolean chunkAligned) {
super(selection, chunkAligned);
}
public abstract double[] pointsX();
public abstract double[] pointsZ();
public abstract List<Vector2> points();
}
@@ -1,13 +1,14 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.util.Vector2;
public abstract class AbstractShape implements Shape {
protected final double centerX, centerZ;
protected final double diameterX, diameterZ;
protected final double radiusX, radiusZ;
protected AbstractShape(Selection selection, boolean chunkAligned) {
protected AbstractShape(final Selection selection, final boolean chunkAligned) {
if (chunkAligned) {
this.centerX = (double) (selection.centerChunkX() << 4) + 8;
this.centerZ = (double) (selection.centerChunkZ() << 4) + 8;
@@ -25,7 +26,7 @@ public abstract class AbstractShape implements Shape {
}
}
public double[] getCenter() {
return new double[]{centerX, centerZ};
public Vector2 center() {
return Vector2.of(centerX, centerZ);
}
}
@@ -1,19 +1,25 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.util.Vector2;
public class Circle extends AbstractEllipse {
public Circle(Selection selection, boolean chunkAligned) {
public Circle(final Selection selection, final boolean chunkAligned) {
super(selection, chunkAligned);
}
@Override
public boolean isBounding(double x, double z) {
public boolean isBounding(final double x, final double z) {
return Math.hypot(centerX - x, centerZ - z) <= radiusX;
}
@Override
public String name() {
return "circle";
return ShapeType.CIRCLE;
}
@Override
public Vector2 radii() {
return Vector2.of(radiusX, radiusX);
}
}
@@ -1,13 +1,17 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.util.Vector2;
import java.util.Arrays;
import java.util.List;
import static org.popcraft.chunky.shape.ShapeUtil.insideLine;
public class Diamond extends AbstractPolygon {
final double p1x, p1z, p2x, p2z, p3x, p3z, p4x, p4z;
public Diamond(Selection selection, boolean chunkAligned) {
public Diamond(final Selection selection, final boolean chunkAligned) {
super(selection, chunkAligned);
this.p1x = centerX;
this.p1z = centerZ + radiusX;
@@ -20,17 +24,17 @@ public class Diamond extends AbstractPolygon {
}
@Override
public double[] pointsX() {
return new double[]{p1x, p2x, p3x, p4x};
public List<Vector2> points() {
return Arrays.asList(
Vector2.of(p1x, p1z),
Vector2.of(p2x, p2z),
Vector2.of(p3x, p3z),
Vector2.of(p4x, p4z)
);
}
@Override
public double[] pointsZ() {
return new double[]{p1z, p2z, p3z, p4z};
}
@Override
public boolean isBounding(double x, double z) {
public boolean isBounding(final double x, final double z) {
if (!insideLine(p1x, p1z, p2x, p2z, x, z)) {
return false;
}
@@ -45,6 +49,6 @@ public class Diamond extends AbstractPolygon {
@Override
public String name() {
return "diamond";
return ShapeType.DIAMOND;
}
}
@@ -1,19 +1,25 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.util.Vector2;
public class Ellipse extends AbstractEllipse {
public Ellipse(Selection selection, boolean chunkAligned) {
public Ellipse(final Selection selection, final boolean chunkAligned) {
super(selection, chunkAligned);
}
@Override
public boolean isBounding(double x, double z) {
public boolean isBounding(final double x, final double z) {
return (Math.pow(x - centerX, 2) / Math.pow(radiusX, 2)) + (Math.pow(z - centerZ, 2) / Math.pow(radiusZ, 2)) <= 1;
}
@Override
public String name() {
return "ellipse";
return ShapeType.ELLIPSE;
}
@Override
public Vector2 radii() {
return Vector2.of(radiusX, radiusZ);
}
}
@@ -0,0 +1,57 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.util.Vector2;
import java.util.Arrays;
import java.util.List;
import static org.popcraft.chunky.shape.ShapeUtil.insideLine;
public class Hexagon extends AbstractPolygon {
private final double p1x, p1z, p2x, p2z, p3x, p3z, p4x, p4z, p5x, p5z, p6x, p6z;
public Hexagon(final Selection selection, final boolean chunkAligned) {
super(selection, chunkAligned);
this.p1x = centerX + radiusX * Math.cos(Math.toRadians(60));
this.p1z = centerZ + radiusX * Math.sin(Math.toRadians(60));
this.p2x = centerX + radiusX * Math.cos(Math.toRadians(120));
this.p2z = centerZ + radiusX * Math.sin(Math.toRadians(120));
this.p3x = centerX + radiusX * Math.cos(Math.toRadians(180));
this.p3z = centerZ + radiusX * Math.sin(Math.toRadians(180));
this.p4x = centerX + radiusX * Math.cos(Math.toRadians(240));
this.p4z = centerZ + radiusX * Math.sin(Math.toRadians(240));
this.p5x = centerX + radiusX * Math.cos(Math.toRadians(300));
this.p5z = centerZ + radiusX * Math.sin(Math.toRadians(300));
this.p6x = centerX + radiusX * Math.cos(Math.toRadians(360));
this.p6z = centerZ + radiusX * Math.sin(Math.toRadians(360));
}
@Override
public List<Vector2> points() {
return Arrays.asList(
Vector2.of(p1x, p1z),
Vector2.of(p2x, p2z),
Vector2.of(p3x, p3z),
Vector2.of(p4x, p4z),
Vector2.of(p5x, p5z),
Vector2.of(p6x, p6z)
);
}
@Override
public boolean isBounding(final double x, final double z) {
final boolean inside12 = insideLine(p1x, p1z, p2x, p2z, x, z);
final boolean inside23 = insideLine(p2x, p2z, p3x, p3z, x, z);
final boolean inside34 = insideLine(p3x, p3z, p4x, p4z, x, z);
final boolean inside45 = insideLine(p4x, p4z, p5x, p5z, x, z);
final boolean inside56 = insideLine(p5x, p5z, p6x, p6z, x, z);
final boolean inside61 = insideLine(p6x, p6z, p1x, p1z, x, z);
return inside12 && inside23 && inside34 && inside45 && inside56 && inside61;
}
@Override
public String name() {
return ShapeType.HEXAGON;
}
}
@@ -1,13 +1,17 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.util.Vector2;
import java.util.Arrays;
import java.util.List;
import static org.popcraft.chunky.shape.ShapeUtil.insideLine;
public class Pentagon extends AbstractPolygon {
private final double p1x, p1z, p2x, p2z, p3x, p3z, p4x, p4z, p5x, p5z;
public Pentagon(Selection selection, boolean chunkAligned) {
public Pentagon(final Selection selection, final boolean chunkAligned) {
super(selection, chunkAligned);
this.p1x = centerX + radiusX * Math.cos(Math.toRadians(54));
this.p1z = centerZ + radiusX * Math.sin(Math.toRadians(54));
@@ -22,27 +26,28 @@ public class Pentagon extends AbstractPolygon {
}
@Override
public double[] pointsX() {
return new double[]{p1x, p2x, p3x, p4x, p5x};
public List<Vector2> points() {
return Arrays.asList(
Vector2.of(p1x, p1z),
Vector2.of(p2x, p2z),
Vector2.of(p3x, p3z),
Vector2.of(p4x, p4z),
Vector2.of(p5x, p5z)
);
}
@Override
public double[] pointsZ() {
return new double[]{p1z, p2z, p3z, p4z, p5z};
}
@Override
public boolean isBounding(double x, double z) {
boolean inside12 = insideLine(p1x, p1z, p2x, p2z, x, z);
boolean inside23 = insideLine(p2x, p2z, p3x, p3z, x, z);
boolean inside34 = insideLine(p3x, p3z, p4x, p4z, x, z);
boolean inside45 = insideLine(p4x, p4z, p5x, p5z, x, z);
boolean inside51 = insideLine(p5x, p5z, p1x, p1z, x, z);
public boolean isBounding(final double x, final double z) {
final boolean inside12 = insideLine(p1x, p1z, p2x, p2z, x, z);
final boolean inside23 = insideLine(p2x, p2z, p3x, p3z, x, z);
final boolean inside34 = insideLine(p3x, p3z, p4x, p4z, x, z);
final boolean inside45 = insideLine(p4x, p4z, p5x, p5z, x, z);
final boolean inside51 = insideLine(p5x, p5z, p1x, p1z, x, z);
return inside12 && inside23 && inside34 && inside45 && inside51;
}
@Override
public String name() {
return "pentagon";
return ShapeType.PENTAGON;
}
}
@@ -1,12 +1,16 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.util.Vector2;
import java.util.Arrays;
import java.util.List;
public class Rectangle extends AbstractPolygon {
final double b1x, b1z, b2x, b2z;
final double p1x, p1z, p2x, p2z, p3x, p3z, p4x, p4z;
protected Rectangle(Selection selection, boolean chunkAligned) {
protected Rectangle(final Selection selection, final boolean chunkAligned) {
super(selection, chunkAligned);
this.b1x = centerX - radiusX;
this.b1z = centerZ - radiusZ;
@@ -23,22 +27,22 @@ public class Rectangle extends AbstractPolygon {
}
@Override
public double[] pointsX() {
return new double[]{p1x, p2x, p3x, p4x};
public List<Vector2> points() {
return Arrays.asList(
Vector2.of(p1x, p1z),
Vector2.of(p2x, p2z),
Vector2.of(p3x, p3z),
Vector2.of(p4x, p4z)
);
}
@Override
public double[] pointsZ() {
return new double[]{p1z, p2z, p3z, p4z};
}
@Override
public boolean isBounding(double x, double z) {
public boolean isBounding(final double x, final double z) {
return x >= b1x && x <= b2x && z >= b1z && z <= b2z;
}
@Override
public String name() {
return "rectangle";
return ShapeType.RECTANGLE;
}
}
@@ -1,32 +1,43 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.util.Translator;
public class ShapeFactory {
public static Shape getShape(Selection selection) {
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
public final class ShapeFactory {
private static final Map<String, BiFunction<Selection, Boolean, Shape>> custom = new HashMap<>();
private ShapeFactory() {
}
public static Shape getShape(final Selection selection) {
return getShape(selection, true);
}
public static Shape getShape(Selection selection, boolean chunkAligned) {
switch (selection.shape()) {
case "circle":
return new Circle(selection, chunkAligned);
case "diamond":
return new Diamond(selection, chunkAligned);
case "oval":
case "ellipse":
return new Ellipse(selection, chunkAligned);
case "pentagon":
return new Pentagon(selection, chunkAligned);
case "rectangle":
return new Rectangle(selection, chunkAligned);
case "star":
return new Star(selection, chunkAligned);
case "triangle":
return new Triangle(selection, chunkAligned);
case "square":
default:
return new Square(selection, chunkAligned);
}
public static Shape getShape(final Selection selection, final boolean chunkAligned) {
return switch (selection.shape()) {
case ShapeType.CIRCLE -> new Circle(selection, chunkAligned);
case ShapeType.DIAMOND -> new Diamond(selection, chunkAligned);
case ShapeType.ELLIPSE, ShapeType.OVAL -> new Ellipse(selection, chunkAligned);
case ShapeType.HEXAGON -> new Hexagon(selection, chunkAligned);
case ShapeType.PENTAGON -> new Pentagon(selection, chunkAligned);
case ShapeType.RECTANGLE -> new Rectangle(selection, chunkAligned);
case ShapeType.STAR -> new Star(selection, chunkAligned);
case ShapeType.TRIANGLE -> new Triangle(selection, chunkAligned);
default -> custom.getOrDefault(selection.shape(), Square::new).apply(selection, chunkAligned);
};
}
public static void registerCustom(final String name, final BiFunction<Selection, Boolean, Shape> shapeFunction) {
custom.put(name, shapeFunction);
Translator.addCustomTranslation("shape_%s".formatted(name), name);
}
public static Set<String> getCustomTypes() {
return custom.keySet();
}
}
@@ -0,0 +1,33 @@
package org.popcraft.chunky.shape;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public final class ShapeType {
public static final String CIRCLE = "circle";
public static final String DIAMOND = "diamond";
public static final String ELLIPSE = "ellipse";
public static final String HEXAGON = "hexagon";
public static final String OVAL = "oval";
public static final String PENTAGON = "pentagon";
public static final String RECTANGLE = "rectangle";
public static final String SQUARE = "square";
public static final String STAR = "star";
public static final String TRIANGLE = "triangle";
private static final List<String> DEFAULTS = List.of(CIRCLE, DIAMOND, ELLIPSE, HEXAGON, PENTAGON, RECTANGLE, SQUARE, STAR, TRIANGLE);
private ShapeType() {
}
public static List<String> all() {
final Set<String> customTypes = ShapeFactory.getCustomTypes();
if (customTypes.isEmpty()) {
return DEFAULTS;
}
final List<String> allTypes = new ArrayList<>(DEFAULTS);
allTypes.addAll(customTypes);
return allTypes;
}
}
@@ -1,8 +1,13 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.platform.util.Vector2;
import java.util.Optional;
public class ShapeUtil {
public final class ShapeUtil {
private ShapeUtil() {
}
/**
* Checks if a point C is inside (to the left of) the line defined by point A and B.
*
@@ -14,7 +19,7 @@ public class ShapeUtil {
* @param cz Point C z
* @return Whether point C can be considered inside of line AB.
*/
public static boolean insideLine(double ax, double az, double bx, double bz, double cx, double cz) {
public static boolean insideLine(final double ax, final double az, final double bx, final double bz, final double cx, final double cz) {
// Compute whether the point is inside the line using a cross product
return (bx - ax) * (cz - az) > (bz - az) * (cx - ax);
}
@@ -22,49 +27,31 @@ public class ShapeUtil {
/**
* Given two lines defined by two points each respectively, return the intersection point, if any.
*
* @param l1p1x Line 1 point 1 x
* @param l1p1z Line 1 point 1 z
* @param l1p2x Line 1 point 2 x
* @param l1p2z Line 1 point 2 z
* @param l2p1x Line 2 point 1 x
* @param l2p1z Line 2 point 1 z
* @param l2p2x Line 2 point 2 x
* @param l2p2z Line 2 point 2 z
* @param l1x1 Line 1 point 1 x
* @param l1z1 Line 1 point 1 z
* @param l1x2 Line 1 point 2 x
* @param l1z2 Line 1 point 2 z
* @param l2x1 Line 2 point 1 x
* @param l2z1 Line 2 point 1 z
* @param l2x2 Line 2 point 2 x
* @param l2z2 Line 2 point 2 z
* @return An optional containing the intersection point, or empty if no intersection.
*/
public static Optional<double[]> intersection(double l1p1x, double l1p1z, double l1p2x, double l1p2z, double l2p1x, double l2p1z, double l2p2x, double l2p2z) {
// Compute delta x for each line
double l1dx = l1p2x - l1p1x, l2dx = l2p2x - l2p1x;
// If both are equal to zero, the lines do not intersect at a point
if (l1dx == 0 && l2dx == 0) {
public static Optional<Vector2> intersection(final double l1x1, final double l1z1, final double l1x2, final double l1z2, final double l2x1, final double l2z1, final double l2x2, final double l2z2) {
final double a1 = l1z2 - l1z1;
final double a2 = l2z2 - l2z1;
final double b1 = l1x1 - l1x2;
final double b2 = l2x1 - l2x2;
final double determinant = a1 * b2 - a2 * b1;
if (determinant == 0) {
return Optional.empty();
} else {
final double c1 = a1 * l1x1 + b1 * l1z1;
final double c2 = a2 * l2x1 + b2 * l2z1;
final double x = (b2 * c1 - b1 * c2) / determinant;
final double z = (a1 * c2 - a2 * c1) / determinant;
return Optional.of(Vector2.of(x, z));
}
// Compute delta z for each line
double l1dz = l1p2z - l1p1z, l2dz = l2p2z - l2p1z;
// Compute the slope and intercept for line 2
double l2m = l2dz / l2dx;
double l2b = l2p1z - (l2m * l2p1x);
// If the slope of line 1 is zero, then the intersection can be computed directly from its x value
if (l1dx == 0) {
return Optional.of(new double[]{l1p1x, l2m * l1p1x + l2b});
}
// Compute the slope and intercept for line 1
double l1m = l1dz / l1dx;
double l1b = l1p1z - (l1m * l1p1x);
// If the slope of line 2 is zero, then the intersection can be computed directly from its x value
if (l2dx == 0) {
return Optional.of(new double[]{l2p1x, l1m * l2p1x + l1b});
}
// If the slope of both lines are equal, then they do not intersect, or are the same line
if (l1m == l2m) {
return Optional.empty();
}
// Solve the system of equations to get the intersection point
double a1 = -l1m, c1 = -l1b;
double a2 = -l2m, c2 = -l2b;
double ix = (c2 - c1) / (a1 - a2);
double iz = (a2 * c1 - a1 * c2) / (a1 - a2);
return Optional.of(new double[]{ix, iz});
}
/**
@@ -78,9 +65,48 @@ public class ShapeUtil {
* @param angle Angle in radians
* @return The point on the ellipse.
*/
public static double[] pointOnEllipse(double centerX, double centerZ, double radiusX, double radiusZ, double angle) {
double pointX = centerX + radiusX * Math.cos(angle);
double pointZ = centerZ + radiusZ * Math.sin(angle);
return new double[]{pointX, pointZ};
public static Vector2 pointOnEllipse(final double centerX, final double centerZ, final double radiusX, final double radiusZ, final double angle) {
final double x = centerX + radiusX * Math.cos(angle);
final double z = centerZ + radiusZ * Math.sin(angle);
return Vector2.of(x, z);
}
/**
* Calculate the closest point on a line from a given position.
*
* @param posX Position x
* @param posZ Position z
* @param p1x Line point 1 x
* @param p1z Line point 1 z
* @param p2x Line point 2 x
* @param p2z Line point 2 z
* @return The closest point to the position on the line.
*/
public static Vector2 closestPointOnLine(final double posX, final double posZ, final double p1x, final double p1z, final double p2x, final double p2z) {
final double dx = p2x - p1x;
final double dz = p2z - p1z;
final double perpendicularSlope = -dx / dz;
final double p3x, p3z;
if (Double.isInfinite(perpendicularSlope)) {
p3x = posX;
p3z = posZ + 1;
} else {
p3x = posX + 1;
p3z = posZ + perpendicularSlope;
}
return ShapeUtil.intersection(p1x, p1z, p2x, p2z, posX, posZ, p3x, p3z).orElseThrow(IllegalStateException::new);
}
/**
* Calculate the distance between 2 points.
*
* @param p1x Point 1 x
* @param p1z Point 1 z
* @param p2x Point 2 x
* @param p2z Point 2 z
* @return The distance between the 2 points.
*/
public static double distanceBetweenPoints(final double p1x, final double p1z, final double p2x, final double p2z) {
return Math.sqrt(Math.pow(p1x - p2x, 2) + Math.pow(p1z - p2z, 2));
}
}
@@ -1,12 +1,16 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.util.Vector2;
import java.util.Arrays;
import java.util.List;
public class Square extends AbstractPolygon {
final double b1x, b1z, b2x, b2z;
final double p1x, p1z, p2x, p2z, p3x, p3z, p4x, p4z;
protected Square(Selection selection, boolean chunkAligned) {
protected Square(final Selection selection, final boolean chunkAligned) {
super(selection, chunkAligned);
this.b1x = centerX - radiusX;
this.b1z = centerZ - radiusX;
@@ -23,22 +27,22 @@ public class Square extends AbstractPolygon {
}
@Override
public double[] pointsX() {
return new double[]{p1x, p2x, p3x, p4x};
public List<Vector2> points() {
return Arrays.asList(
Vector2.of(p1x, p1z),
Vector2.of(p2x, p2z),
Vector2.of(p3x, p3z),
Vector2.of(p4x, p4z)
);
}
@Override
public double[] pointsZ() {
return new double[]{p1z, p2z, p3z, p4z};
}
@Override
public boolean isBounding(double x, double z) {
public boolean isBounding(final double x, final double z) {
return x >= b1x && x <= b2x && z >= b1z && z <= b2z;
}
@Override
public String name() {
return "square";
return ShapeType.SQUARE;
}
}
@@ -1,6 +1,10 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.util.Vector2;
import java.util.Arrays;
import java.util.List;
import static org.popcraft.chunky.shape.ShapeUtil.insideLine;
import static org.popcraft.chunky.shape.ShapeUtil.intersection;
@@ -9,7 +13,7 @@ public class Star extends AbstractPolygon {
private final double p1x, p1z, p2x, p2z, p3x, p3z, p4x, p4z, p5x, p5z;
private final double i1x, i1z, i2x, i2z, i3x, i3z, i4x, i4z, i5x, i5z;
public Star(Selection selection, boolean chunkAligned) {
public Star(final Selection selection, final boolean chunkAligned) {
super(selection, chunkAligned);
this.p1x = centerX + radiusX * Math.cos(Math.toRadians(54));
this.p1z = centerZ + radiusX * Math.sin(Math.toRadians(54));
@@ -21,43 +25,49 @@ public class Star extends AbstractPolygon {
this.p4z = centerZ + radiusX * Math.sin(Math.toRadians(270));
this.p5x = centerX + radiusX * Math.cos(Math.toRadians(342));
this.p5z = centerZ + radiusX * Math.sin(Math.toRadians(342));
double[] i1 = intersection(p1x, p1z, p3x, p3z, p2x, p2z, p5x, p5z).orElse(new double[]{p1x, p1z});
double[] i2 = intersection(p1x, p1z, p3x, p3z, p2x, p2z, p4x, p4z).orElse(new double[]{p2x, p2z});
double[] i3 = intersection(p2x, p2z, p4x, p4z, p3x, p3z, p5x, p5z).orElse(new double[]{p3x, p3z});
double[] i4 = intersection(p3x, p3z, p5x, p5z, p1x, p1z, p4x, p4z).orElse(new double[]{p4x, p4z});
double[] i5 = intersection(p1x, p1z, p4x, p4z, p2x, p2z, p5x, p5z).orElse(new double[]{p5x, p5z});
this.i1x = i1[0];
this.i1z = i1[1];
this.i2x = i2[0];
this.i2z = i2[1];
this.i3x = i3[0];
this.i3z = i3[1];
this.i4x = i4[0];
this.i4z = i4[1];
this.i5x = i5[0];
this.i5z = i5[1];
final Vector2 i1 = intersection(p1x, p1z, p3x, p3z, p2x, p2z, p5x, p5z).orElse(Vector2.of(p1x, p1z));
final Vector2 i2 = intersection(p1x, p1z, p3x, p3z, p2x, p2z, p4x, p4z).orElse(Vector2.of(p2x, p2z));
final Vector2 i3 = intersection(p2x, p2z, p4x, p4z, p3x, p3z, p5x, p5z).orElse(Vector2.of(p3x, p3z));
final Vector2 i4 = intersection(p3x, p3z, p5x, p5z, p1x, p1z, p4x, p4z).orElse(Vector2.of(p4x, p4z));
final Vector2 i5 = intersection(p1x, p1z, p4x, p4z, p2x, p2z, p5x, p5z).orElse(Vector2.of(p5x, p5z));
this.i1x = i1.getX();
this.i1z = i1.getZ();
this.i2x = i2.getX();
this.i2z = i2.getZ();
this.i3x = i3.getX();
this.i3z = i3.getZ();
this.i4x = i4.getX();
this.i4z = i4.getZ();
this.i5x = i5.getX();
this.i5z = i5.getZ();
}
@Override
public double[] pointsX() {
return new double[]{p1x, i1x, p2x, i2x, p3x, i3x, p4x, i4x, p5x, i5x};
public List<Vector2> points() {
return Arrays.asList(
Vector2.of(p1x, p1z),
Vector2.of(i1x, i1z),
Vector2.of(p2x, p2z),
Vector2.of(i2x, i2z),
Vector2.of(p3x, p3z),
Vector2.of(i3x, i3z),
Vector2.of(p4x, p4z),
Vector2.of(i4x, i4z),
Vector2.of(p5x, p5z),
Vector2.of(i5x, i5z)
);
}
@Override
public double[] pointsZ() {
return new double[]{p1z, i1z, p2z, i2z, p3z, i3z, p4z, i4z, p5z, i5z};
}
@Override
public boolean isBounding(double x, double z) {
boolean inside13 = insideLine(p1x, p1z, p3x, p3z, x, z);
boolean inside24 = insideLine(p2x, p2z, p4x, p4z, x, z);
boolean inside35 = insideLine(p3x, p3z, p5x, p5z, x, z);
boolean inside41 = insideLine(p4x, p4z, p1x, p1z, x, z);
public boolean isBounding(final double x, final double z) {
final boolean inside13 = insideLine(p1x, p1z, p3x, p3z, x, z);
final boolean inside24 = insideLine(p2x, p2z, p4x, p4z, x, z);
final boolean inside35 = insideLine(p3x, p3z, p5x, p5z, x, z);
final boolean inside41 = insideLine(p4x, p4z, p1x, p1z, x, z);
if (inside13 && inside24 && inside35 && inside41) {
return true;
}
boolean inside52 = insideLine(p5x, p5z, p2x, p2z, x, z);
final boolean inside52 = insideLine(p5x, p5z, p2x, p2z, x, z);
if (inside24 && inside35 && inside41 && inside52) {
return true;
}
@@ -72,6 +82,6 @@ public class Star extends AbstractPolygon {
@Override
public String name() {
return "star";
return ShapeType.STAR;
}
}
@@ -1,13 +1,17 @@
package org.popcraft.chunky.shape;
import org.popcraft.chunky.Selection;
import org.popcraft.chunky.platform.util.Vector2;
import java.util.Arrays;
import java.util.List;
import static org.popcraft.chunky.shape.ShapeUtil.insideLine;
public class Triangle extends AbstractPolygon {
private final double p1x, p1z, p2x, p2z, p3x, p3z;
public Triangle(Selection selection, boolean chunkAligned) {
public Triangle(final Selection selection, final boolean chunkAligned) {
super(selection, chunkAligned);
this.p1x = centerX + radiusX;
this.p1z = centerZ + radiusX;
@@ -18,17 +22,16 @@ public class Triangle extends AbstractPolygon {
}
@Override
public double[] pointsX() {
return new double[]{p1x, p2x, p3x};
public List<Vector2> points() {
return Arrays.asList(
Vector2.of(p1x, p1z),
Vector2.of(p2x, p2z),
Vector2.of(p3x, p3z)
);
}
@Override
public double[] pointsZ() {
return new double[]{p1z, p2z, p3z};
}
@Override
public boolean isBounding(double x, double z) {
public boolean isBounding(final double x, final double z) {
if (!insideLine(p1x, p1z, p2x, p2z, x, z)) {
return false;
}
@@ -40,6 +43,6 @@ public class Triangle extends AbstractPolygon {
@Override
public String name() {
return "triangle";
return ShapeType.TRIANGLE;
}
}
@@ -1,18 +1,49 @@
package org.popcraft.chunky.util;
public class ChunkCoordinate implements Comparable<ChunkCoordinate> {
public final int x, z;
import java.util.Objects;
import java.util.Optional;
public ChunkCoordinate(int x, int z) {
this.x = x;
this.z = z;
public record ChunkCoordinate(int x, int z) implements Comparable<ChunkCoordinate> {
public static Optional<ChunkCoordinate> fromRegionFile(final String regionFileName) {
if (!regionFileName.startsWith("r.")) {
return Optional.empty();
}
final int extension = regionFileName.indexOf(".mca");
if (extension < 2) {
return Optional.empty();
}
final String regionCoordinates = regionFileName.substring(2, extension);
final int separator = regionCoordinates.indexOf('.');
final Optional<Integer> regionX = Input.tryInteger(regionCoordinates.substring(0, separator));
final Optional<Integer> regionZ = Input.tryInteger(regionCoordinates.substring(separator + 1));
if (regionX.isPresent() && regionZ.isPresent()) {
return Optional.of(new ChunkCoordinate(regionX.get(), regionZ.get()));
}
return Optional.empty();
}
@Override
public int compareTo(ChunkCoordinate o) {
public int compareTo(final ChunkCoordinate o) {
return this.x == o.x ? Integer.compare(this.z, o.z) : Integer.compare(this.x, o.x);
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final ChunkCoordinate that = (ChunkCoordinate) o;
return x == that.x && z == that.z;
}
@Override
public int hashCode() {
return Objects.hash(x, z);
}
@Override
public String toString() {
return String.format("%d, %d", x, z);
@@ -1,13 +1,16 @@
package org.popcraft.chunky.util;
public class ChunkMath {
public static long pack(int x, int z) {
long lx = x & 0xFFFFFFFFL;
long lz = z & 0xFFFFFFFFL;
public final class ChunkMath {
private ChunkMath() {
}
public static long pack(final int x, final int z) {
final long lx = x & 0xFFFFFFFFL;
final long lz = z & 0xFFFFFFFFL;
return lx << 32 | lz;
}
public static int regionIndex(int x, int z) {
public static int regionIndex(final int x, final int z) {
return (x & 0x1F) * 32 + (z & 0x1F);
}
}
@@ -1,26 +0,0 @@
package org.popcraft.chunky.util;
public class Coordinate {
private double x, z;
public Coordinate(double x, double z) {
this.x = x;
this.z = z;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getZ() {
return z;
}
public void setZ(double z) {
this.z = z;
}
}
Loaded 100 of 219 files, more files were not shown because too many files have changed in this diff. Show more