Compare commits

...
Author SHA1 Message Date
Ben Woo 981d368058 Merge pull request #3338 from Multiverse/feat/relative-positioning
Add back support for relative position with ~ in destination coordinates
2025-09-22 20:10:26 +08:00
Ben Woo 7507099a4e Merge pull request #3341 from Multiverse/api/unloadedworldlocation-final
Make UnloadedWorldLocation non-final to allow extending
2025-09-22 20:08:52 +08:00
Ben Woo 29b58b1655 Make UnloadedWorldLocation non-final to allow extending 2025-09-22 19:57:10 +08:00
Ben Woo a1081c1b6b Merge pull request #3340 from Multiverse/feat/generator-test
Improve generator plugin detection and add an option to disable it
2025-09-22 19:55:36 +08:00
Ben Woo 3152b62668 Merge pull request #3339 from Multiverse/fix/worlds-old-exist
Fix issue when trying to backup `worlds.yml.old` and file already exists
2025-09-22 19:51:57 +08:00
Ben Woo 0082cc9852 Improve generator plugin detection and add an option to disable it 2025-09-22 19:51:18 +08:00
Ben Woo f60b656bed Fix issue when trying to backup worlds.yml.old and file already exists 2025-09-22 19:27:58 +08:00
Ben Woo da5f5de5cd Add localization for position parse error messages 2025-09-20 21:33:16 +08:00
Ben Woo e9aacecc2f Add back support for relative position with ~ in destination coordinates 2025-09-20 20:31:13 +08:00
Ben Woo fa3adbe605 Merge pull request #3332 from Multiverse/fix/delete-fail-message
Fix message when delete failed due to invalid world folder
2025-09-06 13:11:39 +08:00
Ben Woo e33b2f7025 Fix message when delete failed due to invalid world folder 2025-09-06 13:07:03 +08:00
Ben Woo a9cc577754 Merge pull request #3330 from ElFrod0/config-server-properties-path
Configurable server.properties path
2025-08-31 10:34:48 +08:00
Ben Woo 02344a31cf Add api status annotation for new server-properties-path config 2025-08-31 10:30:33 +08:00
ElFrod0 2f4871ef71 Add newline for consistency 2025-08-29 19:40:41 +02:00
ElFrod0 9750fad4b7 Update fresh config for tests 2025-08-29 19:35:21 +02:00
ElFrod0 df77372d96 Configurable server.properties file location 2025-08-29 19:33:49 +02:00
Ben Woo 039af3c219 Merge pull request #3327 from Multiverse/5.3
5.3
2025-08-29 10:34:05 +08:00
Ben Woo 09ec4f87ef Merge pull request #3328 from Multiverse/fix/mvapi-not-started
Pass in config object directly into spawn config class instead of using mvapi
2025-08-27 14:06:11 +08:00
Ben Woo f996f22f4b Pass in config object directly into spawn config class instead of using mvapi 2025-08-27 14:02:37 +08:00
Ben Woo b230c5ae9c Merge pull request #3326 from Multiverse/fix/spawning-config-order
Maintain spawn category config order between config saves
2025-08-27 13:50:30 +08:00
Ben Woo 2561107697 Merge pull request #3325 from Multiverse/feat/disable-apply-spawn
Add option to disable mv handling of spawn rate and limit
2025-08-27 13:48:42 +08:00
Ben Woo 70babc8656 Maintain spawn category config order between config saves 2025-08-27 13:46:59 +08:00
Ben Woo bd09d6c950 Merge pull request #3324 from Multiverse/chore/deps
Fix some dependency issues
2025-08-27 13:35:17 +08:00
Ben Woo b94de763ca Depend on latest paper api version 2025-08-27 12:40:52 +08:00
Ben Woo 22ca57543f Change mockbukkit version to fix test issues 2025-08-27 12:40:25 +08:00
Ben Woo 25c70170d9 Add option to disable mv handling of spawn rate and limit 2025-08-27 12:35:09 +08:00
Ben Woo 9926041c2f Merge pull request #3321 from KioProject123/fix/spawn-location-angle
fix spawn location angle
2025-08-18 15:06:10 +08:00
KioProject 1c99898d0d fix 2025-08-18 13:49:01 +08:00
KioProject 41e0261eee fix spawn location angle 2025-08-18 13:13:25 +08:00
Ben Woo 642f3ab1b8 Merge pull request #3320 from MrXiaoM/patch-1
Fix adventure dep in spigot-based server
2025-08-17 22:57:20 +08:00
人間工作 57d1844756 fix NoClassDefFoundError in spigot-based server 2025-08-17 22:09:17 +08:00
Ben Woo 73e1650fc3 Merge pull request #3318 from Multiverse/fix/match-invalid-regex
Properly handle error from invalid regex string in matcher
2025-08-11 13:08:49 +08:00
Ben Woo 800709ca95 Properly annotate parameters for matcher classes 2025-08-11 12:43:38 +08:00
Ben Woo 935cbad21c Properly handle error from invalid wildcard string in matcher 2025-08-11 12:40:05 +08:00
Ben Woo 0e2a27b298 Merge pull request #3317 from Multiverse/fix/remove-logging
Fix unwanted warning logging for matcher
2025-08-11 12:28:59 +08:00
Ben Woo baaa175423 Properly handle error from invalid regex string in matcher 2025-08-11 12:24:36 +08:00
Ben Woo e2b7a31191 Fix unwanted warning logging for matcher 2025-08-11 12:15:16 +08:00
Ben Woo afa2825bc7 Merge pull request #3315 from Multiverse/feat/load-folder-check
Add folder checking before loading world
2025-07-31 16:32:37 +08:00
Ben Woo 21b4f6b087 Add folder checking before loading world 2025-07-31 15:25:14 +08:00
Ben Woo a771652c8f Merge pull request #3291 from Multiverse/5.2
5.2
2025-07-28 10:11:52 +08:00
Ben Woo e89b4f19ee Merge pull request #3314 from Multiverse/fix/debug-permissions
Fix debug-permission comment spacing
2025-07-27 14:23:34 +08:00
Ben Woo eb8ef32a94 Fix debug-permission comment spacing 2025-07-27 12:38:50 +08:00
Ben Woo f9d783bc5c Merge pull request #3311 from Multiverse/refactor/remove-deprecate
Deprecate other removeWorld methods in favour of using RemoveWorldOpt…
2025-07-27 11:36:20 +08:00
Ben Woo 87dd4168cd Merge pull request #3313 from Multiverse/fix/save-flush
Use newer save method with chunk flushing when possible
2025-07-27 11:35:35 +08:00
Ben Woo 2da6ece606 Merge pull request #3312 from Multiverse/chore/bump-deps
Bump dependencies
2025-07-27 11:35:08 +08:00
Ben Woo cbb3a7a518 Merge pull request #3310 from Multiverse/fix/spawn-command
Fix spawn command using deprecated teleport method
2025-07-27 11:34:32 +08:00
Ben Woo 57796d295b Bump dependencies 2025-07-26 12:26:38 +08:00
Ben Woo 09314e8bce Use newer save method with chunk flushing when possible 2025-07-26 12:08:23 +08:00
Ben Woo 907a6dd5c0 Fix spawn command using deprecated teleport method 2025-07-26 11:50:53 +08:00
Ben Woo a50bd807b6 Deprecate other removeWorld methods in favour of using RemoveWorldOptions 2025-07-26 11:50:02 +08:00
Ben Woo 02081b4654 Merge pull request #3305 from Multiverse/fix/text-format-null
Fix ChatTextFormatter handling null strings
2025-07-26 10:44:56 +08:00
Ben Woo 156ec7911d Merge pull request #3308 from Multiverse/fix/world-name-dot
Fix v4 config migration when world name contains dot
2025-07-26 10:44:10 +08:00
Ben Woo 2b95c62a84 Fix v4 config migration when world name contains dot 2025-07-24 22:56:08 +08:00
Ben Woo 539d5c43d1 Merge pull request #3306 from Multiverse/chore/update-game-version
Update supported game version for 1.21.7 and 1.21.8
2025-07-23 14:19:35 +08:00
Ben Woo 1664690472 Update supported game version for 1.21.7 and 1.21.8 2025-07-23 14:08:42 +08:00
Ben Woo 455396713e Fix ChatTextFormatter handling null strings 2025-07-21 22:44:45 +08:00
Ben Woo 8bc3a593d0 Merge pull request #3303 from Multiverse/feat/remove-options
Add options to remove a world without unloading or saving the bukkit …
2025-07-20 15:05:33 +08:00
Ben Woo 5177f290ae Merge pull request #3304 from Multiverse/refactor/wm-cleanup
Refactor/wm cleanup
2025-07-20 15:05:11 +08:00
Ben Woo 5118864bb8 Use our ChatTextFormatter to remove colour from string 2025-07-20 14:56:18 +08:00
Ben Woo 654da03add Remove unnecessary casting 2025-07-20 14:54:41 +08:00
Ben Woo 61229356ad Mark initAllWorlds as internal api 2025-07-20 14:53:16 +08:00
Ben Woo fa5aa41547 Add options to remove a world without unloading or saving the bukkit world 2025-07-20 14:49:57 +08:00
Ben Woo 04f07657eb Merge pull request #3301 from Multiverse/feat/import-bukkit
Improve support for importing worlds already loaded on the server
2025-07-20 11:44:45 +08:00
Ben Woo eea2af169f Improve support for importing worlds already loaded on the server 2025-07-19 23:33:51 +08:00
Ben Woo 6d393723ff Merge pull request #3299 from Multiverse/feat/skip-folder-check
Add option to skip folder check on import
2025-07-19 23:08:28 +08:00
Ben Woo 9137ef04d2 Merge pull request #3300 from Multiverse/feat/load-env-check
Add an environment check when loading a world that already exist in b…
2025-07-19 23:06:56 +08:00
Ben Woo 878c4bea3d Add option to skip folder check on import 2025-07-19 23:05:17 +08:00
Ben Woo 021d86e21c Add an environment check when loading a world that already exist in bukkit 2025-07-19 22:59:15 +08:00
Ben Woo 3e9425c8f1 Merge pull request #3297 from Multiverse/feat/matcher
Implement StringMatcher api
2025-07-18 15:02:20 +08:00
Ben Woo cc60b9c938 Implement StringMatcher api 2025-07-18 14:46:59 +08:00
Ben Woo d3cc950a97 Merge pull request #3296 from Multiverse/feat/attempt-causeby
Add causeBy chaining for Attempt
2025-07-18 11:09:41 +08:00
Ben Woo 5e97b1169b Merge pull request #3294 from Multiverse/fix/removed-property
Add warning on removed animals and monsters world properties
2025-07-18 11:07:39 +08:00
Ben Woo c3f4f117e5 Merge pull request #3293 from Multiverse/fix/delete-tab-complete
Tab complete to include unloaded worlds for delete command
2025-07-18 11:06:54 +08:00
Ben Woo 814901b1c1 Merge pull request #3292 from Multiverse/fix/spawn-egg
Always allow spawner egg to align with v4 behaviour
2025-07-18 11:06:31 +08:00
Ben Woo 7c6b1d07b0 Add causeBy chaining for Attempt 2025-07-18 11:04:41 +08:00
Ben Woo c389a41cdc Add warning on removed animals and monsters world properties 2025-07-17 12:38:33 +08:00
Ben Woo 2db9ae7c66 Tab complete to include unloaded worlds for delete command 2025-07-17 11:59:24 +08:00
Ben Woo b80aea4c38 Always allow spawner egg to align with v4 behaviour 2025-07-17 11:54:56 +08:00
Ben Woo 35b129701f Merge pull request #3285 from Multiverse/feat/joinlocation-bypass
Add bypass permission for join location
2025-07-15 13:46:45 +08:00
Ben Woo d92fe17c4f Merge branch 'main' into feat/joinlocation-bypass
# Conflicts:
#	src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java
2025-07-15 13:40:51 +08:00
Ben Woo 488e258e3f Merge pull request #3290 from Multiverse/fix/gamerule-tab-complete
Fix gamerule tab complete when legacy alias is enabled
2025-07-14 11:26:11 +08:00
Ben Woo d096d1a941 Fix gamerule command flag group name 2025-07-14 11:22:17 +08:00
Ben Woo 3ef4271e63 Fix gamerule tab complete when legacy alias is enabled 2025-07-14 11:18:25 +08:00
Ben Woo 0a33d35b2b Merge pull request #3288 from Multiverse/fix/bukkit-compatility
Fix PlayerSpawnLocationEvent may not be available on bukkit
2025-07-11 11:13:09 +08:00
Ben Woo b4fedea1db Fix PlayerSpawnLocationEvent may not be available on bukkit 2025-07-11 11:06:54 +08:00
Ben Woo 4d92964dda Merge pull request #3287 from Multiverse/fix/worldkey
Fix missing world placeholder in BUKKIT_CREATION_FAILED message
2025-07-09 18:07:57 +08:00
Ben Woo 3443b175ff Fix missing world placeholder in BUKKIT_CREATION_FAILED message 2025-07-09 17:40:11 +08:00
Ben Woo ad3fe9e114 Merge pull request #3284 from Multiverse/fix/teleport-fail-message
Make teleport failure message more descriptive
2025-07-07 11:05:14 +08:00
Ben Woo d1660d7e81 Add bypass permission for join location 2025-07-07 10:41:38 +08:00
Ben Woo 7750b61103 Make teleport failure message more descriptive 2025-07-07 10:19:26 +08:00
Ben Woo eeb1019bbb Merge pull request #3276 from Multiverse/fix/locale-lowercase
Fix message key lowercase issues if java environment is non-english
2025-06-28 17:05:09 +08:00
Ben Woo 6bbaad8bdb Merge pull request #3277 from Multiverse/fix/config-comment
Fix config.yml comment spacing
2025-06-28 17:04:48 +08:00
Missing_Love 1ef4470979 Update multiverse-core_zh.properties (#3274)
* Update multiverse-core_zh.properties

* Update multiverse-core_zh.properties
2025-06-28 17:03:41 +08:00
Ben Woo 3110f462ac Fix config.yml comment spacing 2025-06-28 17:02:10 +08:00
Ben Woo 170e1ac347 Fix message key lowercase issues if java environment is non-english 2025-06-27 18:09:27 +08:00
Ben Woo 86b95034cc Merge pull request #3275 from Multiverse/chore/dbo-1.21.6
Update dbo supported version list with 1.21.6
2025-06-27 12:39:20 +08:00
Ben Woo 8f6d4d5960 Update dbo supported version list with 1.21.6 2025-06-27 12:28:21 +08:00
Ben Woo a34d2f810b Merge pull request #3273 from Multiverse/docs/donate-links
Update donation links to our github sponsor and open collective
2025-06-26 21:09:50 +08:00
Ben Woo db002ab2b1 Update donation links to our github sponsor and open collective 2025-06-26 21:07:33 +08:00
Ben Woo c9c8a1a9c6 Merge pull request #3272 from Tisment/main
Translation of the plugin to Spanish
2025-06-26 20:00:17 +08:00
Tisment cdc22bc533 Created the file multiverse-core_es.properties 2025-06-24 11:40:02 +02:00
Ben Woo 1efe98ab46 Merge pull request #3271 from Multiverse/fix/setspawn-save
Fix setspawn command not saving the updated location to file immediately
2025-06-23 20:29:20 +08:00
Ben Woo 989242810d Fix setspawn command not saving the updated location to file immediately 2025-06-23 19:08:36 +08:00
Ben Woo 29d3c0c8c1 Merge pull request #3244 from Multiverse/5.1
5.1
2025-06-23 12:02:22 +08:00
Ben Woo 97cc7e53f0 Merge pull request #3266 from Multiverse/chore/1.21.6
Add 1.21.6 as supported version for Modrinth and Hangar
2025-06-23 11:08:10 +08:00
Ben Woo 4c4544340a Merge pull request #3270 from Multiverse/fix/dot-world-name
Fix world not loading when the world name has `.` in it
2025-06-22 17:06:54 +08:00
Ben Woo 755fab05ba Add tests for world name with dot 2025-06-22 17:02:50 +08:00
Ben Woo 5f295fd57b Fix world not loading when the world name has . in it 2025-06-22 16:49:14 +08:00
Ben Woo 86b888a68e Add 1.21.6 as supported version for modrinth and hanger 2025-06-20 22:10:42 +08:00
Ben Woo 4fd8350a96 Merge pull request #3265 from Multiverse/feat/destination-sender
Add support for destination parse with sender context
2025-06-20 15:06:30 +08:00
Ben Woo 5702426ddd Fix config test due to destination parsing 2025-06-20 15:01:01 +08:00
Ben Woo 9c562d09ea Add support for destination parse with sender context 2025-06-20 14:20:50 +08:00
Ben Woo 59d1c44c23 Merge pull request #3264 from Multiverse/feat/sender-string-parse
Add support config string parsing with sender context
2025-06-20 14:03:06 +08:00
Ben Woo 1082f73be7 Add support config string parsing with sender context 2025-06-20 12:13:14 +08:00
Ben Woo 1372626b50 Merge pull request #3263 from Multiverse/feat/config-alias-name
Add support for alias name in config node
2025-06-20 12:01:24 +08:00
Ben Woo c09dacb8ec Add support for alias name in config node 2025-06-20 11:11:07 +08:00
Ben Woo 518d64882c Merge pull request #3262 from Multiverse/feat/api-qol
Add qol methods whenLoaded and isLoaded to MultiverseCoreApi
2025-06-16 19:44:30 +08:00
Ben Woo 4f08b9a446 Add qol methods whenLoaded and isLoaded to MultiverseCoreApi 2025-06-16 11:02:19 +08:00
Ben Woo a3b7dd542d Mark CoreConfig#getConfig() as internal use only 2025-06-16 11:01:33 +08:00
Ben Woo c68936f06d Merge pull request #3261 from Multiverse/refactor/destination-suggestion
Streamline destination suggestions with helper methods to directly parse to string
2025-06-15 22:02:19 +08:00
Ben Woo b91214f958 Streamline destination suggestions with helper methods to directly parse to string 2025-06-15 21:55:01 +08:00
Ben Woo ff26ffd9c5 Merge pull request #3258 from Multiverse/feat/sender-context
Implement sender context for config node suggestions
2025-06-15 21:21:31 +08:00
Ben Woo 058a7c97cb Implement SenderNodeSuggester to builders 2025-06-15 21:19:30 +08:00
Ben Woo a50965509d Implement sender context for config node suggestions 2025-06-10 22:48:20 +08:00
Ben Woo 3356724481 Merge pull request #3256 from Multiverse/fix/generator-tab-complete
Improve generator provider tab-complete logic
2025-06-10 11:30:59 +08:00
Ben Woo 8dcdf68b9f Merge pull request #3257 from Whitebrim/patch-1
lang: fix deposit/withdraw (en)
2025-06-10 11:30:45 +08:00
Ben Woo ae0f25a0f8 Merge pull request #3255 from Whitebrim/main
Translation to Russian
2025-06-10 11:30:30 +08:00
Whitebrim 148f513bc2 lang: fix deposit/withdraw (en) 2025-06-10 03:38:57 +03:00
Whitebrim 29f60bcde5 lang: fix deposit/withdraw 2025-06-10 03:36:45 +03:00
Ben Woo e9ddb24706 Improve generator provider tab-complete logic 2025-06-09 23:16:43 +08:00
Whitebrim 71e815a9bd Create multiverse-core_ru.properties
Translated to Russian
2025-06-07 21:17:47 +03:00
Ben Woo a55ce680df Merge pull request #3254 from Multiverse/fix/world-chart
Fix bstats world chart
2025-06-07 13:42:40 +08:00
Ben Woo 3e47460c3b Fix bstats world chart 2025-06-07 13:36:57 +08:00
Ben Woo abd6983eee Merge pull request #3253 from Multiverse/fix/donate-colour
Add green colour to donate and enable message
2025-06-07 00:16:12 +08:00
Ben Woo ce263b5b1f Merge pull request #3252 from Multiverse/feat/improve-context
Feat/improve context
2025-06-07 00:15:53 +08:00
Ben Woo 7f15a28a69 Add green colour to donate and enable message 2025-06-07 00:00:03 +08:00
Ben Woo 4ddcb3f0bf Fix teleport command tab complete for in-game with player arg 2025-06-06 23:48:24 +08:00
Ben Woo d76a6a6ca6 Refactor issuer aware context handling to extract our similar logic 2025-06-06 23:08:20 +08:00
Ben Woo e9a20254b0 Merge pull request #3249 from Multiverse/feat/passengers
Add support for passenger and vehicle teleports
2025-06-06 20:32:24 +08:00
Ben Woo 42a8d33824 Fix some javadocs of the new attempt methods 2025-06-06 20:19:19 +08:00
Ben Woo d89f00a607 Merge pull request #3250 from Multiverse/chore/annotations
Add ApiStatus annotations to new and deprecated api
2025-06-06 20:12:43 +08:00
Ben Woo 41c8c17bc9 Fix config test 2025-06-06 20:07:57 +08:00
Ben Woo a3a621945a Add ApiStatus annotations to new and deprecated api 2025-06-06 20:06:20 +08:00
Ben Woo 71eedd392d Add support for passenger and vehicle teleports 2025-06-06 20:01:30 +08:00
Ben Woo ccfcae9dd0 Merge pull request #3247 from Multiverse/fix/vanila-selector
Hotfix for vanilla selector params getting cut off by comma separation
2025-06-03 22:09:53 +08:00
Ben Woo f50849422b Merge pull request #3248 from Multiverse/chore/issue-template-update
Update issue template new dumps command, and fix typos
2025-06-02 22:43:47 +08:00
Ben Woo 8786a09b8f Update issue template new dumps command, and fix typos 2025-06-02 22:25:55 +08:00
Ben Woo 07b750f11c Hotfix for vanilla selector params getting cut off by comma separation 2025-06-02 21:45:29 +08:00
Ben Woo ba78b7da3a Merge pull request #3245 from Multiverse/feat/chat-color
Use kyori adventure whenever possible to support rgb formatting
2025-06-02 13:07:38 +08:00
Ben Woo 38231ef0d4 Use kyori adventure whenever possible to support rgb formatting 2025-06-02 13:01:03 +08:00
Ben Woo 232d18f8da Merge pull request #3237 from Multiverse/feat/setspawn-safety
Add location safety check for setspawn command
2025-06-02 11:07:17 +08:00
Ben Woo 530a8a9c2c Merge pull request #3243 from Multiverse/feat/time-placeholder
Implement time formatting for world time placeholder
2025-06-02 11:07:06 +08:00
Ben Woo 38bbe8103c Merge pull request #3241 from Multiverse/fix/entry-fee-message
Fix vault economy not showing payment message
2025-06-02 10:55:13 +08:00
Ben Woo d2af009d9f Fix entry check messages 2025-06-02 10:51:43 +08:00
Ben Woo 8a96c7b610 Don't show seconds for default 12h and 24h format 2025-06-02 10:47:22 +08:00
Ben Woo bb2da157da Fix minecraft time calculation 2025-06-02 10:47:03 +08:00
Ben Woo 1c521e0f79 Implement time formatting for world time placeholder 2025-06-01 23:39:04 +08:00
Ben Woo aab13c1ec6 Add support for multiple params in placeholder 2025-06-01 23:38:12 +08:00
Ben Woo fd72d6a9b8 Add locale keys for economy recipt messages 2025-06-01 20:40:15 +08:00
Ben Woo d73e90e46e Fix negative deposit issue 2025-06-01 20:39:48 +08:00
Ben Woo 913f131b3b Cleanup double to int conversion for item economy 2025-06-01 20:32:11 +08:00
Ben Woo 795aeab7a3 Fix vault economy not showing payment message 2025-06-01 16:22:36 +08:00
Ben Woo 2ee01adb6b Fix negative entry fee display in /mv info command 2025-06-01 15:37:15 +08:00
Ben Woo b4e246ffb3 Merge pull request #3240 from Multiverse/fix/chat-prefix-colour
Fix chat prefix colour codes not applying
2025-06-01 10:09:21 +08:00
Ben Woo 64995a0bfd Fix chat prefix colour codes not applying 2025-06-01 09:58:37 +08:00
Ben Woo 3173b5fc18 Merge pull request #3236 from Multiverse/fix/locale-key
Fix content display wrong locale key
2025-05-31 16:22:48 +08:00
Ben Woo ae4d33d7b1 Fix PlayerLocation context logic 2025-05-31 16:20:51 +08:00
Ben Woo d5b2cc884b Support localization for setspawn command output 2025-05-31 16:20:34 +08:00
Ben Woo 50f6152a71 Fix sendError message type 2025-05-31 15:48:57 +08:00
Ben Woo c84ae0493c Add location safety check for setspawn command 2025-05-31 15:48:47 +08:00
Ben Woo e1dadc045f Fix content display wrong locale key 2025-05-31 13:53:46 +08:00
Ben Woo 15812081e0 Merge pull request #3230 from xiaohuang2004/patch-1
Update multiverse-core_zh.properties
2025-05-31 09:58:02 +08:00
Ben Woo 5365ebd939 Fix Localization test 2025-05-31 09:56:14 +08:00
Ben Woo e06770a55f Fix zh property file location 2025-05-31 09:56:04 +08:00
Ben Woo a931c63ba9 Merge pull request #3233 from Multiverse/fix/worldborder-permissions
Add permission for world border
2025-05-16 16:55:01 +08:00
Ben Woo 47fae91a29 Add permission for world border 2025-05-16 16:50:06 +08:00
Ben Woo df010c4978 Merge pull request #3231 from Multiverse/feat/generator
Implement generator info command
2025-05-14 14:50:55 +08:00
Ben Woo 8060e12d57 Merge pull request #3232 from Multiverse/fix/config-comments
Fix some config comment typos
2025-05-14 14:50:14 +08:00
Ben Woo dd8a74c19e Fix some config comment typos 2025-05-14 14:47:46 +08:00
Huang Jinjie f0992dae25 Update multiverse-core_zh.properties
I read Multiverse Core Changelog and found it support message outputs with localisation support. However, my client still display English which has been set to Chinese. I searched the code and don't found a Chinese simplified translation file. So I translate it base on the multiverse-core_en.property. I don't know exactly how to operate it, so I commit the file directly and hope the plugin can support Chinese in next version. If it can be accepted, I will continue to translate other add-one Multiverse plugins. Thanks!
2025-05-14 14:02:17 +08:00
Ben Woo 2935d13cd0 Implement generator info command 2025-05-09 22:56:53 +08:00
Ben Woo d484c539a9 Merge pull request #3228 from Multiverse/feat/config-command
Improve comments for first-spawn and join-destination
2025-05-09 19:07:32 +08:00
Ben Woo 3c511417a5 Improve comments for first-spawn and join-destination 2025-05-09 19:04:32 +08:00
Ben Woo ad6c44bec3 Merge pull request #3227 from Multiverse/fix/event-executor
Fix event executor on spigot
2025-05-09 18:28:25 +08:00
Ben Woo dd155507e6 More isolated error handling for each event method registration 2025-05-09 18:20:38 +08:00
Ben Woo 866a7415b6 Fix event executor on spigot 2025-05-08 22:48:43 +08:00
Ben Woo 230ffd59d4 Merge pull request #3226 from Multiverse/fix/import-issue-on-spigot
Fix adventure text import issues on spigot
2025-05-07 08:25:56 +08:00
Ben Woo ca4e3e11d2 Fix adventure text import issues on spigot 2025-05-07 08:22:35 +08:00
Ben Woo 395c3098c4 Merge pull request #3225 from Multiverse/fix/skip-wrong-class
Fix wrong skip class for AsyncPlayerChatEvent
2025-05-06 15:12:02 +08:00
Ben Woo b0219fbb5b Fix wrong skip class for AsyncPlayerChatEvent 2025-05-06 15:04:33 +08:00
Ben Woo dbeba59561 Merge pull request #3224 from Multiverse/feat/world-border-command
Implement `/mv worldborder` commands
2025-05-06 14:58:32 +08:00
Ben Woo 4104a2db59 Merge pull request #3223 from Multiverse/feat/allow-advancement
Implement world property to disable advancement grant
2025-05-06 14:56:47 +08:00
Ben Woo 1f97a8960a Implement /mv worldborder commands 2025-05-06 14:56:05 +08:00
Ben Woo 8597dd63ca Fix world config in tests due to new allow-advancement-grant property 2025-05-06 11:01:38 +08:00
Ben Woo 3fcfb4d11e Merge pull request #3222 from Multiverse/feat/async-chat-event
Implement paper's AsyncChatEvent for world prefix chat
2025-05-06 10:48:50 +08:00
Ben Woo 72e3ba99c6 Implement world property to disable advancement grant 2025-05-06 10:48:22 +08:00
Ben Woo 27417c53d7 Implement paper's AsyncChatEvent for world prefix chat 2025-05-05 23:08:44 +08:00
Ben Woo db97b6e678 Merge pull request #3221 from Multiverse/feat/dynamic-listener
Implement dynamic listener api
2025-05-05 22:09:48 +08:00
Ben Woo ac050bfb37 Add comments for event-priority config options 2025-05-05 22:07:32 +08:00
Ben Woo 5e75b1cd17 Implement dynamic listener api 2025-05-05 21:57:04 +08:00
Ben Woo 565ed9f1fc Merge pull request #3220 from Multiverse/feat/auto-import
Add option to disable auto import of worlds on init/reload
2025-05-05 00:14:57 +08:00
Ben Woo 41ab66b704 Add option to disable auto import of worlds on init/reload 2025-05-04 22:52:31 +08:00
Ben Woo a9d81bd5a2 Merge pull request #3219 from Multiverse/feat/config-cmd-locale
Implement localisation for config command output
2025-05-04 17:27:08 +08:00
Ben Woo 98095927a9 Implement localisation for config command output 2025-05-04 15:40:36 +08:00
Ben Woo 2e1e8685e9 Tweaks to string message for DimensionFormat 2025-05-04 15:40:12 +08:00
Ben Woo 087fdbbb2c Fix MessageReplacement instanceof parsing at the wrong place 2025-05-04 15:39:32 +08:00
Ben Woo 42cd9ec18d Merge pull request #3217 from Multiverse/fix/unregister
Fix incorrect way of unregistering MultiverseCoreApi from service manager
2025-05-03 20:21:09 +08:00
Ben Woo f63861b1d4 Fix incorrect way of unregistering MultiverseCoreApi from service manager 2025-05-03 20:13:31 +08:00
Ben Woo 3233ae556c Merge pull request #3215 from Multiverse/feat/world-name-format
Implement automatic link of respawn world from nether/end to overworld
2025-05-03 20:03:12 +08:00
Ben Woo d1e1ea56d1 Merge pull request #3216 from Multiverse/fix/displaypermerrors-mgrate
Fix displaypermerrors config migration
2025-05-03 20:02:38 +08:00
Ben Woo 7eede2daeb Fix displaypermerrors config migration 2025-05-03 19:21:18 +08:00
Ben Woo 468399ad84 Add javadoc for DimensionFinder 2025-05-03 19:14:12 +08:00
Ben Woo 200e8cbc30 Improve respawn related config comments 2025-05-03 19:13:54 +08:00
Ben Woo 69ab331a6f Merge pull request #3213 from Multiverse/feat/keep-files
Add option to keep files during world regen
2025-05-03 18:55:00 +08:00
Ben Woo dcb8a354cf Merge pull request #3214 from Multiverse/chore/clone-command-test
Fix clone command test due to World#save being unimplemented
2025-05-03 17:05:07 +08:00
Ben Woo e625e4750f Fix comments in DeleteWorldOptions#keepFiles 2025-05-03 17:04:32 +08:00
Ben Woo dbcbcad6d1 Fix clone command test due to World#save being unimplemented 2025-05-03 17:02:47 +08:00
Ben Woo 84916fe25b Fix saveBukkitWorld parameter name 2025-05-03 17:02:27 +08:00
Ben Woo 535da5e39b Implement automatic link of respawn world from nether/end to overworld 2025-05-03 16:59:57 +08:00
Ben Woo cd2869863f Fix delete world test due to api changes 2025-05-03 14:29:57 +08:00
Ben Woo b9766f87c9 Merge pull request #3212 from Multiverse/fix/clone-default-save
Default save bukkit world before clone to true
2025-05-03 14:23:11 +08:00
Ben Woo 32829531d3 Add option to keep files during world regen 2025-05-03 14:22:50 +08:00
Ben Woo aa82743c25 Default save bukkit world before clone to true 2025-05-03 11:44:04 +08:00
Ben Woo 47e9076c9e Merge pull request #3211 from Multiverse/feat/unload-bukkit
Add `--no-unload-bukkit-world` flag to unload command
2025-05-02 22:15:56 +08:00
Ben Woo 841ad8f55d Merge pull request #3210 from Multiverse/feat/clone-save
Add an option to force save world to disk before cloning world
2025-05-02 22:15:33 +08:00
Ben Woo 407e778841 Add --no-unload-bukkit-world flag to unload command 2025-05-02 22:13:48 +08:00
Ben Woo 5b85e2f119 Add an option to force save world to disk before cloning world 2025-05-02 21:58:28 +08:00
Ben Woo 49bdb568cc Merge pull request #3209 from Multiverse/feat/bukkit-yml
Implement bukkit-yml-path config option
2025-05-02 21:28:00 +08:00
Ben Woo a4e7282a0b Implement bukkit-yml-path config option 2025-05-02 21:24:46 +08:00
Ben Woo 1cfd9e6905 Load all config nodes before calling onSetValue 2025-05-02 21:24:27 +08:00
Ben Woo 064e4a0ed8 Merge pull request #3207 from Multiverse/fix/purge-entities
Actually implement the auto-purge-entities config option
2025-05-02 19:59:30 +08:00
Ben Woo 65d199c65a Merge pull request #3208 from Multiverse/fix/gha-no-release
GHA do not build of upload if there is no release
2025-05-02 19:56:21 +08:00
Ben Woo cb61c81836 GHA do not build of upload if there is no release 2025-05-02 19:53:56 +08:00
Ben Woo 25e1751283 Merge pull request #3206 from Multiverse/fix/api-service
Refactor and properly unregister MultiverseCoreApi from service manager
2025-05-02 19:47:21 +08:00
Ben Woo e417e0479c Actually implement the auto-purge-entities config option 2025-05-02 19:43:54 +08:00
Ben Woo fe4cada82d Refactor and properly unregister MultiverseCoreApi from service manager 2025-05-02 17:29:25 +08:00
Ben Woo c92d04376c Merge pull request #3205 from Multiverse/feat/gha
Github release action improvements
2025-05-02 16:54:43 +08:00
Ben Woo a24a3a54f4 Add a dispatch create release action 2025-05-02 16:53:41 +08:00
Ben Woo 8b4e339665 Fix dispatch javadoc java version 2025-05-02 16:31:01 +08:00
Ben Woo 6c96f506c8 Upload javadoc for pre-release as well 2025-05-02 16:28:00 +08:00
Ben Woo 28475e442d Use tag name instead of publish name for release builds 2025-05-02 16:27:10 +08:00
Ben Woo 59e6fdbfa5 Merge pull request #3204 from Multiverse/chore/trigger
Trigger MV5 release build
2025-05-01 17:20:58 +08:00
Ben Woo 2848889395 Trigger MV5 release build 2025-05-01 17:12:40 +08:00
Ben Woo 3df1e5a547 Merge pull request #3203 from Multiverse/feat/gamemode-flight-enforcement
Feat/gamemode flight enforcement
2025-04-30 23:24:48 +08:00
Ben Woo 0739ac3a70 Merge pull request #3201 from Multiverse/fix/double-load-unload
Fix logic error where bukkit load and unload is called twice
2025-04-30 23:24:31 +08:00
Ben Woo c4a692ad70 Add config option for flight enforcement 2025-04-30 21:09:36 +08:00
Ben Woo 1535238770 Fix gamemode enforcement config not applying 2025-04-30 21:07:05 +08:00
Ben Woo efd8b4b34e Merge pull request #3200 from Multiverse/fix/who-invalid-world
Fix who command null exception when invalid world is specified
2025-04-30 19:54:08 +08:00
Ben Woo 88125ecf9c Merge pull request #3202 from Multiverse/fix/severe-logging
Remove unnecessary severe logging in create command
2025-04-30 19:52:25 +08:00
Ben Woo 675e590ccf Remove unnecessary severe logging in create command 2025-04-30 19:24:34 +08:00
Ben Woo afd89dbb51 Fix logic error where bukkit load and unload is called twice 2025-04-30 19:20:51 +08:00
Ben Woo c961c9ff57 Fix who command null exception when invalid world is specified 2025-04-30 17:53:16 +08:00
Ben Woo 2d02658730 Merge pull request #3196 from Multiverse/benwoo1110-patch-1
Release MV5
2025-04-29 10:43:07 +08:00
Ben Woo dfd4b88031 Update README.md 2025-04-29 09:51:59 +08:00
Ben Woo 52e0b1288a Merge pull request #3195 from Multiverse/fix/publish
Remove onarandombox publish repo as its already in our gradle plugin
2025-04-29 09:40:08 +08:00
Ben Woo d1dd8d7493 Remove onarandombox publish repo as its already in our gradle plugin 2025-04-29 09:37:28 +08:00
Ben Woo ca73265f88 Merge pull request #2824 from Multiverse/MV5
MV5
2025-04-29 09:29:59 +08:00
Ben Woo 5bdec28099 Prep for release 2025-04-29 08:56:31 +08:00
Ben Woo bd0a3dd3fb Update to an even cooler banner 2025-04-28 23:11:56 +08:00
Ben Woo 7a9994004c Add wiki website link 2025-04-28 22:46:38 +08:00
Ben Woo 45b9dd3622 Introduce a cool new banner 2025-04-28 22:45:17 +08:00
Ben Woo 269bbf8783 Improve mv modify messages 2025-04-28 20:32:55 +08:00
Ben Woo fa5bbec9e3 Improve passing of location object and error handling 2025-04-25 17:08:12 +08:00
Ben Woo ee3a1f3a79 Rename NullLocation to NullSpawnLocation 2025-04-25 16:10:16 +08:00
Ben Woo b9e29fddea Fix entity spawn config modify value optional 2025-04-20 16:59:34 +08:00
Ben Woo c9a0dd368d Implement materials tab complete 2025-04-19 15:19:26 +08:00
Ben Woo 2db3589f05 Add shutdownDependencyInjection to MultiverseModule 2025-04-13 13:52:49 +08:00
Ben Woo 702ef3c569 Abstract out MultiverseModuleBinder 2025-04-13 12:49:51 +08:00
Ben Woo 0f21b61529 Better organise test resource files into folders 2025-04-13 12:44:22 +08:00
Ben Woo a568ab66dd Better organise test resource files into folders 2025-04-13 12:06:40 +08:00
Ben Woo 15dfc164e6 Remove ACF overrides as it has been fixed upstream 2025-04-13 12:00:31 +08:00
Ben Woo bbb5ecba1d Add spawn limit setting 2025-04-13 11:54:40 +08:00
Ben Woo 63b5393dea Refactor worldRef for spawn config 2025-04-13 11:44:26 +08:00
Ben Woo 1b8abc2ddd Rename mobsSpawnConfig to entitySpawnConfig 2025-04-13 11:39:29 +08:00
Ben Woo 8ee4e00181 Add support for controlling non-living entity spawn 2025-04-13 11:33:03 +08:00
Ben Woo 757057de39 Fix permissions of newly added commands 2025-04-11 23:51:49 +08:00
Ben Woo 5ce2eedbfd Fix import command syntax 2025-04-11 23:51:22 +08:00
Ben Woo fca6951d36 Fix clone command syntax 2025-04-10 22:51:53 +08:00
Ben Woo dc83349537 Add default failure logging for config load methods 2025-04-10 22:51:46 +08:00
Ben Woo c159b16860 Don't set ticks per spawn for SpawnCategory MISC 2025-04-10 22:50:57 +08:00
Ben Woo 4fba35b68f Automatically set default value of ListConfigNode to an ArrayList 2025-04-10 22:50:08 +08:00
Ben Woo 4ca8cfcaa1 Parse quotes for tab complete as well 2025-04-09 13:38:36 +08:00
Ben Woo 58de984524 Fix edge cases with parseQuotesInArgs and add tests 2025-04-09 13:08:02 +08:00
Ben Woo b615213907 Fix entity spawn config syntax 2025-04-09 10:53:59 +08:00
Ben Woo 807f7c1b66 Merge branch 'main' into MV5
# Conflicts:
#	build.gradle
2025-04-09 10:53:12 +08:00
Ben Woo 77994b991f Fix info command flag tab complete when in console 2025-04-09 10:16:24 +08:00
Ben Woo 677dd475f7 Improve entity spawn config info command output 2025-04-09 10:15:40 +08:00
Ben Woo 0e6a63d60e Fix InjectionTest commands number 2025-04-09 09:39:07 +08:00
Ben Woo 4fc4d7613a PlayerFinder use REPatterns for uuid and comma 2025-04-09 09:38:14 +08:00
Ben Woo 3cf1a9f33b Check null input for addonToCommaSeperated 2025-04-09 09:35:16 +08:00
Ben Woo c1b60c148e Use reflection to get spawn category of entity types 2025-04-09 09:03:23 +08:00
Ben Woo 9a3b28f5f5 Refactor list suggester to use addonToCommaSeperated util method 2025-04-09 09:02:46 +08:00
Ben Woo 6aebd2904f Do not need to set generator if generator string is empty 2025-04-08 23:46:36 +08:00
Ben Woo d0671a25d6 Merge pull request #3192 from Multiverse/ben/mv5/revamp-mobs-spawn
Ben/mv5/revamp mobs spawn
2025-04-08 23:41:47 +08:00
Ben Woo 8e17d2a6bd Add todo on spawn reasoning filter 2025-04-08 23:41:08 +08:00
Ben Woo af7a71753f Fix minor purge entity syntax issues 2025-04-08 23:38:07 +08:00
Ben Woo c0dd1518e1 Add purge entities commands 2025-04-08 23:30:43 +08:00
Ben Woo 3b5a1c1988 Implement MobsSpawnConfigCommand fully 2025-04-08 22:44:09 +08:00
Ben Woo 774c2b2327 Merge pull request #3191 from Multiverse/chore/gha-game-version
Update gha supported game version to 1.21.5
2025-04-08 21:44:25 +08:00
Ben Woo 71b1662b55 Update gha supported game version to 1.21.5 2025-04-08 21:40:29 +08:00
Ben Woo 9317f4cd6b Merge pull request #3190 from Multiverse/fix/javadoc-action
Fix gha javadoc cp path
2025-04-08 21:10:40 +08:00
Ben Woo 00b4a9bab4 Fix gha javadoc cp path 2025-04-08 21:05:38 +08:00
Ben Woo 48a091842a Merge pull request #3188 from Multiverse/ben/fix-gha-publish 2025-04-08 20:48:13 +08:00
Ben Woo 7e9ca7cc79 Refactor spawning to use MobsSpawnConfig with SpawnCategory support 2025-04-08 17:19:32 +08:00
Ben Woo d333cf55d1 Use isBooleanProp on MultiverseWorld getters 2025-04-08 14:23:12 +08:00
Ben Woo cb45ae1590 Reduce wait time for async teleport test 2025-04-08 14:19:27 +08:00
Ben Woo 3c85f1324d Add support for command args with spaces by using quotes 2025-04-08 14:16:27 +08:00
Ben Woo ee3f26b2f3 Refactor destination parsing to have error reasoning 2025-04-08 13:45:18 +08:00
Ben Woo 67f5e47dc9 Add a script to automatically convert locale properties to enum value 2025-04-08 13:30:39 +08:00
Ben Woo 8b530854f6 Use UnloadedWorldLocation for all destinations and fix some edge cases 2025-04-08 10:59:03 +08:00
Ben Woo 91e503e904 Refactor all commands to use FlagBuilder and fix legacy alias bugs 2025-04-07 23:26:37 +08:00
Ben Woo cf287ab9bf Implement new way of having flags with FlagBuilder class 2025-04-06 19:53:47 +08:00
Ben Woo ad51e92d90 Fix config tests 2025-04-06 19:48:41 +08:00
Ben Woo 40f80e8040 Make showLegacyAliases false by default 2025-04-06 19:48:32 +08:00
Ben Woo 5efbeaba92 Implement option to disable legacy aliases 2025-04-06 18:08:03 +08:00
Ben Woo e5eb9a7eba Refactor migrator action to its own package 2025-04-06 14:44:20 +08:00
Ben Woo 0ea7375fe9 Merge pull request #3189 from Multiverse/ben/mv5/mvanchor
Refactor anchors with MultiverseAnchor class and better location handling
2025-04-06 10:18:09 +08:00
Ben Woo 7db9e99fd7 Refactor anchors with MultiverseAnchor class and better location handling 2025-04-06 00:09:20 +08:00
Ben Woo da3248894e Use UnloadedWorldLocation for LocationManipulation 2025-04-06 00:06:23 +08:00
Ben Woo 8934133874 Implement equals, hashcode and string for UnloadedWorldLocation 2025-04-06 00:06:02 +08:00
Ben Woo 0ec184ce4d Improve error handling for location with unloaded worlds 2025-04-05 16:27:16 +08:00
Ben Woo 57ff66ab27 Improve localization for exceptions 2025-04-05 13:19:26 +08:00
Ben Woo 8bfa604584 Improve biome provider and generator error handling 2025-04-05 12:05:10 +08:00
Ben Woo c541c27485 Fix gha gradle publish command 2025-04-02 20:35:49 +08:00
Ben Woo fd02e00da6 Merge pull request #3187 from Multiverse/ben/publish-repo 2025-04-02 16:44:55 +08:00
Ben Woo 729ee19585 Remove need for QueuedCommandAction functional interface 2025-03-29 22:35:08 +08:00
Ben Woo e14853dc11 Use onarandombox repo for publishing 2025-03-29 17:46:15 +08:00
Ben Woo c439412a3c Common class to compile reusable regex pattern 2025-03-29 17:11:24 +08:00
Ben Woo 035728e37f Merge pull request #3185 from Multiverse/ben/mv5/protocol-version
Refactor protocol version to derive from plugin's version string
2025-03-29 16:45:57 +08:00
Ben Woo 4728449137 Remove need for MinBackwardsCompatibleVersion 2025-03-28 15:23:50 +08:00
Ben Woo e94cc41ca8 Refactor protocol version to derive from plugin's version string 2025-03-26 22:56:14 +08:00
Ben Woo ab4856c81e Merge pull request #3184 from Multiverse/ben/mv5/attempt-aggregate
Better handling for a list of attempts with AttemptsAggregate
2025-03-26 21:54:07 +08:00
Ben Woo 7861ac7d63 Remove unused of Async class 2025-03-26 21:42:48 +08:00
Ben Woo cf37956784 Add docs for AsyncAttempt 2025-03-26 21:40:58 +08:00
Ben Woo 0b39e94047 Implement runnable method and add javadocs to AttemptsAggregate 2025-03-26 21:30:50 +08:00
Ben Woo 0dce6293d5 Better handling for a list of attempts with AttemptsAggregate 2025-03-25 22:56:34 +08:00
Ben Woo 8b181cbe43 Add missing world properties in /mv info 2025-03-25 17:48:54 +08:00
Ben Woo 290a12ce02 Refactor method of getting only the world alias without fallback 2025-03-25 17:40:01 +08:00
Ben Woo 8eab3c13ef Add support for localize Message within ContentDisplay api 2025-03-25 17:19:46 +08:00
Ben Woo bf3fba58e2 Implement mv world events - Load, Unload, Clone, Regen, Remove 2025-03-25 15:19:18 +08:00
Ben Woo 34991a722a Minor codestyle and null checking cleanup 2025-03-25 13:45:04 +08:00
Ben Woo cca6c16928 Merge branch 'ben/mv5/dest-packet' into MV5 2025-03-25 12:22:07 +08:00
Ben Woo 1b379bb426 Cleanup hasAnySpawnPermission to remove duplicated code 2025-03-25 12:21:20 +08:00
Ben Woo 294c69524f Include its Destination object in DestinationSuggestionPacket to simplify code 2025-03-25 12:14:26 +08:00
Jeremy Wood 441e6da0ca Merge pull request #3183 from Multiverse/dtm/mv5/cleanup-2
Various cleanup & reorganizing
2025-03-24 23:58:50 -04:00
Jeremy Wood 09da352175 Reorganize config.functions package into config.nodes. 2025-03-24 23:35:59 -04:00
Jeremy Wood 552533f82e Move confighandle.node package to config package. 2025-03-24 23:26:42 -04:00
Jeremy Wood 8ddbadf6fc Move confighandle.migration package to config package. 2025-03-24 23:25:08 -04:00
Jeremy Wood 92297ceba9 Move confighandle.functions package to config package. 2025-03-24 23:24:44 -04:00
Jeremy Wood 766496459d Move confighandle package contents to config.handle. 2025-03-24 23:19:59 -04:00
Jeremy Wood 510c4b287c Revert "Mark everything under commandtools package as Internal."
This reverts commit 8cb35006
2025-03-24 22:29:46 -04:00
Jeremy Wood bc9122cf7e Rename commandtools package to command. 2025-03-24 22:26:35 -04:00
Jeremy Wood 8cb3500690 Mark everything under commandtools package as Internal. 2025-03-24 22:20:13 -04:00
Jeremy Wood 1a8c2c3045 Use lambda expression in AnchorManager. 2025-03-24 22:19:42 -04:00
Jeremy Wood 9295318794 Fix unmodifiable set in AnchorManager. 2025-03-24 22:19:41 -04:00
Ben Woo 81ea96019a CommandQueuePayload's OTP should be a String instead of int 2025-03-24 21:10:16 +08:00
Ben Woo 818c416d81 Fix listeners test 2025-03-24 20:23:22 +08:00
Ben Woo c020a298ef Implement QueuedCommandAction functional interface 2025-03-24 20:22:05 +08:00
Ben Woo d960ec048c Remove MVLocalesListener as it is handled by ACF 2025-03-24 20:11:05 +08:00
Ben Woo f4bca52712 Remove need for OpenBukkitCommandIssuer 2025-03-24 19:48:34 +08:00
Ben Woo 2f73856d17 Refactor configuration to confighandle package 2025-03-24 19:41:15 +08:00
Ben Woo 2608a12cf3 Merge pull request #3181 from Multiverse/ben/mv5/cleanup
Ben/mv5/cleanup
2025-03-22 12:23:52 +08:00
Ben Woo dc3acc8e08 Rename MVCoreConfig to CoreConfig 2025-03-16 11:29:25 +08:00
Ben Woo f5aa3f3de5 Add javadocs to various classes 2025-03-16 11:09:03 +08:00
Ben Woo e43f7de881 Refactor ConfirmMode to config package 2025-03-16 10:11:38 +08:00
Ben Woo 96f17784ec Cleanup DumpService 2025-03-16 10:08:32 +08:00
Ben Woo 02b3fa60c6 Merge pull request #3179 from Multiverse/ben/mv5/run-queue
Refactor runQueuedCommand to relay failure reason to caller
2025-03-16 10:07:51 +08:00
Ben Woo caa35ed6e3 Refactor runQueuedCommand to relay failure reason to caller 2025-03-15 09:42:34 +08:00
Ben Woo dc9a386af1 Implement a default command exception handler to show stacktrace 2025-03-08 12:58:31 +08:00
Ben Woo 120b51e9e6 Minor refactor to PlayerPortalEvent 2025-03-08 10:59:31 +08:00
Ben Woo 626e6d01ee Implement alias tab completion for destinations 2025-03-06 21:07:56 +08:00
Ben Woo ac2ca92d33 Remove colour code when resolving alias name in commands 2025-03-06 20:43:45 +08:00
Ben Woo 781dc35e52 Use runTaskLater instead of scheduleSyncDelayTask 2025-03-06 19:55:35 +08:00
Ben Woo ac8896ed6d Use Try class instead of catching throwable for ConfirmCommand 2025-03-06 19:55:10 +08:00
Ben Woo ca7ac564da Make destination classes final 2025-03-05 21:33:46 +08:00
Ben Woo 4551eae74f Fix MVDebugModeEvent not being called 2025-03-05 20:22:01 +08:00
Ben Woo dd279910bd Fix config test 2025-03-03 22:38:29 +08:00
Ben Woo ef47a70089 Default firstSpawnOverride to false 2025-03-02 16:56:09 +08:00
Ben Woo a2d7bbc58c Merge branch 'main' into MV5 2025-03-01 22:05:14 +08:00
Ben Woo 0ed41e3776 Merge branch 'main' into MV5 2025-03-01 22:04:42 +08:00
Ben Woo babcec1f5d Rename WorldPurger to LegacyWorldPurger 2025-02-28 22:35:27 +08:00
Ben Woo 9e25e1d00b Merge pull request #3176 from Multiverse/ben/update-gradle-action
Update gradle gha deps to v4
2025-02-28 22:30:19 +08:00
Ben Woo e9e0407dbc Refactor null name with hidden method 2025-02-28 22:29:48 +08:00
Ben Woo cfd7be25af Ensure VersionMigrator is applied in the correct order 2025-02-28 17:28:54 +08:00
Ben Woo 74e595b236 Add destination and debug mode tab completion for /mv config 2025-02-28 17:15:24 +08:00
Ben Woo f46449fb99 Fix possible null issue with gamerule completion 2025-02-28 17:10:15 +08:00
Ben Woo 54ebdcb38b Refactor config handling to only serialise and deserialise on load and save 2025-02-26 23:02:16 +08:00
Ben Woo 1a3baf2a98 Remove old dep relocations 2025-02-26 20:06:59 +08:00
Ben Woo 02127fa123 Update to 1.0.2 of gradle plugins. 2025-02-26 20:04:26 +08:00
Ben Woo 16f8f3cd00 Fix spectator mode not flying 2025-02-25 20:36:11 +08:00
Ben Woo a9fd4db245 Add debug block for checking safe location 2025-02-25 14:24:49 +08:00
Ben Woo f966266bdc Merge pull request #3177 from Multiverse/zax71/main/updated-readme 2025-02-24 11:58:24 +08:00
Zax71 c5b4b88647 Change available for to available on in badges and fix release/pre-release badges 2025-02-23 09:21:46 +00:00
Ben Woo fe0be3e6ec Fix destination's velocity not applying 2025-02-23 16:02:21 +08:00
Ben Woo 733a561cf8 Add teleport test for when finer permission is disabled 2025-02-23 15:41:39 +08:00
Ben Woo 61e2d008fe Refactor permission checking with scope enum 2025-02-23 15:38:16 +08:00
Ben Woo 95bd8492d2 Add time to placeholderApi 2025-02-23 13:45:57 +08:00
Ben Woo 5db01b860d Fix mv anchor tab completion 2025-02-23 13:39:29 +08:00
Ben Woo e3409be81b Update gradle gha deps to v4 2025-02-22 13:16:43 +08:00
Ben Woo bb9591ce10 Merge pull request #3172 from Multiverse/zax71/mv5/legacy-gamerule-names
Add back legacy gamerule commands
2025-02-17 19:22:13 +08:00
Ben Woo ab65c01408 Fix remove command tab complete 2025-02-17 19:18:23 +08:00
Zax71 a47125d29b Add back legacy gamerule commands 2025-02-16 12:28:24 +00:00
Ben Woo 3160ac32b4 Urlencode mclogs payload 2025-02-15 16:29:28 +08:00
Ben Woo 2de0c32d32 Add command context for MultiverseWorld array 2025-02-15 15:23:54 +08:00
Ben Woo fb7e496e4e Move addonToCommaSeperated to StringFormatter class 2025-02-15 15:23:54 +08:00
Ben Woo 9956142bba Merge pull request #3169 from Multiverse/zax71/MV5/confirm-command-custom-timeout
Add config option for `/mv confirm` timeout
2025-02-13 13:35:42 +08:00
Ben Woo 188a60ade7 Remove validDuration from ConfirmQueuePayload and directly reference config value 2025-02-13 13:27:49 +08:00
Zax71 b701da15ff Got the config done but DI is hard 2025-02-12 19:19:34 +00:00
Ben Woo eb20386bac Add reflection method to get field value from field name 2025-02-06 22:10:16 +08:00
Ben Woo 757ea6038f Merge pull request #3168 from Multiverse/dtm/mv5/gradle-plugins
Use build logic from custom gradle plugins.
2025-02-03 22:26:30 +08:00
Ben Woo 288a87d551 Fix tests issues with Biome.values() 2025-02-03 22:05:30 +08:00
Ben Woo e6313ceeb9 Add biome tab completion and docs 2025-02-03 21:59:39 +08:00
Jeremy Wood 74c311be96 Update to 1.0.1 of gradle plugins. 2025-02-03 08:43:20 -05:00
Jeremy Wood 0ffb55c4b9 Use build logic from custom gradle plugins. 2025-02-02 20:22:53 -05:00
Ben Woo f76c3e106a Remove unused AsyncSafetyTeleporter in MVPlayerListener 2025-01-31 20:12:41 +08:00
Ben Woo e83f9093f5 Fix tests due to biome string change 2025-01-28 13:22:45 +08:00
Ben Woo 40cafbd743 Add support for custom biome provider plugins 2025-01-28 13:11:07 +08:00
Ben Woo 13d75b1645 Use try with loading api service 2025-01-28 12:02:33 +08:00
Ben Woo 95ca397072 Implement Try<Void> for config save methods 2025-01-28 12:02:33 +08:00
Ben Woo cdfea86283 Add tests for spawn command 2025-01-28 12:02:33 +08:00
Ben Woo 06e7b30963 Use player.performCommand and check returns true 2025-01-28 12:02:33 +08:00
Ben Woo 761455aeb9 Fix teleporter not parsed correctly 2025-01-28 12:02:33 +08:00
Ben Woo c6797363f5 Implement using generator settings when creating world 2025-01-28 12:01:12 +08:00
Jeremy Wood a7ca4499c3 Merge pull request #3164 from Multiverse/dtm/mv5/more-fixes-2
More DumpsCommand cleanup.
2025-01-27 15:30:50 -05:00
Ben Woo e910e88b35 Fallback to WorldManager#getDefaultWorld when removing players from world 2025-01-28 00:20:46 +08:00
Ben Woo 92548a3617 Ensure mapAsyncAttempt is not forcing mapper AsyncAttempt to run synced 2025-01-28 00:10:31 +08:00
Ben Woo 92b13a5057 Make finer teleport config option apply to spawn permissions as well 2025-01-27 23:36:26 +08:00
Ben Woo 4cad57f749 Refactor all config node variables to be camelcase 2025-01-27 23:21:55 +08:00
Jeremy Wood 5cb7fc87c9 Use sealed classes for HttpAPIClient and implementations. 2025-01-27 10:15:53 -05:00
Ben Woo 1eaa76cb3a mapAsyncAttempt should call thenApply instead of thenApplyAsync 2025-01-27 23:04:20 +08:00
Jeremy Wood 72a7b2b511 Reduce complexity of DumpsLogPoster. 2025-01-27 10:00:30 -05:00
Jeremy Wood 35c4392033 Increase ThrowsCount checkstyle to permit max of 2. 2025-01-27 09:59:01 -05:00
Ben Woo df11141d17 Fix protocol version inequality (again lol) 2025-01-25 17:05:42 +08:00
Ben Woo f8b41cea91 Fix isIteratingOverLevelsMethod being null 2025-01-25 17:05:14 +08:00
Ben Woo c4f5ae366c Fix protocol version logic 2025-01-25 15:32:54 +08:00
Ben Woo d6ea37777f Rename getServiceProvider to getServiceLocator 2025-01-25 12:29:49 +08:00
Ben Woo 547c123075 Fix MultiversePlugin abstract methods should be public 2025-01-25 12:13:13 +08:00
Ben Woo 5d09bce8f4 Revamp how submodules should depend on mvcore 2025-01-25 11:55:43 +08:00
Ben Woo 18fd97410e Cleanup MultiverseCoreApi tests 2025-01-25 10:10:37 +08:00
Ben Woo 1ffdc4ad75 Add more common api getters and expose PluginServiceLocator 2025-01-25 10:10:24 +08:00
Ben Woo 0fceff073c Fix comment style in PluginServiceLocator 2025-01-25 09:52:57 +08:00
Ben Woo dee05cb21f Suggesting destination use CommandSender instead of CommandIssuer 2025-01-25 09:51:24 +08:00
Ben Woo 2b70c8bfa5 Merge pull request #3157 from Multiverse/zax71/mv5/whoCommandAutoWorld
`/mv who` command auto world
2025-01-25 09:40:47 +08:00
Ben Woo 3861737918 Merge pull request #3162 from Multiverse/ben/mv5/server-props
Read server.properties files to get default world
2025-01-25 09:32:12 +08:00
Ben Woo ee15740d75 Read server.properties files to get default world 2025-01-25 09:25:00 +08:00
Ben Woo 164e0ffd09 Fix javadoc error with p tag 2025-01-25 09:24:25 +08:00
Ben Woo 878d1d86f3 Merge remote-tracking branch 'origin/MV5' into MV5 2025-01-25 09:19:46 +08:00
Ben Woo 548dc7dae7 Removed unused sendPlayerToDefaultWorld method 2025-01-25 09:17:47 +08:00
Ben Woo 367b9096c5 Fix more sonarcloud nitpicks 2025-01-25 09:17:47 +08:00
Ben Woo 12c9911434 Convert GameRuleValue class to a record 2025-01-25 09:17:46 +08:00
Ben Woo d6f63ee2e9 Private constructor for TestingMode to prevent instantiation 2025-01-25 09:17:46 +08:00
Ben Woo 580e876009 Merge pull request #3161 from Multiverse/dtm/mv5/more-fixes
More checkstyle and sonarqube fixes.
2025-01-25 09:16:22 +08:00
Ben Woo 7aacd73460 Op should have teleport permissions 2025-01-25 09:15:45 +08:00
Jeremy Wood c692632b7f Reduce complexity of DumpsCommand.
Moves most logic to DumpsService.
2025-01-24 13:27:55 -05:00
Jeremy Wood 68595d4acf Extract LogPoster out of DumpsCommand. 2025-01-24 11:21:16 -05:00
Jeremy Wood 78d0e14ffd Add reusable page and filter flags. 2025-01-24 10:29:23 -05:00
Jeremy Wood 28f609858f Rename flags package to flag. 2025-01-24 10:03:44 -05:00
Jeremy Wood 9d72c7b22d Reduce complexity of CreateCommand. 2025-01-24 09:22:34 -05:00
Jeremy Wood d6f4bca6d1 Slightly lower complexity of AnchorManager. 2025-01-24 09:22:34 -05:00
Jeremy Wood 1397d14c4e Slightly lower complexity of RegenWorldOptions. 2025-01-24 09:22:34 -05:00
Ben Woo 0002582991 Convert GameRuleValue class to a record 2025-01-24 09:22:34 -05:00
Ben Woo 21b8008297 Private constructor for TestingMode to prevent instantiation 2025-01-24 09:22:33 -05:00
Ben Woo 906fac1e62 Make bypass gamemode and playerlimit permission false even when oped 2025-01-24 22:18:23 +08:00
Ben Woo 8096a4e185 Use ACFUtil for random instance 2025-01-24 21:27:02 +08:00
Jeremy Wood d3274cced7 Merge pull request #3159 from Multiverse/dtm/mv5/sonarqube-fixes
Checkstyle/Sonarqube fixes.
2025-01-24 00:04:43 -05:00
Jeremy Wood 949c4704f4 Reuse random in RegenWorldOptions. 2025-01-23 23:59:44 -05:00
Jeremy Wood 6d32393145 Remove instance initializer on HashMap. 2025-01-23 23:57:21 -05:00
Jeremy Wood 54f03256f7 Fix potential source of infinite recursion. 2025-01-23 23:55:36 -05:00
Jeremy Wood 715d1cb59b Fix checkstyle and sonarqube issues found in AnchorManager. 2025-01-23 23:45:19 -05:00
Jeremy Wood abe9ddd638 Update a few places using common replacements. 2025-01-23 23:44:53 -05:00
Jeremy Wood fb75953468 Fix many checkstyle and sonarqube issues on commands. 2025-01-23 23:41:59 -05:00
Jeremy Wood 417090489c Add common replacement keys. 2025-01-23 22:23:46 -05:00
Jeremy Wood f6e91fbc03 Disable checkstyle indentation module forceStrictCondition. 2025-01-23 19:53:49 -05:00
Jeremy Wood d7c1954a39 Merge pull request #3158 from Multiverse/ben/mv5/revamp-api
Ben/mv5/revamp api
2025-01-23 18:22:09 -05:00
Ben Woo ddc4785fed Dont call registerConditions in constructor 2025-01-23 23:14:32 +08:00
Ben Woo 402fa87e32 Refactor classes to reduce public exposure 2025-01-23 23:01:36 +08:00
Ben Woo 525171a69a De-interface all classes and remove api package 2025-01-23 22:29:08 +08:00
Zax71 c5a8134bce Switch to using @Flags("resolve=issuerAware") 2025-01-20 15:22:39 +00:00
Ben Woo d9cf3f50ee Merge pull request #3156 from Multiverse/zax71/mv5/improvedLocation 2025-01-20 12:38:53 +08:00
Zax71 9a55e35055 Add /mvw alias for /mv who 2025-01-19 17:33:18 +00:00
Zax71 9660b0c689 Allow not specifying a world to /mv who 2025-01-19 17:31:15 +00:00
Zax71 96b174c78a Improve syntax for /mv setspawn 2025-01-19 16:55:44 +00:00
Zax71 a269c0292f Add /mvss alias and state pitch and yaw when setting 2025-01-19 16:51:40 +00:00
Ben Woo 365b871cf1 Send correct price formatting when player has not enough money 2025-01-19 22:46:13 +08:00
Ben Woo 31524f9d0e Remove FilesManipulator and merge with FileUtils 2025-01-19 22:34:10 +08:00
Ben Woo b646937d23 Make config set methods return Try<Void> 2025-01-19 22:25:39 +08:00
Ben Woo d6a70bbb9b Fix MultiverseWorld#isLoadedWorld returning wrong value 2025-01-19 15:58:12 +08:00
Ben Woo 55cd6b7c59 Make WorldConfigNodes reference MultiverseWorld instead of LoadedMultiverseWorld 2025-01-19 15:51:42 +08:00
Ben Woo 4362453f90 Implement MVWorldPropertyChangeEvent 2025-01-19 15:29:04 +08:00
Ben Woo 36aa144121 Implement MVRespawnEvent as Cancellable and remove respawn method 2025-01-19 14:43:34 +08:00
Ben Woo bb91a74308 Make ItemEconomy a service 2025-01-19 14:36:38 +08:00
Ben Woo b8db6e2645 Implement MultiverseCoreApi 2025-01-18 15:51:26 +08:00
Ben Woo 449f85ee5c Ensure all imports in api does not import other multiverse.core packages 2025-01-18 15:16:41 +08:00
Ben Woo c1e6614423 Implement generator as an api 2025-01-18 15:13:41 +08:00
Ben Woo b17afbd8e0 Merge pull request #3155 from Multiverse/ben/mv5/api
Ben/mv5/api
2025-01-18 14:59:22 +08:00
Ben Woo b3e633b119 Fix and remove some useless todos 2025-01-18 14:57:13 +08:00
Ben Woo 4eb9ff6dae Remove old tests 2025-01-18 14:36:10 +08:00
Ben Woo e3fded9373 Major refactor to support developer api 2025-01-18 14:34:35 +08:00
Ben Woo 53c9cbe81c More configuration option for respawning 2025-01-18 11:37:00 +08:00
Ben Woo b68e8a2cef Localise teleport "you" message 2025-01-17 20:14:59 +08:00
Ben Woo 5742670a90 Implement better spawn command permission checking and tab complete 2025-01-17 20:14:21 +08:00
Ben Woo b66498545a Log unknown world flag 2025-01-17 20:13:18 +08:00
Ben Woo 7a6d328d5a Fix minor issues with teleport CommandCompletion 2025-01-17 18:57:14 +08:00
Ben Woo 2cfcdad4de Improve teleport tab complete handling 2025-01-17 16:50:41 +08:00
Ben Woo 4dc61634bd Don't log permission checks for console 2025-01-17 16:47:24 +08:00
Ben Woo 51731cd178 Only suggest destination that sender have permission for 2025-01-17 15:21:51 +08:00
Ben Woo 14a541cc49 Fix debug permission config not applying 2025-01-17 15:20:17 +08:00
Ben Woo 9d54cc48dd Properly register world and teleport permissions 2025-01-17 11:36:17 +08:00
Ben Woo ed5a13b269 Implement default-respawn-to-world-spawn config option and refactor respawn event 2025-01-16 20:55:27 +08:00
Ben Woo 971a159eae Fix config tests 2025-01-16 19:43:06 +08:00
Ben Woo 83e197aacd Implement config option for debug permission 2025-01-16 19:40:26 +08:00
Ben Woo 9884ec2e82 Fix tests by adding new anchor-respawn property to worlds.yml files 2025-01-16 13:16:58 +08:00
Ben Woo 52a4059f98 Fix typo in anchor respawn method name 2025-01-16 13:16:01 +08:00
Ben Woo c49653ebc8 Add world property toggle allow anchor spawn 2025-01-16 11:00:44 +08:00
Ben Woo 9be7c75dcc Defaults mv dumps to use paste.dev as paste.gg is down 2025-01-16 10:46:01 +08:00
Ben Woo 5b97c3fd1c Fix mv dumps unable to get latest.log if world-container is changed 2025-01-16 10:10:33 +08:00
Ben Woo 3a9eb88197 Merge pull request #3151 from Multiverse/ben/mv5/block-safety
ben/mv5/block-safety
2025-01-16 09:50:31 +08:00
Ben Woo d83d6c6483 Improve safe location search config comments 2025-01-16 09:42:40 +08:00
Ben Woo 6fb620cac6 Fix failing tests caused by new class and config changes 2025-01-16 09:39:38 +08:00
Ben Woo cb2ab3e437 Shift all to use AdvancedBlockSafety and deprecate old BlockSafety 2025-01-16 09:31:53 +08:00
Ben Woo 03dc8e4245 Rename the broken config file and regen a new one 2025-01-15 19:07:00 +08:00
Ben Woo 2df1f39ef9 Implement rest of block safety methods 2025-01-15 14:11:01 +08:00
Ben Woo 3b9a91c010 Implement better search algo that search from the nearest block outwards 2025-01-15 12:43:44 +08:00
Ben Woo 64e7baec3c Implement initial new algorithm for safe location 2025-01-14 23:10:35 +08:00
Ben Woo 06feb84ba9 Add test for worlds.yml with weird formatting 2025-01-13 23:55:01 +08:00
Ben Woo b5f1b32a3d Fix serialisation issues with string and list items 2025-01-13 23:53:57 +08:00
Ben Woo 647e116bc5 Refactor config handles and make world properties alphabetical order 2025-01-13 23:16:32 +08:00
Ben Woo 874b604294 Check if current location is safe before searching around 2025-01-13 19:27:52 +08:00
Ben Woo 1f8a7975ce Add more tests for world config manager 2025-01-13 14:31:17 +08:00
Ben Woo 293417125a Refactor and add config file assertion for config.yml testing 2025-01-13 14:18:30 +08:00
Ben Woo 5b09097fd2 Fix not removing old serialisation tags if file is crlf 2025-01-13 14:17:50 +08:00
Ben Woo c03679d20e Fix new config file not having version number 2025-01-13 14:16:49 +08:00
Ben Woo ef21d83c35 Fix config handle reset not using serialiser 2025-01-13 07:04:31 +08:00
Ben Woo 89871cd418 Ensure onSetValue is ran when setting up nodes 2025-01-12 22:49:15 +08:00
Ben Woo 7d90a11d9c Remove invalid command condition in delete command 2025-01-12 22:34:23 +08:00
Ben Woo ec19f3e786 Defer world operations if world is ticking 2025-01-12 22:33:46 +08:00
Ben Woo de879c7af2 Implement tab-complete for world property respawn-world 2025-01-12 21:57:02 +08:00
Ben Woo f707f29249 Improve worlds config tests with node to node yaml comparison 2025-01-12 18:07:01 +08:00
Ben Woo 4798ddda63 Fix world migration issues with alias and spawnrate 2025-01-12 18:06:01 +08:00
Ben Woo 4f28d38dc1 Fix test failing with lowercase enum 2025-01-12 15:26:23 +08:00
Ben Woo 2356bfa7fc Save all enums in config as lowercased 2025-01-12 15:22:23 +08:00
Ben Woo 643fa604d1 Fix certain config options resetting on reload 2025-01-12 15:21:59 +08:00
Ben Woo a982875558 Fix some english 2025-01-12 14:53:24 +08:00
Ben Woo 803345d09d Fix regen command test by disabling confirm 2025-01-12 12:24:29 +08:00
Ben Woo f1d98a68c5 Implement otp for queue command and confirm mode 2025-01-12 12:16:07 +08:00
Ben Woo 2c82c279e2 Implement option to disable finer teleport permission checking 2025-01-12 10:54:45 +08:00
Ben Woo 1e33689d7b Implement concurrent teleport limit 2025-01-12 10:50:32 +08:00
Ben Woo 8793aa506b Implement new config options for future features 2025-01-12 09:31:39 +08:00
Ben Woo 6d08d1551b Improve first spawn and join location with PlayerSpawnLocationEvent 2025-01-11 09:48:14 +08:00
Ben Woo 2949b96a46 Improve message output for tp command with proper fail reason 2025-01-10 23:58:28 +08:00
Ben Woo cdb5fc0560 Implement option to resolve world by alias for commands and destinations 2025-01-10 23:01:55 +08:00
Ben Woo f37ee00030 Implement --unsafe flag for spawn command 2025-01-10 21:32:44 +08:00
Ben Woo df7b9a3e5b Implement correct default scale for nether and end environment 2025-01-10 21:11:41 +08:00
Ben Woo bb6d236495 Merge pull request #3150 from Multiverse/ben/mv5/revamp-teleport
Ben/mv5/revamp teleport
2025-01-10 20:29:21 +08:00
Ben Woo 7c2b7e5a47 Fix a test for ConfigTeleportInterceptTest 2025-01-10 20:24:02 +08:00
Ben Woo 5f15cce3f3 Add tests for multiple player teleport 2025-01-10 20:21:19 +08:00
Ben Woo 199da71be0 Add docs for new AsyncSafetyTeleporter 2025-01-10 20:13:49 +08:00
Ben Woo 303ac039e0 Add default serialisers for all number types 2025-01-10 20:01:49 +08:00
Ben Woo 067a88eec1 Fix scale double migration 2025-01-10 19:57:21 +08:00
Ben Woo 34ea244e82 Add tests for setPropertyString 2025-01-10 19:48:42 +08:00
Ben Woo 68717d592c Implement double serialization to fix scale config node 2025-01-10 13:23:59 +08:00
Ben Woo c0c9814c10 Improve config node deserialization handling 2025-01-10 13:23:15 +08:00
Ben Woo 0f1a9eb8f7 Fix double migrator action class name 2025-01-10 13:19:03 +08:00
Ben Woo d9aae1ade7 Remove unused TeleportResult.Success enum 2025-01-09 22:36:09 +08:00
Ben Woo 3e69dd4877 Fix teleport test build error 2025-01-09 22:29:35 +08:00
Ben Woo 918d54f466 Only allow proper true/false strings to be parsed 2025-01-09 21:28:46 +08:00
Ben Woo 25e704d6ce Implement test for teleport command 2025-01-09 20:54:49 +08:00
Ben Woo d8d8607592 Convert all usage of AsyncSafetyTeleporter to new api 2025-01-09 20:52:16 +08:00
Ben Woo 2e309cab0d Refactor safety teleporter with AsyncSafetyTeleporterAction 2025-01-08 22:49:11 +08:00
Ben Woo 97b6d240fc Implement attempt thenRun and thenAccept 2025-01-08 22:48:27 +08:00
Ben Woo 9e38142c4f Make file utils a service and optimise server folder file search 2025-01-08 09:34:11 +08:00
Ben Woo 5dddfbebc9 Merge pull request #3149 from Multiverse/ben/mv5/single-biome
Add support for creating worlds with single biome
2025-01-05 23:10:13 +08:00
Ben Woo ddedf2cbdd Fix failed tests caused by new biome property 2025-01-05 23:02:21 +08:00
Ben Woo 71e47d22ef Add support for creating worlds with single biome 2025-01-05 22:55:34 +08:00
Ben Woo 08ac65e372 Add missing SPAWN_FAILED message key 2025-01-05 11:15:18 +08:00
Ben Woo 50ed9e29d8 Merge pull request #3147 from Multiverse/ben/mv5/regen-test
Implement tests for regen command
2025-01-05 10:48:52 +08:00
Ben Woo e492c2801e Implement more tests for regen command 2025-01-05 10:43:55 +08:00
Ben Woo 5259f6a503 Fix spawn location in world config should use SpawnLocation class not bukkit Location 2025-01-04 10:21:47 +08:00
Ben Woo 17bb856242 Add --unsafe flag to teleport command 2025-01-04 10:09:17 +08:00
Ben Woo df1cb2ccc2 Fix config typo in auto-purge-entities comments 2025-01-04 09:50:05 +08:00
Ben Woo 5a90d51e36 Implement tests for regen command 2025-01-03 21:35:21 +08:00
Ben Woo b8f2ebdab7 Enable tests for counting number of commands as a service 2025-01-03 20:37:15 +08:00
Ben Woo 78b9ad532b Fix anchor command inheriting from CoreCommand 2025-01-03 20:36:48 +08:00
Ben Woo 88bb593b84 Refactor and implement tests for create and delete command 2025-01-03 20:27:09 +08:00
Ben Woo f67777b9a7 Allow delete world command to delete unloaded worlds 2025-01-03 20:26:43 +08:00
Ben Woo a5ba42285f Fix crash when CommandManager calling Plugin#getConfig before config is migrated 2025-01-03 20:15:13 +08:00
Ben Woo 33e440ffec Fix regen command random seed flag 2025-01-03 18:13:38 +08:00
Ben Woo 8fdb13765e Fix regen command syntax description and minor notes 2025-01-02 22:59:19 +08:00
Ben Woo 88171da0ab Fix invalid gamerule error 2025-01-02 22:58:24 +08:00
Ben Woo 7db5f8c30c Merge pull request #3146 from Multiverse/ben/mv5/improve-locales
Implement configurable default and per-player locale
2025-01-02 20:50:16 +08:00
Ben Woo 69ab1d3953 Ensure parent locales field is using the same instance in CommandManager 2025-01-02 20:48:37 +08:00
Ben Woo 92f3225144 Merge pull request #3145 from Multiverse/ben/mv5/check-command
Actually implement check command
2025-01-02 20:44:23 +08:00
Ben Woo 1885298222 Implement more detailed localization config tests 2025-01-01 22:23:13 +08:00
Ben Woo 7ae9d2671e Add config methods for the 2 new locale config nodes 2025-01-01 22:22:46 +08:00
Ben Woo c6ceba8ea5 Implement configurable default and per-player locale 2025-01-01 11:34:11 +08:00
Ben Woo 22e18545ed Actually implement check command 2024-12-31 12:04:16 +08:00
Ben Woo f20b4f285f Bump mockbukkit with canGenerateStructures method support 2024-12-31 11:27:06 +08:00
Ben Woo d06407fcde Merge pull request #3142 from Multiverse/ben/mv5/teleport-intercept
Properly implement teleport intercept config option
2024-12-20 23:12:03 +08:00
Ben Woo 8410175f60 Merge pull request #3137 from Multiverse/ben/mv5/command-permissions
Implement more customisable way of doing permission checking
2024-12-20 23:11:43 +08:00
Ben Woo 3bdc7f428b Merge pull request #3140 from Multiverse/ben/mv5/command-tests
Ben/mv5/command tests
2024-12-20 23:09:32 +08:00
Ben Woo a5009bf8a5 Merge pull request #3141 from Multiverse/ben/mv5/flag-group
Allow customisation of flag group prefix used
2024-12-20 23:08:57 +08:00
Ben Woo 018e15483d Improve teleport intercept config comment 2024-12-12 18:37:21 +08:00
Ben Woo d8b56b9feb Properly implement teleport intercept config option 2024-12-12 10:39:35 +08:00
Ben Woo 63b6ab225f Allow customisation of flag group prefix used 2024-12-11 19:47:56 +08:00
Ben Woo 0700d481d1 Add test for config command 2024-12-11 17:23:34 +08:00
Ben Woo 80ad788028 Abstract common action for command testing to a base class 2024-12-11 17:23:19 +08:00
Ben Woo f55bacc83d Improve error handling for string parser 2024-12-11 17:22:40 +08:00
Ben Woo 12ed1885d8 Implement better permission checking for spawn command 2024-12-11 15:59:38 +08:00
Ben Woo 8e64b18102 Merge pull request #3136 from Multiverse/ben/mv5/gamerule-value
Improve gamerule value suggestion
2024-12-07 14:26:30 +08:00
Ben Woo 32f609e9ad Teleport command no longer need to override hasPermission 2024-11-29 22:57:49 +08:00
Ben Woo ea87f2a30b Implement more customisable way of doing permission checking 2024-11-29 22:51:20 +08:00
Ben Woo 75a98f9c67 Improve gamerule value suggestion 2024-11-27 16:48:31 +08:00
Ben Woo 974887d13f Throw error if number parsing fails 2024-11-27 15:23:24 +08:00
Ben Woo c6983a5f44 Ensure value is not null for the relevant PropertyModifyAction 2024-11-27 15:07:35 +08:00
Ben Woo 0d6daa1cd6 Improve MultiverseWorld command context error handling 2024-11-27 15:06:39 +08:00
Ben Woo 6a351fd38a Backup old mv4's worlds config to worlds.yml.old 2024-11-27 11:58:59 +08:00
Ben Woo 6c6553bf85 Delete old entryfee path from world config during migration 2024-11-27 11:58:18 +08:00
Ben Woo 7e8ba18ba1 Fix worlds config file name from worlds2 to worlds 2024-11-27 11:57:38 +08:00
Jeremy Wood aac076f19a Ignore ordering for potential worlds list test. 2024-11-25 09:23:51 -08:00
Ben Woo ecf0260a9f Revert "Remove unused MVPlayerTouchedPortalEvent"
This reverts commit 9781e8350c.
2024-11-24 13:39:28 +08:00
Ben Woo 470e657e3e Merge pull request #3133 from Multiverse/ben/mv5/events
Ben/mv5/events
2024-11-24 13:28:09 +08:00
Ben Woo d0774614c8 Rename MVDumpsDebugInfoEvent methods to reflect the class name change 2024-11-24 13:26:22 +08:00
Ben Woo 8d852c5269 Add todo for MVWorldPropertyChangeEvent 2024-11-24 11:21:13 +08:00
Ben Woo e93e2aa5f4 Implement MVWorldDeleteEvent event 2024-11-24 11:21:01 +08:00
Ben Woo 07ed9fbc48 Cleanup MVConfigReloadEvent and deprecate MVRespawnEvent 2024-11-23 21:28:05 +08:00
Ben Woo c0ceb4f993 Rename MVVersionEvent to MVDumpsDebugInfoEvent for clarity 2024-11-23 21:25:23 +08:00
Ben Woo 9781e8350c Remove unused MVPlayerTouchedPortalEvent 2024-11-23 21:24:30 +08:00
Ben Woo b1afd8f786 Implement MVTeleportDestinationEvent event 2024-11-23 21:24:14 +08:00
Ben Woo 668856dccc Add temporary publishing repo 2024-11-23 18:06:53 +08:00
Ben Woo e59089f706 Fix mvimport description locale 2024-11-22 20:52:17 +08:00
Ben Woo 295259a456 Merge pull request #3132 from Multiverse/ben/mv5/merge-main
Ben/mv5/merge main
2024-11-22 20:51:45 +08:00
Ben Woo 8ebb5cff5a Merge branch 'main' into ben/mv5/merge-main
# Conflicts:
#	src/main/java/com/onarandombox/MultiverseCore/listeners/MVPlayerListener.java
2024-11-22 17:14:17 +08:00
Ben Woo 8a28bcafec Expose MV's completion and context instances in CommandManager 2024-11-21 22:28:48 +08:00
Ben Woo 0cc1676e6c Fix finer permission checking filter 2024-11-21 22:28:10 +08:00
Ben Woo b434903739 Merge pull request #3131 from Multiverse/ben/mv5/NewAndRemovedWorlds
Use a record NewAndRemovedWorlds instead of generic tuple for clarity
2024-11-21 22:10:41 +08:00
Ben Woo 5b3477af3e Merge pull request #3117 from Multiverse/ben/pr-comment-artifact
Comment build artifact in PR
2024-11-21 21:36:51 +08:00
Ben Woo e2050c825e Merge pull request #3130 from Multiverse/ben/mv5/improve-destination
Refactor to remove need for ParsedDestination and add destination tests
2024-11-21 11:52:08 +08:00
Ben Woo faf8d1a7d0 Merge pull request #3129 from Multiverse/ben/mv5/material-converter
Re-add MaterialConverter class and add tests
2024-11-21 11:49:24 +08:00
Ben Woo e15dd7e69b Implement DestinationInstance#getIdentifier shortcut 2024-11-21 10:56:41 +08:00
Ben Woo cee6a4879a Implement ExactDestination#fromLocation 2024-11-21 09:57:07 +08:00
Ben Woo f5b5148f82 Use a record NewAndRemovedWorlds instead of generic tuple for clarity 2024-11-21 09:46:57 +08:00
Ben Woo d3f6b11fe5 Refactor to remove need for ParsedDestination and add destination tests 2024-11-20 22:25:33 +08:00
Ben Woo 1eeb265f4e Merge pull request #3127 from Multiverse/ben/mv5/testssss
More testsss
2024-11-20 21:48:37 +08:00
Ben Woo 2f8d23390a Merge pull request #3128 from Multiverse/fix-publish-repo 2024-11-19 07:08:32 +08:00
Jeremy Wood c4d01b65b7 Publish to correct repo url based on version. 2024-11-18 15:36:47 -05:00
Ben Woo d415a7fc5c Re-add MaterialConverter class and add tests 2024-11-17 15:05:12 +08:00
Ben Woo 0b89738ec0 Update to mockbukkit 4.0 2024-11-16 17:03:11 +08:00
Jeremy Wood e33fc93a1d Merge pull request #3126 from Multiverse/ben/fix-publish
Fix gha publish command
2024-11-16 02:29:27 -05:00
Ben Woo 656b318be3 Fix gha publish command 2024-11-16 15:27:32 +08:00
Jeremy Wood 562dece6f9 Merge pull request #3125 from Multiverse/jwood/reposilite
Publish to new repo using temp url.
2024-11-16 02:02:19 -05:00
Jeremy Wood fa30b432a9 Publish to new repo using temp url. 2024-11-15 23:19:53 -05:00
Ben Woo 20af0350aa Implement tests for WorldNameChecker class 2024-11-14 20:39:56 +08:00
Ben Woo 30e8b93786 Fix null and empty world name checking 2024-11-14 20:39:35 +08:00
Ben Woo b7d067123c Implement more detailed world manager tests 2024-11-14 19:50:08 +08:00
Ben Woo 38dd0018e7 Fix and refactor cloneWorld hasWorldFolder check 2024-11-14 19:48:51 +08:00
Ben Woo e889e406ae Fix bstats shadowing and archive name build issues 2024-11-14 15:28:17 +08:00
Jeremy Wood 83b6236ed7 Merge pull request #3124 from Multiverse/ben/javadoc-latest
Add latest dir for javadoc
2024-11-14 00:51:18 -05:00
Ben Woo 41cab5b290 Improve replace_latest description 2024-11-14 13:40:53 +08:00
Ben Woo 99a716ccaa latest boolean 2024-11-14 13:38:42 +08:00
Ben Woo ecca904aef Add latest dir for javadoc 2024-11-14 13:26:32 +08:00
Ben Woo a2a740f371 Merge pull request #3123 from Multiverse/ben/javadoc
Javadoc with github action
2024-11-14 12:32:51 +08:00
Ben Woo c66b1a2b96 Add manual dispatch action for javadoc 2024-11-14 12:25:13 +08:00
Ben Woo 52b2170f8f Deploy javadoc to github pages on release 2024-11-14 12:22:30 +08:00
Ben Woo 4264c0c9d2 Merge pull request #3119 from Multiverse/ben/mv5/java-21
Make test run on java 21 for latest mockbukkit to work
2024-11-10 16:29:55 +08:00
Ben Woo 061b90ce90 Config gha to use java 21 for build 2024-10-14 14:23:45 +08:00
Ben Woo 38aa534cf5 Bump mockbukkit to latest 1.21 version 2024-10-14 14:19:29 +08:00
Ben Woo 234d42f1f1 Make tests run on java 21 instead of 17 2024-10-14 14:18:39 +08:00
Ben Woo a226ae5d4a Merge pull request #3098 from Multiverse/ben/mv5/anchorcommands
Implement anchor commands
2024-10-14 10:58:11 +08:00
Ben Woo 1ba0505c4b Merge pull request #3109 from Multiverse/ben/mv5/entryfee-currency
Properly implement entryfee for world config
2024-10-14 10:57:35 +08:00
Ben Woo bc27f0548a Merge pull request #3106 from Multiverse/ben/mv5/remove-old-serial
Remove old SerializationConfig dependency
2024-10-14 10:56:12 +08:00
Ben Woo 17d376d4d4 Bump upload artifact action to v4 2024-10-13 10:54:38 +08:00
Ben Woo 1547ea84c6 Comment build artifact in PR 2024-10-13 10:54:19 +08:00
Ben Woo c8e50ff7fc Merge pull request #3115 from Multiverse/ben/mv5/config-improvements
Ben/mv5/config improvements
2024-10-12 13:56:59 +08:00
Ben Woo 5a9b9771bc Merge pull request #3116 from Multiverse/ben/mv5/fix-version-test
Fix version command test
2024-10-12 13:56:45 +08:00
Ben Woo 7def05e112 Fix version command test 2024-10-12 13:50:45 +08:00
Ben Woo 42cfd9dcc4 Fix enforcementHandler being null in WorldConfigNodes 2024-10-12 13:43:49 +08:00
Ben Woo 7868a1971e Dont need to deserialize enum if it is already an enum 2024-10-12 13:43:17 +08:00
Ben Woo 9c929fccca Fix modify command string parsing 2024-10-12 13:42:15 +08:00
Ben Woo 18f4600ca4 Merge pull request #3107 from Multiverse/ben/mv5/version-cmd-test
Add tests for version command
2024-10-12 13:37:47 +08:00
Ben Woo 1ea3531d26 Merge pull request #3104 from Multiverse/ben/mv5/multi-plugin-hk2-support
multi plugin hk2 support
2024-10-12 13:36:47 +08:00
Ben Woo 11e2deaa23 Properly implement entryfee for world config 2024-09-18 21:12:17 +08:00
Ben Woo a68b78e5a4 Make contract for core specific listeners and commands 2024-09-15 13:36:29 +08:00
Ben Woo c3674e0a2f Revamp hk2 injection to support injection in dependent plugins 2024-09-15 13:36:29 +08:00
Ben Woo 64584cc6cf Fix gradle group id 2024-09-15 13:36:29 +08:00
Ben Woo 7368ff257c Add tests for version command 2024-09-15 13:34:35 +08:00
Ben Woo 54736baf33 Remove old SerializationConfig dependency 2024-09-15 13:30:36 +08:00
Ben Woo 1528fdfed1 Merge pull request #3105 from Multiverse/ben/mv5/papi-dep
Update placeholderAPI repo and version
2024-09-15 10:44:04 +08:00
Ben Woo c05d1401ea Update placeholderAPI repo and version 2024-09-15 09:43:41 +08:00
Ben Woo 4be2034588 Merge pull request #3093 from Multiverse/Zax71/MV5/implement-1420 2024-09-10 14:19:20 +08:00
Ben Woo 7bde4c1033 Merge pull request #3057 from Multiverse/zax71/MV5/spawnCommand
Add `/mv spawn` and `/mv setspawn`
2024-08-30 14:27:15 +08:00
Ben Woo f00e2964f3 Proper spawn permission checking 2024-08-30 14:23:45 +08:00
Ben Woo 1ec4e4b8f0 Improve setspawn command location param 2024-08-30 13:55:39 +08:00
Ben Woo 330d4fbe39 Improve spawn command messaging 2024-08-30 13:06:27 +08:00
Ben Woo 8cbf2bc050 Merge branch 'MV5' into zax71/MV5/spawnCommand 2024-08-30 12:29:50 +08:00
Ben Woo 77350539a7 Merge pull request #3095 from Multiverse/zax71/mv5/legacy-aliases
Add legacy aliases to all commands
2024-08-30 12:27:49 +08:00
Ben Woo d530c2aa4d Fix some formatting issues. 2024-08-30 12:25:50 +08:00
Ben Woo d83ecd3b94 Merge pull request #3097 from Multiverse/ben/mv5/getWorld-bukkit
Fix WorldManager#getWorld from bukkit World not mapping to worldname
2024-08-29 11:14:01 +08:00
Ben Woo fe79580e86 Merge pull request #3099 from Multiverse/ben/mv5/AsyncAttempt
Add onFailure methods to AsyncAttempt class
2024-08-29 11:00:28 +08:00
Ben Woo e0d0ee0ad0 Add onFailure methods to AsyncAttempt class. 2024-08-28 20:01:03 +08:00
Ben Woo 1d1f5940d7 Implement anchor commands 2024-08-28 19:15:03 +08:00
Ben Woo ad3a6fc8fc Fix WorldManager#getWorld from bukkit World not mapping to worldname 2024-08-28 17:56:36 +08:00
Ben Woo 533e843cdb Merge pull request #3055 from Multiverse/zax71/MV5/whoCommand
Add `/mv who` and `/mv whoall`
2024-08-28 17:53:08 +08:00
Ben Woo 833c33e26e Merge pull request #3094 from Multiverse/zax71/mv5/mvm-coloring 2024-08-28 10:21:58 +08:00
Zax71 949ba46a16 Add legacy aliases 2024-08-27 19:59:21 +01:00
Zax71 62c9497365 Fix colouring on /mv modify 2024-08-27 19:30:41 +01:00
Zax71 fef9ba44cd Allow /mv list to show world names instead of aliases 2024-08-27 18:51:30 +01:00
Ben Woo 38f97a81c8 Merge pull request #3091 from Multiverse/1.21.1-update
Update to 1.21.1
2024-08-25 10:09:26 +08:00
Zax71 e13349949a Update to 1.21.1 2024-08-23 12:08:41 +01:00
Ben Woo 2d059eabc3 Merge pull request #3084 from Multiverse/ben/fix-getTo-null 2024-08-13 10:19:25 +08:00
Ben Woo 006fe56d18 Fix edge case where PlayerTeleportEvent getTo is null 2024-08-12 21:41:39 +08:00
Ben Woo 2cf8b6961c Merge pull request #3082 from Multiverse/1.21-update 2024-08-08 08:10:15 +08:00
Zax71 9c519e1ea4 Maybe don't be so dumb this time 2024-08-07 15:42:55 +01:00
Ben Woo f1db561262 Merge pull request #3081 from Multiverse/1.21-update
Change versions to support 1.21
2024-08-07 22:07:19 +08:00
Zax71 f0cf1b7d4c Change versions to support 1.21 2024-08-07 13:10:26 +01:00
Ben Woo 7b20cd6443 Merge branch 'MV5' into zax71/MV5/whoCommand 2024-03-12 10:39:36 +08:00
Ben Woo 278c51d056 Merge pull request #3056 from Multiverse/zax71MV5/versionCommand
Add `/mv version`
2024-03-12 10:38:12 +08:00
Zax71 caed081f82 Update Ben's name 2024-03-08 18:03:42 +00:00
Zax71 7276da7665 Implement i18n 2024-02-24 13:48:57 +00:00
Zax71 90b8729129 Add Ben and Zax71 to authors 2024-02-24 13:37:23 +00:00
Zax71 c24f198ed0 Add /mv version 2024-02-24 13:36:13 +00:00
Zax71 c640a251b6 Add i18n and tell user when a world is empty when using /mv who 2024-02-24 13:03:12 +00:00
Zax71 a6f4f2d2d5 Add /mv who and /mv whoall 2024-02-24 12:26:05 +00:00
439 changed files with 23043 additions and 11481 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
github: Multiverse
open_collective: multiverse-plugins
+5 -5
View File
@@ -18,10 +18,10 @@ body:
When completing the report, please ensure all fields are filled with the correct details requested. Failure to do so will result in your issue being closed.
- type: input
id: mutliverse-version-info
id: multiverse-version-info
attributes:
label: '`/mv version -p` output'
description: Run `/mv version -p` in the console, then copy and paste the link from the output of the command into this box.
label: '`/mv dumps` output'
description: Run `/mv dumps` in the console, then copy and paste the link from the output of the command into this box. If `/mv dumps` command is not available, update multiverse to v5.
validations:
required: true
- type: input
@@ -29,7 +29,7 @@ body:
attributes:
label: Server logs
description: >
Include full startup log and console error (if any). upload all relevant logs to either https://paste.gg
Include full startup log and console error (if any). upload all relevant logs to either https://pastes.dev
or https://gist.github.com, save and the paste the link in this box.
validations:
required: true
@@ -54,7 +54,7 @@ body:
id: reproduce-steps
attributes:
label: Steps to reproduce
description: Provide detailed intructions to trigger the bug.
description: Provide detailed instructions to trigger the bug.
placeholder: |
Example:
1. Start a server with only multiverse installed.
@@ -0,0 +1,39 @@
name: 'Dispatch: Create Release'
on:
workflow_dispatch:
inputs:
release_type:
description: 'Stable or pre-release'
required: true
type: choice
options:
- prerelease
- release
version_bump:
description: 'Version bump type'
required: true
type: choice
options:
- major
- minor
- patch
jobs:
github_release:
uses: ./.github/workflows/call.github_release.yml
secrets: inherit
with:
release_mode: ${{ inputs.release_type }}
version_bump: ${{ inputs.version_bump }}
platform_uploads:
needs: github_release
if: needs.github_release.outputs.release_created == 'true'
uses: ./.github/workflows/call.platform_uploads.yml
secrets: inherit
with:
target_tag: ${{ needs.github_release.outputs.tag_name }}
upload_modrinth: 'true'
upload_dbo: 'false'
upload_hangar: 'false'
+55
View File
@@ -0,0 +1,55 @@
name: 'Dispatch: Deploy Javadoc'
on:
workflow_dispatch:
inputs:
target_tag:
description: 'Version to generate javadoc'
required: true
replace_latest:
description: 'Replace the latest folder'
type: boolean
jobs:
javadocs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.target_tag }}
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'adopt'
cache: gradle
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: build javadoc
run: ./gradlew javadoc
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_VERSION: ${{ inputs.target_tag }}
- name: Copy javadoc to deploy folder
run: |
mkdir -p build/docs/javadoc-deploy/${{ inputs.target_tag }}
cp -r build/docs/javadoc/* build/docs/javadoc-deploy/${{ inputs.target_tag }}
- name: Copy javadoc to latest folder
if: inputs.replace_latest
run: |
mkdir -p build/docs/javadoc-deploy/latest
cp -r build/docs/javadoc/* build/docs/javadoc-deploy/latest
- name: Deploy javadoc to gh pages
uses: JamesIves/github-pages-deploy-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
folder: build/docs/javadoc-deploy
branch: javadoc
target-folder: javadoc
clean: false
commit-message: Deploy javadoc for ${{ inputs.target_tag }}
+54 -14
View File
@@ -43,28 +43,28 @@ jobs:
echo "version_bump: ${{ inputs.version_bump }}"
echo "promote_from: ${{ inputs.promote_from }}"
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
ref: ${{ inputs.promote_from }}
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
java-version: '11'
java-version: '21'
distribution: 'adopt'
cache: gradle
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build and test
uses: gradle/gradle-build-action@v2
with:
arguments: clean build -x assemble -x shadowJar
- name: Build to test
id: test
run: ./gradlew clean build -x assemble -x shadowJar
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create release
id: release
if: steps.test.outcome == 'success'
uses: benwoo1110/semantic-release-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
@@ -74,18 +74,58 @@ jobs:
- name: Publish package
if: steps.release.outputs.release_created == 'true'
uses: gradle/gradle-build-action@v2
with:
arguments: publish -x test
run: >
./gradlew publish -x test
-PmultiverseUsername=${{ secrets.REPOSILITE_REPO_USERNAME }}
-PmultiversePassword=${{ secrets.REPOSILITE_REPO_PASSWORD }}
env:
GITHUB_VERSION: ${{ steps.release.outputs.publish_version }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload release artifact
- name: Build for upload
id: build
if: steps.release.outputs.release_created == 'true'
run: ./gradlew build -x test
env:
GITHUB_VERSION: ${{ steps.release.outputs.tag_name }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload release artifact
if: steps.build.outcome == 'success'
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: build/libs/${{ inputs.plugin_name }}-${{ steps.release.outputs.publish_version }}.jar
file: build/libs/${{ inputs.plugin_name }}-${{ steps.release.outputs.tag_name }}.jar
asset_name: ${{ inputs.plugin_name }}-${{ steps.release.outputs.tag_name }}.jar
tag: ${{ steps.release.outputs.tag_name }}
- name: Generate javadoc
id: javadoc
if: steps.build.outcome == 'success'
run: ./gradlew javadoc -x test
env:
GITHUB_VERSION: ${{ steps.release.outputs.publish_version }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload javadoc
if: steps.javadoc.outcome == 'success'
run: |
mkdir -p build/docs/javadoc-deploy/${{ steps.release.outputs.publish_version }}
cp -r build/docs/javadoc/* build/docs/javadoc-deploy/${{ steps.release.outputs.publish_version }}
- name: Override latest javadoc for stable release
if: steps.javadoc.outcome == 'success' && steps.release.outputs.release_type == 'release'
run: |
mkdir -p build/docs/javadoc-deploy/latest
cp -r build/docs/javadoc/* build/docs/javadoc-deploy/latest
- name: Deploy javadoc to gh pages
if: steps.javadoc.outcome == 'success'
uses: JamesIves/github-pages-deploy-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
folder: build/docs/javadoc-deploy
branch: javadoc
target-folder: javadoc
clean: false
commit-message: Deploy javadoc for ${{ steps.release.outputs.tag_name }}
@@ -107,7 +107,7 @@ jobs:
files: '["${{ github.workspace }}/${{ inputs.plugin_name }}-${{ steps.release-info.outputs.tag_name }}.jar"]'
name: ${{ steps.release-info.outputs.tag_name }}
changelog: ${{ steps.release-artifact.outputs.body }}
game_versions: 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2, 1.18.1, 1.18, 1.17.1, 1.17, 1.16.5, 1.16.4, 1.16.3, 1.16.2, 1.16.1, 1.16, 1.15.2, 1.15.1, 1.15, 1.14.4, 1.14.3, 1.14.2, 1.14.1, 1.14, 1.13.2, 1.13.1, 1.13
game_versions: 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
version_type: ${{ steps.parse-release-type.outputs.release_type }}
loaders: bukkit, spigot, paper
dependencies: ${{ inputs.modrinth_dependencies }}
@@ -121,7 +121,7 @@ jobs:
changelog: ${{ steps.release-artifact.outputs.body }}
changelog_type: markdown
display_name: ${{ steps.release-info.outputs.tag_name }}
game_versions: 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2, 1.18.1, 1.18, 1.17, 1.16, 1.15, 1.14, 1.13
game_versions: 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
release_type: ${{ steps.parse-release-type.outputs.release_type }}
project_relations: ${{ inputs.dbo_project_relations }}
file_path: ${{ github.workspace }}/${{ inputs.plugin_name }}-${{ steps.release-info.outputs.tag_name }}.jar
@@ -136,5 +136,5 @@ jobs:
channel: ${{ steps.parse-release-type.outputs.release_type }}
files: '[{"path": "${{ github.workspace }}/${{ inputs.plugin_name }}-${{ steps.release-info.outputs.tag_name }}.jar", "platforms": ["PAPER"]}]'
description: ${{ steps.release-artifact.outputs.body }}
platform_dependencies: '{"PAPER": ["1.13-1.20.1"]}'
platform_dependencies: '{"PAPER": ["1.18.2-1.21.8"]}'
plugin_dependencies: ${{ inputs.hangar_plugin_dependencies }}
+18 -10
View File
@@ -9,32 +9,40 @@ on:
type: string
jobs:
test:
test-and-artifact:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
java-version: '17'
java-version: '21'
distribution: 'adopt'
cache: gradle
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Run unit tests
uses: gradle/gradle-build-action@v2
with:
arguments: build
run: ./gradlew build
env:
GITHUB_VERSION: pr${{ github.event.pull_request.number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Artifact output
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.plugin_name }}-pr${{ github.event.pull_request.number }}
path: build/libs/${{ inputs.plugin_name }}-pr${{ github.event.pull_request.number }}.jar
comment-artifact:
needs: test-and-artifact
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: benwoo1110/artifact-comment-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
+16 -15
View File
@@ -1,22 +1,21 @@
<p align="center">
<img src="config/multiverse2-long.png" alt="Multiverse Logo">
<img src="config/multiverse-banner.png" alt="Multiverse Logo">
</p>
[![Modrinth](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/available/modrinth_vector.svg)](https://modrinth.com/plugin/multiverse-core)
[![hangar](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/available/hangar_vector.svg)](https://hangar.papermc.io/Multiverse/Multiverse-Core)
[![bukkit](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/supported/bukkit_vector.svg)](https://dev.bukkit.org/projects/multiverse-core)
[![Spigot](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/supported/spigot_vector.svg)](https://www.spigotmc.org/resources/multiverse-core.390/)
[![Release](https://img.shields.io/nexus/r/com.onarandombox.multiversecore/multiverse-core?server=https%3A%2F%2Frepo.onarandombox.com&label=release&color=dark-green)](https://dev.bukkit.org/projects/multiverse-core)
[![Dev builds](https://img.shields.io/nexus/s/com.onarandombox.multiversecore/multiverse-core?server=https%3A%2F%2Frepo.onarandombox.com&label=prerelease&color=orange)](https://ci.onarandombox.com/job/Multiverse-Core/)
[![Discord](https://img.shields.io/discord/325459248047980545?label=discord&logo=discord)](https://discord.gg/NZtfKky)
[![Support us on Patreon](https://img.shields.io/badge/donate-patreon-white?logo=patreon)](https://patreon.com/dumptruckman)
[![License](https://img.shields.io/github/license/Multiverse/Multiverse-Core?color=blue)](LICENSE.md)
[![Hangar](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/available/hangar_vector.svg)](https://hangar.papermc.io/Multiverse/Multiverse-Core)
[![Bukkit](https://raw.githubusercontent.com/intergrav/devins-badges/refs/heads/v3/assets/cozy/available/bukkit_vector.svg)](https://dev.bukkit.org/projects/multiverse-core)
[![Spigot](https://raw.githubusercontent.com/intergrav/devins-badges/refs/heads/v3/assets/cozy/available/spigot_vector.svg)](https://www.spigotmc.org/resources/multiverse-core.390/)
[![Release](https://img.shields.io/github/v/release/multiverse/multiverse-core)](https://github.com/Multiverse/Multiverse-Core/releases/latest)
[![Pre-Release](https://img.shields.io/github/v/release/multiverse/multiverse-core?include_prereleases&label=Pre-release)](https://github.com/Multiverse/Multiverse-Core/releases)
[![Discord](https://img.shields.io/discord/325459248047980545?label=Discord&logo=discord)](https://discord.gg/NZtfKky)
[![Donate on Github Sponsor](https://img.shields.io/badge/Github%20Sponsor-Donate-pink?logo=githubsponsors)](https://github.com/sponsors/Multiverse)
[![Donate on Open Collective](https://img.shields.io/badge/Open%20Collective-Donate-blue?style=flat&logo=opencollective)](https://opencollective.com/multiverse-plugins)
# About
[Multiverse](https://dev.bukkit.org/projects/multiverse-core) was created at the dawn of Bukkit multiworld support. It has since then grown into a **complete world management solution!** Multiverse provides the easiest to use world management solution for your Minecraft server, big or small, and with great addons like [Portals](https://dev.bukkit.org/projects/multiverse-portals) and [NetherPortals](https://dev.bukkit.org/projects/multiverse-netherportals/), what's not to love!
[Multiverse](https://modrinth.com/plugin/multiverse-core) was created at the dawn of Bukkit multiworld support. It has since then grown into a **complete world management solution!** Multiverse provides the easiest to use world management solution for your Minecraft server, big or small, and with great addons like [Portals](https://dev.bukkit.org/projects/multiverse-portals) and [NetherPortals](https://dev.bukkit.org/projects/multiverse-netherportals/), what's not to love!
Now it's time to create your very own Multiverse server, do check out our [Wiki](https://github.com/Multiverse/Multiverse-Core/wiki) and [Usage Guide](https://github.com/Multiverse/Multiverse-Core/wiki/Basics) to get started. Feel free to hop onto our [Discord](https://discord.gg/NZtfKky) if you have any question or just want to have a chat with us!
@@ -27,22 +26,24 @@ Now it's time to create your very own Multiverse server, do check out our [Wiki]
* [Multiverse-Inventories](https://github.com/Multiverse/Multiverse-Inventories) -> Have separated players stats and inventories per world or per group of worlds.
* [Multiverse-SignPortals](https://github.com/Multiverse/Multiverse-SignPortals) -> Signs as teleporters!
## Usage Guide
We have a cool new website hosting our Wiki: https://mvplugins.org
## Building
Simply build the source with Gradle:
```
./gradlew build
```
More details are available on the [build instructions wiki page](https://github.com/Multiverse/Multiverse-Core/wiki/Building).
## Contributing
**Want to help improve Multiverse?** There are several ways you can support and contribute to the project.
* Take a look at our "Bug: Unconfirmed" issues, where you can find issues that need extra testing and investigation.
* Want others to love Multiverse too? You can join the [Multiverse Discord community](https://discord.gg/NZtfKky) and help others with issues and setup!
* A Multiverse guru? You can update our [Wiki](https://github.com/Multiverse/Multiverse-Core/wiki) with your latest tip, tricks and guides! The wiki open for all to edit and improve.
* A Multiverse guru? You can update our [Wiki](https://github.com/Multiverse/multiverse-web) with your latest tip, tricks and guides! The wiki open for all to edit and improve.
* Love coding? You could look at ["State: Open to PR"](https://github.com/Multiverse/Multiverse-Core/labels/State%3A%20Open%20to%20PR) and ["Resolution: Accepted"](https://github.com/Multiverse/Multiverse-Core/labels/Resolution%3A%20Accepted) issues. We're always happy to receive bug fixes and feature additions as [pull requests](https://www.freecodecamp.org/news/how-to-make-your-first-pull-request-on-github-3/).
* If you'd like to make a financial contribution to the project, do consider joining our [Patreon](https://www.patreon.com/dumptruckman) or make a one-time donation [here](https://paypal.me/dumptruckman)!
* If you'd like to make a financial contribution to the project, do consider donating to our [Github Sponsors](https://github.com/sponsors/Multiverse) or [Open Collective](https://opencollective.com/multiverse-plugins)!
Additionally, we would like to give a big thanks to everyone that has supported Multiverse over the years, as well as those in the years to come. Thank you!
+31 -235
View File
@@ -1,50 +1,13 @@
import org.apache.tools.ant.filters.ReplaceTokens
plugins {
id 'java-library'
id 'maven-publish'
id 'com.github.johnrengelman.shadow' version '7.1.2'
id "org.jetbrains.kotlin.jvm" version "1.8.10"
id 'org.mvplugins.multiverse-plugin' version '1.2.1'
}
version = System.getenv('GITHUB_VERSION') ?: 'local'
group = 'com.onarandombox.multiversecore'
group = 'org.mvplugins.multiverse.core'
description = 'Multiverse-Core'
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
repositories {
mavenLocal()
mavenCentral()
maven {
url = uri('https://hub.spigotmc.org/nexus/content/repositories/snapshots/')
}
maven {
url = uri('https://repo.onarandombox.com/content/groups/public')
}
maven {
url = uri('https://hub.spigotmc.org/nexus/content/groups/public/')
}
maven {
url = uri('https://jitpack.io')
}
maven {
url = uri('https://repo.minebench.de/')
}
maven {
url = uri('https://repo.maven.apache.org/maven2/')
}
maven {
name = 'aikar repo'
url = uri('https://repo.aikar.co/content/groups/aikar/')
@@ -56,179 +19,61 @@ repositories {
}
maven {
name = 'PlaceholderAPI'
url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/'
}
maven {
name = 'papermc repo'
url = 'https://repo.papermc.io/repository/maven-public/'
name = "helpchatRepoReleases"
url = uri("https://repo.helpch.at/releases/")
}
}
configurations {
// Add configuration for server API
compileOnly.extendsFrom serverApi
runtimeClasspath.extendsFrom serverApi
// Add configuration for external plugins
implementation.extendsFrom externalPlugin
// Add configuration for dependencies that will be included in fat jar
compileClasspath.extendsFrom shadowed
testCompileClasspath.extendsFrom shadowed
testRuntimeClasspath.extendsFrom shadowed
oldTestCompileClasspath.extendsFrom shadowed
oldTestRuntimeClasspath.extendsFrom shadowed
// Add configuration for old tests
oldTestImplementation.extendsFrom implementation
oldTestRuntime.extendsFrom runtime
configure(apiDependencies) {
serverApiVersion = '1.21.8-R0.1-SNAPSHOT'
mockBukkitServerApiVersion = '1.21'
mockBukkitVersion = '4.69.2'
}
dependencies {
serverApi 'org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT'
// Economy
externalPlugin('com.github.MilkBowl:VaultAPI:1.7.1') {
exclude group: 'org.bukkit', module: 'bukkit'
}
// PlaceholderAPI
externalPlugin 'me.clip:placeholderapi:2.11.3'
externalPlugin 'me.clip:placeholderapi:2.11.6'
// Command Framework
shadowed 'co.aikar:acf-paper:0.5.1-SNAPSHOT'
// Config
shadowed('me.main__.util:SerializationConfig:1.7') {
exclude group: 'org.bukkit', module: 'bukkit'
}
shadowed('io.github.townyadvanced.commentedconfiguration:CommentedConfiguration:1.0.1') {
exclude group: 'org.spigotmc', module: 'spigot-api'
}
// Utils
shadowed 'io.vavr:vavr:0.10.4'
shadowed 'org.glassfish.hk2:hk2-locator:3.0.3'
shadowed 'io.vavr:vavr:0.10.7'
shadowed 'org.glassfish.hk2:hk2-locator:3.1.1'
shadowed('org.glassfish.hk2:hk2-inhabitant-generator:3.1.1') {
exclude group: 'org.apache.maven', module: 'maven-core'
}
shadowed('com.dumptruckman.minecraft:Logging:1.1.1') {
exclude group: 'junit', module: 'junit'
}
shadowed 'de.themoep.idconverter:mappings:1.2-SNAPSHOT'
shadowed 'org.bstats:bstats-bukkit:2.2.1'
shadowed 'net.minidev:json-smart:2.4.9'
shadowed 'org.jetbrains:annotations:22.0.0'
shadowed('org.bstats:bstats-bukkit:3.1.0') {
exclude group: 'org.bukkit', module: 'bukkit'
}
shadowed 'net.minidev:json-smart:2.5.2'
shadowed 'org.jetbrains:annotations:26.0.2'
shadowed 'io.papermc:paperlib:1.0.8'
// Tests
testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.10'
testImplementation 'com.github.seeseemelk:MockBukkit-v1.20:3.20.2'
testImplementation('com.googlecode.json-simple:json-simple:1.1.1') {
exclude group: 'junit', module: 'junit'
}
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'com.natpryce:hamkrest:1.8.0.1'
testImplementation 'org.mockito.kotlin:mockito-kotlin:4.1.0'
// Old Tests
oldTestImplementation 'org.spigotmc:spigot-api:1.20.1-R0.1-SNAPSHOT'
oldTestImplementation('com.googlecode.json-simple:json-simple:1.1.1') {
exclude group: 'junit', module: 'junit'
}
oldTestImplementation 'junit:junit:4.13.1'
oldTestImplementation 'org.mockito:mockito-core:3.11.2'
oldTestImplementation 'commons-io:commons-io:2.7'
// Annotation Processors
annotationProcessor 'org.glassfish.hk2:hk2-metadata-generator:3.0.3'
testAnnotationProcessor 'org.glassfish.hk2:hk2-metadata-generator:3.0.3'
testImplementation("org.hamcrest:hamcrest:3.0")
}
java {
withSourcesJar()
withJavadocJar()
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
tasks.withType(Javadoc) {
options.encoding = 'UTF-8'
}
configurations {
[apiElements, runtimeElements].each {
it.outgoing.artifacts.removeIf { it.buildDependencies.getDependencies(null).contains(jar) }
it.outgoing.artifact(shadowJar)
}
}
sourceSets {
oldTest {
java {
compileClasspath += main.output
runtimeClasspath += main.output
srcDir file('src/old-test/java')
}
resources.srcDir file('src/old-test/resources')
}
}
publishing {
publications {
maven(MavenPublication) {
from components.java
pom.withXml {
Node pomNode = asNode()
// Remove Kotlin dependency
pomNode.dependencies.'*'.findAll() {
it.groupId.text() == 'org.jetbrains.kotlin'
}.each() {
it.parent().remove(it)
}
// Switch runtime deps to provided
pomNode.dependencies.'*'.findAll() {
it.scope.text() == 'runtime'
}.each() {
it.scope*.value = 'provided'
}
// Add spigot api to pom
project.configurations.serverApi.allDependencies.each { dependency ->
pomNode.dependencies[0].appendNode("dependency").with {
it.appendNode("groupId", dependency.group)
it.appendNode("artifactId", dependency.name)
it.appendNode("version", dependency.version)
it.appendNode("scope", "provided")
}
}
}
}
}
repositories {
maven {
name = "GitHubPackages"
url = "https://maven.pkg.github.com/Multiverse/Multiverse-Core"
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
}
ext.bitlyAccessToken = System.getenv('BITLY_ACCESS_TOKEN') ?: 'bitly-access-token'
task prepareSource(type: Sync) {
tasks.register('prepareSource', Sync) {
inputs.property 'bitlyAccessToken', bitlyAccessToken
from sourceSets.main.java
into "$buildDir/src"
@@ -237,52 +82,12 @@ task prepareSource(type: Sync) {
])
}
compileJava {
source = prepareSource.outputs
}
tasks.withType(JavaCompile) {
configure(options) {
options.compilerArgs << '-Aorg.glassfish.hk2.metadata.location=META-INF/hk2-locator/Multiverse-Core'
}
}
compileKotlin {
// We're not using Kotlin in the plugin itself, just tests!
enabled = false
}
configurations.findAll { !it.name.startsWith('test') }.each {
it.exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
}
processResources {
def props = [version: "${project.version}"]
inputs.properties props
filteringCharset 'UTF-8'
filesMatching('plugin.yml') {
expand props
}
// This task should never be skipped. The tests depend on this having been run but we want the new version number
// that is created after tests are run and before we run again to publish.
outputs.upToDateWhen { false }
}
javadoc {
source = sourceSets.main.allJava
classpath = configurations.compileClasspath
}
project.configurations.api.canBeResolved = true
shadowJar {
relocate 'co.aikar', 'org.mvplugins.multiverse.external.acf'
relocate 'com.dumptruckman.minecraft.util.Logging', 'org.mvplugins.multiverse.core.utils.CoreLogging'
relocate 'com.dumptruckman.minecraft.util.DebugLog', 'org.mvplugins.multiverse.core.utils.DebugFileLogger'
relocate 'de.themoep.idconverter', 'org.mvplugins.multiverse.external.idconverter'
relocate 'io.github.townyadvanced.commentedconfiguration', 'org.mvplugins.multiverse.external.commentedconfiguration'
relocate 'me.main__.util', 'org.mvplugins.multiverse.external.serializationconfig'
relocate 'org.bstats', 'org.mvplugins.multiverse.external.bstats'
relocate 'com.sun', 'org.mvplugins.multiverse.external.sun'
relocate 'net.minidev', 'org.mvplugins.multiverse.external.minidev'
@@ -296,26 +101,17 @@ shadowJar {
relocate 'org.intellij', 'org.mvplugins.multiverse.external.intellij'
relocate 'org.jetbrains', 'org.mvplugins.multiverse.external.jetbrains'
relocate 'io.papermc.lib', 'org.mvplugins.multiverse.external.paperlib'
configurations = [project.configurations.shadowed]
archiveFileName = "$baseName-$version.$extension"
dependencies {
exclude(dependency {
it.moduleGroup == 'org.jetbrains.kotlin'
})
}
//classifier = ''
}
build.dependsOn shadowJar
jar.enabled = false
test {
useJUnitPlatform()
testLogging {
exceptionFormat = 'full'
publishing {
repositories {
maven {
name = "GitHubPackages"
url = "https://maven.pkg.github.com/Multiverse/Multiverse-Core"
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
}
+1
View File
@@ -0,0 +1 @@
out.txt
+20
View File
@@ -0,0 +1,20 @@
filepath = "../src/main/resources/multiverse-core_en.properties"
enumList = []
with open(filepath) as f:
for line in f:
line = line.strip()
if len(line) == 0:
enumList.append("")
elif line.startswith("#"):
enumList.append(line.replace("#", "//"))
else:
parts = line.split("=")
if (len(parts) < 2):
continue
parts = parts[0].split(".")[1:]
enumList.append("_".join([p.upper() for p in parts]) + ",")
with open("out.txt", "w") as f:
f.write("\n".join(enumList))
Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

+2 -2
View File
@@ -134,7 +134,7 @@
<module name="MutableException"/>
<module name="OneTopLevelClass"/>
<module name="ThrowsCount">
<property name="max" value="0"/>
<property name="max" value="2"/>
</module>
<module name="VisibilityModifier">
<property name="protectedAllowed" value="true"/>
@@ -380,7 +380,7 @@
<property name="throwsIndent" value="8"/>
<property name="arrayInitIndent" value="4"/>
<property name="lineWrappingIndentation" value="8"/>
<property name="forceStrictCondition" value="true"/>
<property name="forceStrictCondition" value="false"/>
</module>
<module name="NoCodeInFile"/>
<module name="OuterTypeFilename"/>
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+3
View File
@@ -5,6 +5,9 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven {
url = uri('https://repo.onarandombox.com/multiverse-releases')
}
}
}
@@ -1,13 +0,0 @@
package co.aikar.commands;
import org.bukkit.command.CommandSender;
/**
* Exists just so we can extend BukkitCommandIssuer since it has a package-private constructor.
*/
public abstract class OpenBukkitCommandIssuer extends BukkitCommandIssuer {
protected OpenBukkitCommandIssuer(BukkitCommandManager manager, CommandSender sender) {
super(manager, sender);
}
}
@@ -0,0 +1,108 @@
package org.mvplugins.multiverse.core;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import com.dumptruckman.minecraft.util.Logging;
import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
import org.apache.commons.lang3.text.WordUtils;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.AdvancedPie;
import org.bstats.charts.SingleLineChart;
import org.bukkit.World;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.utils.REPatterns;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
/**
* Helper class that registers and sends metrics to <a href="https://bstats.org">bstats website</a>.
*/
@Service
final class BstatsMetricsConfigurator {
private static final int PLUGIN_ID = 7765;
private static final String NO_GENERATOR_NAME = "N/A";
private final WorldManager worldManager;
private final Metrics metrics;
@Inject
private BstatsMetricsConfigurator(MultiverseCore plugin, WorldManager worldManager) {
this.worldManager = worldManager;
this.metrics = new Metrics(plugin, PLUGIN_ID);
}
@PostConstruct
private void initMetrics() {
try {
addCustomGeneratorsMetric();
addEnvironmentsMetric();
addWorldCountMetric();
Logging.fine("Metrics enabled.");
} catch (Exception e) {
Logging.warning("There was an issue while enabling metrics:");
e.printStackTrace();
}
}
private void addCustomGeneratorsMetric() {
addAdvancedPieMetric("custom_generators", map -> {
for (MultiverseWorld w : worldManager.getLoadedWorlds()) {
incrementCount(map, getGeneratorName(w));
}
});
}
private String getGeneratorName(MultiverseWorld world) {
String gen = world.getGenerator();
return (gen != null && !gen.equalsIgnoreCase("null"))
? REPatterns.COLON.split(gen)[0]
: NO_GENERATOR_NAME;
}
private void addEnvironmentsMetric() {
addAdvancedPieMetric("environments", map -> {
for (MultiverseWorld w : worldManager.getLoadedWorlds()) {
incrementCount(map, titleCaseEnv(w.getEnvironment()));
}
});
}
private String titleCaseEnv(World.Environment env) {
String envName = env.name().replaceAll("_+", " ");
return WordUtils.capitalizeFully(envName);
}
private void addWorldCountMetric() {
addLineMetric("worlds", () -> worldManager.getWorlds().size());
}
private void addAdvancedPieMetric(String chartId, Consumer<Map<String, Integer>> metricsFunc) {
metrics.addCustomChart(createAdvancedPieChart(chartId, metricsFunc));
}
private void addLineMetric(String chartId, Callable<Integer> metricsFunc) {
metrics.addCustomChart(createLineChart(chartId, metricsFunc));
}
private void incrementCount(Map<String, Integer> map, String key) {
Integer count = map.getOrDefault(key, 0);
map.put(key, count + 1);
}
private AdvancedPie createAdvancedPieChart(String chartId, Consumer<Map<String, Integer>> metricsFunc) {
Map<String, Integer> map = new HashMap<>();
metricsFunc.accept(map);
return new AdvancedPie(chartId, () -> map);
}
private SingleLineChart createLineChart(String chartId, Callable<Integer> metricsFunc) {
return new SingleLineChart(chartId, metricsFunc);
}
}
@@ -7,73 +7,51 @@
package org.mvplugins.multiverse.core;
import java.io.File;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import com.dumptruckman.minecraft.util.Logging;
import io.vavr.control.Try;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import me.main__.util.SerializationConfig.SerializationConfig;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.java.JavaPluginLoader;
import org.glassfish.hk2.api.MultiException;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.ServiceLocator;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.api.Destination;
import org.mvplugins.multiverse.core.api.MVCore;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.PluginLocales;
import org.mvplugins.multiverse.core.config.MVCoreConfig;
import org.mvplugins.multiverse.core.destination.Destination;
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
import org.mvplugins.multiverse.core.commands.CoreCommand;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.economy.MVEconomist;
import org.mvplugins.multiverse.core.inject.InjectableListener;
import org.mvplugins.multiverse.core.inject.PluginInjection;
import org.mvplugins.multiverse.core.placeholders.MultiverseCorePlaceholders;
import org.mvplugins.multiverse.core.utils.TestingMode;
import org.mvplugins.multiverse.core.utils.metrics.MetricsConfigurator;
import org.mvplugins.multiverse.core.listeners.CoreListener;
import org.mvplugins.multiverse.core.inject.PluginServiceLocatorFactory;
import org.mvplugins.multiverse.core.module.MultiverseModule;
import org.mvplugins.multiverse.core.utils.StringFormatter;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.config.NullLocation;
import org.mvplugins.multiverse.core.world.config.SpawnLocation;
import org.mvplugins.multiverse.core.world.location.NullSpawnLocation;
import org.mvplugins.multiverse.core.world.location.SpawnLocation;
import org.mvplugins.multiverse.core.world.location.UnloadedWorldLocation;
/**
* The implementation of the Multiverse-{@link MVCore}.
* The start of the Multiverse-Core plugin
*/
@Service
public class MultiverseCore extends JavaPlugin implements MVCore {
private static final int PROTOCOL = 50;
public class MultiverseCore extends MultiverseModule {
private ServiceLocator serviceLocator;
@Inject
private Provider<MVCoreConfig> configProvider;
private Provider<CoreConfig> configProvider;
@Inject
private Provider<WorldManager> worldManagerProvider;
@Inject
private Provider<AnchorManager> anchorManagerProvider;
@Inject
private Provider<MVCommandManager> commandManagerProvider;
@Inject
private Provider<DestinationsProvider> destinationsProviderProvider;
@Inject
private Provider<MetricsConfigurator> metricsConfiguratorProvider;
private Provider<BstatsMetricsConfigurator> metricsConfiguratorProvider;
@Inject
private Provider<MVEconomist> economistProvider;
@Inject
private Provider<PluginLocales> pluginLocalesProvider;
// Counter for the number of plugins that have registered with us
private int pluginCount;
/**
* This is the constructor for the MultiverseCore.
@@ -95,9 +73,9 @@ public class MultiverseCore extends JavaPlugin implements MVCore {
}
// Register our config classes
SerializationConfig.initLogging(Logging.getLogger());
ConfigurationSerialization.registerClass(NullLocation.class);
ConfigurationSerialization.registerClass(NullSpawnLocation.class);
ConfigurationSerialization.registerClass(SpawnLocation.class);
ConfigurationSerialization.registerClass(UnloadedWorldLocation.class);
}
/**
@@ -105,11 +83,13 @@ public class MultiverseCore extends JavaPlugin implements MVCore {
*/
@Override
public void onEnable() {
initializeDependencyInjection();
super.onEnable();
initializeDependencyInjection(new MultiverseCorePluginBinder(this));
// Load our configs first as we need them for everything else.
var config = configProvider.get();
if (!config.isLoaded()) {
var loadSuccess = config.load().andThenTry(config::save).isSuccess();
if (!loadSuccess || !config.isLoaded()) {
Logging.severe("Your configs were not loaded.");
Logging.severe("Please check your configs and restart the server.");
this.getServer().getPluginManager().disablePlugin(this);
@@ -119,17 +99,15 @@ public class MultiverseCore extends JavaPlugin implements MVCore {
// Initialize the worlds
worldManagerProvider.get().initAllWorlds().andThenTry(() -> {
// Setup economy here so vault is loaded
loadEconomist();
// Init all the other stuff
loadEconomist(); // Setup economy here so vault is loaded
loadAnchors();
registerEvents();
registerDynamicListeners(CoreListener.class);
setUpLocales();
registerCommands();
registerCommands(CoreCommand.class);
registerDestinations();
setupMetrics();
loadPlaceholderApiIntegration();
loadApiService();
saveAllConfigs();
logEnableMessage();
}).onFailure(e -> {
@@ -144,30 +122,14 @@ public class MultiverseCore extends JavaPlugin implements MVCore {
*/
@Override
public void onDisable() {
super.onDisable();
MultiverseCoreApi.shutdown();
saveAllConfigs();
shutdownDependencyInjection();
PluginServiceLocatorFactory.get().shutdown();
Logging.shutdown();
}
private void initializeDependencyInjection() {
serviceLocator = PluginInjection.createServiceLocator(new MultiverseCorePluginBinder(this))
.andThenTry(locator -> {
PluginInjection.enable(this, locator);
})
.getOrElseThrow(exception -> {
Logging.severe("Failed to initialize dependency injection");
getServer().getPluginManager().disablePlugin(this);
return new RuntimeException(exception);
});
}
private void shutdownDependencyInjection() {
if (serviceLocator != null) {
PluginInjection.disable(this, serviceLocator);
serviceLocator = null;
}
}
private boolean shouldShowConfig() {
return !configProvider.get().getSilentStart();
}
@@ -182,60 +144,13 @@ public class MultiverseCore extends JavaPlugin implements MVCore {
private void loadAnchors() {
Try.of(() -> anchorManagerProvider.get())
.onSuccess(AnchorManager::loadAnchors)
.flatMap(AnchorManager::loadAnchors)
.onFailure(e -> {
Logging.severe("Failed to load anchors");
e.printStackTrace();
});
}
/**
* Function to Register all the Events needed.
*/
private void registerEvents() {
var pluginManager = getServer().getPluginManager();
Try.run(() -> serviceLocator.getAllServices(InjectableListener.class).forEach(
listener -> pluginManager.registerEvents(listener, this)))
.onFailure(e -> {
throw new RuntimeException("Failed to register listeners. Terminating...", e);
});
}
/**
* Register Multiverse-Core commands to Command Manager.
*/
private void registerCommands() {
Try.of(() -> commandManagerProvider.get())
.andThenTry(commandManager -> {
serviceLocator.getAllServices(MultiverseCommand.class)
.forEach(commandManager::registerCommand);
})
.onFailure(e -> {
Logging.severe("Failed to register commands");
e.printStackTrace();
});
}
/**
* Register locales.
*/
private void setUpLocales() {
Try.of(() -> commandManagerProvider.get())
.andThen(commandManager -> {
commandManager.usePerIssuerLocale(true, true);
})
.mapTry(commandManager -> pluginLocalesProvider.get())
.andThen(pluginLocales -> {
pluginLocales.addFileResClassLoader(this);
pluginLocales.addMessageBundles("multiverse-core");
})
.onFailure(e -> {
Logging.severe("Failed to register locales");
e.printStackTrace();
});
}
/**
* Register all the destinations.
*/
@@ -268,21 +183,12 @@ public class MultiverseCore extends JavaPlugin implements MVCore {
}
/**
* Logs the enable message.
* Setup placeholder api hook
*/
private void logEnableMessage() {
Logging.config("Version %s (API v%s) Enabled - By %s", this.getDescription().getVersion(), PROTOCOL, getAuthors());
if (configProvider.get().isShowingDonateMessage()) {
Logging.config("Help dumptruckman keep this project alive. Become a patron! https://www.patreon.com/dumptruckman");
Logging.config("One time donations are also appreciated: https://www.paypal.me/dumptruckman");
}
}
private void loadPlaceholderApiIntegration() {
if (configProvider.get().isRegisterPapiHook()
&& getServer().getPluginManager().getPlugin("PlaceholderAPI") != null) {
Try.run(() -> serviceLocator.createAndInitialize(MultiverseCorePlaceholders.class))
Try.run(() -> serviceLocator.getService(PlaceholderExpansionHook.class))
.onFailure(e -> {
Logging.severe("Failed to load PlaceholderAPI integration.");
e.printStackTrace();
@@ -291,53 +197,67 @@ public class MultiverseCore extends JavaPlugin implements MVCore {
}
/**
* {@inheritDoc}
* Setup the api service for {@link MultiverseCoreApi}
*/
@Override
public MVCore getCore() {
return this;
private void loadApiService() {
Try.run(() -> MultiverseCoreApi.init(this))
.onSuccess(ignore -> Logging.info("API service loaded!"))
.onFailure(e -> {
Logging.severe("Failed to load API service!");
e.printStackTrace();
});
}
/**
* {@inheritDoc}
* Save config.yml, worlds.yml, and anchors.yml.
*
* @return {@link Try#isSuccess()} true if all configs were successfully saved
*/
@Override
public int getProtocolVersion() {
return PROTOCOL;
private Try<Void> saveAllConfigs() {
return configProvider.get().save()
.flatMap(ignore -> worldManagerProvider.get().saveWorldsConfig())
.flatMap(ignore ->anchorManagerProvider.get().saveAllAnchors())
.onFailure(e -> {
Logging.severe("Failed to save configs, things may not work as expected.");
});
}
/**
* {@inheritDoc}
* Logs the enable message.
*/
@Override
public String getAuthors() {
List<String> authorsList = this.getDescription().getAuthors();
if (authorsList.isEmpty()) {
return "";
private void logEnableMessage() {
Logging.config("\u001B[32mVersion %s (API v%s) Enabled - By %s\u001B[39m",
this.getDescription().getVersion(), getVersionAsNumber(), StringFormatter.joinAnd(getDescription().getAuthors()));
if (configProvider.get().isShowingDonateMessage()) {
Logging.config("\u001B[32mLoving Multiverse-Core? Please consider supporting the project with a small donation: https://github.com/sponsors/Multiverse\u001B[39m");
}
}
StringBuilder authors = new StringBuilder();
authors.append(authorsList.get(0));
for (int i = 1; i < authorsList.size(); i++) {
if (i == authorsList.size() - 1) {
authors.append(" and ").append(authorsList.get(i));
} else {
authors.append(", ").append(authorsList.get(i));
}
}
return authors.toString();
/**
* Gets the MultiverseCoreApi
*
* @return The MultiverseCoreApi
*
* @deprecated Use {@link MultiverseCoreApi#get()} directly.
*/
@Deprecated(since = "5.1", forRemoval = true)
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
public MultiverseCoreApi getApi() {
return MultiverseCoreApi.get();
}
/**
* {@inheritDoc}
*/
@Override
public int getPluginCount() {
return this.pluginCount;
public double getTargetCoreVersion() {
return getVersionAsNumber();
}
/**
* {@inheritDoc}
*/
@NotNull
@Override
public Logger getLogger() {
@@ -348,78 +268,33 @@ public class MultiverseCore extends JavaPlugin implements MVCore {
* {@inheritDoc}
*/
@Override
public void incrementPluginCount() {
this.pluginCount += 1;
}
/**
* {@inheritDoc}
*/
@Override
public void decrementPluginCount() {
this.pluginCount -= 1;
}
/**
* {@inheritDoc}
*/
@Override
public boolean saveAllConfigs() {
// TODO: Make this all Try<Void>
return configProvider.get().save().isSuccess()
&& worldManagerProvider.get().saveWorldsConfig()
&& anchorManagerProvider.get().saveAnchors();
}
/**
* Gets the best service from this plugin that implements the given contract or has the given implementation.
*
* @param contractOrImpl The contract or concrete implementation to get the best instance of
* @param qualifiers The set of qualifiers that must match this service definition
* @param <T> The type of the contract to get
* @return An instance of the contract or impl if it is a service and is already instantiated, null otherwise
* @throws MultiException if there was an error during service lookup
*/
@Nullable
public <T> T getService(@NotNull Class<T> contractOrImpl, Annotation... qualifiers) throws MultiException {
var handle = serviceLocator.getServiceHandle(contractOrImpl, qualifiers);
if (handle != null && handle.isActive()) {
return handle.getService();
public @NotNull FileConfiguration getConfig() {
CoreConfig coreConfig = this.configProvider.get();
var config = coreConfig.getConfig();
if (config != null && coreConfig.isLoaded()) {
return config;
}
return null;
var loadSuccess = coreConfig.load().isSuccess();
if (!loadSuccess || !coreConfig.isLoaded()) {
throw new RuntimeException("Failed to load configs");
}
return coreConfig.getConfig();
}
/**
* Gets all services from this plugin that implement the given contract or have the given implementation and have
* the provided qualifiers.
*
* @param contractOrImpl The contract or concrete implementation to get the best instance of
* @param qualifiers The set of qualifiers that must match this service definition
* @param <T> The type of the contract to get
* @return A list of services implementing this contract or concrete implementation. May not return null, but may
* return an empty list.
* @throws MultiException if there was an error during service lookup
* {@inheritDoc}
*/
@NotNull
public <T> List<T> getAllServices(
@NotNull Class<T> contractOrImpl,
Annotation... qualifiers) throws MultiException {
var handles = serviceLocator.getAllServiceHandles(contractOrImpl, qualifiers);
return handles.stream()
.filter(ServiceHandle::isActive)
.map(ServiceHandle::getService)
.collect(Collectors.toList());
@Override
public void reloadConfig() {
this.configProvider.get().load();
}
/**
* This is for unit testing ONLY. Do not use this constructor.
*
* @param loader The PluginLoader to use.
* @param description The Description file to use.
* @param dataFolder The folder that other datafiles can be found in.
* @param file The location of the plugin.
* {@inheritDoc}
*/
public MultiverseCore(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) {
super(loader, description, dataFolder, file);
@Override
public void saveConfig() {
this.configProvider.get().save();
}
}
@@ -0,0 +1,205 @@
package org.mvplugins.multiverse.core;
import org.bukkit.Bukkit;
import org.bukkit.plugin.ServicePriority;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
import org.mvplugins.multiverse.core.economy.MVEconomist;
import org.mvplugins.multiverse.core.inject.PluginServiceLocator;
import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter;
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.biomeprovider.BiomeProviderFactory;
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
/**
* Provides access to the MultiverseCore API.
*/
public class MultiverseCoreApi {
private static MultiverseCoreApi instance;
private static final List<Consumer<MultiverseCoreApi>> whenLoadedCallbacks = new ArrayList<>();
static void init(@NotNull MultiverseCore multiverseCore) {
if (instance != null) {
throw new IllegalStateException("MultiverseCoreApi has already been initialized!");
}
instance = new MultiverseCoreApi(multiverseCore.getServiceLocator());
Bukkit.getServicesManager().register(MultiverseCoreApi.class, instance, multiverseCore, ServicePriority.Normal);
whenLoadedCallbacks.forEach(c -> c.accept(instance));
whenLoadedCallbacks.clear();
}
static void shutdown() {
Bukkit.getServicesManager().unregister(instance);
instance = null;
}
/**
* Hook your plugin into the MultiverseCoreApi here to ensure you only start using the API after it has been initialized.
* Use this if you know your plugin may load before Multiverse-Core is fully initialized.
* <br/>
* This handy method removes the need for you to check with plugin manager or listen to plugin enable event.
* <br/>
* Callback will be called immediately if the MultiverseCoreApi has already been initialized.
*
* @param consumer The callback to execute when the MultiverseCoreApi has been initialized.
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public static void whenLoaded(@NotNull Consumer<MultiverseCoreApi> consumer) {
if (instance != null) {
consumer.accept(instance);
} else {
whenLoadedCallbacks.add(consumer);
}
}
/**
* Checks if the MultiverseCoreApi has been initialized.
*
* @return True if the MultiverseCoreApi has been initialized, false otherwise
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public static boolean isLoaded() {
return instance != null;
}
/**
* Gets the MultiverseCoreApi. This will throw an exception if the Multiverse-Core has not been initialized.
* <br/>
* You can check if the MultiverseCoreApi has been initialized with {@link #isLoaded()} before using this method.
* <br/>
* Alternatively, you can use {@link #whenLoaded(Consumer)} to hook into the MultiverseCoreApi if your plugin may
* load before Multiverse-Core is fully initialized.
*
* @return The MultiverseCoreApi
* @throws IllegalStateException if the MultiverseCoreApi has not been initialized
*/
public static @NotNull MultiverseCoreApi get() {
if (instance == null) {
throw new IllegalStateException("MultiverseCoreApi has not been initialized!");
}
return instance;
}
private final PluginServiceLocator serviceLocator;
private MultiverseCoreApi(@NotNull PluginServiceLocator serviceProvider) {
this.serviceLocator = serviceProvider;
}
/**
* Gets the instance of AnchorManager.
*
* @return The AnchorManager instance
*/
public @NotNull AnchorManager getAnchorManager() {
return Objects.requireNonNull(serviceLocator.getActiveService(AnchorManager.class));
}
/**
* Gets the instance of BiomeProviderFactory.
*
* @return The BiomeProviderFactory instance
*/
public @NotNull BiomeProviderFactory getBiomeProviderFactory() {
return Objects.requireNonNull(serviceLocator.getActiveService(BiomeProviderFactory.class));
}
/**
* Gets the instance of BlockSafety.
*
* @return The BlockSafety instance
*/
public @NotNull BlockSafety getBlockSafety() {
return Objects.requireNonNull(serviceLocator.getActiveService(BlockSafety.class));
}
/**
* Gets the instance of CoreConfig.
*
* @return The CoreConfig instance
*/
public @NotNull CoreConfig getCoreConfig() {
return Objects.requireNonNull(serviceLocator.getActiveService(CoreConfig.class));
}
/**
* Gets the instance of DestinationsProvider.
*
* @return The DestinationsProvider instance
*/
public @NotNull DestinationsProvider getDestinationsProvider() {
return Objects.requireNonNull(serviceLocator.getActiveService(DestinationsProvider.class));
}
/**
* Gets the instance of GeneratorProvider.
*
* @return The GeneratorProvider instance
*/
public @NotNull GeneratorProvider getGeneratorProvider() {
return Objects.requireNonNull(serviceLocator.getActiveService(GeneratorProvider.class));
}
/**
* Gets the instance of LocationManipulation.
*
* @return The LocationManipulation instance
*/
public @NotNull LocationManipulation getLocationManipulation() {
return Objects.requireNonNull(serviceLocator.getActiveService(LocationManipulation.class));
}
/**
* Gets the instance of MVEconomist.
*
* @return The MVEconomist instance
*/
public @NotNull MVEconomist getMVEconomist() {
return Objects.requireNonNull(serviceLocator.getActiveService(MVEconomist.class));
}
/**
* Gets the instance of SafetyTeleporter.
*
* @return The SafetyTeleporter instance
*/
public @NotNull AsyncSafetyTeleporter getSafetyTeleporter() {
return Objects.requireNonNull(serviceLocator.getActiveService(AsyncSafetyTeleporter.class));
}
/**
* Gets the instance of WorldManager.
*
* @return The WorldManager instance
*/
public @NotNull WorldManager getWorldManager() {
return Objects.requireNonNull(serviceLocator.getActiveService(WorldManager.class));
}
/**
* Gets the instance of Multiverse-Core's PluginServiceLocator.
* <br/>
* You can use this to hook into the hk2 dependency injection system used by Multiverse-Core.
*
* @return The Multiverse-Core's PluginServiceLocator
*/
public @NotNull PluginServiceLocator getServiceLocator() {
return serviceLocator;
}
}
@@ -3,18 +3,17 @@ package org.mvplugins.multiverse.core;
import org.glassfish.hk2.utilities.binding.ScopedBindingBuilder;
import org.jetbrains.annotations.NotNull;
import org.mvplugins.multiverse.core.api.MVCore;
import org.mvplugins.multiverse.core.inject.binder.JavaPluginBinder;
import org.mvplugins.multiverse.core.module.MultiverseModuleBinder;
class MultiverseCorePluginBinder extends JavaPluginBinder<MultiverseCore> {
final class MultiverseCorePluginBinder extends MultiverseModuleBinder<MultiverseCore> {
protected MultiverseCorePluginBinder(@NotNull MultiverseCore plugin) {
MultiverseCorePluginBinder(@NotNull MultiverseCore plugin) {
super(plugin);
}
@Override
protected ScopedBindingBuilder<MultiverseCore> bindPluginClass(
ScopedBindingBuilder<MultiverseCore> bindingBuilder) {
return super.bindPluginClass(bindingBuilder).to(MVCore.class).to(MultiverseCore.class);
return super.bindPluginClass(bindingBuilder).to(MultiverseCore.class);
}
}
@@ -1,5 +1,6 @@
package org.mvplugins.multiverse.core.placeholders;
package org.mvplugins.multiverse.core;
import com.google.common.collect.Lists;
import io.vavr.control.Option;
import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
@@ -7,24 +8,31 @@ import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.entity.SpawnCategory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.economy.MVEconomist;
import org.mvplugins.multiverse.core.utils.MinecraftTimeFormatter;
import org.mvplugins.multiverse.core.utils.REPatterns;
import org.mvplugins.multiverse.core.utils.StringFormatter;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
public class MultiverseCorePlaceholders extends PlaceholderExpansion {
final class PlaceholderExpansionHook extends PlaceholderExpansion {
private final MultiverseCore plugin;
private final WorldManager worldManager;
private final MVEconomist economist;
@Inject
public MultiverseCorePlaceholders(MultiverseCore plugin, WorldManager worldManager, MVEconomist economist) {
public PlaceholderExpansionHook(MultiverseCore plugin, WorldManager worldManager, MVEconomist economist) {
this.plugin = plugin;
this.worldManager = worldManager;
this.economist = economist;
@@ -43,7 +51,7 @@ public class MultiverseCorePlaceholders extends PlaceholderExpansion {
@Override
public @NotNull String getAuthor() {
return plugin.getAuthors();
return StringFormatter.joinAnd(plugin.getDescription().getAuthors());
}
@Override
@@ -66,39 +74,61 @@ public class MultiverseCorePlaceholders extends PlaceholderExpansion {
@Override
public @Nullable String onRequest(OfflinePlayer offlinePlayer, @NotNull String params) {
// Split string in to an Array with underscores
String[] paramsArray = params.split("_", 2);
List<String> paramsArray = Lists.newArrayList(REPatterns.UNDERSCORE.split(params));
// No placeholder defined
if (paramsArray.length < 1) {
warning("No placeholder defined");
if (paramsArray.isEmpty()) {
warning("No placeholder key defined");
return null;
}
final var placeholder = paramsArray[0];
Option<LoadedMultiverseWorld> targetWorld;
final var placeholder = paramsArray.removeFirst();
// If no world is defined, use the player's world
if (paramsArray.length == 1) {
if (!offlinePlayer.isOnline()) {
return null;
}
targetWorld = worldManager.getLoadedWorld(((Player)offlinePlayer).getWorld());
} else {
targetWorld = worldManager.getLoadedWorld(paramsArray[1]);
}
String worldName = parseWorldName(offlinePlayer, paramsArray);
if (worldName == null) return null;
// Fail if world is null
return targetWorld.map(world -> getWorldPlaceHolderValue(placeholder, world)).getOrNull();
return worldManager.getLoadedWorld(worldName)
.onEmpty(() -> warning("Multiverse World not found: " + worldName))
.map(world -> getWorldPlaceHolderValue(placeholder, paramsArray, world))
.getOrNull();
}
private @Nullable String getWorldPlaceHolderValue(@NotNull String placeholder, @NotNull LoadedMultiverseWorld world) {
private @Nullable String parseWorldName(OfflinePlayer offlinePlayer, List<String> paramsArray) {
// No world defined, get from player
if (paramsArray.isEmpty()) {
if (offlinePlayer instanceof Player player) {
return player.getWorld().getName();
} else {
warning("You must specify a world name for non-player placeholders");
return null;
}
}
// Try get from params
String paramWorldName = paramsArray.getLast();
if (worldManager.isLoadedWorld(paramWorldName)) {
paramsArray.removeLast();
return paramWorldName;
}
// Param not a world, fallback to player
if (offlinePlayer instanceof Player player) {
return player.getWorld().getName();
}
warning("Multiverse World not found: " + paramWorldName);
return null;
}
private @Nullable String getWorldPlaceHolderValue(@NotNull String placeholder,
@NotNull List<String> placeholderParams,
@NotNull LoadedMultiverseWorld world) {
// Switch to find what specific placeholder we want
switch (placeholder.toLowerCase()) {
case "alias" -> {
return world.getAlias();
return world.getAliasOrName();
}
case "animalspawn" -> {
return String.valueOf(world.getSpawningAnimals());
return String.valueOf(world.getEntitySpawnConfig().getSpawnCategoryConfig(SpawnCategory.ANIMAL).isSpawn());
}
case "autoheal" -> {
return String.valueOf(world.getAutoHeal());
@@ -119,7 +149,7 @@ public class MultiverseCorePlaceholders extends PlaceholderExpansion {
return world.getEnvironment().toString().toLowerCase();
}
case "flight" -> {
return String.valueOf(world.getAllowFlight());
return String.valueOf(world.isAllowFlight());
}
case "gamemode" -> {
return world.getGameMode().toString().toLowerCase();
@@ -128,10 +158,10 @@ public class MultiverseCorePlaceholders extends PlaceholderExpansion {
return world.getGenerator();
}
case "hunger" -> {
return String.valueOf(world.getHunger());
return String.valueOf(world.isHunger());
}
case "monstersspawn" -> {
return String.valueOf(world.getSpawningMonsters());
return String.valueOf(world.getEntitySpawnConfig().getSpawnCategoryConfig(SpawnCategory.MONSTER).isSpawn());
}
case "name" -> {
return world.getName();
@@ -148,15 +178,29 @@ public class MultiverseCorePlaceholders extends PlaceholderExpansion {
case "seed" -> {
return String.valueOf(world.getSeed());
}
// TODO: Time is removed, not sure if it's worth adding back
// case "time" -> {
// return world.getTime();
// }
case "time" -> {
String timeFormat = !placeholderParams.isEmpty() ? placeholderParams.getFirst() : "";
long time = world.getBukkitWorld().map(World::getTime).getOrElse(0L);
switch (timeFormat) {
case "" -> {
return String.valueOf(time);
}
case "12h" -> {
return MinecraftTimeFormatter.format12h(time);
}
case "24h" -> {
return MinecraftTimeFormatter.format24h(time);
}
default -> {
return MinecraftTimeFormatter.formatTime(time, timeFormat);
}
}
}
case "type" -> {
return world.getBukkitWorld().map(World::getWorldType).map(Enum::name).getOrElse("null");
}
case "weather" -> {
return String.valueOf(world.getAllowWeather());
return String.valueOf(world.isAllowWeather());
}
default -> {
warning("Unknown placeholder: " + placeholder);
@@ -1,18 +1,22 @@
package org.mvplugins.multiverse.core.utils;
package org.mvplugins.multiverse.core;
/**
* A utility class that enables automated tests to flag Multiverse for testing. This allows Multiverse to not perform
* certain behaviors such as enabled stats uploads.
*/
public class TestingMode {
final class TestingMode {
private static boolean enabled = false;
public static void enable() {
private TestingMode() {
// No instantiation
}
static void enable() {
enabled = true;
}
public static boolean isDisabled() {
static boolean isDisabled() {
return !enabled;
}
}
@@ -8,14 +8,14 @@
package org.mvplugins.multiverse.core.anchor;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.dumptruckman.minecraft.util.Logging;
import io.vavr.control.Option;
import io.vavr.control.Try;
import jakarta.inject.Inject;
import org.bukkit.Location;
import org.bukkit.configuration.ConfigurationSection;
@@ -23,167 +23,183 @@ import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.api.LocationManipulation;
import org.mvplugins.multiverse.core.config.MVCoreConfig;
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
import org.mvplugins.multiverse.core.utils.result.Result;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.entrycheck.WorldEntryCheckerProvider;
import org.mvplugins.multiverse.core.world.location.UnloadedWorldLocation;
/**
* Manages anchors.
* Manages anchors and it's location.
*/
@Service
public class AnchorManager {
private Map<String, Location> anchors;
public final class AnchorManager {
private static final String ANCHORS_FILE = "anchors.yml";
private static final String ANCHORS_CONFIG_SECTION = "anchors";
private final Map<String, MultiverseAnchor> anchors;
private FileConfiguration anchorConfig;
private final Plugin plugin;
private final LocationManipulation locationManipulation;
private final MVCoreConfig config;
private final WorldEntryCheckerProvider entryCheckerProvider;
private final WorldManager worldManager;
@Inject
public AnchorManager(
AnchorManager(
MultiverseCore plugin,
LocationManipulation locationManipulation,
MVCoreConfig config
WorldEntryCheckerProvider entryCheckerProvider,
WorldManager worldManager
) {
this.plugin = plugin;
this.locationManipulation = locationManipulation;
this.config = config;
this.entryCheckerProvider = entryCheckerProvider;
this.worldManager = worldManager;
this.anchors = new HashMap<String, Location>();
this.anchors = new HashMap<>();
}
/**
* Loads all anchors.
*/
public void loadAnchors() {
this.anchors = new HashMap<String, Location>();
this.anchorConfig = YamlConfiguration.loadConfiguration(new File(this.plugin.getDataFolder(), "anchors.yml"));
this.ensureConfigIsPrepared();
ConfigurationSection anchorsSection = this.anchorConfig.getConfigurationSection("anchors");
public Try<Void> loadAnchors() {
anchors.clear();
return Try.run(() -> {
anchorConfig = YamlConfiguration.loadConfiguration(new File(plugin.getDataFolder(), ANCHORS_FILE));
parseAnchorsFromConfig();
});
}
private void parseAnchorsFromConfig() {
var anchorsSection = getAnchorsConfigSection();
Set<String> anchorKeys = anchorsSection.getKeys(false);
for (String key : anchorKeys) {
//world:x,y,z:pitch:yaw
Location anchorLocation = this.locationManipulation.stringToLocation(anchorsSection.getString(key, ""));
Location anchorLocation = locationManipulation.stringToLocation(anchorsSection.getString(key, ""));
if (anchorLocation != null) {
Logging.config("Loading anchor: '%s'...", key);
this.anchors.put(key, anchorLocation);
anchors.put(key, new MultiverseAnchor(key, UnloadedWorldLocation.fromLocation(anchorLocation)));
} else {
Logging.warning("The location for anchor '%s' is INVALID.", key);
}
}
}
private void ensureConfigIsPrepared() {
if (this.anchorConfig.getConfigurationSection("anchors") == null) {
this.anchorConfig.createSection("anchors");
private ConfigurationSection getAnchorsConfigSection() {
var anchorsConfigSection = anchorConfig.getConfigurationSection(ANCHORS_CONFIG_SECTION);
if (anchorsConfigSection == null) {
anchorsConfigSection = anchorConfig.createSection(ANCHORS_CONFIG_SECTION);
}
return anchorsConfigSection;
}
/**
* Saves all anchors.
*
* @return True if all anchors were successfully saved.
*/
public boolean saveAnchors() {
try {
this.anchorConfig.save(new File(this.plugin.getDataFolder(), "anchors.yml"));
return true;
} catch (IOException e) {
Logging.severe("Failed to save anchors.yml. Please check your file permissions.");
return false;
}
public Try<Void> saveAllAnchors() {
return Try.run(() -> anchorConfig.save(new File(plugin.getDataFolder(), ANCHORS_FILE)))
.onFailure(failure ->
Logging.severe("Failed to save anchors.yml. Please check your file permissions."));
}
/**
* Gets the {@link Location} associated with an anchor.
* @param anchor The name of the anchor.
*
* @param anchorName The name of the anchor.
* @return The {@link Location}.
*/
public Location getAnchorLocation(String anchor) {
if (this.anchors.containsKey(anchor)) {
return this.anchors.get(anchor);
}
return null;
public Option<MultiverseAnchor> getAnchor(String anchorName) {
return Option.of(anchors.get(anchorName));
}
/**
* Saves an anchor.
* @param anchor The name of the anchor.
* @param location The location of the anchor as string.
* Sets a new location for an anchor. Creates a new anchor if it doesn't exist.
*
* @param anchorName The name of the anchor.
* @param location The location of the anchor as string (world:x,y,z:pitch:yaw).
* @return True if the anchor was successfully saved.
*/
public boolean saveAnchorLocation(String anchor, String location) {
Location parsed = this.locationManipulation.stringToLocation(location);
return parsed != null && this.saveAnchorLocation(anchor, parsed);
public Try<Void> setAnchor(@NotNull String anchorName, @NotNull String location) {
Location parsed = locationManipulation.stringToLocation(location);
if (parsed == null) {
return Try.failure(new IllegalArgumentException("The location for anchor '" + anchorName + "' is INVALID."));
}
return setAnchor(anchorName, parsed);
}
/**
* Saves an anchor.
* @param anchor The name of the anchor.
* @param l The {@link Location} of the anchor.
* Sets a new location for an anchor. Creates a new anchor if it doesn't exist.
*
* @param anchorName The name of the anchor.
* @param location The {@link Location} of the anchor.
* @return True if the anchor was successfully saved.
*/
public boolean saveAnchorLocation(String anchor, Location l) {
if (l == null) {
return false;
}
this.anchorConfig.set("anchors." + anchor, this.locationManipulation.locationToString(l));
this.anchors.put(anchor, l);
return this.saveAnchors();
public Try<Void> setAnchor(@NotNull String anchorName, @NotNull Location location) {
Option.of(anchors.get(anchorName))
.peek(anchor -> anchor.setLocation(UnloadedWorldLocation.fromLocation(location)))
.onEmpty(() -> anchors.put(anchorName, new MultiverseAnchor(anchorName, UnloadedWorldLocation.fromLocation(location))));
getAnchorsConfigSection().set(anchorName, locationManipulation.locationToString(location));
return saveAllAnchors();
}
/**
* Gets all anchors.
* @return An unmodifiable {@link Set} containing all anchors.
*
* @return An unmodifiable {@link List} containing all anchors.
*/
public Set<String> getAllAnchors() {
return Collections.unmodifiableSet(this.anchors.keySet());
public List<MultiverseAnchor> getAllAnchors() {
return anchors.values().stream().toList();
}
/**
* Gets all anchors that the specified {@link Player} can access.
* @param p The {@link Player}.
* @return An unmodifiable {@link Set} containing all anchors the specified {@link Player} can access.
*
* @param player The {@link Player}.
* @return An unmodifiable {@link List} containing all anchors the specified {@link Player} can access.
*/
public Set<String> getAnchors(Player p) {
if (p == null) {
return this.anchors.keySet();
public List<MultiverseAnchor> getAnchors(@Nullable Player player) {
if (player == null) {
return getAllAnchors();
} else {
return getAnchorsForPlayer(player);
}
Set<String> myAnchors = new HashSet<String>();
for (String anchor : this.anchors.keySet()) {
Location ancLoc = this.anchors.get(anchor);
if (ancLoc == null) {
continue;
}
String worldPerm = "multiverse.access." + ancLoc.getWorld().getName();
// Add to the list if we're not enforcing access
// OR
// We are enforcing access and the user has the permission.
if (!config.getEnforceAccess() ||
(config.getEnforceAccess() && p.hasPermission(worldPerm))) {
myAnchors.add(anchor);
} else {
Logging.finer(String.format("Not adding anchor %s to the list, user %s doesn't have the %s " +
"permission and 'enforceaccess' is enabled!",
anchor, p.getName(), worldPerm));
}
}
return Collections.unmodifiableSet(myAnchors);
}
private List<MultiverseAnchor> getAnchorsForPlayer(@NotNull Player player) {
return anchors.values().stream()
.filter(anchor -> shouldIncludeAnchorForPlayer(anchor, player))
.toList();
}
private boolean shouldIncludeAnchorForPlayer(MultiverseAnchor anchor, Player player) {
return worldManager.getWorld(anchor.getLocationWorld())
.map(mvWorld -> entryCheckerProvider.forSender(player).canAccessWorld(mvWorld))
.peek(result -> Logging.finer("Result for %s can access anchor %s: %s", player.getName(), anchor.getName(), result))
.map(Result::isSuccess)
.getOrElse(true);
}
/**
* Deletes the specified anchor.
* @param s The name of the anchor.
*
* @param anchor The anchor to delete.
* @return True if the anchor was successfully deleted.
*/
public boolean deleteAnchor(String s) {
if (this.anchors.containsKey(s)) {
this.anchors.remove(s);
this.anchorConfig.set("anchors." + s, null);
return this.saveAnchors();
public Try<Void> deleteAnchor(@NotNull MultiverseAnchor anchor) {
if (anchors.containsKey(anchor.getName())) {
anchors.remove(anchor.getName());
getAnchorsConfigSection().set(anchor.getName(), null);
return saveAllAnchors();
}
return false;
return Try.failure(new IllegalArgumentException("Anchor does not exist"));
}
}
@@ -0,0 +1,54 @@
package org.mvplugins.multiverse.core.anchor;
import org.bukkit.Location;
import org.bukkit.World;
import org.jetbrains.annotations.Nullable;
import org.mvplugins.multiverse.core.world.location.UnloadedWorldLocation;
/**
* Represents a single anchor location.
*/
public final class MultiverseAnchor {
private final String name;
private UnloadedWorldLocation location;
MultiverseAnchor(String name, UnloadedWorldLocation location) {
this.name = name;
this.location = location;
}
/**
* Gets the name of the anchor.
*
* @return The name.
*/
public String getName() {
return name;
}
/**
* Gets a copy of the anchor's location.
*
* @return The location.
*/
public Location getLocation() {
return location.toBukkitLocation();
}
void setLocation(Location location) {
this.location = new UnloadedWorldLocation(location);
}
/**
* Gets the world of the anchor's location.
*
* @return The world.
*/
@Nullable World getLocationWorld() {
if (location == null) {
return null;
}
return location.getWorld();
}
}
@@ -1,114 +0,0 @@
package org.mvplugins.multiverse.core.api;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Minecart;
import org.bukkit.entity.Vehicle;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Contract;
/**
* Used to get block/location-related information.
*/
@Contract
public interface BlockSafety {
/**
* This function checks whether the block at the given coordinates are above air or not.
* @param l The {@link Location} of the block.
* @return True if the block at that {@link Location} is above air.
*/
boolean isBlockAboveAir(Location l);
/**
* Checks if a player can spawn safely at the given coordinates.
* @param world The {@link World}.
* @param x The x-coordinate.
* @param y The y-coordinate.
* @param z The z-coordinate.
* @return True if a player can spawn safely at the given coordinates.
*/
boolean playerCanSpawnHereSafely(World world, double x, double y, double z);
/**
* This function checks whether the block at the coordinates given is safe or not by checking for Lava/Fire/Air
* etc. This also ensures there is enough space for a player to spawn!
*
* @param l The {@link Location}
* @return Whether the player can spawn safely at the given {@link Location}
*/
boolean playerCanSpawnHereSafely(Location l);
/**
* Finds a portal-block next to the specified {@link Location}.
*
* @param location The {@link Location}
* @return The next portal-block's {@link Location}.
*/
Location findPortalBlockNextTo(Location location);
/**
* Gets the next safe location around the given location.
*
* @param location A {@link Location}.
* @return A safe {@link Location}.
*/
@Nullable Location getSafeLocation(Location location);
/**
* Gets the next safe location around the given location.
*
* @param location A {@link Location}.
* @param tolerance The tolerance of how far up and down to search.
* @param radius The radius around given location to search.
* @return A safe {@link Location}.
*/
@Nullable Location getSafeLocation(Location location, int tolerance, int radius);
/**
* Gets a safe bed spawn location OR null if the bed is invalid.
*
* @param l The location of the bead head (block with the pillow on it).
* @return Safe location around the bed or null if no location was found.
*/
Location getSafeBedSpawn(Location l);
/**
* Gets the location of the top block at the specified {@link Location}.
*
* @param l Any {@link Location}.
* @return The {@link Location} of the top-block.
*/
Location getTopBlock(Location l);
/**
* Gets the location of the top block at the specified {@link Location}.
*
* @param l Any {@link Location}.
* @return The {@link Location} of the top-block.
*/
Location getBottomBlock(Location l);
/**
* Checks if an entity would be on track at the specified {@link Location}.
*
* @param l The {@link Location}.
* @return True if an entity would be on tracks at the specified {@link Location}.
*/
boolean isEntitiyOnTrack(Location l);
/**
* Checks if the specified {@link Minecart} can spawn safely.
*
* @param cart The {@link Minecart}.
* @return True if the minecart can spawn safely.
*/
boolean canSpawnCartSafely(Minecart cart);
/**
* Checks if the specified {@link Vehicle} can spawn safely.
*
* @param vehicle The {@link Vehicle}.
* @return True if the vehicle can spawn safely.
*/
boolean canSpawnVehicleSafely(Vehicle vehicle);
}
@@ -1,48 +0,0 @@
package org.mvplugins.multiverse.core.api;
import java.util.Collection;
import co.aikar.commands.BukkitCommandIssuer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Contract;
@Contract
public interface Destination<T extends DestinationInstance> {
/**
* Returns the identifier or prefix that is required for this destination.
*
* <p>Portals have a prefix of "p" for example and OpenWarp (third party plugin) uses "ow". This is derived from a
* hash and cannot have duplicate values. Read that as your plugin cannot use 'p' because it's already used.
* Please check the wiki when adding a custom destination!</p>
*
* @return The identifier or prefix that is required for this destination.
*/
@NotNull String getIdentifier();
/**
* Returns the destination instance for the given destination parameters.
*
* @param destinationParams The destination parameters. ex: p:MyPortal:nw
* @return The destination instance, or null if the destination parameters are invalid.
*/
@Nullable T getDestinationInstance(@Nullable String destinationParams);
/**
* Returns a list of possible destinations for the given destination parameters.
*
* @param issuer The command issuer.
* @param destinationParams The destination parameters. ex: p:MyPortal:nw
* @return A list of possible destinations.
*/
@NotNull Collection<String> suggestDestinations(@NotNull BukkitCommandIssuer issuer, @Nullable String destinationParams);
/**
* Should the Multiverse SafeTeleporter be used?
*
* <p>If not, MV will blindly take people to the location specified.</p>
*
* @return True if the SafeTeleporter will be used, false if not.
*/
boolean checkTeleportSafety();
}
@@ -1,45 +0,0 @@
package org.mvplugins.multiverse.core.api;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface DestinationInstance {
/**
* Gets the exact location to teleport an entity to.
*
* @param teleportee The entity to teleport.
* @return The location to teleport to.
*/
@Nullable Location getLocation(@NotNull Entity teleportee);
/**
* Gets the velocity to apply to an entity after teleporting.
*
* @param teleportee The entity to teleport.
* @return A vector representing the speed/direction the player should travel when arriving at the destination.
*/
@Nullable Vector getVelocity(@NotNull Entity teleportee);
/**
* Gets the permission suffix to check for when teleporting to this destination.
* This is used for finer per world/player permissions, such as "multiverse.teleport.self.worldname".
*
* <p>For example, if the destination is "w:world", the permission suffix is "world".</p>
*
* @return The permission suffix.
*/
@Nullable String getFinerPermissionSuffix();
/**
* Serialises the destination instance to a savable string.
*
* <p>This is used when plugins save destinations to configuration,
* and when the destination is displayed to the user.</p>
*
* @return The serialised destination instance.
*/
@NotNull String serialise();
}
@@ -1,101 +0,0 @@
package org.mvplugins.multiverse.core.api;
import org.bukkit.Location;
import org.bukkit.entity.Vehicle;
import org.bukkit.util.Vector;
import org.jvnet.hk2.annotations.Contract;
/**
* Used to manipulate locations.
*/
@Contract
public interface LocationManipulation {
/**
* Convert a Location into a Colon separated string to allow us to store it in text.
* <p>
* WORLD:X,Y,Z:yaw:pitch
* <p>
* The corresponding String2Loc function is {@link #stringToLocation}
*
* @param location The Location to save.
* @return The location as a string in this format: WORLD:x,y,z:yaw:pitch
*/
String locationToString(Location location);
/**
* This method simply does some rounding, rather than forcing a call to the server to get the blockdata.
*
* @param l The location to round to the block location
* @return A rounded location.
*/
Location getBlockLocation(Location l);
/**
* Returns a new location from a given string. The format is as follows:
* <p>
* WORLD:X,Y,Z:yaw:pitch
* <p>
* The corresponding Location2String function is {@link #stringToLocation}
*
* @param locationString The location represented as a string (WORLD:X,Y,Z:yaw:pitch)
* @return A new location defined by the string or null if the string was invalid.
*/
Location stringToLocation(String locationString);
/**
* Returns a colored string with the coords.
*
* @param l The {@link Location}
* @return The {@link String}
*/
String strCoords(Location l);
/**
* Converts a location to a printable readable formatted string including pitch/yaw.
*
* @param l The {@link Location}
* @return The {@link String}
*/
String strCoordsRaw(Location l);
/**
* Return the NESW Direction a Location is facing.
*
* @param location The {@link Location}
* @return The NESW Direction
*/
String getDirection(Location location);
/**
* Returns the float yaw position for the given cardinal direction.
*
* @param orientation The cardinal direction
* @return The yaw
*/
float getYaw(String orientation);
/**
* Returns a speed float from a given vector.
*
* @param v The {@link Vector}
* @return The speed
*/
float getSpeed(Vector v);
/**
* Returns a translated vector from the given direction.
*
* @param v The old {@link Vector}
* @param direction The new direction
* @return The translated {@link Vector}
*/
Vector getTranslatedVector(Vector v, String direction);
/**
* Returns the next Location that a {@link Vehicle} is traveling at.
*
* @param v The {@link Vehicle}
* @return The {@link Location}
*/
Location getNextBlock(Vehicle v);
}
@@ -1,237 +0,0 @@
package org.mvplugins.multiverse.core.api;
import io.vavr.control.Try;
import org.jvnet.hk2.annotations.Contract;
import org.mvplugins.multiverse.core.configuration.handle.StringPropertyHandle;
import org.mvplugins.multiverse.core.placeholders.MultiverseCorePlaceholders;
@Contract
public interface MVConfig {
/**
* Loads the config from disk.
* @return True if the config was loaded successfully.
*/
Try<Void> load();
/**
* Whether the config has been loaded.
* @return True if the config has been loaded.
*/
boolean isLoaded();
/**
* Saves the config to disk.
*/
Try<Void> save();
/**
* Gets the handler for managing config with string names and values.
*
* @return The config handle for string properties.
*/
StringPropertyHandle getStringPropertyHandle();
/**
* Sets world access permissions should be enforced.
* @param enforceAccess The new value.
*/
void setEnforceAccess(boolean enforceAccess);
/**
* Gets enforceAccess.
* @return enforceAccess.
*/
boolean getEnforceAccess();
/**
* Sets whether the game mode should be enforced.
* @param enforceGameMode The new value.
*/
void setEnforceGameMode(boolean enforceGameMode);
/**
* Gets enforceGameMode value.
* @return True if game mode should be enforced.
*/
boolean getEnforceGameMode();
/**
* Sets whether or not the automatic purge of entities is enabled.
*
* @param autopurge True if automatic purge should be enabled.
*/
void setAutoPurgeEntities(boolean autopurge);
/**
* Gets whether or not the automatic purge of entities is enabled.
*
* @return True if automatic purge is enabled.
*/
boolean isAutoPurgeEntities();
/**
* Sets teleportIntercept.
* @param teleportIntercept The new value.
*/
void setTeleportIntercept(boolean teleportIntercept);
/**
* Gets teleportIntercept.
* @return teleportIntercept.
*/
boolean getTeleportIntercept();
/**
* Sets firstSpawnOverride.
* @param firstSpawnOverride The new value.
*/
void setFirstSpawnOverride(boolean firstSpawnOverride);
/**
* Gets firstSpawnOverride.
* @return firstSpawnOverride.
*/
boolean getFirstSpawnOverride();
/**
* Sets firstSpawnWorld.
* @param firstSpawnWorld The new value.
*/
void setFirstSpawnLocation(String firstSpawnWorld);
/**
* Gets firstSpawnWorld.
* @return firstSpawnWorld.
*/
String getFirstSpawnLocation();
/**
* Sets whether join destination should be enabled.
* @param enableJoinDestination The new value
*/
void setEnableJoinDestination(boolean enableJoinDestination);
/**
* Gets enableJoinDestination.
* @return enableJoinDestination
*/
boolean getEnableJoinDestination();
/**
* Sets alwaysSpawnDestination.
* @param alwaysSpawnDestination The new value
*/
void setJoinDestination(String alwaysSpawnDestination);
/**
* Gets alwaysSpawnDestination.
* @return alwaysSpawnLocation
*/
String getJoinDestination();
/**
* Sets whether or not to let Bukkit determine portal search radius on its own or if Multiverse should give input.
*
* @param useDefaultPortalSearch True to let Bukkit determine portal search radius on its own.
*/
void setUseCustomPortalSearch(boolean useDefaultPortalSearch);
/**
* Gets whether or not Bukkit will be determining portal search radius on its own or if Multiverse should help.
*
* @return True means Bukkit will use its own default values.
*/
boolean isUsingCustomPortalSearch();
/**
* Sets the radius at which vanilla style portals will be searched for to connect to worlds together.
*
* @param searchRadius The portal search radius.
*/
void setCustomPortalSearchRadius(int searchRadius);
/**
* Gets the radius at which vanilla style portals will be searched for to connect to worlds together.
*
* @return The portal search radius.
*/
int getCustomPortalSearchRadius();
/**
* Sets prefixChat.
* @param prefixChat The new value.
*/
void setEnablePrefixChat(boolean prefixChat);
/**
* Gets prefixChat.
* @return prefixChat.
*/
boolean isEnablePrefixChat();
/**
* Sets prefixChatFormat.
* @param prefixChatFormat The new value.
*/
void setPrefixChatFormat(String prefixChatFormat);
/**
* Gets prefixChatFormat.
* @return prefixChatFormat.
*/
String getPrefixChatFormat();
/**
* Sets whether to register the {@link MultiverseCorePlaceholders} class with PlaceholderAPI plugin.
* @param registerPapiHook The new value.
*/
void setRegisterPapiHook(boolean registerPapiHook);
/**
* Gets whether to register the {@link MultiverseCorePlaceholders} class with PlaceholderAPI plugin.
* @return registerPapiHook.
*/
boolean isRegisterPapiHook();
/**
* Sets globalDebug.
* @param globalDebug The new value.
*/
void setGlobalDebug(int globalDebug);
/**
* Gets globalDebug.
* @return globalDebug.
*/
int getGlobalDebug();
/**
* Sets whether to suppress startup messages.
*
* @param silentStart true to suppress messages.
*/
void setSilentStart(boolean silentStart);
/**
* Whether we are suppressing startup messages.
*
* @return true if we are suppressing startup messages.
*/
boolean getSilentStart();
/**
* Sets whether or not the donation/patreon messages are shown.
*
* @param idonotwanttodonate True if donation/patreon messages should be shown.
*/
void setShowDonateMessage(boolean idonotwanttodonate);
/**
* Gets whether or not the donation/patreon messages are shown.
*
* @return True if donation/patreon messages should be shown.
*/
boolean isShowingDonateMessage();
}
@@ -1,40 +0,0 @@
/******************************************************************************
* Multiverse 2 Copyright (c) the Multiverse Team 2011. *
* Multiverse 2 is licensed under the BSD License. *
* For more information please check the README.md file included *
* with this project. *
******************************************************************************/
package org.mvplugins.multiverse.core.api;
/**
* Multiverse 2 Core API
* <p>
* This API contains a bunch of useful things you can get out of Multiverse in general!
*/
public interface MVCore extends MVPlugin {
/**
* Saves all configs.
*
* @return Whether the config was successfully saved
*/
boolean saveAllConfigs();
/**
* Decrements the number of plugins that have specifically hooked into core.
*/
void decrementPluginCount();
/**
* Increments the number of plugins that have specifically hooked into core.
*/
void incrementPluginCount();
/**
* Returns the number of plugins that have specifically hooked into core.
*
* @return The number if plugins that have hooked into core.
*/
int getPluginCount();
}
@@ -1,36 +0,0 @@
/******************************************************************************
* Multiverse 2 Copyright (c) the Multiverse Team 2011. *
* Multiverse 2 is licensed under the BSD License. *
* For more information please check the README.md file included *
* with this project. *
******************************************************************************/
package org.mvplugins.multiverse.core.api;
/**
* This interface is implemented by every official Multiverse-plugin.
*/
public interface MVPlugin {
/**
* Gets the reference to MultiverseCore.
*
* @return A valid {@link org.mvplugins.multiverse.core.MultiverseCore}.
*/
MVCore getCore();
/**
* Allows Multiverse or a plugin to query another Multiverse plugin to see what version its protocol is. This
* number
* should change when something will break the code.
*
* @return The Integer protocol version.
*/
int getProtocolVersion();
/**
* Parse the Authors Array into a readable String with ',' and 'and'.
*
* @return The readable authors-{@link String}
*/
String getAuthors();
}
@@ -1,5 +0,0 @@
/**
* The Multiverse-API, containing lots of interfaces that can be quite useful for other
* plugins when interacting with Multiverse.
*/
package org.mvplugins.multiverse.core.api;
@@ -0,0 +1,7 @@
package org.mvplugins.multiverse.core.command;
/**
* Extends a command class to add legacy alias annotations.
*/
public interface LegacyAliasCommand {
}
@@ -0,0 +1,361 @@
package org.mvplugins.multiverse.core.command;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import co.aikar.commands.BukkitCommandCompletionContext;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.PaperCommandCompletions;
import co.aikar.commands.RegisteredCommand;
import co.aikar.commands.RootCommand;
import com.dumptruckman.minecraft.util.Logging;
import io.vavr.control.Option;
import io.vavr.control.Try;
import jakarta.inject.Inject;
import org.apache.commons.lang3.Validate;
import org.bukkit.Bukkit;
import org.bukkit.Difficulty;
import org.bukkit.GameMode;
import org.bukkit.GameRule;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.entity.SpawnCategory;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.anchor.MultiverseAnchor;
import org.mvplugins.multiverse.core.command.context.issueraware.IssuerAwareValue;
import org.mvplugins.multiverse.core.command.context.issueraware.MultiverseWorldValue;
import org.mvplugins.multiverse.core.command.context.issueraware.PlayerArrayValue;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.config.node.functions.DefaultSuggesterProvider;
import org.mvplugins.multiverse.core.config.handle.PropertyModifyAction;
import org.mvplugins.multiverse.core.destination.DestinationInstance;
import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket;
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker;
import org.mvplugins.multiverse.core.utils.REPatterns;
import org.mvplugins.multiverse.core.utils.StringFormatter;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.generators.GeneratorPlugin;
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
import static org.mvplugins.multiverse.core.utils.StringFormatter.addonToCommaSeperated;
@Service
public class MVCommandCompletions extends PaperCommandCompletions {
private final MVCommandManager commandManager;
private final WorldManager worldManager;
private final DestinationsProvider destinationsProvider;
private final CoreConfig config;
private final CorePermissionsChecker corePermissionsChecker;
private final AnchorManager anchorManager;
private final GeneratorProvider generatorProvider;
@Inject
MVCommandCompletions(
@NotNull MVCommandManager mvCommandManager,
@NotNull WorldManager worldManager,
@NotNull DestinationsProvider destinationsProvider,
@NotNull CoreConfig config,
@NotNull CorePermissionsChecker corePermissionsChecker,
@NotNull AnchorManager anchorManager,
@NotNull GeneratorProvider generatorProvider
) {
super(mvCommandManager);
this.commandManager = mvCommandManager;
this.worldManager = worldManager;
this.destinationsProvider = destinationsProvider;
this.config = config;
this.corePermissionsChecker = corePermissionsChecker;
this.anchorManager = anchorManager;
this.generatorProvider = generatorProvider;
registerAsyncCompletion("anchornames", this::suggestAnchorNames);
registerAsyncCompletion("commands", this::suggestCommands);
registerAsyncCompletion("destinations", this::suggestDestinations);
registerStaticCompletion("difficulties", suggestEnums(Difficulty.class));
registerStaticCompletion("environments", suggestEnums(World.Environment.class));
registerAsyncCompletion("flags", this::suggestFlags);
registerStaticCompletion("gamemodes", suggestEnums(GameMode.class));
registerStaticCompletion("gamerules", this::suggestGamerules);
registerAsyncCompletion("gamerulesvalues", this::suggestGamerulesValues);
registerAsyncCompletion("generatorplugins", this::suggestGeneratorPlugins);
registerStaticCompletion("materials", suggestEnums(Material.class));
registerStaticCompletion("mvconfigs", config.getStringPropertyHandle().getAllPropertyNames());
registerAsyncCompletion("mvconfigvalues", this::suggestMVConfigValues);
registerAsyncCompletion("mvworlds", this::suggestMVWorlds);
registerAsyncCompletion("mvworldpropsname", this::suggestMVWorldPropsName);
registerAsyncCompletion("mvworldpropsvalue", this::suggestMVWorldPropsValue);
registerAsyncCompletion("playersarray", this::suggestPlayersArray);
registerStaticCompletion("propsmodifyaction", suggestEnums(PropertyModifyAction.class));
registerStaticCompletion("spawncategories", suggestEnums(SpawnCategory.class));
registerAsyncCompletion("spawncategorypropsname", this::suggestSpawnCategoryPropsName);
registerAsyncCompletion("spawncategorypropsvalue", this::suggestSpawnCategoryPropsValue);
setDefaultCompletion("destinations", DestinationInstance.class);
setDefaultCompletion("difficulties", Difficulty.class);
setDefaultCompletion("environments", World.Environment.class);
setDefaultCompletion("flags", String[].class);
setDefaultCompletion("gamemodes", GameMode.class);
setDefaultCompletion("gamerules", GameRule.class);
setDefaultCompletion("mvworlds", LoadedMultiverseWorld.class);
}
@Override
public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler<BukkitCommandCompletionContext> handler) {
return super.registerCompletion(id, context ->
completeWithPreconditions(context, handler));
}
@Override
public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler<BukkitCommandCompletionContext> handler) {
return super.registerAsyncCompletion(id, context ->
completeWithPreconditions(context, handler));
}
private Collection<String> completeWithPreconditions(
BukkitCommandCompletionContext context,
CommandCompletionHandler<BukkitCommandCompletionContext> handler) {
if (context.hasConfig("playerOnly") && !context.getIssuer().isPlayer()) {
return Collections.emptyList();
}
if (context.hasConfig("byIssuerForArg")) {
Boolean byIssuerForArg = Try.of(() -> context.getContextValueByName(IssuerAwareValue.class, context.getConfig("byIssuerForArg")))
.map(IssuerAwareValue::isByIssuer)
.getOrElse(false);
if (!byIssuerForArg) {
return Collections.emptyList();
}
}
if (context.hasConfig("notByIssuerForArg")) {
Boolean byIssuerForArg = Try.of(() -> context.getContextValueByName(IssuerAwareValue.class, context.getConfig("notByIssuerForArg")))
.map(IssuerAwareValue::isByIssuer)
.getOrElse(false);
if (byIssuerForArg) {
return Collections.emptyList();
}
}
if (context.hasConfig("resolveUntil")) {
if (!Try.run(() -> context.getContextValueByName(Object.class, context.getConfig("resolveUntil"))).isSuccess()) {
return Collections.emptyList();
}
}
if (context.hasConfig("checkPermissions")) {
for (String permission : REPatterns.SEMICOLON.split(context.getConfig("checkPermissions"))) {
if (!commandManager.getCommandPermissions().hasPermission(context.getIssuer(), permission)) {
return Collections.emptyList();
}
}
}
if (context.hasConfig("multiple")) {
return addonToCommaSeperated(context.getInput(), handler.getCompletions(context));
}
return handler.getCompletions(context);
}
/**
* Shortcut to suggest enums values
*
* @param enumClass The enum class with values
* @return A collection of possible string values
* @param <T> The enum type
*/
public <T extends Enum<T>> Collection<String> suggestEnums(Class<T> enumClass) {
return EnumSet.allOf(enumClass).stream()
.map(Enum::name)
.map(String::toLowerCase)
.toList();
}
private Collection<String> suggestAnchorNames(BukkitCommandCompletionContext context) {
return anchorManager.getAnchors(context.getPlayer()).stream()
.map(MultiverseAnchor::getName)
.toList();
}
private Collection<String> suggestCommands(BukkitCommandCompletionContext context) {
String rootCmdName = context.getConfig();
if (rootCmdName == null) {
return Collections.emptyList();
}
RootCommand rootCommand = this.commandManager.getRegisteredRootCommands().stream()
.unordered()
.filter(c -> c.getCommandName().equals(rootCmdName))
.findFirst()
.orElse(null);
if (rootCommand == null) {
return Collections.emptyList();
}
return rootCommand.getSubCommands().entries().stream()
.filter(entry -> checkPerms(context.getIssuer(), entry.getValue()))
.map(Map.Entry::getKey)
.filter(cmdName -> !cmdName.startsWith("__"))
.collect(Collectors.toList());
}
private boolean checkPerms(CommandIssuer issuer, RegisteredCommand<?> command) {
return this.commandManager.hasPermission(issuer, command.getRequiredPermissions());
}
private Collection<String> suggestDestinations(BukkitCommandCompletionContext context) {
return Try.of(() -> context.getContextValue(PlayerArrayValue.class))
.map(PlayerArrayValue::value)
.recover(IllegalStateException.class, e -> context.getContextValue(Player[].class))
.recover(IllegalStateException.class, e -> new Player[]{context.getContextValue(Player.class)})
.map(players -> {
if (players.length == 0) {
// Most likely console did not specify a player
return Collections.<String>emptyList();
}
if (context.hasConfig("othersOnly") && (players.length == 1 && players[0].equals(context.getIssuer().getIssuer()))) {
return Collections.<String>emptyList();
}
return suggestDestinationsWithPerms(context.getIssuer().getIssuer(), players, context.getInput());
})
.getOrElse(Collections.emptyList());
}
private Collection<String> suggestDestinationsWithPerms(CommandSender teleporter, Player[] players, String deststring) {
return destinationsProvider.suggestDestinations(teleporter, deststring).stream()
.filter(packet -> corePermissionsChecker
.checkDestinationPacketPermission(teleporter, Arrays.asList(players), packet))
.map(DestinationSuggestionPacket::parsableString)
.toList();
}
private Collection<String> suggestFlags(@NotNull BukkitCommandCompletionContext context) {
String groupName = context.getConfig("groupName", "");
return Try.of(() -> context.getContextValue(String[].class))
.map(flags -> commandManager.getFlagsManager().suggest(groupName, flags))
.getOrElse(Collections.emptyList());
}
private Collection<String> suggestGamerules() {
return Arrays.stream(GameRule.values()).map(GameRule::getName).collect(Collectors.toList());
}
private Collection<String> suggestGamerulesValues(BukkitCommandCompletionContext context) {
return Try.of(() -> context.getContextValue(GameRule.class))
// Just use our suggester from configuration lib since gamerules are only boolean or int
.mapTry(gamerule -> Option.of(DefaultSuggesterProvider.getDefaultSuggester(gamerule.getType()))
.map(s -> s.suggest(context.getInput())).getOrElse(Collections.emptyList()))
.getOrElse(Collections.emptyList());
}
private Collection<String> suggestGeneratorPlugins(BukkitCommandCompletionContext context) {
return generatorProvider.getRegisteredGeneratorPlugins().stream()
.map(GeneratorPlugin::getPluginName)
.toList();
}
private Collection<String> suggestMVConfigValues(BukkitCommandCompletionContext context) {
return Try.of(() -> context.getContextValue(String.class))
.map(propertyName -> config.getStringPropertyHandle()
.getSuggestedPropertyValue(propertyName, context.getInput(), PropertyModifyAction.SET, context.getSender()))
.getOrElse(Collections.emptyList());
}
private Collection<String> suggestMVWorlds(BukkitCommandCompletionContext context) {
String scope = context.getConfig("scope", "loaded");
switch (scope) {
case "both" -> {
return worldManager.getWorlds().stream()
.map(MultiverseWorld::getTabCompleteName)
.map(StringFormatter::quoteMultiWordString)
.toList();
}
case "loaded" -> {
return worldManager.getLoadedWorlds().stream()
.map(MultiverseWorld::getTabCompleteName)
.map(StringFormatter::quoteMultiWordString)
.toList();
}
case "unloaded" -> {
return worldManager.getUnloadedWorlds().stream()
.map(MultiverseWorld::getTabCompleteName)
.map(StringFormatter::quoteMultiWordString)
.toList();
}
case "potential" -> {
return worldManager.getPotentialWorlds();
}
}
Logging.severe("Invalid MVWorld scope: " + scope);
return Collections.emptyList();
}
private Collection<String> suggestMVWorldPropsName(BukkitCommandCompletionContext context) {
return Try.of(() -> {
MultiverseWorld world = context.getContextValue(MultiverseWorldValue.class).value();
PropertyModifyAction action = context.getContextValue(PropertyModifyAction.class);
return world.getStringPropertyHandle().getModifiablePropertyNames(action);
}).getOrElse(Collections.emptyList());
}
private Collection<String> suggestMVWorldPropsValue(BukkitCommandCompletionContext context) {
return Try.of(() -> {
MultiverseWorld world = context.getContextValue(MultiverseWorldValue.class).value();
PropertyModifyAction action = context.getContextValue(PropertyModifyAction.class);
String propertyName = context.getContextValue(String.class);
return world.getStringPropertyHandle().getSuggestedPropertyValue(propertyName, context.getInput(), action, context.getSender());
}).getOrElse(Collections.emptyList());
}
private Collection<String> suggestPlayersArray(BukkitCommandCompletionContext context) {
CommandSender sender = context.getSender();
Validate.notNull(sender, "Sender cannot be null");
Player senderPlayer = sender instanceof Player ? (Player)sender : null;
List<String> matchedPlayers = new ArrayList<>();
for(Player player : Bukkit.getOnlinePlayers()) {
String name = player.getName();
if ((senderPlayer == null || senderPlayer.canSee(player))
&& (!context.hasConfig("excludeSelf") || !player.equals(senderPlayer))) {
matchedPlayers.add(name);
}
}
return addonToCommaSeperated(context.getInput(), matchedPlayers);
}
private Collection<String> suggestSpawnCategoryPropsName(BukkitCommandCompletionContext context) {
return Try.of(() -> {
MultiverseWorld world = context.getContextValue(MultiverseWorld.class);
SpawnCategory spawnCategory = context.getContextValue(SpawnCategory.class);
PropertyModifyAction action = context.getContextValue(PropertyModifyAction.class);
return world.getEntitySpawnConfig()
.getSpawnCategoryConfig(spawnCategory)
.getStringPropertyHandle()
.getModifiablePropertyNames(action);
}).getOrElse(Collections.emptyList());
}
private Collection<String> suggestSpawnCategoryPropsValue(BukkitCommandCompletionContext context) {
return Try.of(() -> {
MultiverseWorld world = context.getContextValue(MultiverseWorld.class);
SpawnCategory spawnCategory = context.getContextValue(SpawnCategory.class);
PropertyModifyAction action = context.getContextValue(PropertyModifyAction.class);
String propertyName = context.getContextValue(String.class);
return world.getEntitySpawnConfig()
.getSpawnCategoryConfig(spawnCategory)
.getStringPropertyHandle()
.getSuggestedPropertyValue(propertyName, context.getInput(), action, context.getSender());
}).getOrElse(Collections.emptyList());
}
}
@@ -1,4 +1,4 @@
package org.mvplugins.multiverse.core.commandtools;
package org.mvplugins.multiverse.core.command;
import co.aikar.commands.BukkitCommandExecutionContext;
import co.aikar.commands.BukkitCommandIssuer;
@@ -8,12 +8,16 @@ import co.aikar.commands.ConditionContext;
import co.aikar.commands.ConditionFailedException;
import org.jetbrains.annotations.NotNull;
import org.mvplugins.multiverse.core.world.WorldNameChecker;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.helpers.WorldNameChecker;
public class MVCommandConditions {
static void load(MVCommandManager commandManager, WorldManager worldManager, WorldNameChecker worldNameChecker) {
new MVCommandConditions(commandManager, worldManager, worldNameChecker);
static void load(
@NotNull MVCommandManager commandManager,
@NotNull WorldManager worldManager,
@NotNull WorldNameChecker worldNameChecker) {
MVCommandConditions mvCommandConditions = new MVCommandConditions(commandManager, worldManager, worldNameChecker);
mvCommandConditions.registerConditions();
}
private final WorldManager worldManager;
@@ -27,7 +31,6 @@ public class MVCommandConditions {
this.worldManager = worldManager;
this.commandManager = commandManager;
this.worldNameChecker = worldNameChecker;
registerConditions();
}
private void registerConditions() {
@@ -0,0 +1,383 @@
package org.mvplugins.multiverse.core.command;
import java.util.HashSet;
import java.util.Set;
import co.aikar.commands.ACFUtil;
import co.aikar.commands.BukkitCommandExecutionContext;
import co.aikar.commands.BukkitCommandIssuer;
import co.aikar.commands.InvalidCommandArgument;
import co.aikar.commands.MinecraftMessageKeys;
import co.aikar.commands.PaperCommandContexts;
import co.aikar.commands.contexts.ContextResolver;
import com.google.common.base.Strings;
import io.vavr.control.Try;
import jakarta.inject.Inject;
import org.bukkit.Bukkit;
import org.bukkit.GameRule;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.entity.SpawnCategory;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.anchor.MultiverseAnchor;
import org.mvplugins.multiverse.core.command.context.GameRuleValue;
import org.mvplugins.multiverse.core.command.context.issueraware.IssuerAwareContextBuilder;
import org.mvplugins.multiverse.core.command.context.PlayerLocation;
import org.mvplugins.multiverse.core.command.context.issueraware.MultiverseWorldValue;
import org.mvplugins.multiverse.core.command.context.issueraware.PlayerArrayValue;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.destination.DestinationInstance;
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
import org.mvplugins.multiverse.core.display.filters.ContentFilter;
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
import org.mvplugins.multiverse.core.display.filters.RegexContentFilter;
import org.mvplugins.multiverse.core.exceptions.command.MVInvalidCommandArgument;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.utils.PlayerFinder;
import org.mvplugins.multiverse.core.utils.REPatterns;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.generators.GeneratorPlugin;
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
@Service
public class MVCommandContexts extends PaperCommandContexts {
private final MVCommandManager mvCommandManager;
private final DestinationsProvider destinationsProvider;
private final WorldManager worldManager;
private final CoreConfig config;
private final AnchorManager anchorManager;
private final GeneratorProvider generatorProvider;
@Inject
MVCommandContexts(
MVCommandManager mvCommandManager,
DestinationsProvider destinationsProvider,
WorldManager worldManager,
CoreConfig config,
AnchorManager anchorManager,
GeneratorProvider generatorProvider
) {
super(mvCommandManager);
this.mvCommandManager = mvCommandManager;
this.destinationsProvider = destinationsProvider;
this.worldManager = worldManager;
this.config = config;
this.anchorManager = anchorManager;
this.generatorProvider = generatorProvider;
registerIssuerOnlyContext(BukkitCommandIssuer.class, BukkitCommandExecutionContext::getIssuer);
registerIssuerOnlyContext(MVCommandIssuer.class, this::parseMVCommandIssuer);
registerOptionalContext(ContentFilter.class, this::parseContentFilter);
registerContext(DestinationInstance.class, this::parseDestination);
registerContext(GameRule.class, this::parseGameRule);
registerContext(GameRuleValue.class, this::parseGameRuleValue);
registerContext(GeneratorPlugin.class, this::parseGeneratorPlugin);
registerIssuerAwareContext(LoadedMultiverseWorld.class, loadedMultiverseWorldContextBuilder().generateContext());
registerIssuerAwareContext(LoadedMultiverseWorld[].class, loadedMultiverseWorldArrayContextBuilder().generateContext());
registerIssuerAwareContext(MultiverseWorld.class, multiverseWorldContextBuilder().generateContext());
registerIssuerAwareContext(MultiverseWorldValue.class, multiverseWorldContextBuilder().generateContext(MultiverseWorldValue::new));
registerIssuerAwareContext(MultiverseWorld[].class, multiverseWorldArrayContextBuilder().generateContext());
registerContext(MultiverseAnchor.class, this::parseMultiverseAnchor);
registerIssuerAwareContext(Player.class, playerContextBuilder().generateContext());
registerIssuerAwareContext(Player[].class, playerArrayContextBuilder().generateContext());
registerIssuerAwareContext(PlayerArrayValue.class, playerArrayContextBuilder().generateContext(PlayerArrayValue::new));
registerIssuerAwareContext(PlayerLocation.class, this::parsePlayerLocation);
registerContext(SpawnCategory[].class, this::parseSpawnCategories);
}
private MVCommandIssuer parseMVCommandIssuer(BukkitCommandExecutionContext context) {
if (context.getIssuer() instanceof MVCommandIssuer) {
return (MVCommandIssuer) context.getIssuer();
}
return mvCommandManager.getCommandIssuer(context.getSender());
}
private ContentFilter parseContentFilter(BukkitCommandExecutionContext context) {
if (Strings.isNullOrEmpty(context.getFirstArg())) {
return DefaultContentFilter.get();
}
String filterString = context.popFirstArg();
return RegexContentFilter.fromString(filterString);
}
private DestinationInstance<?, ?> parseDestination(BukkitCommandExecutionContext context) {
String destination = context.popFirstArg();
if (Strings.isNullOrEmpty(destination)) {
throw new InvalidCommandArgument("No destination specified.");
}
return destinationsProvider.parseDestination(context.getSender(), destination)
.getOrThrow(failure ->
new InvalidCommandArgument(failure.getFailureMessage().formatted(context.getIssuer())));
}
private GameRule<?> parseGameRule(BukkitCommandExecutionContext context) {
String gameRuleName = context.popFirstArg();
if (Strings.isNullOrEmpty(gameRuleName)) {
throw new InvalidCommandArgument("No game rule specified.");
}
GameRule<?> gameRule = GameRule.getByName(gameRuleName);
if (gameRule == null) {
throw new InvalidCommandArgument("The game rule " + gameRuleName + " is not valid.");
}
return gameRule;
}
private GameRuleValue parseGameRuleValue(BukkitCommandExecutionContext context) {
GameRule<?> gameRule = (GameRule<?>) context.getResolvedArg(GameRule.class);
if (gameRule == null) {
throw new InvalidCommandArgument("No game rule specified.");
}
String valueString = context.getFirstArg();
if (Strings.isNullOrEmpty(valueString)) {
throw new InvalidCommandArgument("No game rule value specified.");
}
ContextResolver<?, BukkitCommandExecutionContext> resolver = getResolver(gameRule.getType());
if (resolver == null) {
return new GameRuleValue(valueString);
}
Object resolvedValue = resolver.getContext(context);
if (resolvedValue == null) {
throw new InvalidCommandArgument("The game rule value " + valueString + " is not valid for game rule " + gameRule.getName() + ".");
}
return new GameRuleValue(resolvedValue);
}
private GeneratorPlugin parseGeneratorPlugin(BukkitCommandExecutionContext context) {
GeneratorPlugin generatorPlugin = generatorProvider.getGeneratorPlugin(context.popFirstArg());
if (generatorPlugin == null) {
throw new InvalidCommandArgument("Invalid generator plugin: " + context.getFirstArg());
}
return generatorPlugin;
}
private IssuerAwareContextBuilder<LoadedMultiverseWorld> loadedMultiverseWorldContextBuilder() {
return new IssuerAwareContextBuilder<LoadedMultiverseWorld>()
.fromPlayer((context, player) -> worldManager.getLoadedWorld(player.getWorld()).getOrNull())
.fromInput((context, input) -> getLoadedMultiverseWorld(input))
.issuerOnlyFailMessage((context) -> Message.of("This command can only be used by a player in a loaded Multiverse World."))
.issuerAwarePlayerFailMessage((context, player) -> Message.of("You are not in a loaded multiverse world. Either specify a multiverse world name or use this command in a loaded multiverse world."))
.issuerAwareInputFailMessage((context, input) -> Message.of("World '" + input + "' is not a loaded multiverse world. Remember to specify the world name when using this command in console."))
.inputOnlyFailMessage((context, input) -> Message.of("World " + input + " is not a loaded multiverse world."));
}
private IssuerAwareContextBuilder<LoadedMultiverseWorld[]> loadedMultiverseWorldArrayContextBuilder() {
return new IssuerAwareContextBuilder<LoadedMultiverseWorld[]>()
.fromPlayer((context, player) -> worldManager.getLoadedWorld(player.getWorld())
.map(world -> new LoadedMultiverseWorld[]{world})
.getOrNull())
.fromInput((context, input) -> {
String[] worldNames = input == null ? new String[0] : REPatterns.COMMA.split(input);
Set<LoadedMultiverseWorld> worlds = new HashSet<>(worldNames.length);
for (String worldName : worldNames) {
if ("*".equals(worldName)) {
worlds.addAll(worldManager.getLoadedWorlds());
break;
}
LoadedMultiverseWorld world = getLoadedMultiverseWorld(worldName);
if (world == null) {
throw new InvalidCommandArgument("World " + worldName + " is not a loaded multiverse world.");
}
worlds.add(world);
}
return worlds.isEmpty() ? null : worlds.toArray(new LoadedMultiverseWorld[0]);
})
.issuerOnlyFailMessage((context) -> Message.of("This command can only be used by a player in a loaded Multiverse World."))
.issuerAwarePlayerFailMessage((context, player) -> Message.of("You are not in a loaded multiverse world. Either specify a multiverse world name or use this command in a loaded multiverse world."))
.issuerAwareInputFailMessage((context, input) -> Message.of("World '" + input + "' is not a loaded multiverse world. Remember to specify the world name when using this command in console."))
.inputOnlyFailMessage((context, input) -> Message.of("World " + input + " is not a loaded multiverse world."));
}
@Nullable
private LoadedMultiverseWorld getLoadedMultiverseWorld(String worldName) {
return config.getResolveAliasName()
? worldManager.getLoadedWorldByNameOrAlias(worldName).getOrNull()
: worldManager.getLoadedWorld(worldName).getOrNull();
}
private IssuerAwareContextBuilder<MultiverseWorld> multiverseWorldContextBuilder() {
return new IssuerAwareContextBuilder<MultiverseWorld>()
.fromPlayer((context, player) -> worldManager.getWorld(player.getWorld()).getOrNull())
.fromInput((context, input) -> getMultiverseWorld(input))
.issuerOnlyFailMessage((context) -> Message.of("This command can only be used by a player in a Multiverse World."))
.issuerAwarePlayerFailMessage((context, player) -> Message.of("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."))
.issuerAwareInputFailMessage((context, input) -> Message.of("World '" + input + "' is not a multiverse world. Remember to specify the world name when using this command in console."))
.inputOnlyFailMessage((context, input) -> Message.of("World " + input + " is not a multiverse world."));
}
private IssuerAwareContextBuilder<MultiverseWorld[]> multiverseWorldArrayContextBuilder() {
return new IssuerAwareContextBuilder<MultiverseWorld[]>()
.fromPlayer((context, player) -> worldManager.getWorld(player.getWorld())
.map(world -> new MultiverseWorld[]{world})
.getOrNull())
.fromInput((context, input) -> {
String[] worldNames = input == null ? new String[0] : REPatterns.COMMA.split(input);
Set<MultiverseWorld> worlds = new HashSet<>(worldNames.length);
for (String worldName : worldNames) {
if ("*".equals(worldName)) {
worlds.addAll(worldManager.getWorlds());
break;
}
MultiverseWorld world = getMultiverseWorld(worldName);
if (world == null) {
throw new InvalidCommandArgument("World " + worldName + " is not a multiverse world.");
}
worlds.add(world);
}
return worlds.isEmpty() ? null : worlds.toArray(new MultiverseWorld[0]);
})
.issuerOnlyFailMessage((context) -> Message.of("This command can only be used by a player in a Multiverse World."))
.issuerAwarePlayerFailMessage((context, player) -> Message.of("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."))
.issuerAwareInputFailMessage((context, input) -> Message.of("World '" + input + "' is not a multiverse world. Remember to specify the world name when using this command in console."))
.inputOnlyFailMessage((context, input) -> Message.of("World " + input + " is not a multiverse world."));
}
@Nullable
private MultiverseWorld getMultiverseWorld(String worldName) {
return config.getResolveAliasName()
? worldManager.getWorldByNameOrAlias(worldName).getOrNull()
: worldManager.getWorld(worldName).getOrNull();
}
private MultiverseAnchor parseMultiverseAnchor(BukkitCommandExecutionContext context) {
String anchorName = context.popFirstArg();
return anchorManager.getAnchor(anchorName)
.getOrElseThrow(() -> new InvalidCommandArgument("The anchor '" +anchorName + "' does not exist."));
}
private IssuerAwareContextBuilder<Player> playerContextBuilder() {
return new IssuerAwareContextBuilder<Player>()
.fromPlayer((context, player) -> player)
.fromInput((context, input) -> PlayerFinder.get(input, context.getSender()))
.issuerOnlyFailMessage((context) -> Message.of("This command can only be used by a player."))
.issuerAwareInputFailMessage((context, input) -> Message.of("Invalid player: " + input + ". Either specify an online player or use this command as a player."))
.inputOnlyFailMessage((context, input) -> Message.of("Player " + input + " not found."));
}
private IssuerAwareContextBuilder<Player[]> playerArrayContextBuilder() {
return new IssuerAwareContextBuilder<Player[]>()
.fromPlayer((context, player) -> new Player[]{player})
.fromInput((context, input) -> {
Player[] players = PlayerFinder.getMulti(input, context.getSender()).toArray(new Player[0]);
return (players.length == 0) ? null : players;
})
.issuerOnlyFailMessage((context) -> Message.of("This command can only be used by a player."))
.issuerAwareInputFailMessage((context, input) -> Message.of("Invalid player: " + input + ". Either specify an online player or use this command as a player."))
.inputOnlyFailMessage((context, input) -> Message.of("Player " + input + " not found."));
}
private PlayerLocation parsePlayerLocation(BukkitCommandExecutionContext context) {
Try<Location> location = Try.of(() -> parseLocationFromInput(context.getFirstArg(), context.getSender()));
if (location.isSuccess()) {
context.popFirstArg();
return new PlayerLocation(location.get());
}
if (context.getPlayer() != null) {
return new PlayerLocation(context.getPlayer().getLocation());
}
if (context.getFirstArg() == null) {
throw new InvalidCommandArgument("You must specify a world location when using this command in console.");
}
if (location.getCause() instanceof InvalidCommandArgument) {
throw (InvalidCommandArgument)location.getCause();
}
throw new RuntimeException(location.getCause());
}
// copied from ACF
private Location parseLocationFromInput(String input, CommandSender sender) {
String[] split = REPatterns.COLON.split(input, 2);
if (split.length == 0) {
throw new InvalidCommandArgument(true);
} else if (split.length < 2 && !(sender instanceof Player) && !(sender instanceof BlockCommandSender)) {
throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_WORLD, new String[0]);
} else {
Location sourceLoc = null;
String world;
String rest;
if (split.length == 2) {
world = split[0];
rest = split[1];
} else if (sender instanceof Player) {
sourceLoc = ((Player)sender).getLocation();
world = sourceLoc.getWorld().getName();
rest = split[0];
} else {
if (!(sender instanceof BlockCommandSender)) {
throw new InvalidCommandArgument(true);
}
sourceLoc = ((BlockCommandSender)sender).getBlock().getLocation();
world = sourceLoc.getWorld().getName();
rest = split[0];
}
boolean rel = rest.startsWith("~");
split = REPatterns.COMMA.split(rel ? rest.substring(1) : rest);
if (split.length < 3) {
throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ, new String[0]);
} else {
Double x = ACFUtil.parseDouble(split[0], rel ? (double)0.0F : null);
Double y = ACFUtil.parseDouble(split[1], rel ? (double)0.0F : null);
Double z = ACFUtil.parseDouble(split[2], rel ? (double)0.0F : null);
if (sourceLoc != null && rel) {
x = x + sourceLoc.getX();
y = y + sourceLoc.getY();
z = z + sourceLoc.getZ();
} else if (rel) {
throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_CONSOLE_NOT_RELATIVE, new String[0]);
}
if (x != null && y != null && z != null) {
World worldObj = Bukkit.getWorld(world);
if (worldObj == null) {
throw new InvalidCommandArgument(MinecraftMessageKeys.INVALID_WORLD, new String[0]);
} else if (split.length >= 5) {
Float yaw = ACFUtil.parseFloat(split[3]);
Float pitch = ACFUtil.parseFloat(split[4]);
if (pitch != null && yaw != null) {
return new Location(worldObj, x, y, z, yaw, pitch);
} else {
throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ, new String[0]);
}
} else {
return new Location(worldObj, x, y, z);
}
} else {
throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ, new String[0]);
}
}
}
}
private SpawnCategory[] parseSpawnCategories(BukkitCommandExecutionContext context) {
if (context.isOptional() && context.getArgs().isEmpty()) {
return new SpawnCategory[0];
}
Set<SpawnCategory> categories = new HashSet<>();
String[] split = REPatterns.COMMA.split(context.popFirstArg());
for (String category : split) {
SpawnCategory spawnCategory = ACFUtil.simpleMatch(SpawnCategory.class, category);
if (spawnCategory != null) {
categories.add(spawnCategory);
}
}
return categories.toArray(new SpawnCategory[0]);
}
}
@@ -1,16 +1,16 @@
package org.mvplugins.multiverse.core.commandtools;
package org.mvplugins.multiverse.core.command;
import co.aikar.commands.MessageKeys;
import co.aikar.commands.BukkitCommandIssuer;
import co.aikar.commands.MessageType;
import co.aikar.commands.OpenBukkitCommandIssuer;
import co.aikar.locales.MessageKeyProvider;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.mvplugins.multiverse.core.utils.message.Message;
import org.mvplugins.multiverse.core.utils.message.MessageReplacement;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
public class MVCommandIssuer extends OpenBukkitCommandIssuer {
public class MVCommandIssuer extends BukkitCommandIssuer {
private final MVCommandManager commandManager;
@@ -24,16 +24,29 @@ public class MVCommandIssuer extends OpenBukkitCommandIssuer {
return commandManager;
}
public void sendError(String message) {
sendMessage(MessageType.INFO, MessageKeys.INFO_MESSAGE, "{message}", message);
@Override
public void sendMessageInternal(String message) {
ChatTextFormatter.sendFormattedMessage(getIssuer(), message);
}
public void sendSyntax(String message) {
sendMessage(MessageType.SYNTAX, MessageKeys.INFO_MESSAGE, "{message}", message);
public void sendError(String message, MessageReplacement... replacements) {
sendMessage(MessageType.ERROR, message, replacements);
}
public void sendInfo(String message) {
sendMessage(MessageType.INFO, MessageKeys.INFO_MESSAGE, "{message}", message);
public void sendSyntax(String message, MessageReplacement... replacements) {
sendMessage(MessageType.SYNTAX, message, replacements);
}
public void sendInfo(String message, MessageReplacement... replacements) {
sendMessage(MessageType.INFO, message, replacements);
}
public void sendMessage(String message, MessageReplacement... replacements) {
sendMessage(null, Message.of(message, replacements));
}
public void sendMessage(MessageType type, String message, MessageReplacement... replacements) {
sendMessage(type, Message.of(message, replacements));
}
public void sendMessage(MessageType messageType, String message) {
@@ -68,12 +81,12 @@ public class MVCommandIssuer extends OpenBukkitCommandIssuer {
public void sendInfo(MessageKeyProvider key, MessageReplacement... replacements) {
sendMessage(MessageType.INFO, key, replacements);
}
public void sendMessage(MessageKeyProvider key, MessageReplacement... replacements) {
sendMessage(null, Message.of(key, replacements));
}
private void sendMessage(MessageType messageType, MessageKeyProvider key, MessageReplacement... replacements) {
sendMessage(messageType, Message.of(
key,
"{error_key: %s}".formatted(key.getMessageKey().getKey()),
replacements));
public void sendMessage(MessageType messageType, MessageKeyProvider key, MessageReplacement... replacements) {
sendMessage(messageType, Message.of(key, replacements));
}
public void sendError(Message message) {
@@ -88,6 +101,10 @@ public class MVCommandIssuer extends OpenBukkitCommandIssuer {
sendMessage(MessageType.INFO, message);
}
public void sendMessage(Message message) {
sendMessage(null, message);
}
public void sendMessage(MessageType messageType, Message message) {
if (message instanceof MessageKeyProvider) {
sendMessage(messageType, (MessageKeyProvider) message,
@@ -1,14 +1,17 @@
package org.mvplugins.multiverse.core.commandtools;
package org.mvplugins.multiverse.core.command;
import java.util.List;
import co.aikar.commands.BukkitCommandCompletionContext;
import co.aikar.commands.BukkitCommandExecutionContext;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.CommandCompletions;
import co.aikar.commands.CommandContexts;
import co.aikar.commands.CommandHelp;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.HelpEntry;
import co.aikar.commands.LogLevel;
import co.aikar.commands.PaperCommandManager;
import co.aikar.commands.RootCommand;
import com.dumptruckman.minecraft.util.Logging;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import org.bukkit.Bukkit;
@@ -17,10 +20,12 @@ import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlagsManager;
import org.mvplugins.multiverse.core.commandtools.queue.CommandQueueManager;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.queue.CommandQueueManager;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.locale.PluginLocales;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.WorldNameChecker;
import org.mvplugins.multiverse.core.world.helpers.WorldNameChecker;
/**
* Main class to manage permissions.
@@ -32,6 +37,9 @@ public class MVCommandManager extends PaperCommandManager {
private final CommandQueueManager commandQueueManager;
private final Provider<MVCommandContexts> commandContextsProvider;
private final Provider<MVCommandCompletions> commandCompletionsProvider;
private final MVCommandPermissions commandPermissions;
private final CoreConfig config;
private final PluginLocales pluginLocales;
@Inject
MVCommandManager(
@@ -41,22 +49,54 @@ public class MVCommandManager extends PaperCommandManager {
@NotNull Provider<MVCommandContexts> commandContextsProvider,
@NotNull Provider<MVCommandCompletions> commandCompletionsProvider,
@NotNull WorldManager worldManager,
@NotNull WorldNameChecker worldNameChecker) {
@NotNull WorldNameChecker worldNameChecker,
@NotNull MVCommandPermissions commandPermissions,
@NotNull CoreConfig config
) {
super(plugin);
this.flagsManager = flagsManager;
this.commandQueueManager = commandQueueManager;
this.commandContextsProvider = commandContextsProvider;
this.commandCompletionsProvider = commandCompletionsProvider;
this.commandPermissions = commandPermissions;
this.config = config;
this.pluginLocales = new PluginLocales(this);
this.locales = this.pluginLocales;
this.pluginLocales.loadLanguages();
MVCommandConditions.load(this, worldManager, worldNameChecker);
this.enableUnstableAPI("help");
this.setDefaultExceptionHandler(new MVDefaultExceptionHandler());
}
void loadLanguages(PluginLocales locales) {
if (this.locales == null) {
this.locales = locales;
this.locales.loadLanguages();
/**
* Registers a list of commands and handles {@link LegacyAliasCommand} based on config option.
* @param commands The commands to register
* @param <T> The type of the commands
*/
public <T extends BaseCommand> void registerAllCommands(List<T> commands) {
List<T> noLegacy = commands.stream().filter(command -> !(command instanceof LegacyAliasCommand)).toList();
if (!config.getShowLegacyAliases()) {
noLegacy.forEach(this::registerCommand);
return;
}
List<T> legacy = commands.stream().filter(command -> command instanceof LegacyAliasCommand).toList();
List<T> noLegacyInheritance = noLegacy.stream()
.filter(command -> legacy.stream().noneMatch(
legacyAliasCommand -> command.getClass().isAssignableFrom(legacyAliasCommand.getClass())))
.toList();
legacy.forEach(this::registerCommand);
noLegacyInheritance.forEach(this::registerCommand);
}
@Override
public void registerCommand(BaseCommand command, boolean force) {
if (command instanceof LegacyAliasCommand && !config.getShowLegacyAliases()) {
return;
}
super.registerCommand(command, force);
}
/**
@@ -64,7 +104,7 @@ public class MVCommandManager extends PaperCommandManager {
*/
@Override
public PluginLocales getLocales() {
return (PluginLocales) this.locales;
return this.pluginLocales;
}
/**
@@ -85,17 +125,21 @@ public class MVCommandManager extends PaperCommandManager {
return commandQueueManager;
}
public synchronized @NotNull MVCommandPermissions getCommandPermissions() {
return commandPermissions;
}
/**
* Gets class responsible for parsing string args into objects.
*
* @return A not-null {@link CommandContexts}.
*/
@Override
public synchronized @NotNull CommandContexts<BukkitCommandExecutionContext> getCommandContexts() {
public synchronized @NotNull MVCommandContexts getCommandContexts() {
if (this.contexts == null) {
this.contexts = commandContextsProvider.get();
}
return this.contexts;
return (MVCommandContexts) this.contexts;
}
/**
@@ -104,11 +148,16 @@ public class MVCommandManager extends PaperCommandManager {
* @return A not-null {@link CommandCompletions}.
*/
@Override
public synchronized @NotNull CommandCompletions<BukkitCommandCompletionContext> getCommandCompletions() {
public synchronized @NotNull MVCommandCompletions getCommandCompletions() {
if (this.completions == null) {
this.completions = commandCompletionsProvider.get();
}
return this.completions;
return (MVCommandCompletions) this.completions;
}
@Override
public boolean hasPermission(CommandIssuer issuer, String permission) {
return commandPermissions.hasPermission(issuer, permission);
}
/**
@@ -137,4 +186,17 @@ public class MVCommandManager extends PaperCommandManager {
return new MVCommandIssuer(this, (CommandSender)issuer);
}
}
@Override
public RootCommand createRootCommand(String cmd) {
return new MVRootCommand(this, cmd);
}
@Override
public void log(LogLevel level, String message, Throwable throwable) {
Logging.finer("[%s] %s", level, message);
if (throwable != null) {
throwable.printStackTrace();
}
}
}
@@ -0,0 +1,62 @@
package org.mvplugins.multiverse.core.command;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandPermission;
import io.vavr.control.Option;
import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker;
import org.mvplugins.multiverse.core.permissions.PermissionUtils;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
import static org.mvplugins.multiverse.core.permissions.CorePermissionsChecker.*;
/**
* Maps permission checking to custom logic for commands, to allow more complex permission checking.
*/
@Service
public class MVCommandPermissions {
private final Map<String, Predicate<CommandIssuer>> permissionsCheckMap;
@Inject
MVCommandPermissions(@NotNull CorePermissionsChecker permissionsChecker) {
this.permissionsCheckMap = new HashMap<>();
registerPermissionChecker("mvteleport", issuer -> permissionsChecker.hasAnyTeleportPermission(issuer.getIssuer()));
registerPermissionChecker("mvteleportother", issuer -> permissionsChecker.hasAnyTeleportPermission(issuer.getIssuer(), Scope.OTHER));
registerPermissionChecker("mvspawn", issuer -> permissionsChecker.hasAnySpawnPermission(issuer.getIssuer()));
registerPermissionChecker("mvspawnother", issuer -> permissionsChecker.hasAnySpawnPermission(issuer.getIssuer(), Scope.OTHER));
}
/**
* Registers a custom permission checker callback. Use `@id-name` in {@link CommandPermission} decorator to use
* the callback instead of the default permission string checking.
*
* @param id The permission id
* @param checker The permission checker callback
*/
public void registerPermissionChecker(String id, Predicate<CommandIssuer> checker) {
permissionsCheckMap.put(prepareId(id), checker);
}
private static @NotNull String prepareId(String id) {
return (id.startsWith("@") ? "" : "@") + id.toLowerCase(Locale.ENGLISH);
}
/**
* Check if the issuer has the given permission.
* @param issuer The issuer
* @param permission The permission to check
* @return True if the issuer has the permission
*/
boolean hasPermission(CommandIssuer issuer, String permission) {
return Option.of(permissionsCheckMap.get(permission))
.map(checker -> checker.test(issuer))
.getOrElse(() -> PermissionUtils.hasPermission(issuer.getIssuer(), permission));
}
}
@@ -0,0 +1,22 @@
package org.mvplugins.multiverse.core.command;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.ExceptionHandler;
import co.aikar.commands.RegisteredCommand;
import com.dumptruckman.minecraft.util.Logging;
import java.util.List;
/**
* Default handler that will print the stack trace when the command throws an exception.
*/
public class MVDefaultExceptionHandler implements ExceptionHandler {
@Override
public boolean execute(BaseCommand command, RegisteredCommand registeredCommand, CommandIssuer sender, List<String> args, Throwable t) {
Logging.severe("Failed to execute command /%s %s %s",
registeredCommand.getCommand(), registeredCommand.getPrefSubCommand(), String.join(" ", args));
t.printStackTrace();
return false;
}
}
@@ -0,0 +1,27 @@
package org.mvplugins.multiverse.core.command;
import co.aikar.commands.BaseCommand;
import co.aikar.commands.BukkitCommandManager;
import co.aikar.commands.BukkitRootCommand;
import co.aikar.commands.CommandIssuer;
import org.mvplugins.multiverse.core.utils.StringFormatter;
import java.util.List;
public class MVRootCommand extends BukkitRootCommand {
protected MVRootCommand(BukkitCommandManager manager, String name) {
super(manager, name);
}
@Override
public BaseCommand execute(CommandIssuer sender, String commandLabel, String[] args) {
String[] quoteFormatedArgs = StringFormatter.parseQuotesInArgs(args).toArray(String[]::new);
return super.execute(sender, commandLabel, quoteFormatedArgs);
}
@Override
public List<String> getTabCompletions(CommandIssuer sender, String alias, String[] args, boolean commandsOnly, boolean isAsync) {
String[] quoteFormatedArgs = StringFormatter.parseQuotesInArgs(args).toArray(String[]::new);
return super.getTabCompletions(sender, alias, quoteFormatedArgs, commandsOnly, isAsync);
}
}
@@ -0,0 +1,19 @@
package org.mvplugins.multiverse.core.command;
import co.aikar.commands.BaseCommand;
import com.dumptruckman.minecraft.util.Logging;
import jakarta.annotation.PostConstruct;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Contract;
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
import org.mvplugins.multiverse.core.command.flag.CommandFlagGroup;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
/**
* Base class for all Multiverse commands.
*/
@Contract
public abstract class MultiverseCommand extends BaseCommand {
}
@@ -0,0 +1,7 @@
package org.mvplugins.multiverse.core.command.context;
/**
* Simple wrapper for game rule value, as they may be different types.
*/
public record GameRuleValue(Object value) {
}
@@ -0,0 +1,16 @@
package org.mvplugins.multiverse.core.command.context;
import org.bukkit.Location;
import org.jetbrains.annotations.ApiStatus;
/**
* Wrapper for context that get location from player if executed by player,
* else requires user input of location coordinates.
*
* @param value The location
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public record PlayerLocation(Location value) {
}
@@ -0,0 +1,226 @@
package org.mvplugins.multiverse.core.command.context.issueraware;
import co.aikar.commands.BukkitCommandExecutionContext;
import co.aikar.commands.BukkitCommandIssuer;
import co.aikar.commands.InvalidCommandArgument;
import co.aikar.commands.contexts.IssuerAwareContextResolver;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
import org.mvplugins.multiverse.core.locale.message.Message;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Reusable logic of issuer only and issuer aware context resolvers
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public final class IssuerAwareContextBuilder<T> {
private BiFunction<BukkitCommandExecutionContext, Player, T> fromPlayer;
private BiFunction<BukkitCommandExecutionContext, String, T> fromInput;
private Function<BukkitCommandExecutionContext, Message> issuerOnlyFailMessage = context -> Message.of("This command can only be used by a player.");
private BiFunction<BukkitCommandExecutionContext, Player, Message> issuerAwarePlayerFailMessage = (context, player) -> Message.of("Unable to resolve context for player '" + player.getName() + "'.");
private BiFunction<BukkitCommandExecutionContext, String, Message> issuerAwareInputFailMessage = (context, input) -> Message.of("Unable to resolve context for input '" + input + "'.");
private BiFunction<BukkitCommandExecutionContext, String, Message> inputOnlyFailMessage = (context, input) -> Message.of("Unable to resolve context for input '" + input + "'.");
public IssuerAwareContextBuilder() {
}
/**
* Parse value from player itself.
*
* @param fromInput the parsing function
* @return The same builder for chaining
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public IssuerAwareContextBuilder<T> fromPlayer(BiFunction<BukkitCommandExecutionContext, Player, T> fromInput) {
this.fromPlayer = fromInput;
return this;
}
/**
* Parse value from command input string.
*
* @param fromInput the parsing function
* @return The same builder for chaining
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public IssuerAwareContextBuilder<T> fromInput(BiFunction<BukkitCommandExecutionContext, String, T> fromInput) {
this.fromInput = fromInput;
return this;
}
/**
* When getting value fails as issuer is not a player.
*
* @param issuerOnlyFailMessage The message
* @return The same builder for chaining
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public IssuerAwareContextBuilder<T> issuerOnlyFailMessage(Function<BukkitCommandExecutionContext, Message> issuerOnlyFailMessage) {
this.issuerOnlyFailMessage = issuerOnlyFailMessage;
return this;
}
/**
* When getting value fails as player cannot parse the value.
*
* @param issuerAwarePlayerFailMessage The message
* @return The same builder for chaining
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public IssuerAwareContextBuilder<T> issuerAwarePlayerFailMessage(BiFunction<BukkitCommandExecutionContext, Player, Message> issuerAwarePlayerFailMessage) {
this.issuerAwarePlayerFailMessage = issuerAwarePlayerFailMessage;
return this;
}
/**
* When getting value fails as input cannot parse the value.
*
* @param issuerAwareInputFailMessage The message
* @return The same builder for chaining
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public IssuerAwareContextBuilder<T> issuerAwareInputFailMessage(BiFunction<BukkitCommandExecutionContext, String, Message> issuerAwareInputFailMessage) {
this.issuerAwareInputFailMessage = issuerAwareInputFailMessage;
return this;
}
/**
* When getting value fails as input cannot parse the value.
*
* @param inputOnlyFailMessage The message
* @return The same builder for chaining
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public IssuerAwareContextBuilder<T> inputOnlyFailMessage(BiFunction<BukkitCommandExecutionContext, String, Message> inputOnlyFailMessage) {
this.inputOnlyFailMessage = inputOnlyFailMessage;
return this;
}
/**
* Creates the issuer aware context resolver logic.
*
* @return The generated context resolver
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public IssuerAwareContextResolver<T, BukkitCommandExecutionContext> generateContext() {
Objects.requireNonNull(fromPlayer);
Objects.requireNonNull(fromInput);
Objects.requireNonNull(issuerOnlyFailMessage);
Objects.requireNonNull(issuerAwarePlayerFailMessage);
Objects.requireNonNull(issuerAwareInputFailMessage);
Objects.requireNonNull(inputOnlyFailMessage);
return context -> {
BukkitCommandIssuer issuer = context.getIssuer();
String resolve = context.getFlagValue("resolve", "");
if (resolve.equals("issuerOnly")) {
if (issuer.isPlayer()) {
T result = fromPlayer.apply(context, issuer.getPlayer());
if (result != null) {
return result;
}
}
throw new InvalidCommandArgument(issuerOnlyFailMessage.apply(context).formatted(issuer));
}
String input = context.getFirstArg();
T result = fromInput.apply(context, input);
if (result != null) {
context.popFirstArg();
return result;
}
if (resolve.equals("issuerAware")) {
if (issuer.isPlayer()) {
Player player = issuer.getPlayer();
result = fromPlayer.apply(context, player);
if (result != null) {
return result;
}
throw new InvalidCommandArgument(issuerAwarePlayerFailMessage.apply(context, player).formatted(issuer));
}
throw new InvalidCommandArgument(issuerAwareInputFailMessage.apply(context, input).formatted(issuer));
}
throw new InvalidCommandArgument(inputOnlyFailMessage.apply(context, input).formatted(issuer));
};
}
/**
* Creates the issuer aware context resolver logic, with marking of whether the value was resolved from player or input.
*
* @param createValue The function to create the {@link IssuerAwareValue} value
* @param <I> The type of the value
* @return The generated context resolver
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public <I extends IssuerAwareValue> IssuerAwareContextResolver<I, BukkitCommandExecutionContext> generateContext(BiFunction<Boolean, T, I> createValue) {
// todo: This is a copy and paste from above
Objects.requireNonNull(fromPlayer);
Objects.requireNonNull(fromInput);
Objects.requireNonNull(issuerOnlyFailMessage);
Objects.requireNonNull(issuerAwarePlayerFailMessage);
Objects.requireNonNull(issuerAwareInputFailMessage);
Objects.requireNonNull(inputOnlyFailMessage);
return context -> {
BukkitCommandIssuer issuer = context.getIssuer();
String resolve = context.getFlagValue("resolve", "");
if (resolve.equals("issuerOnly")) {
if (issuer.isPlayer()) {
T result = fromPlayer.apply(context, issuer.getPlayer());
if (result != null) {
return createValue.apply(true, result);
}
}
throw new InvalidCommandArgument(issuerOnlyFailMessage.apply(context).formatted(issuer));
}
String input = context.getFirstArg();
T result = fromInput.apply(context, input);
if (result != null) {
context.popFirstArg();
return createValue.apply(false, result);
}
if (resolve.equals("issuerAware")) {
if (issuer.isPlayer()) {
Player player = issuer.getPlayer();
result = fromPlayer.apply(context, player);
if (result != null) {
return createValue.apply(true, result);
}
throw new InvalidCommandArgument(issuerAwarePlayerFailMessage.apply(context, player).formatted(issuer));
}
throw new InvalidCommandArgument(issuerAwareInputFailMessage.apply(context, input).formatted(issuer));
}
throw new InvalidCommandArgument(inputOnlyFailMessage.apply(context, input).formatted(issuer));
};
}
}
@@ -0,0 +1,35 @@
package org.mvplugins.multiverse.core.command.context.issueraware;
import org.jetbrains.annotations.ApiStatus;
/**
* Base class for issuer aware values
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public abstract class IssuerAwareValue {
protected final boolean byIssuer;
/**
* Constructor to create an issuer aware value
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
protected IssuerAwareValue(boolean byIssuer) {
this.byIssuer = byIssuer;
}
/**
* Gets whether the value is by issuer or input
*
* @return true if by issuer, false if by input
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public boolean isByIssuer() {
return byIssuer;
}
}
@@ -0,0 +1,38 @@
package org.mvplugins.multiverse.core.command.context.issueraware;
import org.jetbrains.annotations.ApiStatus;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
/**
* Issuer aware value wrapper for {@link MultiverseWorld}
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public final class MultiverseWorldValue extends IssuerAwareValue {
private final MultiverseWorld world;
/**
* Constructor for issuer aware value
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public MultiverseWorldValue(boolean byIssuer, MultiverseWorld world) {
super(byIssuer);
this.world = world;
}
/**
* The containing world wrapped
*
* @return wrapped world
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public MultiverseWorld value() {
return world;
}
}
@@ -0,0 +1,36 @@
package org.mvplugins.multiverse.core.command.context.issueraware;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
/**
* Issuer aware value wrapper for {@link Player} array
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public final class PlayerArrayValue extends IssuerAwareValue {
private final Player[] players;
/**
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public PlayerArrayValue(boolean byIssuer, Player[] players) {
super(byIssuer);
this.players = players;
}
/**
* The containing player array wrapped
*
* @return wrapped player array
*
* @since 5.1
*/
@ApiStatus.AvailableSince("5.1")
public Player[] value() {
return players;
}
}
@@ -0,0 +1 @@
package org.mvplugins.multiverse.core.command.context;
@@ -1,4 +1,4 @@
package org.mvplugins.multiverse.core.commandtools.flags;
package org.mvplugins.multiverse.core.command.flag;
import java.util.ArrayList;
import java.util.Collections;
@@ -1,4 +1,4 @@
package org.mvplugins.multiverse.core.commandtools.flags;
package org.mvplugins.multiverse.core.command.flag;
import java.util.ArrayList;
import java.util.HashMap;
@@ -1,4 +1,4 @@
package org.mvplugins.multiverse.core.commandtools.flags;
package org.mvplugins.multiverse.core.command.flag;
import java.util.ArrayList;
import java.util.Collection;
@@ -7,6 +7,7 @@ import java.util.HashMap;
import java.util.Map;
import co.aikar.commands.InvalidCommandArgument;
import com.dumptruckman.minecraft.util.Logging;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Service;
@@ -54,6 +55,7 @@ public class CommandFlagsManager {
public @NotNull Collection<String> suggest(@Nullable String groupName, @NotNull String[] flags) {
CommandFlagGroup flagGroup = this.getFlagGroup(groupName);
if (flagGroup == null) {
Logging.warning("Unknown flag group: " + groupName);
return Collections.emptyList();
}
@@ -1,4 +1,4 @@
package org.mvplugins.multiverse.core.commandtools.flags;
package org.mvplugins.multiverse.core.command.flag;
import co.aikar.commands.InvalidCommandArgument;
@@ -68,9 +68,7 @@ public class CommandFlagsParser {
this.currentFlag = potentialFlag;
if (this.currentFlag instanceof CommandValueFlag) {
CommandValueFlag<?> valueFlag = (CommandValueFlag<?>) this.currentFlag;
if (this.currentFlag instanceof CommandValueFlag<?> valueFlag) {
if (valueFlag.isOptional()) {
parsedFlags.addFlagResult(valueFlag.getKey(), valueFlag.getDefaultValue());
this.nextArgMayBeKey = true;
@@ -98,7 +96,7 @@ public class CommandFlagsParser {
*/
private boolean parseValue(String flag) {
if (this.currentFlag == null) {
throw new InvalidCommandArgument("Some flag logic error occurred at " + flag + "");
throw new InvalidCommandArgument("Some flag logic error occurred at '" + flag + "'!");
}
if (flagGroup.hasKey(flag)) {
throw new InvalidCommandArgument(currentFlag.getKey() + " requires a value!");
@@ -1,10 +1,9 @@
package org.mvplugins.multiverse.core.commandtools.flags;
package org.mvplugins.multiverse.core.command.flag;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import co.aikar.commands.InvalidCommandArgument;
import org.jetbrains.annotations.NotNull;
@@ -192,7 +191,7 @@ public class CommandValueFlag<T> extends CommandFlag {
@Override
public @NotNull CommandValueFlag<T> build() {
if (context == null && !String.class.equals(type)) {
throw new IllegalStateException("Context is required for none-string value flags");
throw new IllegalStateException("Context is required for non-string value flags");
}
return new CommandValueFlag<>(key, aliases, type, optional, defaultValue, context, completion);
}
@@ -230,8 +229,8 @@ public class CommandValueFlag<T> extends CommandFlag {
private void setEnumCompletion() {
List<String> types = Arrays.stream(type.getEnumConstants())
.map(type -> type.name().toLowerCase())
.collect(Collectors.toList());
.map(typeClass -> typeClass.name().toLowerCase())
.toList();
this.completion = (input) -> types;
}
@@ -0,0 +1,61 @@
package org.mvplugins.multiverse.core.command.flag;
import com.dumptruckman.minecraft.util.Logging;
import jakarta.annotation.PostConstruct;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Contract;
import java.util.ArrayList;
import java.util.List;
/**
* Builds and registers a flag group on initialization. Flags should be final fields that calls {@link #flag(CommandFlag)}.
*/
@Contract
public abstract class FlagBuilder {
private final String name;
private final CommandFlagsManager flagsManager;
private final List<CommandFlag> flags;
protected FlagBuilder(@NotNull String name, @NotNull CommandFlagsManager flagsManager) {
this.name = name;
this.flagsManager = flagsManager;
this.flags = new ArrayList<>();
}
@PostConstruct
private void postConstruct() {
CommandFlagGroup.Builder flagGroupBuilder = CommandFlagGroup.builder(name);
flags.forEach(flagGroupBuilder::add);
registerFlagGroup(flagGroupBuilder.build());
}
private void registerFlagGroup(@NotNull CommandFlagGroup flagGroup) {
flagsManager.registerFlagGroup(flagGroup);
Logging.finer("Registered flag group: " + flagGroup.getName());
}
/**
* Add a new flag to the flag builder.
*
* @param flag The flag to add.
* @param <T> The type of the flag.
* @return The flag.
*/
protected <T extends CommandFlag> T flag(T flag) {
flags.add(flag);
Logging.finer("Added flag: " + flag);
return flag;
}
/**
* Parses flags.
*
* @param flags The raw string array to parse into flags.
* @return The parsed flags.
*/
public @NotNull ParsedCommandFlags parse(@NotNull String[] flags) {
return flagsManager.parse(name, flags);
}
}
@@ -1,4 +1,4 @@
package org.mvplugins.multiverse.core.commandtools.flags;
package org.mvplugins.multiverse.core.command.flag;
import java.util.HashMap;
import java.util.Map;
@@ -0,0 +1,4 @@
/**
* This package contains classes related to parsing command flags.
*/
package org.mvplugins.multiverse.core.command.flag;
@@ -0,0 +1,41 @@
package org.mvplugins.multiverse.core.command.flags;
import java.util.List;
import java.util.function.Function;
import co.aikar.commands.InvalidCommandArgument;
import org.jetbrains.annotations.NotNull;
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
import org.mvplugins.multiverse.core.display.filters.ContentFilter;
import org.mvplugins.multiverse.core.display.filters.RegexContentFilter;
/**
* A command flag for regex filtering.
* <br/>
* Parses the value of the --filter (or -f) flag as a {@link ContentFilter}.
*/
public final class FilterCommandFlag extends CommandValueFlag<ContentFilter> {
private static final Function<String, ContentFilter> VALUE_PARSER = value -> {
try {
return RegexContentFilter.fromString(value);
} catch (IllegalArgumentException e) {
throw new InvalidCommandArgument("Invalid filter: " + value);
}
};
/**
* Creates a new instance of this flag.
*
* @return The new instance.
*/
@NotNull
public static FilterCommandFlag create() {
return new FilterCommandFlag();
}
private FilterCommandFlag() {
super("--filter", List.of("-f"), ContentFilter.class, false, null, VALUE_PARSER, null);
}
}
@@ -0,0 +1,38 @@
package org.mvplugins.multiverse.core.command.flags;
import java.util.List;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
/**
* A command flag for page number.
* <br/>
* Parses the value of the --page (or -p) flag as an integer.
*/
public final class PageCommandFlag extends CommandValueFlag<Integer> {
private static final Function<String, Integer> VALUE_PARSER = value -> {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid page number: " + value);
}
};
/**
* Creates a new instance of this flag.
*
* @return The new instance.
*/
@NotNull
public static PageCommandFlag create() {
return new PageCommandFlag();
}
private PageCommandFlag() {
super("--page", List.of("-p"), Integer.class, false, null, VALUE_PARSER, null);
}
}
@@ -0,0 +1,28 @@
package org.mvplugins.multiverse.core.command.flags;
import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
import org.mvplugins.multiverse.core.display.filters.ContentFilter;
@Service
public class PageFilterFlags extends FlagBuilder {
public static final String NAME = "pagefilter";
protected PageFilterFlags(@NotNull String name, @NotNull CommandFlagsManager flagsManager) {
super(name, flagsManager);
}
@Inject
protected PageFilterFlags(@NotNull CommandFlagsManager flagsManager) {
super(NAME, flagsManager);
}
public final CommandValueFlag<Integer> page = flag(PageCommandFlag.create());
public final CommandValueFlag<ContentFilter> filter = flag(FilterCommandFlag.create());
}
@@ -0,0 +1,27 @@
package org.mvplugins.multiverse.core.command.flags;
import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
@Service
public class RemovePlayerFlags extends FlagBuilder {
public static final String NAME = "removeplayer";
protected RemovePlayerFlags(@NotNull String name, @NotNull CommandFlagsManager flagsManager) {
super(name, flagsManager);
}
@Inject
protected RemovePlayerFlags(@NotNull CommandFlagsManager flagsManager) {
super(NAME, flagsManager);
}
public final CommandFlag removePlayers = flag(CommandFlag.builder("--remove-players")
.addAlias("-r")
.build());
}
@@ -0,0 +1,27 @@
package org.mvplugins.multiverse.core.command.flags;
import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
@Service
public class UnsafeFlags extends FlagBuilder {
public static final String NAME = "unsafe";
protected UnsafeFlags(@NotNull String name, @NotNull CommandFlagsManager flagsManager) {
super(name, flagsManager);
}
@Inject
protected UnsafeFlags(@NotNull CommandFlagsManager flagsManager) {
super(NAME, flagsManager);
}
public final CommandFlag unsafe = flag(CommandFlag.builder("--unsafe")
.addAlias("-u")
.build());
}
@@ -0,0 +1,4 @@
/**
* This package contains predefined flags for commands.
*/
package org.mvplugins.multiverse.core.command.flags;
@@ -1,4 +1,4 @@
/**
* This package contains the classes that manages command handling.
*/
package org.mvplugins.multiverse.core.commandtools;
package org.mvplugins.multiverse.core.command;
@@ -0,0 +1,192 @@
/******************************************************************************
* Multiverse 2 Copyright (c) the Multiverse Team 2020. *
* Multiverse 2 is licensed under the BSD License. *
* For more information please check the README.md file included *
* with this project. *
******************************************************************************/
package org.mvplugins.multiverse.core.command.queue;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
import com.dumptruckman.minecraft.util.Logging;
import io.vavr.control.Option;
import jakarta.inject.Inject;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.block.data.type.CommandBlock;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.utils.result.Attempt;
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.*;
/**
* <p>Manages the queuing of dangerous commands that require {@code /mv confirm} before executing.</p>
*
* <p>Each sender can only have one command in queue at any given time. When a queued command is added
* for a sender that already has a command in queue, it will replace the old queued command.</p>
*/
@Service
public class CommandQueueManager {
private static final String CONSOLE_NAME = "@console";
private static final String COMMAND_BLOCK_NAME = "@commandblock";
private static final long TICKS_PER_SECOND = 20;
private final Plugin plugin;
private final CoreConfig config;
private final Map<String, CommandQueuePayload> queuedCommandMap;
@Inject
CommandQueueManager(@NotNull MultiverseCore plugin, @NotNull CoreConfig config) {
this.plugin = plugin;
this.config = config;
this.queuedCommandMap = new WeakHashMap<>();
}
/**
* Adds a {@link CommandQueuePayload} into queue.
*
* @param payload The command queue payload to add.
*/
public void addToQueue(CommandQueuePayload payload) {
String senderName = parseSenderName(payload.issuer());
if (canRunImmediately(senderName)) {
payload.action().run();
return;
}
this.removeFromQueue(senderName);
Logging.finer("Add new command to queue for sender %s.", senderName);
this.queuedCommandMap.put(senderName, payload);
payload.expireTask(runExpireLater(senderName, config.getConfirmTimeout()));
payload.issuer().sendInfo(payload.prompt());
var confirmCommand = "/mv confirm";
if (config.getUseConfirmOtp()) {
confirmCommand += " " + payload.otp();
}
payload.issuer().sendMessage(String.format("Run %s%s %sto continue. This will expire in %s seconds.",
ChatColor.GREEN, confirmCommand, ChatColor.WHITE, config.getConfirmTimeout()));
}
/**
* Check if sender does not need to queue before running.
*
* @param senderName The name of sender.
* @return True if sender does not need to queue before running.
*/
private boolean canRunImmediately(@NotNull String senderName) {
return switch (config.getConfirmMode()) {
case ENABLE -> false;
case PLAYER_ONLY -> senderName.equals(CONSOLE_NAME) || senderName.equals(COMMAND_BLOCK_NAME);
case DISABLE_COMMAND_BLOCKS -> senderName.equals(COMMAND_BLOCK_NAME);
case DISABLE_CONSOLE -> senderName.equals(CONSOLE_NAME);
case DISABLE -> true;
};
}
/**
* Expire task that removes a {@link CommandQueuePayload} from queue after valid duration defined.
*
* @param senderName The name of the sender.
* @return The expire {@link BukkitTask}.
*/
@NotNull
private BukkitTask runExpireLater(@NotNull String senderName, int validDuration) {
return Bukkit.getScheduler().runTaskLater(
this.plugin,
expireRunnable(senderName),
validDuration * TICKS_PER_SECOND);
}
/**
* Runnable responsible for expiring the queued command.
*
* @param senderName The name of the sender.
* @return The expire runnable.
*/
@NotNull
private Runnable expireRunnable(@NotNull String senderName) {
return () -> {
CommandQueuePayload payload = this.queuedCommandMap.remove(senderName);
if (payload == null) {
// Payload already removed
return;
}
payload.issuer().sendMessage("Your queued command has expired.");
};
}
/**
* Runs the command in queue for the given sender, if any.
*
* @param issuer Sender that confirmed the command.
* @return True if queued command ran successfully, else false.
*/
public Attempt<Void, RunQueuedFailedReason> runQueuedCommand(@NotNull MVCommandIssuer issuer, String otpInput) {
String senderName = parseSenderName(issuer);
return Option.of(this.queuedCommandMap.get(senderName)).fold(
() -> Attempt.failure(RunQueuedFailedReason.NO_COMMAND_IN_QUEUE),
payload -> runPayload(senderName, otpInput, payload));
}
private Attempt<Void, RunQueuedFailedReason> runPayload(String senderName, String otpInput, CommandQueuePayload payload) {
if (config.getUseConfirmOtp() && !Objects.equals(payload.otp(), otpInput)) {
return Attempt.failure(RunQueuedFailedReason.INVALID_OTP, replace("{otp}").with(otpInput));
}
this.removeFromQueue(senderName);
Logging.finer("Running queued command...");
payload.action().run();
return Attempt.success(null);
}
/**
* Since only one command is stored in queue per sender, we remove the old one.
*
* @param senderName The sender that executed the command.
*/
public void removeFromQueue(@NotNull String senderName) {
CommandQueuePayload payload = this.queuedCommandMap.remove(senderName);
if (payload == null) {
Logging.finer("No queue command to remove for sender %s.", senderName);
return;
}
Option.of(payload.expireTask()).peek(BukkitTask::cancel);
Logging.finer("Removed queue command for sender %s.", senderName);
}
private String parseSenderName(MVCommandIssuer issuer) {
CommandSender sender = issuer.getIssuer();
if (isCommandBlock(sender)) {
return COMMAND_BLOCK_NAME;
} else if (sender instanceof ConsoleCommandSender) {
return CONSOLE_NAME;
}
return sender.getName();
}
/**
* Checks if the sender is a command block.
*
* @param sender The sender to check.
* @return True if sender is a command block, else false.
*/
private boolean isCommandBlock(@NotNull CommandSender sender) {
return sender instanceof BlockCommandSender
&& ((BlockCommandSender) sender).getBlock().getBlockData() instanceof CommandBlock;
}
}
@@ -0,0 +1,116 @@
package org.mvplugins.multiverse.core.command.queue;
import co.aikar.commands.ACFUtil;
import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.locale.message.Message;
/**
* Represents a single command used in {@link CommandQueueManager} for confirming before running potentially
* dangerous action.
*/
public class CommandQueuePayload {
/**
* Creates a new {@link CommandQueuePayload}
*
* @param issuer The issuer of the command
* @return The new {@link CommandQueuePayload}
*/
public static CommandQueuePayload issuer(@NotNull MVCommandIssuer issuer) {
return new CommandQueuePayload(issuer);
}
private static final String DEFAULT_PROMPT_MESSAGE = "The command you are trying to run is deemed dangerous."; // todo: localize
private final MVCommandIssuer issuer;
private String otp;
private Runnable action = () -> {};
private Message prompt = Message.of(DEFAULT_PROMPT_MESSAGE);
private BukkitTask expireTask;
protected CommandQueuePayload(@NotNull MVCommandIssuer issuer) {
this.otp = String.valueOf(ACFUtil.rand(100, 999));
this.issuer = issuer;
}
/**
* Gets the issuer of the command.
* @return The issuer
*/
@NotNull
public MVCommandIssuer issuer() {
return issuer;
}
/**
* Sets the logic to execute upon confirming.
* @param action The logic to execute
* @return The same {@link CommandQueuePayload} for method chaining
*/
public CommandQueuePayload action(@NotNull Runnable action) {
this.action = action;
return this;
}
/**
* Gets the logic to execute upon confirming.
*
* @return The logic to execute
*/
@NotNull
public Runnable action() {
return action;
}
/**
* Sets the OTP number for validation.
*
* @param otp The OTP number
* @return The same {@link CommandQueuePayload} for method chaining
*/
public CommandQueuePayload otp(String otp) {
this.otp = otp;
return this;
}
/**
* Gets the OTP number for validation.
*
* @return The OTP number
*/
public String otp() {
return otp;
}
/**
* Sets the question to ask sender to confirm.
*
* @param prompt The prompt message.
* @return The same {@link CommandQueuePayload} for method chaining.
*/
public CommandQueuePayload prompt(Message prompt) {
this.prompt = prompt;
return this;
}
/**
* Gets the question to ask sender to confirm.
*
* @return The prompt message.
*/
public Message prompt() {
return prompt;
}
void expireTask(BukkitTask expireTask) {
this.expireTask = expireTask;
}
@Nullable
BukkitTask expireTask() {
return expireTask;
}
}
@@ -0,0 +1,31 @@
package org.mvplugins.multiverse.core.command.queue;
/**
* Sets whether the command executor needs to confirm before executing a command.
*/
public enum ConfirmMode {
/**
* Every command executor needs to confirm before executing a command.
*/
ENABLE,
/**
* Only players need to confirm before executing a command. Command blocks and console will not need to confirm.
*/
PLAYER_ONLY,
/**
* Everyone except command blocks will not need to confirm before executing a command.
*/
DISABLE_COMMAND_BLOCKS,
/**
* Everyone except console will not need to confirm before executing a command.
*/
DISABLE_CONSOLE,
/**
* Everyone will not need to confirm before executing a command.
*/
DISABLE,
}
@@ -0,0 +1,27 @@
package org.mvplugins.multiverse.core.command.queue;
import co.aikar.locales.MessageKey;
import co.aikar.locales.MessageKeyProvider;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.utils.result.FailureReason;
public enum RunQueuedFailedReason implements FailureReason {
NO_COMMAND_IN_QUEUE(MVCorei18n.QUEUECOMMAND_NOCOMMANDINQUEUE),
INVALID_OTP(MVCorei18n.QUEUECOMMAND_INVALIDOTP),
COMMAND_EXECUTION_ERROR(MVCorei18n.QUEUECOMMAND_COMMANDEXECUTIONERROR),
;
private final MessageKeyProvider message;
RunQueuedFailedReason(MessageKeyProvider message) {
this.message = message;
}
/**
* {@inheritDoc}
*/
@Override
public MessageKey getMessageKey() {
return message.getMessageKey();
}
}
@@ -1,4 +1,4 @@
/**
* Manager queuing of dangerous commands in need of confirmation.
*/
package org.mvplugins.multiverse.core.commandtools.queue;
package org.mvplugins.multiverse.core.command.queue;
@@ -0,0 +1,43 @@
package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.anchor.MultiverseAnchor;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
@Service
final class AnchorDeleteCommand extends CoreCommand {
private final AnchorManager anchorManager;
@Inject
AnchorDeleteCommand(@NotNull AnchorManager anchorManager) {
this.anchorManager = anchorManager;
}
@Subcommand("anchor delete")
@CommandPermission("multiverse.core.anchor.delete")
@CommandCompletion("@anchornames")
@Syntax("<name>")
@Description("")
void onAnchorDeleteCommand(
MVCommandIssuer issuer,
@Syntax("<name>")
@Description("")
MultiverseAnchor anchor) {
if (anchorManager.deleteAnchor(anchor).isSuccess()) {
issuer.sendMessage("&aAnchor &f" + anchor.getName() + "&a deleted.");
} else {
issuer.sendMessage("&cFailed to delete anchor.");
}
}
}
@@ -0,0 +1,75 @@
package org.mvplugins.multiverse.core.commands;
import java.util.List;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import jakarta.inject.Inject;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.flags.PageFilterFlags;
import org.mvplugins.multiverse.core.display.ContentDisplay;
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
import org.mvplugins.multiverse.core.display.parsers.ListContentProvider;
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
@Service
final class AnchorListCommand extends CoreCommand {
private final AnchorManager anchorManager;
private final LocationManipulation locationManipulation;
private final PageFilterFlags flags;
@Inject
AnchorListCommand(
@NotNull AnchorManager anchorManager,
@NotNull LocationManipulation locationManipulation,
@NotNull PageFilterFlags flags
) {
this.anchorManager = anchorManager;
this.locationManipulation = locationManipulation;
this.flags = flags;
}
@Subcommand("anchor list")
@CommandPermission("multiverse.core.anchor.list")
@CommandCompletion("@flags:groupName=" + PageFilterFlags.NAME)
@Syntax("[--page <page>] [--filter <filter>]")
@Description("")
void onAnchorListCommand(
MVCommandIssuer issuer,
@Optional
@Syntax("[--page <page>] [--filter <filter>]")
@Description("")
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
ContentDisplay.create()
.addContent(ListContentProvider.forContent(getAnchors(issuer.getPlayer())))
.withSendHandler(PagedSendHandler.create()
.withHeader("&3==== [ Multiverse Anchors ] ====")
.doPagination(true)
.withTargetPage(parsedFlags.flagValue(flags.page, 1))
.withFilter(parsedFlags.flagValue(flags.filter, DefaultContentFilter.get())))
.send(issuer);
}
private List<String> getAnchors(Player player) {
return anchorManager.getAnchors(player).stream()
.map(anchor ->
"&a%s&7 - &f%s".formatted(
anchor.getName(), locationManipulation.locationToString(anchor.getLocation())))
.toList();
}
}
@@ -0,0 +1,89 @@
package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Flags;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import jakarta.inject.Inject;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
@Service
final class AnchorSetCommand extends CoreCommand {
private final AnchorManager anchorManager;
private final LocationManipulation locationManipulation;
@Inject
AnchorSetCommand(
@NotNull AnchorManager anchorManager,
@NotNull LocationManipulation locationManipulation
) {
this.anchorManager = anchorManager;
this.locationManipulation = locationManipulation;
}
@Subcommand("anchor set")
@CommandPermission("multiverse.core.anchor.create")
@CommandCompletion("@empty @locations")
@Syntax("<name> [location]")
@Description("")
void onAnchorSetCommand(
MVCommandIssuer issuer,
@Flags("resolve=issuerOnly")
Player player,
@Single
@Syntax("<name>")
@Description("")
//TODO: Check if anchor name is valid or exists already.
String anchorName) {
Location anchorLocation = player.getLocation();
if (anchorManager.setAnchor(anchorName, anchorLocation).isSuccess()) {
sendAnchorSetSuccessMessage(issuer, anchorName, locationManipulation.locationToString(anchorLocation));
} else {
sendAnchorSetFailedMessage(issuer, anchorName);
}
}
@Subcommand("anchor set")
@CommandPermission("multiverse.core.anchor.create")
@CommandCompletion("@anchornames @locations")
@Syntax("<name> [location]")
@Description("")
void onAnchorSetCommand(
MVCommandIssuer issuer,
@Syntax("<name>")
@Description("")
String anchorName,
@Single
@Syntax("[location]")
@Description("")
String locationString) {
if (anchorManager.setAnchor(anchorName, locationString).isSuccess()) {
sendAnchorSetSuccessMessage(issuer, anchorName, locationString);
} else {
sendAnchorSetFailedMessage(issuer, anchorName);
}
}
private void sendAnchorSetSuccessMessage(MVCommandIssuer issuer, String anchorName, String locationString) {
issuer.sendMessage("&aAnchor &f" + anchorName + "&a set to &f" + locationString);
}
private void sendAnchorSetFailedMessage(MVCommandIssuer issuer, String anchorName) {
issuer.sendMessage("&cFailed to set anchor &f" + anchorName + ".");
}
}
@@ -11,28 +11,35 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
import org.mvplugins.multiverse.core.destination.ParsedDestination;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.destination.DestinationInstance;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker;
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
@Service
@CommandAlias("mv")
class CheckCommand extends MultiverseCommand {
class CheckCommand extends CoreCommand {
private final DestinationsProvider destinationsProvider;
private final CorePermissionsChecker corePermissionsChecker;
private final LocationManipulation locationManipulation;
@Inject
CheckCommand(@NotNull MVCommandManager commandManager, @NotNull DestinationsProvider destinationsProvider) {
super(commandManager);
this.destinationsProvider = destinationsProvider;
CheckCommand(
@NotNull CorePermissionsChecker corePermissionsChecker,
@NotNull LocationManipulation locationManipulation
) {
this.corePermissionsChecker = corePermissionsChecker;
this.locationManipulation = locationManipulation;
}
@Subcommand("check")
@CommandPermission("multiverse.core.check")
@CommandCompletion("@players @destinations|@mvworlds")
@CommandCompletion("@players @destinations")
@Syntax("<player> <destination>")
@Description("{@@mv-core.check.description}")
void onCheckCommand(
@@ -44,11 +51,32 @@ class CheckCommand extends MultiverseCommand {
@Syntax("<destination>")
@Description("{@@mv-core.check.destination.description}")
ParsedDestination<?> destination) {
issuer.sendInfo(MVCorei18n.CHECK_CHECKING,
"{player}", player.getName(),
"{destination}", destination.toString());
// TODO: More detailed output on permissions required.
// this.destinationsProvider.checkTeleportPermissions(issuer, player, destination);
DestinationInstance<?, ?> destination) {
issuer.sendInfo(this.corePermissionsChecker.checkTeleportPermission(player, player, destination)
? MVCorei18n.CHECK_HASPERMISSION
: MVCorei18n.CHECK_NOPERMISSION,
Replace.PLAYER.with(player.getName()),
Replace.DESTINATION.with(destination));
issuer.sendInfo(MVCorei18n.CHECK_LOCATION,
replace("{location}").with(destination.getLocation(player)
.map(locationManipulation::locationToString)
.map(Message::of)
.getOrElse(() -> Message.of(MVCorei18n.GENERIC_NULL, "Null!"))));
// TODO: Show permission required for this particular destination
}
@Service
private final static class LegacyAlias extends CheckCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(@NotNull CorePermissionsChecker corePermissionsChecker, @NotNull LocationManipulation locationManipulation) {
super(corePermissionsChecker, locationManipulation);
}
@Override
@CommandAlias("mvcheck")
void onCheckCommand(MVCommandIssuer issuer, Player player, DestinationInstance<?, ?> destination) {
super.onCheckCommand(issuer, player, destination);
}
}
}
@@ -12,44 +12,36 @@ import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.options.CloneWorldOptions;
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
@Service
@CommandAlias("mv")
class CloneCommand extends MultiverseCommand {
class CloneCommand extends CoreCommand {
private final WorldManager worldManager;
private final CommandFlag RESET_WORLD_CONFIG_FLAG = flag(CommandFlag.builder("--reset-world-config")
.addAlias("-wc")
.build());
private final CommandFlag RESET_GAMERULES_FLAG = flag(CommandFlag.builder("--reset-gamerules")
.addAlias("-gm")
.build());
private final CommandFlag RESET_WORLD_BORDER_FLAG = flag(CommandFlag.builder("--reset-world-border")
.addAlias("-wb")
.build());
private final CloneCommand.Flags flags;
@Inject
CloneCommand(@NotNull MVCommandManager commandManager, @NotNull WorldManager worldManager) {
super(commandManager);
CloneCommand(@NotNull WorldManager worldManager, @NotNull Flags flags) {
this.worldManager = worldManager;
this.flags = flags;
}
@Subcommand("clone")
@CommandPermission("multiverse.core.clone")
@CommandCompletion("@mvworlds:scope=loaded @empty @flags:groupName=mvclonecommand")
@Syntax("<world> <new world name>")
@CommandCompletion("@mvworlds:scope=loaded @empty @flags:groupName=" + Flags.NAME)
@Syntax("<world> <new-world-name> [--reset-world-config --reset-gamerules --reset-world-border]")
@Description("{@@mv-core.clone.description}")
void onCloneCommand(
MVCommandIssuer issuer,
@@ -58,28 +50,72 @@ class CloneCommand extends MultiverseCommand {
@Description("{@@mv-core.clone.world.description}")
LoadedMultiverseWorld world,
@Syntax("<new world name>")
@Syntax("<new-world-name>")
@Description("{@@mv-core.clone.newWorld.description}")
String newWorldName,
@Optional
@Syntax(/* TODO */ "")
@Syntax("[--reset-world-config --reset-gamerules --reset-world-border]")
@Description("{@@mv-core.regen.other.description}")
String[] flags) {
ParsedCommandFlags parsedFlags = parseFlags(flags);
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
issuer.sendInfo(MVCorei18n.CLONE_CLONING, "{world}", world.getName(), "{newworld}", newWorldName);
issuer.sendInfo(MVCorei18n.CLONE_CLONING,
Replace.WORLD.with(world.getName()),
replace("{newworld}").with(newWorldName));
CloneWorldOptions cloneWorldOptions = CloneWorldOptions.fromTo(world, newWorldName)
.keepWorldConfig(!parsedFlags.hasFlag(RESET_WORLD_CONFIG_FLAG))
.keepGameRule(!parsedFlags.hasFlag(RESET_GAMERULES_FLAG))
.keepWorldBorder(!parsedFlags.hasFlag(RESET_WORLD_BORDER_FLAG));
.keepWorldConfig(!parsedFlags.hasFlag(flags.resetWorldConfig))
.keepGameRule(!parsedFlags.hasFlag(flags.resetGamerules))
.keepWorldBorder(!parsedFlags.hasFlag(flags.resetWorldBorder))
.saveBukkitWorld(!parsedFlags.hasFlag(flags.noSave));
worldManager.cloneWorld(cloneWorldOptions)
.onSuccess(newWorld -> {
Logging.fine("World clone success: " + newWorld);
issuer.sendInfo(MVCorei18n.CLONE_SUCCESS, "{world}", newWorld.getName());
issuer.sendInfo(MVCorei18n.CLONE_SUCCESS, Replace.WORLD.with(newWorld.getName()));
}).onFailure(failure -> {
Logging.fine("World clone failure: " + failure);
issuer.sendError(failure.getFailureMessage());
});
}
@Service
private static final class Flags extends FlagBuilder {
private static final String NAME = "mvclone";
@Inject
private Flags(@NotNull CommandFlagsManager flagsManager) {
super(NAME, flagsManager);
}
private final CommandFlag resetWorldConfig = flag(CommandFlag.builder("--reset-world-config")
.addAlias("-wc")
.build());
private final CommandFlag resetGamerules = flag(CommandFlag.builder("--reset-gamerules")
.addAlias("-gm")
.build());
private final CommandFlag resetWorldBorder = flag(CommandFlag.builder("--reset-world-border")
.addAlias("-wb")
.build());
private final CommandFlag noSave = flag(CommandFlag.builder("--no-save")
.addAlias("-s")
.build());
}
@Service
private final static class LegacyAlias extends CloneCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(@NotNull WorldManager worldManager, @NotNull Flags flags) {
super(worldManager, flags);
}
@Override
@CommandAlias("mvcl|mvclone")
void onCloneCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, String newWorldName, String[] flags) {
super.onCloneCommand(issuer, world, newWorldName, flags);
}
}
}
@@ -12,21 +12,22 @@ import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.config.MVCoreConfig;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
@Service
@CommandAlias("mv")
class ConfigCommand extends MultiverseCommand {
class ConfigCommand extends CoreCommand {
private final MVCoreConfig config;
private final CoreConfig config;
@Inject
ConfigCommand(@NotNull MVCommandManager commandManager, @NotNull MVCoreConfig config) {
super(commandManager);
ConfigCommand(@NotNull CoreConfig config) {
this.config = config;
}
@@ -34,17 +35,17 @@ class ConfigCommand extends MultiverseCommand {
@CommandPermission("multiverse.core.config")
@CommandCompletion("@mvconfigs @mvconfigvalues")
@Syntax("<name> [value]")
@Description("Show or set a config value.")
@Description("{@@mv-core.config.description}")
void onConfigCommand(
MVCommandIssuer issuer,
@Syntax("<name>")
@Description("The name of the config to set or show.")
@Description("{@@mv-core.config.name.description}")
String name,
@Optional
@Syntax("[value]")
@Description("The value to set the config to. If not specified, the current value will be shown.")
@Description("{@@mv-core.config.value.description}")
String value) {
if (value == null) {
showConfigValue(issuer, name);
@@ -55,17 +56,40 @@ class ConfigCommand extends MultiverseCommand {
private void showConfigValue(MVCommandIssuer issuer, String name) {
config.getStringPropertyHandle().getProperty(name)
.onSuccess(value -> issuer.sendMessage(name + "is currently set to " + value))
.onFailure(e -> issuer.sendMessage(e.getMessage()));
.onSuccess(value -> issuer.sendMessage(MVCorei18n.CONFIG_SHOW_SUCCESS,
Replace.NAME.with(name),
Replace.VALUE.with(value)))
.onFailure(e -> issuer.sendMessage(MVCorei18n.CONFIG_SHOW_ERROR,
Replace.NAME.with(name),
Replace.ERROR.with(e)));
}
private void updateConfigValue(MVCommandIssuer issuer, String name, String value) {
config.getStringPropertyHandle().setPropertyString(name, value)
var stringPropertyHandle = config.getStringPropertyHandle();
stringPropertyHandle.setPropertyString(issuer.getIssuer(), name, value)
.onSuccess(ignore -> {
config.save();
issuer.sendMessage("Successfully set " + name + " to " + value);
issuer.sendMessage(MVCorei18n.CONFIG_SET_SUCCESS,
Replace.NAME.with(name),
Replace.VALUE.with(stringPropertyHandle.getProperty(name).getOrNull()));
})
.onFailure(ignore -> issuer.sendMessage("Unable to set " + name + " to " + value + "."))
.onFailure(MultiverseException.class, e -> Option.of(e.getMVMessage()).peek(issuer::sendError));
.onFailure(e -> issuer.sendMessage(MVCorei18n.CONFIG_SET_ERROR,
Replace.NAME.with(name),
Replace.VALUE.with(value),
Replace.ERROR.with(e)));
}
@Service
private final static class LegacyAlias extends ConfigCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(@NotNull CoreConfig config) {
super(config);
}
@Override
@CommandAlias("mvconfig|mvconf")
void onConfigCommand(MVCommandIssuer issuer, String name, String value) {
super.onConfigCommand(issuer, name, value);
}
}
}
@@ -1,30 +1,54 @@
package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.BukkitCommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Default;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.queue.CommandQueueManager;
@Service
@CommandAlias("mv")
class ConfirmCommand extends MultiverseCommand {
class ConfirmCommand extends CoreCommand {
@NotNull
private final CommandQueueManager commandQueueManager;
@Inject
ConfirmCommand(@NotNull MVCommandManager commandManager) {
super(commandManager);
ConfirmCommand(@NotNull CommandQueueManager commandQueueManager) {
this.commandQueueManager = commandQueueManager;
}
@Subcommand("confirm")
@CommandPermission("multiverse.core.confirm")
@Syntax("[otp]")
@Description("{@@mv-core.confirm.description}")
void onConfirmCommand(@NotNull BukkitCommandIssuer issuer) {
this.commandManager.getCommandQueueManager().runQueuedCommand(issuer.getIssuer());
void onConfirmCommand(
MVCommandIssuer issuer,
@Default("0")
String otp) {
this.commandQueueManager.runQueuedCommand(issuer, otp)
.onFailure(failure -> issuer.sendError(failure.getFailureMessage()));
}
@Service
private final static class LegacyAlias extends ConfirmCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(@NotNull CommandQueueManager commandQueueCommand) {
super(commandQueueCommand);
}
@Override
@CommandAlias("mvconfirm")
void onConfirmCommand(MVCommandIssuer issuer, String otp) {
super.onConfirmCommand(issuer, otp);
}
}
}
@@ -1,6 +1,5 @@
package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.BukkitCommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
@@ -11,42 +10,60 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.api.LocationManipulation;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
@Service
@CommandAlias("mv")
class CoordinatesCommand extends MultiverseCommand {
class CoordinatesCommand extends CoreCommand {
private final LocationManipulation locationManipulation;
@Inject
CoordinatesCommand(
@NotNull MVCommandManager commandManager,
@NotNull LocationManipulation locationManipulation) {
super(commandManager);
CoordinatesCommand(@NotNull LocationManipulation locationManipulation) {
this.locationManipulation = locationManipulation;
}
@Subcommand("coordinates|coords|coord|co")
@Subcommand("coordinates")
@CommandPermission("multiverse.core.coord")
@Description("{@@mv-core.coordinates.description}")
void onCoordinatesCommand(
BukkitCommandIssuer issuer,
MVCommandIssuer issuer,
@Flags("resolve=issuerOnly")
Player player,
@Flags("resolve=issuerOnly")
LoadedMultiverseWorld world) {
MultiverseWorld world) {
issuer.sendInfo(MVCorei18n.COORDINATES_INFO_TITLE);
issuer.sendInfo(MVCorei18n.COORDINATES_INFO_WORLD, "{world}", world.getName());
issuer.sendInfo(MVCorei18n.COORDINATES_INFO_ALIAS, "{alias}", world.getAlias());
issuer.sendInfo(MVCorei18n.COORDINATES_INFO_WORLDSCALE, "{scale}", String.valueOf(world.getScale()));
issuer.sendInfo(MVCorei18n.COORDINATES_INFO_COORDINATES, "{coordinates}", locationManipulation.strCoords(player.getLocation()));
issuer.sendInfo(MVCorei18n.COORDINATES_INFO_DIRECTION, "{direction}", locationManipulation.getDirection(player.getLocation()));
issuer.sendInfo(MVCorei18n.COORDINATES_INFO_WORLD, Replace.WORLD.with(world.getName()));
issuer.sendInfo(MVCorei18n.COORDINATES_INFO_ALIAS, replace("{alias}").with(world.getAliasOrName()));
issuer.sendInfo(MVCorei18n.COORDINATES_INFO_WORLDSCALE,
replace("{scale}").with(String.valueOf(world.getScale())));
issuer.sendInfo(MVCorei18n.COORDINATES_INFO_COORDINATES,
replace("{coordinates}").with(locationManipulation.strCoords(player.getLocation())));
issuer.sendInfo(MVCorei18n.COORDINATES_INFO_DIRECTION,
replace("{direction}").with(locationManipulation.getDirection(player.getLocation())));
}
@Service
private static final class LegacyAlias extends CoordinatesCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(@NotNull LocationManipulation locationManipulation) {
super(locationManipulation);
}
@Override
@CommandAlias("mvcoord|mvco")
@Subcommand("coords|coord|co")
void onCoordinatesCommand(MVCommandIssuer issuer, Player player, MultiverseWorld world) {
super.onCoordinatesCommand(issuer, player, world);
}
}
}
@@ -0,0 +1,16 @@
package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.annotation.CommandAlias;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Contract;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.command.MultiverseCommand;
/**
* Represents a command that is part of the Multiverse-Core plugin.
*/
@Contract
@CommandAlias("mv")
public abstract class CoreCommand extends MultiverseCommand {
}
@@ -1,8 +1,8 @@
package org.mvplugins.multiverse.core.commands;
import java.util.Collections;
import java.util.Random;
import co.aikar.commands.ACFUtil;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
@@ -17,62 +17,43 @@ import org.bukkit.WorldType;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag;
import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
import org.mvplugins.multiverse.core.utils.result.Attempt.Failure;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.biomeprovider.BiomeProviderFactory;
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
import org.mvplugins.multiverse.core.world.options.CreateWorldOptions;
import org.mvplugins.multiverse.core.world.reasons.CreateFailureReason;
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
@Service
@CommandAlias("mv")
class CreateCommand extends MultiverseCommand {
class CreateCommand extends CoreCommand {
private final WorldManager worldManager;
private GeneratorProvider generatorProvider;
private final CommandValueFlag<String> SEED_FLAG = flag(CommandValueFlag.builder("--seed", String.class)
.addAlias("-s")
.completion(input -> Collections.singleton(String.valueOf(new Random().nextLong())))
.build());
private final CommandValueFlag<String> GENERATOR_FLAG = flag(CommandValueFlag
.builder("--generator", String.class)
.addAlias("-g")
.completion(input -> generatorProvider.suggestGeneratorString(input))
.build());
private final CommandValueFlag<WorldType> WORLD_TYPE_FLAG = flag(CommandValueFlag
.enumBuilder("--world-type", WorldType.class)
.addAlias("-t")
.build());
private final CommandFlag NO_ADJUST_SPAWN_FLAG = flag(CommandFlag.builder("--no-adjust-spawn")
.addAlias("-n")
.build());
private final CommandFlag NO_STRUCTURES_FLAG = flag(CommandFlag.builder("--no-structures")
.addAlias("-a")
.build());
private final CreateCommand.Flags flags;
@Inject
CreateCommand(
@NotNull MVCommandManager commandManager,
@NotNull WorldManager worldManager,
@NotNull GeneratorProvider generatorProvider) {
super(commandManager);
CreateCommand(@NotNull WorldManager worldManager, @NotNull Flags flags) {
this.worldManager = worldManager;
this.generatorProvider = generatorProvider;
this.flags = flags;
}
@Subcommand("create")
@CommandPermission("multiverse.core.create")
@CommandCompletion("@empty @environments @flags:groupName=mvcreatecommand")
@Syntax("<name> <environment> --seed [seed] --generator [generator[:id]] --world-type [worldtype] --adjust-spawn --no-structures")
@CommandCompletion("@empty @environments @flags:groupName=" + Flags.NAME)
@Syntax("<name> <environment> [--seed <seed> --generator <generator[:id]> --world-type <worldtype> --adjust-spawn "
+ "--no-structures --biome <biome>]")
@Description("{@@mv-core.create.description}")
void onCreateCommand(
MVCommandIssuer issuer,
@@ -86,41 +67,132 @@ class CreateCommand extends MultiverseCommand {
World.Environment environment,
@Optional
@Syntax("--seed [seed] --generator [generator[:id]] --world-type [worldtype] --adjust-spawn --no-structures")
@Syntax("[--seed <seed> --generator <generator[:id]> --world-type <worldtype> --adjust-spawn "
+ "--no-structures --biome <biome>]")
@Description("{@@mv-core.create.flags.description}")
String[] flags) {
ParsedCommandFlags parsedFlags = parseFlags(flags);
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES,
"{worldName}", worldName);
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_ENVIRONMENT,
"{environment}", environment.name());
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_SEED,
"{seed}", parsedFlags.flagValue(SEED_FLAG, "RANDOM"));
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_WORLDTYPE,
"{worldType}", parsedFlags.flagValue(WORLD_TYPE_FLAG, WorldType.NORMAL).name());
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_ADJUSTSPAWN,
"{adjustSpawn}", String.valueOf(!parsedFlags.hasFlag(NO_ADJUST_SPAWN_FLAG)));
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_GENERATOR,
"{generator}", parsedFlags.flagValue(GENERATOR_FLAG, ""));
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_STRUCTURES,
"{structures}", String.valueOf(!parsedFlags.hasFlag(NO_STRUCTURES_FLAG)));
messageWorldDetails(issuer, worldName, environment, parsedFlags);
issuer.sendInfo(MVCorei18n.CREATE_LOADING);
worldManager.createWorld(CreateWorldOptions.worldName(worldName)
.biome(parsedFlags.flagValue(flags.biome, ""))
.environment(environment)
.seed(parsedFlags.flagValue(SEED_FLAG))
.worldType(parsedFlags.flagValue(WORLD_TYPE_FLAG, WorldType.NORMAL))
.useSpawnAdjust(!parsedFlags.hasFlag(NO_ADJUST_SPAWN_FLAG))
.generator(parsedFlags.flagValue(GENERATOR_FLAG, ""))
.generateStructures(!parsedFlags.hasFlag(NO_STRUCTURES_FLAG)))
.onSuccess(newWorld -> {
Logging.fine("World create success: " + newWorld);
issuer.sendInfo(MVCorei18n.CREATE_SUCCESS, "{world}", newWorld.getName());
}).onFailure(failure -> {
Logging.fine("World create failure: " + failure);
issuer.sendError(failure.getFailureMessage());
});
.seed(parsedFlags.flagValue(flags.seed))
.worldType(parsedFlags.flagValue(flags.worldType, WorldType.NORMAL))
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn))
.generator(parsedFlags.flagValue(flags.generator, ""))
.generatorSettings(parsedFlags.flagValue(flags.generatorSettings, ""))
.generateStructures(!parsedFlags.hasFlag(flags.noStructures)))
.onSuccess(newWorld -> messageSuccess(issuer, newWorld))
.onFailure(failure -> messageFailure(issuer, failure));
}
private void messageWorldDetails(MVCommandIssuer issuer, String worldName,
World.Environment environment, ParsedCommandFlags parsedFlags) {
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES,
replace("{worldName}").with(worldName));
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_ENVIRONMENT,
replace("{environment}").with(environment.name()));
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_SEED,
replace("{seed}").with(parsedFlags.flagValue(flags.seed, "RANDOM")));
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_WORLDTYPE,
replace("{worldType}").with(parsedFlags.flagValue(flags.worldType, WorldType.NORMAL).name()));
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_ADJUSTSPAWN,
replace("{adjustSpawn}").with(String.valueOf(!parsedFlags.hasFlag(flags.noAdjustSpawn))));
if (parsedFlags.hasFlag(flags.biome)) {
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_BIOME,
replace("{biome}").with(parsedFlags.flagValue(flags.biome)));
}
if (parsedFlags.hasFlag(flags.generator)) {
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_GENERATOR,
replace("{generator}").with(parsedFlags.flagValue(flags.generator)));
}
if (parsedFlags.hasFlag(flags.generatorSettings)) {
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_GENERATORSETTINGS,
replace("{generatorSettings}").with(parsedFlags.flagValue(flags.generatorSettings)));
}
issuer.sendInfo(MVCorei18n.CREATE_PROPERTIES_STRUCTURES,
replace("{structures}").with(String.valueOf(!parsedFlags.hasFlag(flags.noStructures))));
}
private void messageSuccess(MVCommandIssuer issuer, LoadedMultiverseWorld newWorld) {
Logging.fine("World create success: " + newWorld);
issuer.sendInfo(MVCorei18n.CREATE_SUCCESS, Replace.WORLD.with(newWorld.getName()));
}
private void messageFailure(MVCommandIssuer issuer, Failure<LoadedMultiverseWorld, CreateFailureReason> failure) {
Logging.fine("World create failure: " + failure);
issuer.sendError(failure.getFailureMessage());
}
@Service
private static final class Flags extends FlagBuilder {
private static final String NAME = "mvcreate";
private GeneratorProvider generatorProvider;
private BiomeProviderFactory biomeProviderFactory;
@Inject
private Flags(
@NotNull CommandFlagsManager flagsManager,
@NotNull GeneratorProvider generatorProvider,
@NotNull BiomeProviderFactory biomeProviderFactory
) {
super(NAME, flagsManager);
this.generatorProvider = generatorProvider;
this.biomeProviderFactory = biomeProviderFactory;
}
private final CommandValueFlag<String> seed = flag(CommandValueFlag.builder("--seed", String.class)
.addAlias("-s")
.completion(input -> Collections.singleton(String.valueOf(ACFUtil.RANDOM.nextLong())))
.build());
private final CommandValueFlag<String> generator = flag(CommandValueFlag
.builder("--generator", String.class)
.addAlias("-g")
.completion(input -> generatorProvider.suggestGeneratorString(input))
.build());
private final CommandValueFlag<String> generatorSettings = flag(CommandValueFlag
.builder("--generator-settings", String.class)
.addAlias("-gs")
.build());
private final CommandValueFlag<WorldType> worldType = flag(CommandValueFlag
.enumBuilder("--world-type", WorldType.class)
.addAlias("-t")
.build());
private final CommandFlag noAdjustSpawn = flag(CommandFlag.builder("--no-adjust-spawn")
.addAlias("-n")
.build());
private final CommandFlag noStructures = flag(CommandFlag.builder("--no-structures")
.addAlias("-a")
.build());
private final CommandValueFlag<String> biome = flag(CommandValueFlag.builder("--biome", String.class)
.addAlias("-b")
.completion(input -> biomeProviderFactory.suggestBiomeString(input))
.build());
}
@Service
private static final class LegacyAlias extends CreateCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(@NotNull WorldManager worldManager, @NotNull Flags flags) {
super(worldManager, flags);
}
@Override
@CommandAlias("mvcreate|mvc")
void onCreateCommand(MVCommandIssuer issuer, String worldName, World.Environment environment, String[] flags) {
super.onCreateCommand(issuer, worldName, environment, flags);
}
}
}
@@ -12,20 +12,18 @@ import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.config.MVCoreConfig;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
@Service
@CommandAlias("mv")
class DebugCommand extends MultiverseCommand {
class DebugCommand extends CoreCommand {
private final MVCoreConfig config;
private final CoreConfig config;
@Inject
DebugCommand(@NotNull MVCommandManager commandManager, @NotNull MVCoreConfig config) {
super(commandManager);
DebugCommand(@NotNull CoreConfig config) {
this.config = config;
}
@@ -62,4 +60,18 @@ class DebugCommand extends MultiverseCommand {
issuer.sendInfo(MVCorei18n.DEBUG_INFO_ON, "{level}", String.valueOf(debugLevel));
Logging.fine("Multiverse Debug ENABLED.");
}
@Service
private static final class LegacyAlias extends DebugCommand implements LegacyAliasCommand {
@Inject
public LegacyAlias(@NotNull CoreConfig config) {
super(config);
}
@Override
@CommandAlias("mvdebug")
void onChangeDebugCommand(BukkitCommandIssuer issuer, int level) {
super.onChangeDebugCommand(issuer, level);
}
}
}
@@ -1,12 +1,8 @@
package org.mvplugins.multiverse.core.commands;
import java.util.Collections;
import co.aikar.commands.MessageType;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Conditions;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Single;
@@ -17,85 +13,114 @@ import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.commandtools.queue.QueuedCommand;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.utils.result.Async;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
import org.mvplugins.multiverse.core.command.queue.CommandQueueManager;
import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
import org.mvplugins.multiverse.core.utils.WorldTickDeferrer;
import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions;
@Service
@CommandAlias("mv")
class DeleteCommand extends MultiverseCommand {
class DeleteCommand extends CoreCommand {
private final CommandQueueManager commandQueueManager;
private final WorldManager worldManager;
private final PlayerWorldTeleporter playerWorldTeleporter;
private final CommandFlag REMOVE_PLAYERS_FLAG = flag(CommandFlag.builder("--remove-players")
.addAlias("-r")
.build());
private final WorldTickDeferrer worldTickDeferrer;
private final RemovePlayerFlags flags;
@Inject
DeleteCommand(
@NotNull MVCommandManager commandManager,
@NotNull CommandQueueManager commandQueueManager,
@NotNull WorldManager worldManager,
@NotNull PlayerWorldTeleporter playerWorldTeleporter) {
super(commandManager);
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
@NotNull WorldTickDeferrer worldTickDeferrer,
@NotNull RemovePlayerFlags flags
) {
this.commandQueueManager = commandQueueManager;
this.worldManager = worldManager;
this.playerWorldTeleporter = playerWorldTeleporter;
this.worldTickDeferrer = worldTickDeferrer;
this.flags = flags;
}
@Subcommand("delete")
@CommandPermission("multiverse.core.delete")
@CommandCompletion("@mvworlds:scope=loaded @flags:groupName=mvdeletecommand")
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + RemovePlayerFlags.NAME)
@Syntax("<world>")
@Description("{@@mv-core.delete.description}")
void onDeleteCommand(
MVCommandIssuer issuer,
@Single
@Conditions("worldname:scope=loaded")
@Syntax("<world>")
@Description("The world you want to delete.")
LoadedMultiverseWorld world,
MultiverseWorld world,
@Optional
@Syntax("[--remove-players]")
@Description("")
String[] flags) {
ParsedCommandFlags parsedFlags = parseFlags(flags);
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
this.commandManager.getCommandQueueManager().addToQueue(new QueuedCommand(
issuer.getIssuer(),
() -> {
runDeleteCommand(issuer, world, parsedFlags);
}, this.commandManager.formatMessage(issuer, MessageType.INFO, MVCorei18n.DELETE_PROMPT,
"{world}", world.getName())));
commandQueueManager.addToQueue(CommandQueuePayload
.issuer(issuer)
.action(() -> runDeleteCommand(issuer, world, parsedFlags))
.prompt(Message.of(MVCorei18n.DELETE_PROMPT, "",
Replace.WORLD.with(world.getName()))));
}
private void runDeleteCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, ParsedCommandFlags parsedFlags) {
issuer.sendInfo(MVCorei18n.DELETE_DELETING, "{world}", world.getName());
private void runDeleteCommand(MVCommandIssuer issuer, MultiverseWorld world, ParsedCommandFlags parsedFlags) {
issuer.sendInfo(MVCorei18n.DELETE_DELETING, Replace.WORLD.with(world.getName()));
var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
? playerWorldTeleporter.removeFromWorld(world)
: Async.completedFuture(Collections.emptyList());
var future = parsedFlags.hasFlag(flags.removePlayers)
&& world.isLoaded()
&& world instanceof LoadedMultiverseWorld loadedWorld
? playerWorldTeleporter.removeFromWorld(loadedWorld)
: AsyncAttemptsAggregate.emptySuccess();
future.thenRun(() -> doWorldDeleting(issuer, world));
future.onSuccess(() -> worldTickDeferrer.deferWorldTick(() -> doWorldDeleting(issuer, world)))
.onFailure(() -> issuer.sendError("Failed to teleport one or more players out of the world!"));
}
private void doWorldDeleting(MVCommandIssuer issuer, LoadedMultiverseWorld world) {
worldManager.deleteWorld(world)
private void doWorldDeleting(MVCommandIssuer issuer, MultiverseWorld world) {
worldManager.deleteWorld(DeleteWorldOptions.world(world))
.onSuccess(deletedWorldName -> {
Logging.fine("World delete success: " + deletedWorldName);
issuer.sendInfo(MVCorei18n.DELETE_SUCCESS, "{world}", deletedWorldName);
issuer.sendInfo(MVCorei18n.DELETE_SUCCESS, Replace.WORLD.with(deletedWorldName));
}).onFailure(failure -> {
Logging.fine("World delete failure: " + failure);
issuer.sendError(failure.getFailureMessage());
});
}
@Service
private static final class LegacyAlias extends DeleteCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(
@NotNull CommandQueueManager commandQueueManager,
@NotNull WorldManager worldManager,
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
@NotNull WorldTickDeferrer worldTickDeferrer,
@NotNull RemovePlayerFlags flags) {
super(commandQueueManager, worldManager, playerWorldTeleporter, worldTickDeferrer, flags);
}
@Override
@CommandAlias("mvdelete")
void onDeleteCommand(MVCommandIssuer issuer, MultiverseWorld world, String[] flags) {
super.onDeleteCommand(issuer, world, flags);
}
}
}
@@ -1,282 +1,81 @@
package org.mvplugins.multiverse.core.commands;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import com.dumptruckman.minecraft.util.Logging;
import jakarta.inject.Inject;
import org.apache.commons.lang.StringUtils;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag;
import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.event.MVVersionEvent;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.utils.webpaste.PasteFailedException;
import org.mvplugins.multiverse.core.utils.webpaste.PasteService;
import org.mvplugins.multiverse.core.utils.webpaste.PasteServiceFactory;
import org.mvplugins.multiverse.core.utils.webpaste.PasteServiceType;
import org.mvplugins.multiverse.core.world.WorldManager;
import static org.mvplugins.multiverse.core.utils.file.FileUtils.getBukkitConfig;
import static org.mvplugins.multiverse.core.utils.file.FileUtils.getServerProperties;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
import org.mvplugins.multiverse.core.commands.DumpsLogPoster.LogsType;
import org.mvplugins.multiverse.core.commands.DumpsLogPoster.UploadType;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
@Service
@CommandAlias("mv")
class DumpsCommand extends MultiverseCommand {
final class DumpsCommand extends CoreCommand {
private final MultiverseCore plugin;
private final WorldManager worldManager;
private final CommandValueFlag<LogsTypeOption> LOGS_FLAG = flag(CommandValueFlag
.enumBuilder("--logs", LogsTypeOption.class)
.addAlias("-l")
.build());
private final CommandValueFlag<ServiceTypeOption> UPLOAD_FLAG = flag(CommandValueFlag
.enumBuilder("--upload", ServiceTypeOption.class)
.addAlias("-u")
.build());
// Does not upload logs or plugin list (except if --logs mclogs is there)
private final CommandFlag PARANOID_FLAG = flag(CommandFlag.builder("--paranoid")
.addAlias("-p")
.build());
private final DumpsService dumpsService;
private final DumpsCommand.Flags flags;
@Inject
DumpsCommand(@NotNull MVCommandManager commandManager,
@NotNull MultiverseCore plugin,
@NotNull WorldManager worldManager) {
super(commandManager);
this.plugin = plugin;
this.worldManager = worldManager;
}
private enum ServiceTypeOption {
PASTEGG,
PASTESDEV
}
private enum LogsTypeOption {
APPEND,
MCLOGS
DumpsCommand(@NotNull DumpsService dumpsService, @NotNull Flags flags) {
this.dumpsService = dumpsService;
this.flags = flags;
}
@Subcommand("dumps")
@CommandPermission("multiverse.core.dumps")
@CommandCompletion("@flags:groupName=mvdumpscommand")
@CommandCompletion("@flags:groupName=" + Flags.NAME)
@Syntax("[--logs <mclogs | append>] [--upload <pastesdev | pastegg>] [--paranoid]")
@Description("{@@mv-core.dumps.description}")
void onDumpsCommand(
CommandIssuer issuer,
MVCommandIssuer issuer,
@Optional
@Syntax("[--logs <mclogs | append>] [--upload <pastesdev | pastegg>] [--paranoid]")
String[] flags) {
final ParsedCommandFlags parsedFlags = parseFlags(flags);
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
// Grab all our flags
final boolean paranoid = parsedFlags.hasFlag(PARANOID_FLAG);
final LogsTypeOption logsType = parsedFlags.flagValue(LOGS_FLAG, LogsTypeOption.MCLOGS);
final ServiceTypeOption servicesType = parsedFlags.flagValue(UPLOAD_FLAG, ServiceTypeOption.PASTEGG);
boolean paranoid = parsedFlags.hasFlag(flags.paranoid);
LogsType logsType = parsedFlags.flagValue(flags.logs, LogsType.MCLOGS);
UploadType servicesType = parsedFlags.flagValue(flags.upload, UploadType.PASTESDEV);
// Initialise and add info to the debug event
MVVersionEvent versionEvent = new MVVersionEvent();
this.addDebugInfoToEvent(versionEvent);
plugin.getServer().getPluginManager().callEvent(versionEvent);
// Add plugin list if user isn't paranoid
if (!paranoid) {
versionEvent.putDetailedVersionInfo("plugins.md", "# Plugins\n\n" + getPluginList());
}
BukkitRunnable logPoster = new BukkitRunnable() {
@Override
public void run() {
// TODO: Refactor into smaller methods
Logging.finer("Logs type is: " + logsType);
Logging.finer("Services is: " + servicesType);
// Deal with logs flag
if (!paranoid) {
switch (logsType) {
case MCLOGS -> issuer.sendInfo(MVCorei18n.DUMPS_URL_LIST,
"{service}", "Logs",
"{link}", postToService(PasteServiceType.MCLOGS, true, getLogs(), null));
case APPEND -> versionEvent.putDetailedVersionInfo("latest.log", getLogs());
}
}
// Get the files from the event
final Map<String, String> files = versionEvent.getDetailedVersionInfo();
// Deal with uploading debug info
switch (servicesType) {
case PASTEGG -> issuer.sendInfo(MVCorei18n.DUMPS_URL_LIST,
"{service}", "paste.gg",
"{link}", postToService(PasteServiceType.PASTEGG, true, null, files));
case PASTESDEV -> issuer.sendInfo(MVCorei18n.DUMPS_URL_LIST,
"{service}", "pastes.dev",
"{link}", postToService(PasteServiceType.PASTESDEV, true, null, files));
}
}
};
// Run the uploader async as it could take some time to upload the debug info
logPoster.runTaskAsynchronously(plugin);
dumpsService.postLogs(issuer, logsType, servicesType, paranoid);
}
/**
* Get the contents of the latest.log file
*
* @return A string containing the latest.log file
*/
private String getLogs() {
// Get the Path of latest.log
Path logsPath = plugin.getServer().getWorldContainer().toPath().resolve("logs").resolve("latest.log");
File logsFile = logsPath.toFile();
@Service
private static final class Flags extends FlagBuilder {
if (!logsFile.exists()) {
Logging.warning("Could not read logs/latest.log");
return "Could not find log";
private static final String NAME = "mvdumps";
@Inject
private Flags(@NotNull CommandFlagsManager flagsManager) {
super(NAME, flagsManager);
}
// Try reading as ANSI encoded
try {
return Files.readString(logsPath, StandardCharsets.ISO_8859_1);
} catch (IOException e) {
Logging.finer("Log is not ANSI encoded. Trying UTF-8");
// Must be a UTF-8 encoded log then
}
private final CommandValueFlag<LogsType> logs = flag(CommandValueFlag
.enumBuilder("--logs", LogsType.class)
.addAlias("-l")
.build());
// Try reading as UTF-8 encoded
try {
return Files.readString(logsPath, StandardCharsets.UTF_8);
} catch (IOException ex) {
// It is some other strange encoding
Logging.severe("Could not read ./logs/latest.log. See below for stack trace");
ex.printStackTrace();
}
return "Could not read log";
}
private final CommandValueFlag<UploadType> upload = flag(CommandValueFlag
.enumBuilder("--upload", UploadType.class)
.addAlias("-u")
.build());
private String getVersionString() {
return "# Multiverse-Core Version info" + "\n\n"
+ " - Multiverse-Core Version: " + this.plugin.getDescription().getVersion() + '\n'
+ " - Bukkit Version: " + this.plugin.getServer().getVersion() + '\n'
+ " - Loaded Worlds: " + worldManager.getLoadedWorlds() + '\n'
+ " - Multiverse Plugins Loaded: " + this.plugin.getPluginCount() + '\n';
}
private void addDebugInfoToEvent(MVVersionEvent event) {
// Add the legacy file, but as markdown, so it's readable
event.putDetailedVersionInfo("version.md", this.getVersionString());
// add config.yml
File configFile = new File(plugin.getDataFolder(), "config.yml");
event.putDetailedVersionInfo("multiverse-core/config.yml", configFile);
// add worlds.yml
File worldsFile = new File(plugin.getDataFolder(), "worlds.yml");
event.putDetailedVersionInfo("multiverse-core/worlds.yml", worldsFile);
// Add bukkit.yml if we found it
if (getBukkitConfig() != null) {
event.putDetailedVersionInfo(getBukkitConfig().getPath(), getBukkitConfig());
} else {
Logging.warning("/mv version could not find bukkit.yml. Not including file");
}
// Add server.properties if we found it
if (getServerProperties() != null) {
event.putDetailedVersionInfo(getServerProperties().getPath(), getServerProperties());
} else {
Logging.warning("/mv version could not find server.properties. Not including file");
}
}
private String getPluginList() {
return " - " + StringUtils.join(plugin.getServer().getPluginManager().getPlugins(), "\n - ");
}
/**
* Turns a list of files in to a string containing askii art.
*
* @param files Map of filenames/contents
* @return The askii art
*/
private String encodeAsString(Map<String, String> files) {
StringBuilder uploadData = new StringBuilder();
for (String file : files.keySet()) {
String data = files.get(file);
uploadData.append("# ---------- ")
.append(file)
.append(" ----------\n\n")
.append(data)
.append("\n\n");
}
return uploadData.toString();
}
/**
* Send the current contents of this.pasteBinBuffer to a web service.
*
* @param type Service type to send paste data to.
* @param isPrivate Should the paste be marked as private.
* @param rawPasteData Legacy string containing only data to post to a service.
* @param pasteFiles Map of filenames/contents of debug info.
* @return URL of visible paste
*/
private String postToService(@NotNull PasteServiceType type, boolean isPrivate, @Nullable String rawPasteData, @Nullable Map<String, String> pasteFiles) {
PasteService pasteService = PasteServiceFactory.getService(type, isPrivate);
try {
// Upload normally when multi file is supported
if (pasteService.supportsMultiFile()) {
return pasteService.postData(pasteFiles);
}
// When there is raw paste data, use that
if (rawPasteData != null) { // For the logs
return pasteService.postData(rawPasteData);
}
// If all we have are files and the paste service does not support multi file then encode them
if (pasteFiles != null) {
return pasteService.postData(this.encodeAsString(pasteFiles));
}
// Should never get here
return "No data specified in code";
} catch (PasteFailedException e) {
e.printStackTrace();
return "Error posting to service.";
} catch (NullPointerException e) {
e.printStackTrace();
return "That service isn't supported yet.";
}
// Does not upload logs or plugin list (except if --logs mclogs is there)
private final CommandFlag paranoid = flag(CommandFlag.builder("--paranoid")
.addAlias("-p")
.build());
}
}
@@ -0,0 +1,143 @@
package org.mvplugins.multiverse.core.commands;
import java.util.Map;
import com.dumptruckman.minecraft.util.Logging;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.utils.webpaste.PasteFailedException;
import org.mvplugins.multiverse.core.utils.webpaste.PasteService;
import org.mvplugins.multiverse.core.utils.webpaste.PasteServiceFactory;
import org.mvplugins.multiverse.core.utils.webpaste.PasteServiceType;
final class DumpsLogPoster extends BukkitRunnable {
enum UploadType {
// BEGIN CHECKSTYLE-SUPPRESSION: JavadocVariable
PASTEGG(PasteServiceType.PASTEGG, "paste.gg"),
PASTESDEV(PasteServiceType.PASTESDEV, "pastes.dev");
// END CHECKSTYLE-SUPPRESSION: JavadocVariable
private final PasteServiceType pasteServiceType;
private final String serviceName;
UploadType(PasteServiceType pasteServiceType, String serviceName) {
this.pasteServiceType = pasteServiceType;
this.serviceName = serviceName;
}
}
enum LogsType {
/**
* Appends the log file to the debug info.
*/
APPEND,
/**
* Uploads the log file to a paste service.
*/
MCLOGS
}
private final MVCommandIssuer issuer;
private final LogsType logsType;
private final UploadType uploadType;
private final boolean paranoid;
private final String logs;
private final MVDumpsDebugInfoEvent versionEvent;
DumpsLogPoster(@NotNull MVCommandIssuer issuer,
@NotNull DumpsLogPoster.LogsType logsType,
@NotNull DumpsLogPoster.UploadType uploadType,
boolean paranoid,
@NotNull String logs,
@NotNull MVDumpsDebugInfoEvent versionEvent) {
this.issuer = issuer;
this.logsType = logsType;
this.uploadType = uploadType;
this.paranoid = paranoid;
this.logs = logs;
this.versionEvent = versionEvent;
}
@Override
public void run() {
handleLogs();
handleVersionEvent();
}
@SuppressWarnings("checkstyle:MissingSwitchDefault")
private void handleLogs() {
Logging.finer("Logs type is: " + logsType);
if (!paranoid) {
switch (logsType) {
case MCLOGS -> sendDumpsUrl("Logs", postRawDataToMcLogs(logs));
case APPEND -> versionEvent.putDetailedDebugInfo("latest.log", logs);
default -> Logging.finer("Not uploading logs.");
}
} else {
Logging.finer("Paranoid mode is on, not uploading logs.");
}
}
private void handleVersionEvent() {
Logging.finer("Upload service is: " + uploadType);
final Map<String, String> files = versionEvent.getDetailedDebugInfo();
sendDumpsUrl(uploadType.serviceName, postFilesToService(files));
}
private void sendDumpsUrl(String service, String url) {
issuer.sendInfo(MVCorei18n.DUMPS_URL_LIST, "{service}", service, "{link}", url);
}
private String postRawDataToMcLogs(@NotNull String rawPasteData) {
PasteService pasteService = PasteServiceFactory.getService(PasteServiceType.MCLOGS, true);
return pasteToService(() -> pasteService.postData(rawPasteData));
}
private String postFilesToService(@NotNull Map<String, String> pasteFiles) {
PasteService pasteService = PasteServiceFactory.getService(uploadType.pasteServiceType, true);
return pasteToService(() -> {
if (pasteService.supportsMultiFile()) {
return pasteService.postData(pasteFiles);
} else {
return pasteService.postData(this.encodeAsString(pasteFiles));
}
});
}
private String encodeAsString(Map<String, String> files) {
StringBuilder uploadData = new StringBuilder();
for (Map.Entry<String, String> entry : files.entrySet()) {
String file = entry.getKey();
String data = entry.getValue();
uploadData.append("# ---------- ")
.append(file)
.append(" ----------\n\n")
.append(data)
.append("\n\n");
}
return uploadData.toString();
}
private String pasteToService(LogPaster paster) {
try {
return paster.postLogs();
} catch (PasteFailedException e) {
e.printStackTrace();
return "Error posting to service.";
}
}
private interface LogPaster {
String postLogs() throws PasteFailedException;
}
}
@@ -0,0 +1,141 @@
package org.mvplugins.multiverse.core.commands;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import com.dumptruckman.minecraft.util.Logging;
import jakarta.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.module.MultiverseModulesRegistry;
import org.mvplugins.multiverse.core.commands.DumpsLogPoster.LogsType;
import org.mvplugins.multiverse.core.commands.DumpsLogPoster.UploadType;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent;
import org.mvplugins.multiverse.core.utils.FileUtils;
import org.mvplugins.multiverse.core.utils.StringFormatter;
import org.mvplugins.multiverse.core.world.WorldManager;
@Service
final class DumpsService {
private final MultiverseCore plugin;
private final WorldManager worldManager;
private final FileUtils fileUtils;
@Inject
DumpsService(@NotNull MultiverseCore plugin,
@NotNull WorldManager worldManager,
@NotNull FileUtils fileUtils) {
this.plugin = plugin;
this.worldManager = worldManager;
this.fileUtils = fileUtils;
}
void postLogs(MVCommandIssuer issuer, LogsType logsType, UploadType servicesType, boolean paranoid) {
// Initialise and add info to the debug event
MVDumpsDebugInfoEvent versionEvent = createAndCallDebugInfoEvent();
// Add plugin list if user isn't paranoid
if (!paranoid) {
versionEvent.putDetailedDebugInfo("plugins.md", "# Plugins\n\n" + getPluginList());
}
new DumpsLogPoster(issuer, logsType, servicesType, paranoid, getLogs(), versionEvent)
.runTaskAsynchronously(plugin);
}
/**
* Get the contents of the latest.log file.
*
* @return A string containing the latest.log file
*/
private @NotNull String getLogs() {
// Get the Path of latest.log
Path logsPath = fileUtils.getServerFolder().toPath().resolve("logs/latest.log");
File logsFile = logsPath.toFile();
if (!logsFile.exists()) {
Logging.warning("Could not read logs/latest.log");
return "Could not find log";
}
return readLogsFromFile(logsPath);
}
private @NotNull String readLogsFromFile(Path logsPath) {
String logs = "Could not read log";
// Try reading as ANSI encoded
try {
logs = Files.readString(logsPath, StandardCharsets.UTF_8);
} catch (IOException e) {
Logging.severe("Could not read logs/latest.log. See below for stack trace");
e.printStackTrace();
try {
logs = Files.readString(logsPath, StandardCharsets.UTF_8);
} catch (IOException ex) {
// It is some other strange encoding
Logging.severe("Could not read ./logs/latest.log. See below for stack trace");
ex.printStackTrace();
}
}
return logs;
}
private String getDebugInfoString() {
return "# Multiverse-Core Version info" + "\n\n"
+ " - Multiverse-Core Version: " + this.plugin.getDescription().getVersion() + '\n'
+ " - Bukkit Version: " + this.plugin.getServer().getVersion() + '\n'
+ " - Loaded Worlds: " + worldManager.getLoadedWorlds() + '\n'
+ " - Multiverse Plugins Loaded: " + StringFormatter.joinAnd(MultiverseModulesRegistry.get().getRegisteredPlugins()) + '\n'
+ " - Multiverse Plugins Count: " + MultiverseModulesRegistry.get().getPluginCount() + '\n';
}
private MVDumpsDebugInfoEvent createAndCallDebugInfoEvent() {
MVDumpsDebugInfoEvent event = new MVDumpsDebugInfoEvent();
addDebugInfoToEvent(event);
plugin.getServer().getPluginManager().callEvent(event);
return event;
}
private void addDebugInfoToEvent(MVDumpsDebugInfoEvent event) {
// Add the legacy file, but as markdown, so it's readable
event.putDetailedDebugInfo("version.md", this.getDebugInfoString());
// add config.yml
File configFile = new File(plugin.getDataFolder(), "config.yml");
event.putDetailedDebugInfo("Multiverse-Core/config.yml", configFile);
// add worlds.yml
File worldsFile = new File(plugin.getDataFolder(), "worlds.yml");
event.putDetailedDebugInfo("Multiverse-Core/worlds.yml", worldsFile);
// Add bukkit.yml if we found it
if (fileUtils.getBukkitConfig() != null) {
event.putDetailedDebugInfo(fileUtils.getBukkitConfig().getPath(), fileUtils.getBukkitConfig());
} else {
Logging.warning("/mv dumps could not find bukkit.yml. Not including file");
}
// Add server.properties if we found it
if (fileUtils.getServerProperties() != null) {
event.putDetailedDebugInfo(fileUtils.getServerProperties().getPath(), fileUtils.getServerProperties());
} else {
Logging.warning("/mv dumps could not find server.properties. Not including file");
}
}
private String getPluginList() {
return " - " + StringUtils.join(plugin.getServer().getPluginManager().getPlugins(), "\n - ");
}
}
@@ -0,0 +1,123 @@
package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Flags;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import jakarta.inject.Inject;
import org.bukkit.entity.SpawnCategory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.flags.PageFilterFlags;
import org.mvplugins.multiverse.core.config.handle.PropertyModifyAction;
import org.mvplugins.multiverse.core.display.ContentDisplay;
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
import org.mvplugins.multiverse.core.display.parsers.ListContentProvider;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.utils.StringFormatter;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.entity.SpawnCategoryConfig;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
@Subcommand("entity-spawn-config")
final class EntitySpawnConfigCommand extends CoreCommand {
private final WorldManager worldManager;
private final PageFilterFlags flags;
@Inject
EntitySpawnConfigCommand(@NotNull WorldManager worldManager, @NotNull PageFilterFlags flags) {
this.worldManager = worldManager;
this.flags = flags;
}
@Subcommand("info")
@CommandPermission("multiverse.core.entityspawnconfig.info")
@CommandCompletion("@mvworlds:scope=both|@flags:resolveUntil=arg1,groupName=" + PageFilterFlags.NAME + " @flags:groupName=" + PageFilterFlags.NAME)
@Syntax("[world] [--page <page>] [--filter <filter>]")
@Description("")
void onInfoCommand(
MVCommandIssuer issuer,
@Flags("resolve=issuerAware")
@Syntax("[world]")
MultiverseWorld world,
@Optional
@Syntax("[--page <page>] [--filter <filter>]")
String[] flagArray
) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
ContentDisplay.create()
.addContent(ListContentProvider.forContent(getEntitySpawnConfigList(world)))
.withSendHandler(PagedSendHandler.create()
.withHeader(Message.of("==== [ Entity Spawn Config '" + world.getName() + "' ] ===="))
.withLinesPerPage(8)
.withTargetPage(parsedFlags.flagValue(flags.page, 1))
.withFilter(parsedFlags.flagValue(flags.filter, DefaultContentFilter.get())))
.send(issuer);
}
private List<String> getEntitySpawnConfigList(MultiverseWorld world) {
List<String> list = new ArrayList<>();
Arrays.stream(SpawnCategory.values()).forEach(spawnCategory -> {
list.add(spawnCategory.name() + ": ");
SpawnCategoryConfig spawnCategoryConfig = world.getEntitySpawnConfig().getSpawnCategoryConfig(spawnCategory);
list.add(" spawn: " + spawnCategoryConfig.isSpawn());
list.add(" tick-rate: " + spawnCategoryConfig.getTickRate());
list.add(" exceptions: " + StringFormatter.join(spawnCategoryConfig.getExceptions(), ", "));
});
return list;
}
@Subcommand("modify")
@CommandPermission("multiverse.core.entityspawnconfig.modify")
@CommandCompletion("@mvworlds:scope=both @spawncategories @propsmodifyaction @spawncategorypropsname @spawncategorypropsvalue")
@Syntax("[world] <spawn-category> <set|add|reset|remove> <property> [value]")
@Description("")
void onModifyCommand(
MVCommandIssuer issuer,
@Flags("resolve=issuerAware")
@Syntax("[world]")
MultiverseWorld world,
@Syntax("<spawn-category>")
SpawnCategory spawnCategory,
@Syntax("<set|add|reset|remove>")
PropertyModifyAction action,
@Syntax("<property>")
String property,
@Optional
@Single
@Syntax("[value]")
@Nullable String value
) {
world.getEntitySpawnConfig()
.getSpawnCategoryConfig(spawnCategory)
.getStringPropertyHandle()
.modifyPropertyString(property, value, action)
.onSuccess(ignore -> issuer.sendMessage("Successfully set " + property + " to " + value
+ " for " + spawnCategory.name() + " in " + world.getName()))
.onFailure(e -> issuer.sendMessage("Unable to set " + property + " to " + value
+ " for " + spawnCategory.name() + " in " + world.getName() + ": " + e.getMessage()));
worldManager.saveWorldsConfig();
}
}
@@ -6,7 +6,6 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.InvalidCommandArgument;
import co.aikar.commands.MessageType;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
@@ -16,65 +15,45 @@ import co.aikar.commands.annotation.Flags;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import io.vavr.control.Option;
import jakarta.inject.Inject;
import org.bukkit.ChatColor;
import org.bukkit.GameRule;
import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.context.GameRuleValue;
import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.command.context.GameRuleValue;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.flags.PageFilterFlags;
import org.mvplugins.multiverse.core.display.ContentDisplay;
import org.mvplugins.multiverse.core.display.filters.ContentFilter;
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
import org.mvplugins.multiverse.core.display.filters.RegexContentFilter;
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
import org.mvplugins.multiverse.core.display.parsers.MapContentProvider;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
@Service
@CommandAlias("mv")
@Subcommand("gamerule|rule|gamerules|rules")
class GameruleCommand extends MultiverseCommand {
class GameruleCommand extends CoreCommand {
private final CommandValueFlag<Integer> PAGE_FLAG = flag(CommandValueFlag
.builder("--page", Integer.class)
.addAlias("-p")
.context(value -> {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new InvalidCommandArgument("Invalid page number: " + value);
}
})
.build());
private final CommandValueFlag<ContentFilter> FILTER_FLAG = flag(CommandValueFlag
.builder("--filter", ContentFilter.class)
.addAlias("-f")
.context(value -> {
try {
return RegexContentFilter.fromString(value);
} catch (IllegalArgumentException e) {
throw new InvalidCommandArgument("Invalid filter: " + value);
}
})
.build());
private final PageFilterFlags flags;
@Inject
GameruleCommand(@NotNull MVCommandManager commandManager) {
super(commandManager);
GameruleCommand(@NotNull PageFilterFlags flags) {
this.flags = flags;
}
@Subcommand("set")
@Subcommand("gamerule|rule set")
@CommandPermission("multiverse.core.gamerule.set")
@CommandCompletion("@gamerules true|false|@range:1-10 @mvworlds:multiple|*")
@CommandCompletion("@gamerules @gamerulesvalues @mvworlds:multiple|*")
@Syntax("<Gamerule> <Gamerule value> [World or *]")
@Description("{@@mv-core.gamerule.set.description}")
void onGameruleSetCommand(
@@ -92,17 +71,17 @@ class GameruleCommand extends MultiverseCommand {
@Syntax("[World or *]")
@Description("{@@mv-core.gamerule.set.world.description}")
LoadedMultiverseWorld[] worlds) {
Object value = gameRuleValue.getValue();
Object value = gameRuleValue.value();
boolean success = true;
for (LoadedMultiverseWorld world : worlds) {
// Set gamerules and add false to list if it fails
World bukkitWorld = world.getBukkitWorld().getOrNull();
if (bukkitWorld == null || !bukkitWorld.setGameRule(gamerule, value)) {
issuer.sendError(MVCorei18n.GAMERULE_SET_FAILED,
"{gamerule}", gamerule.getName(),
"{value}", value.toString(),
"{world}", world.getName(),
"{type}", gamerule.getType().getName());
Replace.GAMERULE.with(gamerule.getName()),
Replace.VALUE.with(value.toString()),
Replace.WORLD.with(world.getName()),
replace("{type}").with(gamerule.getType().getName()));
success = false;
}
}
@@ -110,24 +89,25 @@ class GameruleCommand extends MultiverseCommand {
if (success) {
if (worlds.length == 1) {
issuer.sendInfo(MVCorei18n.GAMERULE_SET_SUCCESS_SINGLE,
"{gamerule}", gamerule.getName(),
"{value}", value.toString(),
"{world}", worlds[0].getName());
Replace.GAMERULE.with(gamerule.getName()),
Replace.VALUE.with(value.toString()),
Replace.WORLD.with(worlds[0].getName()));
} else if (worlds.length > 1) {
issuer.sendInfo(MVCorei18n.GAMERULE_SET_SUCCESS_MULTIPLE,
"{gamerule}", gamerule.getName(),
"{value}", value.toString(),
"{count}", String.valueOf(worlds.length));
Replace.GAMERULE.with(gamerule.getName()),
Replace.VALUE.with(value.toString()),
Replace.COUNT.with(String.valueOf(worlds.length)));
}
}
}
@Subcommand("reset")
@SuppressWarnings("rawtypes,unchecked")
@Subcommand("gamerule|rule reset")
@CommandPermission("multiverse.core.gamerule.set")
@CommandCompletion("@gamerules @mvworlds:multiple|*")
@Syntax("<Gamerule> [World or *]")
@Description("{@@mv-core.gamerule.reset.description}")
void onGameruleSetCommand(
void onGameruleResetCommand(
MVCommandIssuer issuer,
@Syntax("<Gamerule>")
@@ -139,32 +119,34 @@ class GameruleCommand extends MultiverseCommand {
@Description("{@@mv-core.gamerule.reset.world.description}")
LoadedMultiverseWorld[] worlds) {
AtomicBoolean success = new AtomicBoolean(true);
Arrays.stream(worlds).forEach(world -> world.getBukkitWorld().peek(bukkitWorld -> {
bukkitWorld.setGameRule(gamerule, bukkitWorld.getGameRuleDefault(gamerule));
}).onEmpty(() -> {
success.set(false);
issuer.sendError(MVCorei18n.GAMERULE_RESET_FAILED,
"{gamerule}", gamerule.getName(),
"{world}", world.getName());
}));
Arrays.stream(worlds)
.forEach(world -> world.getBukkitWorld()
.flatMap(bukkitWorld -> Option.of(bukkitWorld.getGameRuleDefault(gamerule))
.map(value -> bukkitWorld.setGameRule(gamerule, value)))
.onEmpty(() -> {
success.set(false);
issuer.sendError(MVCorei18n.GAMERULE_RESET_FAILED,
Replace.GAMERULE.with(gamerule.getName()),
Replace.WORLD.with(world.getName()));
}));
// Tell user if it was successful
if (success.get()) {
if (worlds.length == 1) {
issuer.sendInfo(MVCorei18n.GAMERULE_RESET_SUCCESS_SINGLE,
"{gamerule}", gamerule.getName(),
"{world}", worlds[0].getName());
Replace.GAMERULE.with(gamerule.getName()),
Replace.WORLD.with(worlds[0].getName()));
} else if (worlds.length > 1) {
issuer.sendInfo(MVCorei18n.GAMERULE_RESET_SUCCESS_MULTIPLE,
"{gamerule}", gamerule.getName(),
"{count}", String.valueOf(worlds.length));
Replace.GAMERULE.with(gamerule.getName()),
Replace.COUNT.with(String.valueOf(worlds.length)));
}
}
}
@Subcommand("list")
@Subcommand("gamerule|rule list")
@CommandPermission("multiverse.core.gamerule.list")
@CommandCompletion("@mvworlds|@flags:groupName=mvgamerulecommand @flags:groupName=mvgamerulecommand")
@CommandCompletion("@mvworlds|@flags:groupName=" + PageFilterFlags.NAME + " @flags:groupName=" + PageFilterFlags.NAME)
@Syntax("[world] [--page <page>] [--filter <filter>]")
@Description("{@@mv-core.gamerule.list.description}")
void onGameruleListCommand(
@@ -178,18 +160,18 @@ class GameruleCommand extends MultiverseCommand {
@Optional
@Syntax("[--page <page>] [--filter <filter>]")
@Description("{@@mv-core.gamerule.list.description.page}")
String[] flags) {
ParsedCommandFlags parsedFlags = parseFlags(flags);
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
ContentDisplay.create()
.addContent(MapContentProvider.forContent(getGameRuleMap(world.getBukkitWorld().getOrNull())) // TODO: Handle null
.addContent(MapContentProvider.forContent(getGameRuleMap(world.getBukkitWorld().getOrNull()))
.withKeyColor(ChatColor.AQUA)
.withValueColor(ChatColor.WHITE))
.withSendHandler(PagedSendHandler.create()
.withHeader(this.getListTitle(issuer, world.getBukkitWorld().getOrNull()))
.withHeader(this.getListTitle(world.getBukkitWorld().getOrNull()))
.doPagination(true)
.withTargetPage(parsedFlags.flagValue(PAGE_FLAG, 1))
.withFilter(parsedFlags.flagValue(FILTER_FLAG, DefaultContentFilter.get())))
.withTargetPage(parsedFlags.flagValue(flags.page, 1))
.withFilter(parsedFlags.flagValue(flags.filter, DefaultContentFilter.get())))
.send(issuer);
}
@@ -201,23 +183,51 @@ class GameruleCommand extends MultiverseCommand {
*/
private Map<String, String> getGameRuleMap(World world) {
Map<String, String> gameRuleMap = new HashMap<>();
if (world == null) {
return gameRuleMap;
}
for (GameRule<?> gamerule : GameRule.values()) {
Object gameruleValue = world.getGameRuleValue(gamerule);
if (gameruleValue == null) {
gameRuleMap.put(gamerule.getName(), "null");
for (String gamerule : world.getGameRules()) {
GameRule<?> gameruleEnum = GameRule.getByName(gamerule);
if (gameruleEnum == null) {
continue;
}
gameRuleMap.put(gamerule.getName(), gameruleValue.toString());
Object gameruleValue = world.getGameRuleValue(gameruleEnum);
if (gameruleValue == null) {
gameRuleMap.put(gameruleEnum.getName(), "null");
continue;
}
gameRuleMap.put(gameruleEnum.getName(), gameruleValue.toString());
}
return gameRuleMap;
}
private String getListTitle(CommandIssuer issuer, World world) {
return this.commandManager.formatMessage(
issuer,
MessageType.INFO,
MVCorei18n.GAMERULE_LIST_TITLE,
"{world}", world.getName());
private Message getListTitle(@Nullable World world) {
return Message.of(MVCorei18n.GAMERULE_LIST_TITLE, Replace.WORLD.with(world == null ? "null" : world.getName()));
}
@Service
private static final class LegacyAlias extends GameruleCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(@NotNull PageFilterFlags flags) {
super(flags);
}
@Override
@CommandAlias("mvrule|mvgamerule")
void onGameruleSetCommand(MVCommandIssuer issuer, GameRule gamerule, GameRuleValue gameRuleValue, LoadedMultiverseWorld[] worlds) {
super.onGameruleSetCommand(issuer, gamerule, gameRuleValue, worlds);
}
@Override
void onGameruleResetCommand(MVCommandIssuer issuer, GameRule gamerule, LoadedMultiverseWorld[] worlds) {
super.onGameruleResetCommand(issuer, gamerule, worlds);
}
@Override
@CommandAlias("mvrules|mvgamerules")
void onGameruleListCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, String[] flags) {
super.onGameruleListCommand(issuer, world, flags);
}
}
}
@@ -2,8 +2,6 @@ package org.mvplugins.multiverse.core.commands;
import java.util.List;
import co.aikar.commands.InvalidCommandArgument;
import co.aikar.commands.MessageType;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
@@ -16,79 +14,54 @@ import org.bukkit.ChatColor;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.flags.PageFilterFlags;
import org.mvplugins.multiverse.core.display.ContentDisplay;
import org.mvplugins.multiverse.core.display.filters.ContentFilter;
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
import org.mvplugins.multiverse.core.display.filters.RegexContentFilter;
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
import org.mvplugins.multiverse.core.display.parsers.ListContentProvider;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.utils.StringFormatter;
import org.mvplugins.multiverse.core.world.generators.GeneratorPlugin;
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
/**
* List all gamerules in your current or specified world.
*/
@Service
@CommandAlias("mv")
class GeneratorsCommand extends MultiverseCommand {
class GeneratorsCommand extends CoreCommand {
private final GeneratorProvider generatorProvider;
private final CommandValueFlag<Integer> PAGE_FLAG = flag(CommandValueFlag
.builder("--page", Integer.class)
.addAlias("-p")
.context(value -> {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new InvalidCommandArgument("Invalid page number: " + value);
}
})
.build());
private final CommandValueFlag<ContentFilter> FILTER_FLAG = flag(CommandValueFlag
.builder("--filter", ContentFilter.class)
.addAlias("-f")
.context(value -> {
try {
return RegexContentFilter.fromString(value);
} catch (IllegalArgumentException e) {
throw new InvalidCommandArgument("Invalid filter: " + value);
}
})
.build());
private final PageFilterFlags flags;
@Inject
GeneratorsCommand(@NotNull MVCommandManager commandManager, @NotNull GeneratorProvider generatorProvider) {
super(commandManager);
GeneratorsCommand(@NotNull GeneratorProvider generatorProvider, @NotNull PageFilterFlags flags) {
this.generatorProvider = generatorProvider;
this.flags = flags;
}
@Subcommand("generators|gens")
@Subcommand("generators list")
@CommandPermission("multiverse.core.generator")
@CommandCompletion("@flags:groupName=mvgeneratorscommand @flags:groupName=mvgeneratorscommand")
@Syntax("")
@CommandCompletion("@flags:groupName=" + PageFilterFlags.NAME)
@Syntax("[--page <page>] [--filter <filter>]")
@Description("{@@mv-core.generators.description}")
void onGamerulesCommand(
void onGeneratorsListCommand(
@NotNull MVCommandIssuer issuer,
@Optional
@Syntax("[--page <page>] [--filter <filter>]")
@Description("{@@mv-core.generators.description.flags}")
String[] flags) {
ParsedCommandFlags parsedFlags = parseFlags(flags);
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
// Get the generators loaded using the command suggestions
List<String> generators = (List<String>) generatorProvider.suggestGeneratorString("");
// Tell the user if we cannot find any generator plugins, then abort
if (generators.isEmpty()) {
issuer.sendMessage(commandManager.formatMessage(issuer, MessageType.INFO, MVCorei18n.GENERATORS_EMPTY));
issuer.sendMessage(MVCorei18n.GENERATORS_EMPTY);
return;
}
@@ -97,8 +70,38 @@ class GeneratorsCommand extends MultiverseCommand {
.withSendHandler(PagedSendHandler.create()
.withHeader("%s====[ Multiverse Generator List ]====", ChatColor.AQUA)
.doPagination(true)
.withTargetPage(parsedFlags.flagValue(PAGE_FLAG, 1))
.withFilter(parsedFlags.flagValue(FILTER_FLAG, DefaultContentFilter.get())))
.withTargetPage(parsedFlags.flagValue(flags.page, 1))
.withFilter(parsedFlags.flagValue(flags.filter, DefaultContentFilter.get())))
.send(issuer);
}
@Subcommand("generators info")
@CommandPermission("multiverse.core.generator")
@CommandCompletion("@generatorplugins")
@Syntax("<generator>")
void onGeneratorsInfoCommand(
@NotNull MVCommandIssuer issuer,
@Syntax("<generator>")
GeneratorPlugin generatorPlugin
) {
issuer.sendMessage(ChatColor.RESET + "Generator Plugin: " + generatorPlugin.getPluginName());
issuer.sendMessage(ChatColor.RESET + "Example usages: ");
issuer.sendMessage(ChatColor.RESET + StringFormatter.join(generatorPlugin.getExampleUsages(), "\n"));
issuer.sendMessage(ChatColor.RESET + "Link to more info: " + generatorPlugin.getInfoLink());
}
@Service
private static final class LegacyAlias extends GeneratorsCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(@NotNull GeneratorProvider generatorProvider, @NotNull PageFilterFlags flags) {
super(generatorProvider, flags);
}
@Override
@CommandAlias("mvgenerators|mvgens")
void onGeneratorsListCommand(@NotNull MVCommandIssuer issuer, String[] flags) {
super.onGeneratorsListCommand(issuer, flags);
}
}
}
@@ -5,36 +5,51 @@ import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.HelpCommand;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.config.CoreConfig;
@Service
@CommandAlias("mv")
class UsageCommand extends MultiverseCommand {
class HelpCommand extends CoreCommand {
private final MVCommandManager commandManager;
@Inject
UsageCommand(@NotNull MVCommandManager commandManager) {
super(commandManager);
HelpCommand(@NotNull MVCommandManager commandManager) {
this.commandManager = commandManager;
}
@HelpCommand
@co.aikar.commands.annotation.HelpCommand
@Subcommand("help")
@CommandPermission("multiverse.core.help")
@CommandCompletion("@commands:mv")
@Syntax("[filter] [page]")
@Description("{@@mv-core.usage.description}")
void onUsageCommand(CommandHelp help) {
void onHelpCommand(CommandHelp help) {
if (help.getIssuer().isPlayer()) {
// Prevent flooding the chat
help.setPerPage(4);
}
this.commandManager.showUsage(help);
}
@Service
private static final class LegacyAlias extends HelpCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(@NotNull MVCommandManager commandManager) {
super(commandManager);
}
@Override
@CommandAlias("mvhelp")
public void onHelpCommand(CommandHelp help) {
super.onHelpCommand(help);
}
}
}
@@ -14,49 +14,37 @@ import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag;
import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
import org.mvplugins.multiverse.core.world.options.ImportWorldOptions;
@Service
@CommandAlias("mv")
class ImportCommand extends MultiverseCommand {
class ImportCommand extends CoreCommand {
private final WorldManager worldManager;
private GeneratorProvider generatorProvider;
private final CommandValueFlag<String> GENERATOR_FLAG = flag(CommandValueFlag
.builder("--generator", String.class)
.addAlias("-g")
.completion(input -> generatorProvider.suggestGeneratorString(input))
.build());
private final CommandFlag NO_ADJUST_SPAWN_FLAG = flag(CommandFlag.builder("--no-adjust-spawn")
.addAlias("-n")
.build());
private final ImportCommand.Flags flags;
@Inject
ImportCommand(
@NotNull MVCommandManager commandManager,
@NotNull WorldManager worldManager,
@NotNull GeneratorProvider generatorProvider) {
super(commandManager);
ImportCommand(@NotNull WorldManager worldManager, @NotNull Flags flags) {
this.worldManager = worldManager;
this.generatorProvider = generatorProvider;
this.flags = flags;
}
@Subcommand("import")
@CommandPermission("multiverse.core.import")
@CommandCompletion("@mvworlds:scope=potential @environments @flags:groupName=mvimportcommand")
@Syntax("<name> <env> --generator [generator[:id]] --adjust-spawn")
@Description("{@@mv-core.import.description")
@CommandCompletion("@mvworlds:scope=potential @environments @flags:groupName=" + Flags.NAME)
@Syntax("<name> <environment> [--generator <generator[:id]> --adjust-spawn --biome <biome>]")
@Description("{@@mv-core.import.description}")
void onImportCommand(
MVCommandIssuer issuer,
@@ -70,23 +58,71 @@ class ImportCommand extends MultiverseCommand {
World.Environment environment,
@Optional
@Syntax("--generator [generator[:id]] --adjust-spawn")
@Syntax("[--generator <generator[:id]> --adjust-spawn --biome <biome>]")
@Description("{@@mv-core.import.other.description}")
String[] flags) {
ParsedCommandFlags parsedFlags = parseFlags(flags);
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
issuer.sendInfo(MVCorei18n.IMPORT_IMPORTING, "{world}", worldName);
issuer.sendInfo(MVCorei18n.IMPORT_IMPORTING, Replace.WORLD.with(worldName));
worldManager.importWorld(ImportWorldOptions.worldName(worldName)
.environment(environment)
.generator(parsedFlags.flagValue(GENERATOR_FLAG, String.class))
.useSpawnAdjust(!parsedFlags.hasFlag(NO_ADJUST_SPAWN_FLAG)))
.biome(parsedFlags.flagValue(flags.biome, ""))
.environment(environment)
.generator(parsedFlags.flagValue(flags.generator, String.class))
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn))
.doFolderCheck(!parsedFlags.hasFlag(flags.skipFolderCheck)))
.onSuccess(newWorld -> {
Logging.fine("World import success: " + newWorld);
issuer.sendInfo(MVCorei18n.IMPORT_SUCCESS, "{world}", newWorld.getName());
issuer.sendInfo(MVCorei18n.IMPORT_SUCCESS, Replace.WORLD.with(newWorld.getName()));
})
.onFailure(failure -> {
Logging.fine("World import failure: " + failure);
issuer.sendError(failure.getFailureMessage());
});
}
@Service
private static final class Flags extends FlagBuilder {
private static final String NAME = "mvimport";
private GeneratorProvider generatorProvider;
@Inject
private Flags(@NotNull CommandFlagsManager flagsManager, @NotNull GeneratorProvider generatorProvider) {
super(NAME, flagsManager);
this.generatorProvider = generatorProvider;
}
private final CommandValueFlag<String> generator = flag(CommandValueFlag
.builder("--generator", String.class)
.addAlias("-g")
.completion(input -> generatorProvider.suggestGeneratorString(input))
.build());
private final CommandFlag noAdjustSpawn = flag(CommandFlag.builder("--no-adjust-spawn")
.addAlias("-n")
.build());
private final CommandValueFlag<String> biome = flag(CommandValueFlag.builder("--biome", String.class)
.addAlias("-b")
.build());
private final CommandFlag skipFolderCheck = flag(CommandFlag.builder("--skip-folder-check")
.addAlias("-f")
.build());
}
@Service
private static final class LegacyAlias extends ImportCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(@NotNull WorldManager worldManager, @NotNull Flags flags) {
super(worldManager, flags);
}
@Override
@CommandAlias("mvimport|mvim")
void onImportCommand(MVCommandIssuer issuer, String worldName, World.Environment environment, String[] flags) {
super.onImportCommand(issuer, worldName, environment, flags);
}
}
}
@@ -3,7 +3,6 @@ package org.mvplugins.multiverse.core.commands;
import java.util.LinkedHashMap;
import java.util.Map;
import co.aikar.commands.InvalidCommandArgument;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
@@ -14,72 +13,51 @@ import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import jakarta.inject.Inject;
import org.bukkit.ChatColor;
import org.bukkit.WorldType;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.api.LocationManipulation;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.flags.PageFilterFlags;
import org.mvplugins.multiverse.core.display.ContentDisplay;
import org.mvplugins.multiverse.core.display.filters.ContentFilter;
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
import org.mvplugins.multiverse.core.display.filters.RegexContentFilter;
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
import org.mvplugins.multiverse.core.display.parsers.MapContentProvider;
import org.mvplugins.multiverse.core.economy.MVEconomist;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
@Service
@CommandAlias("mv")
class InfoCommand extends MultiverseCommand {
private final CommandValueFlag<Integer> PAGE_FLAG = flag(CommandValueFlag
.builder("--page", Integer.class)
.addAlias("-p")
.context(value -> {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new InvalidCommandArgument("Invalid page number: " + value);
}
})
.build());
private final CommandValueFlag<ContentFilter> FILTER_FLAG = flag(CommandValueFlag
.builder("--filter", ContentFilter.class)
.addAlias("-f")
.context(value -> {
try {
return RegexContentFilter.fromString(value);
} catch (IllegalArgumentException e) {
throw new InvalidCommandArgument("Invalid filter: " + value);
}
})
.build());
class InfoCommand extends CoreCommand {
private final LocationManipulation locationManipulation;
private final MVEconomist economist;
private final PageFilterFlags flags;
@Inject
InfoCommand(
@NotNull MVCommandManager commandManager,
@NotNull LocationManipulation locationManipulation,
@NotNull MVEconomist economist) {
super(commandManager);
@NotNull MVEconomist economist,
@NotNull PageFilterFlags flags
) {
this.locationManipulation = locationManipulation;
this.economist = economist;
this.flags = flags;
}
// TODO: support info for unloaded worlds
@Subcommand("info")
@CommandPermission("multiverse.core.info")
@CommandCompletion("@mvworlds:scope=both|@flags:groupName=mvinfocommand @flags:groupName=mvinfocommand")
@CommandCompletion("@mvworlds:scope=both|@flags:resolveUntil=arg1,groupName=" + PageFilterFlags.NAME + " @flags:groupName=" + PageFilterFlags.NAME)
@Syntax("[world] [--page <page>] [--filter <filter>]")
@Description("{@@mv-core.info.description")
@Description("{@@mv-core.info.description}")
public void onInfoCommand(
MVCommandIssuer issuer,
@@ -89,85 +67,113 @@ class InfoCommand extends MultiverseCommand {
LoadedMultiverseWorld world,
@Optional
@Syntax("[--page <page>]")
@Description("{@@mv-core.info.description.page}")
String[] flags) {
ParsedCommandFlags parsedFlags = parseFlags(flags);
@Syntax("[--page <page>] [--filter <filter>]")
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
ContentDisplay.create()
.addContent(MapContentProvider.forContent(getInfo(world))
.withKeyColor(ChatColor.AQUA)
.withValueColor(ChatColor.WHITE))
.withSendHandler(PagedSendHandler.create()
.withHeader(getTitle(world))
.withHeader(Message.of(MVCorei18n.INFO_HEADER, replace("{world}").with(world.getName())))
.noContentMessage(Message.of(MVCorei18n.INFO_NOCONTENT))
.doPagination(true)
.withTargetPage(parsedFlags.flagValue(PAGE_FLAG, 1))
.withFilter(parsedFlags.flagValue(FILTER_FLAG, DefaultContentFilter.get())))
.withTargetPage(parsedFlags.flagValue(flags.page, 1))
.withFilter(parsedFlags.flagValue(flags.filter, DefaultContentFilter.get())))
.send(issuer);
}
private String getTitle(MultiverseWorld world) {
return "&a&l---- World Info: &f&l%s&a&l ----".formatted(world.getName());
}
private Map<String, String> getInfo(LoadedMultiverseWorld world) {
Map<String, String> outMap = new LinkedHashMap<>();
outMap.put("World Name", world.getName());
outMap.put("World Alias", world.getAlias());
outMap.put("World UID", world.getUID().toString());
outMap.put("Game Mode: ", world.getGameMode().toString());
outMap.put("Game Mode", world.getGameMode().toString());
outMap.put("Difficulty", world.getDifficulty().toString());
outMap.put("Spawn Location", locationManipulation.strCoords(world.getSpawnLocation()));
outMap.put("Seed", String.valueOf(world.getSeed()));
getEntryFeeInfo(outMap, world); // Entry fee/reward
outMap.put("Respawn World", world.getRespawnWorldName());
outMap.put("World Type", world.getWorldType().get().toString());
outMap.put("Bed Respawn", String.valueOf(world.getBedRespawn()));
outMap.put("Anchor Respawn", String.valueOf(world.getAnchorRespawn()));
outMap.put("Seed", String.valueOf(world.getSeed()));
outMap.put("Environment", String.valueOf(world.getEnvironment()));
outMap.put("World Type", world.getWorldType().map(WorldType::getName).getOrNull());
outMap.put("Biome", world.getBiome());
outMap.put("Generator", world.getGenerator());
outMap.put("Generate Structures", world.canGenerateStructures().get().toString());
outMap.put("Generate Structures", world.canGenerateStructures().map(String::valueOf).getOrNull());
outMap.put("Auto Load", String.valueOf(world.isAutoLoad()));
outMap.put("Keep Spawn In Memory", String.valueOf(world.isKeepSpawnInMemory()));
getEntryFeeInfo(outMap, world);
outMap.put("World Scale", String.valueOf(world.getScale()));
outMap.put("Weather Enabled", String.valueOf(world.getAllowWeather()));
outMap.put("Hunger Depletes", String.valueOf(world.getHunger()));
outMap.put("Keep Spawn In Memory", String.valueOf(world.getKeepSpawnInMemory()));
outMap.put("Weather Enabled", String.valueOf(world.isAllowWeather()));
outMap.put("Allow Flight", String.valueOf(world.isAllowFlight()));
outMap.put("Hunger Depletes", String.valueOf(world.isHunger()));
outMap.put("Keep Spawn In Memory", String.valueOf(world.isKeepSpawnInMemory()));
outMap.put("PVP Enabled", String.valueOf(world.getPvp()));
getAnimalSpawningInfo(outMap, world); // Animals that can spawn
getMonsterSpawningInfo(outMap, world); // Monsters that can spawn
outMap.put("Portal Form", String.valueOf(world.getPortalForm()));
outMap.put("Player Limit", String.valueOf(world.getPlayerLimit()));
// getAnimalSpawningInfo(outMap, world);
// getMonsterSpawningInfo(outMap, world);
outMap.put("World Blacklist", String.join(", ", world.getWorldBlacklist()));
return outMap;
}
private void getEntryFeeInfo(Map<String, String> outMap, LoadedMultiverseWorld world) {
private void getEntryFeeInfo(Map<String, String> outMap, MultiverseWorld world) {
double price = world.getPrice();
if (price == 0) {
outMap.put("Entry Fee", "FREE!");
} else if (price > 0) {
outMap.put("Entry Fee", economist.formatPrice(-price, world.getCurrency()));
outMap.put("Entry Fee", economist.formatPrice(price, world.getCurrency()));
} else if (price < 0) {
outMap.put("Entry Reward", economist.formatPrice(price, world.getCurrency()));
outMap.put("Entry Reward", economist.formatPrice(-price, world.getCurrency()));
}
}
private void getAnimalSpawningInfo(Map<String, String> outMap, LoadedMultiverseWorld world) {
if (world.getSpawningAnimals()) {
outMap.put("Spawning Animals", "ALL");
} else {
if (!world.getSpawningAnimalsExceptions().isEmpty()) {
outMap.put("Spawning Animals", world.getSpawningAnimalsExceptions().toString());
} else {
outMap.put("Spawning Animals", "NONE");
}
}
}
// private void getAnimalSpawningInfo(Map<String, String> outMap, MultiverseWorld world) {
// if (world.isSpawningAnimals()) {
// outMap.put("Spawning Animals", "ALL");
// } else {
// if (!world.getSpawningAnimalsExceptions().isEmpty()) {
// outMap.put("Spawning Animals", world.getSpawningAnimalsExceptions().toString());
// } else {
// outMap.put("Spawning Animals", "NONE");
// }
// }
// }
//
// private void getMonsterSpawningInfo(Map<String, String> outMap, MultiverseWorld world) {
// if (world.isSpawningMonsters()) {
// outMap.put("Spawning Monsters", "ALL");
// } else {
// if (!world.getSpawningMonstersExceptions().isEmpty()) {
// outMap.put("Spawning Monsters", world.getSpawningMonstersExceptions().toString());
// } else {
// outMap.put("Spawning Monsters", "NONE");
// }
// }
// }
private void getMonsterSpawningInfo(Map<String, String> outMap, LoadedMultiverseWorld world) {
if (world.getSpawningMonsters()) {
outMap.put("Spawning Monsters", "ALL");
} else {
if (!world.getSpawningMonstersExceptions().isEmpty()) {
outMap.put("Spawning Monsters", world.getSpawningMonstersExceptions().toString());
} else {
outMap.put("Spawning Monsters", "NONE");
}
@Service
private static final class LegacyAlias extends InfoCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(
@NotNull LocationManipulation locationManipulation,
@NotNull MVEconomist economist,
@NotNull PageFilterFlags flags
) {
super(locationManipulation, economist, flags);
}
@Override
@CommandAlias("mvinfo|mvi")
public void onInfoCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, String[] flags) {
super.onInfoCommand(issuer, world, flags);
}
}
}
@@ -3,7 +3,6 @@ package org.mvplugins.multiverse.core.commands;
import java.util.ArrayList;
import java.util.List;
import co.aikar.commands.InvalidCommandArgument;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
@@ -16,110 +15,107 @@ import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.flags.PageFilterFlags;
import org.mvplugins.multiverse.core.display.ContentDisplay;
import org.mvplugins.multiverse.core.display.filters.ContentFilter;
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
import org.mvplugins.multiverse.core.display.filters.RegexContentFilter;
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
import org.mvplugins.multiverse.core.display.parsers.ListContentProvider;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.entrycheck.WorldEntryChecker;
import org.mvplugins.multiverse.core.world.entrycheck.WorldEntryCheckerProvider;
@Service
@CommandAlias("mv")
class ListCommand extends MultiverseCommand {
class ListCommand extends CoreCommand {
private final WorldManager worldManager;
private final WorldEntryCheckerProvider worldEntryCheckerProvider;
private final CommandValueFlag<Integer> PAGE_FLAG = flag(CommandValueFlag
.builder("--page", Integer.class)
.addAlias("-p")
.context(value -> {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new InvalidCommandArgument("Invalid page number: " + value);
}
})
.build());
private final CommandValueFlag<ContentFilter> FILTER_FLAG = flag(CommandValueFlag
.builder("--filter", ContentFilter.class)
.addAlias("-f")
.context(value -> {
try {
return RegexContentFilter.fromString(value);
} catch (IllegalArgumentException e) {
throw new InvalidCommandArgument("Invalid filter: " + value);
}
})
.build());
private final ListCommand.Flags flags;
@Inject
ListCommand(
@NotNull MVCommandManager commandManager,
@NotNull WorldManager worldManager,
@NotNull WorldEntryCheckerProvider worldEntryCheckerProvider) {
super(commandManager);
@NotNull WorldEntryCheckerProvider worldEntryCheckerProvider,
@NotNull Flags flags
) {
this.worldManager = worldManager;
this.worldEntryCheckerProvider = worldEntryCheckerProvider;
this.flags = flags;
}
@Subcommand("list")
@CommandPermission("multiverse.core.list.worlds")
@CommandCompletion("@flags:groupName=mvlistcommand")
@Syntax("--filter [filter] --page [page]")
@Description("Displays a listing of all worlds that you can enter.")
@CommandCompletion("@flags:groupName=" + Flags.NAME)
@Syntax("--filter [filter] --page [page] --raw")
@Description("{{@mv-core.list.description}}")
public void onListCommand(
MVCommandIssuer issuer,
@Syntax("[--filter <filter>] [--page <page>]")
@Description("Filters the list of worlds by the given regex and displays the given page.")
String[] flags) {
ParsedCommandFlags parsedFlags = parseFlags(flags);
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
ContentDisplay.create()
.addContent(ListContentProvider.forContent(getListContents(issuer)))
.addContent(ListContentProvider.forContent(getListContents(issuer, parsedFlags.hasFlag(flags.raw))))
.withSendHandler(PagedSendHandler.create()
.withHeader("%s====[ Multiverse World List ]====", ChatColor.GOLD)
.withTargetPage(parsedFlags.flagValue(PAGE_FLAG, 1))
.withFilter(parsedFlags.flagValue(FILTER_FLAG, DefaultContentFilter.get())))
.noContentMessage(Message.of(MVCorei18n.LIST_NOCONTENT))
.withHeader(Message.of(MVCorei18n.LIST_HEADER))
.withTargetPage(parsedFlags.flagValue(flags.page, 1))
.withFilter(parsedFlags.flagValue(flags.filter, DefaultContentFilter.get())))
.send(issuer);
}
private List<String> getListContents(MVCommandIssuer issuer) {
private List<String> getListContents(MVCommandIssuer issuer, boolean useRawNames) {
List<String> worldList = new ArrayList<>();
WorldEntryChecker worldEntryChecker = worldEntryCheckerProvider.forSender(issuer.getIssuer());
worldManager.getLoadedWorlds().stream()
.filter(world -> worldEntryChecker.canAccessWorld(world).isSuccess())
.filter(world -> canSeeWorld(issuer, world))
.map(world -> hiddenText(world) + world.getAlias() + " - " + parseColouredEnvironment(world.getEnvironment()))
.map(world -> hiddenText(world) + getWorldName(world, useRawNames) + " - "
+ parseColouredEnvironment(world.getEnvironment()))
.sorted()
.forEach(worldList::add);
worldManager.getUnloadedWorlds().stream()
.filter(world -> worldEntryChecker.canAccessWorld(world).isSuccess())
.map(world -> ChatColor.GRAY + world.getAlias() + " - UNLOADED")
.map(world -> ChatColor.GRAY + getWorldName(world, useRawNames) + " - UNLOADED")
.sorted()
.forEach(worldList::add);
return worldList;
}
private boolean canSeeWorld(MVCommandIssuer issuer, LoadedMultiverseWorld world) {
return !world.isHidden()
|| issuer.hasPermission("multiverse.core.modify"); // TODO: Refactor stray permission check
/**
* Gets a world's name or alias.
*
* @param world The world to retrieve the name of
* @param useRawNames True to return the name, false to return the alias
* @return The name
*/
private String getWorldName(MultiverseWorld world, boolean useRawNames) {
if (useRawNames) {
return world.getName();
}
return world.getAliasOrName();
}
private String hiddenText(LoadedMultiverseWorld world) {
private boolean canSeeWorld(MVCommandIssuer issuer, MultiverseWorld world) {
return !world.isHidden()
// TODO: Refactor stray permission check
|| issuer.hasPermission("multiverse.core.modify");
}
private String hiddenText(MultiverseWorld world) {
return (world.isHidden()) ? String.format("%s[H] ", ChatColor.GRAY) : "";
}
@@ -132,4 +128,37 @@ class ListCommand extends MultiverseCommand {
};
return color + env.toString();
}
@Service
private static final class Flags extends PageFilterFlags {
private static final String NAME = "mvlist";
@Inject
private Flags(@NotNull CommandFlagsManager flagsManager) {
super(NAME, flagsManager);
}
private final CommandFlag raw = flag(CommandFlag.builder("--raw")
.addAlias("-r")
.build());
}
@Service
private static final class LegacyAlias extends ListCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(
@NotNull WorldManager worldManager,
@NotNull WorldEntryCheckerProvider worldEntryCheckerProvider,
@NotNull Flags flags
) {
super(worldManager, worldEntryCheckerProvider, flags);
}
@Override
@CommandAlias("mvlist|mvl")
public void onListCommand(MVCommandIssuer issuer, String[] flags) {
super.onListCommand(issuer, flags);
}
}
}
@@ -3,9 +3,8 @@ package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Conditions;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import com.dumptruckman.minecraft.util.Logging;
@@ -13,22 +12,28 @@ import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.options.LoadWorldOptions;
@Service
@CommandAlias("mv")
class LoadCommand extends MultiverseCommand {
class LoadCommand extends CoreCommand {
private final WorldManager worldManager;
private final LoadCommand.Flags flags;
@Inject
LoadCommand(@NotNull MVCommandManager commandManager, @NotNull WorldManager worldManager) {
super(commandManager);
LoadCommand(@NotNull WorldManager worldManager, @NotNull Flags flags) {
this.worldManager = worldManager;
this.flags = flags;
}
@Subcommand("load")
@@ -39,19 +44,54 @@ class LoadCommand extends MultiverseCommand {
void onLoadCommand(
MVCommandIssuer issuer,
@Single
@Conditions("worldname:scope=unloaded")
@Syntax("<world>")
@Description("{@@mv-core.load.world.description}")
String worldName) {
issuer.sendInfo(MVCorei18n.LOAD_LOADING, "{world}", worldName);
worldManager.loadWorld(worldName)
MultiverseWorld world,
@Optional
@Syntax("[--remove-players]")
@Description("")
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
issuer.sendInfo(MVCorei18n.LOAD_LOADING, Replace.WORLD.with(world.getName()));
worldManager.loadWorld(LoadWorldOptions.world(world)
.doFolderCheck(!parsedFlags.hasFlag(flags.skipFolderCheck)))
.onSuccess(newWorld -> {
Logging.fine("World load success: " + newWorld);
issuer.sendInfo(MVCorei18n.LOAD_SUCCESS, "{world}", newWorld.getName());
issuer.sendInfo(MVCorei18n.LOAD_SUCCESS, Replace.WORLD.with(newWorld.getName()));
}).onFailure(failure -> {
Logging.fine("World load failure: " + failure);
issuer.sendError(failure.getFailureMessage());
});
}
@Service
private static final class Flags extends RemovePlayerFlags {
private static final String NAME = "mvunload";
@Inject
private Flags(@NotNull CommandFlagsManager flagsManager) {
super(NAME, flagsManager);
}
private final CommandFlag skipFolderCheck = flag(CommandFlag.builder("--skip-folder-check")
.addAlias("-f")
.build());
}
@Service
private static final class LegacyAlias extends LoadCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(@NotNull WorldManager worldManager, @NotNull Flags flags) {
super(worldManager, flags);
}
@Override
@CommandAlias("mvload")
void onLoadCommand(MVCommandIssuer issuer, MultiverseWorld world, String[] flagArray) {
super.onLoadCommand(issuer, world, flagArray);
}
}
}
@@ -6,72 +6,130 @@ import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Flags;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import com.dumptruckman.minecraft.util.Logging;
import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.configuration.handle.PropertyModifyAction;
import org.mvplugins.multiverse.core.configuration.handle.StringPropertyHandle;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.context.issueraware.MultiverseWorldValue;
import org.mvplugins.multiverse.core.config.handle.PropertyModifyAction;
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import java.util.Map;
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.*;
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
@Service
@CommandAlias("mv")
class ModifyCommand extends MultiverseCommand {
class ModifyCommand extends CoreCommand {
private static final Map<String, String> REMOVED_PROPERTIES = Map.of(
"animals", "/mv entity-spawn-config modify [world] animal set spawn <true|false>",
"monsters", "/mv entity-spawn-config modify [world] monster set spawn <true|false>"
);
private final WorldManager worldManager;
@Inject
ModifyCommand(@NotNull MVCommandManager commandManager, WorldManager worldManager) {
super(commandManager);
ModifyCommand(WorldManager worldManager) {
this.worldManager = worldManager;
}
@Subcommand("modify")
@CommandPermission("multiverse.core.modify")
@CommandCompletion("@mvworlds:scope=both @propsmodifyaction @mvworldpropsname @mvworldpropsvalue")
@CommandCompletion("@mvworlds:scope=both|@propsmodifyaction:byIssuerForArg=arg1 " +
"@propsmodifyaction:notByIssuerForArg=arg1|@mvworldpropsname:byIssuerForArg=arg1 " +
"@mvworldpropsname:notByIssuerForArg=arg1|@mvworldpropsvalue:byIssuerForArg=arg1 " +
"@mvworldpropsvalue:notByIssuerForArg=arg1")
@Syntax("[world] <set|add|remove|reset> <property> <value>")
@Description("")
void onModifyCommand(
@Description("{@@mv-core.modify.description}")
void onModifyCommand(// SUPPRESS CHECKSTYLE: ParameterNumber
MVCommandIssuer issuer,
@Flags("resolve=issuerAware")
@Syntax("[world]")
@Description("")
MultiverseWorld world,
@Description("{@@mv-core.modify.world.description}")
@NotNull MultiverseWorldValue worldValue,
@Syntax("<set|add|remove|reset>")
@Description("")
PropertyModifyAction action,
@NotNull PropertyModifyAction action,
@Syntax("<property>")
@Description("")
String propertyName,
@Description("{@@mv-core.modify.property.description}")
@NotNull String propertyName,
@Optional
@Single
@Syntax("[value]")
@Description("")
String propertyValue) {
@Description("{@@mv-core.modify.value.description}")
@Nullable String propertyValue) {
if (REMOVED_PROPERTIES.containsKey(propertyName)) {
issuer.sendMessage(MVCorei18n.MODIFY_PROPERTYREMOVED,
replace("{property}").with(propertyName),
replace("{replacement}").with(REMOVED_PROPERTIES.get(propertyName)));
return;
}
MultiverseWorld world = worldValue.value();
if (action.isRequireValue() && propertyValue == null) {
issuer.sendMessage(MVCorei18n.MODIFY_SPECIFYVALUE,
replace("{action}").with(action.name().toLowerCase()),
replace("{property}").with(propertyName));
return;
} else if (!action.isRequireValue() && propertyValue != null) {
issuer.sendMessage(MVCorei18n.MODIFY_CANNOTHAVEVALUE,
replace("{action}").with(action.name().toLowerCase()),
replace("{property}").with(propertyName));
return;
}
StringPropertyHandle worldPropertyHandle = world.getStringPropertyHandle();
worldPropertyHandle.modifyProperty(propertyName, propertyValue, action).onSuccess(ignore -> {
issuer.sendMessage("Property %s set to %s for world %s.".formatted(
propertyName,
worldPropertyHandle.getProperty(propertyName).getOrNull(),
world.getName()));
worldPropertyHandle.modifyPropertyString(issuer.getIssuer(), propertyName, propertyValue, action).onSuccess(ignore -> {
issuer.sendMessage(MVCorei18n.MODIFY_SUCCESS,
replace("{action}").with(action.name().toLowerCase()),
replace("{property}").with(propertyName),
Replace.VALUE.with(worldPropertyHandle.getProperty(propertyName).getOrNull()),
Replace.WORLD.with(world.getName()));
worldManager.saveWorldsConfig();
}).onFailure(exception -> {
issuer.sendMessage("Failed to %s property %s to %s for world %s.".formatted(
action.name().toLowerCase(),
propertyName,
propertyValue,
world.getName()));
issuer.sendMessage(exception.getMessage());
if (propertyValue == null) {
issuer.sendMessage(MVCorei18n.MODIFY_FAILURE_NOVALUE,
replace("{action}").with(action.name().toLowerCase()),
replace("{property}").with(propertyName),
Replace.WORLD.with(world.getName()),
Replace.ERROR.with(exception.getMessage()));
} else {
issuer.sendMessage(MVCorei18n.MODIFY_FAILURE,
replace("{action}").with(action.name().toLowerCase()),
replace("{property}").with(propertyName),
Replace.VALUE.with(propertyValue),
Replace.WORLD.with(world.getName()),
Replace.ERROR.with(exception.getMessage()));
}
});
}
@Service
private static final class LegacyAlias extends ModifyCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(WorldManager worldManager) {
super(worldManager);
}
@Override
@CommandAlias("mvmodify|mvm")
void onModifyCommand(MVCommandIssuer issuer, @NotNull MultiverseWorldValue world, @NotNull PropertyModifyAction action, @NotNull String propertyName, @Nullable String propertyValue) {
super.onModifyCommand(issuer, world, action, propertyName, propertyValue);
}
}
}
@@ -0,0 +1,54 @@
package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Flags;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import jakarta.inject.Inject;
import org.bukkit.entity.SpawnCategory;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.utils.StringFormatter;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.entity.EntityPurger;
import java.util.List;
@Service
final class PurgeAllEntitiesCommand extends CoreCommand {
private final EntityPurger entityPurger;
@Inject
PurgeAllEntitiesCommand(EntityPurger entityPurger) {
this.entityPurger = entityPurger;
}
@Subcommand("purge-all-entities")
@CommandPermission("multiverse.core.purgeall")
@CommandCompletion("@mvworlds:scope=loaded|@spawncategories:multiple,resolveUntil=arg1 @spawncategories:multiple")
@Syntax("[world] [spawn-categories]")
void onPurgeAllEntitiesCommand(
MVCommandIssuer issuer,
@Flags("resolve=issuerAware")
@Syntax("[world]")
LoadedMultiverseWorld world,
@Optional
@Syntax("[spawn-categories]")
SpawnCategory[] spawnCategories
) {
if (spawnCategories == null || spawnCategories.length == 0) {
int purgeCount = entityPurger.purgeAllEntities(world);
issuer.sendMessage("Successfully purged " + purgeCount + " entities in world " + world.getName() + ".");
return;
}
int purgeCount = entityPurger.purgeEntities(world, spawnCategories);
issuer.sendMessage("Successfully purged " + purgeCount + " entities in world " + world.getName() +
" for spawn categories " + StringFormatter.join(List.of(spawnCategories), ", ") + ".");
}
}
@@ -0,0 +1,38 @@
package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Flags;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import jakarta.inject.Inject;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.entity.EntityPurger;
@Service
final class PurgeEntitiesCommand extends CoreCommand {
private final EntityPurger entityPurger;
@Inject
PurgeEntitiesCommand(EntityPurger entityPurger) {
this.entityPurger = entityPurger;
}
@Subcommand("purge-entities")
@CommandPermission("multiverse.core.purge")
@CommandCompletion("@mvworlds:scope=loaded")
@Syntax("[world]")
void onPurgeEntityCommand(
MVCommandIssuer issuer,
@Flags("resolve=issuerAware")
@Syntax("[world]")
LoadedMultiverseWorld world
) {
int purgeCount = entityPurger.purgeEntities(world);
issuer.sendMessage("Successfully purged " + purgeCount + " entities in world " + world.getName() + ".");
}
}
@@ -2,9 +2,8 @@ package org.mvplugins.multiverse.core.commands;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import co.aikar.commands.MessageType;
import co.aikar.commands.ACFUtil;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
@@ -18,62 +17,55 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag;
import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.commandtools.queue.QueuedCommand;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.utils.result.Async;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
import org.mvplugins.multiverse.core.command.queue.CommandQueueManager;
import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
import org.mvplugins.multiverse.core.utils.REPatterns;
import org.mvplugins.multiverse.core.utils.WorldTickDeferrer;
import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
import org.mvplugins.multiverse.core.world.options.RegenWorldOptions;
@Service
@CommandAlias("mv")
class RegenCommand extends MultiverseCommand {
class RegenCommand extends CoreCommand {
@NotNull
private final CommandQueueManager commandQueueManager;
private final WorldManager worldManager;
private final PlayerWorldTeleporter playerWorldTeleporter;
private final CommandValueFlag<String> SEED_FLAG = flag(CommandValueFlag.builder("--seed", String.class)
.addAlias("-s")
.completion(input -> Collections.singleton(String.valueOf(new Random().nextLong())))
.build());
private final CommandFlag RESET_WORLD_CONFIG_FLAG = flag(CommandFlag.builder("--reset-world-config")
.addAlias("-wc")
.build());
private final CommandFlag RESET_GAMERULES_FLAG = flag(CommandFlag.builder("--reset-gamerules")
.addAlias("-gm")
.build());
private final CommandFlag RESET_WORLD_BORDER_FLAG = flag(CommandFlag.builder("--reset-world-border")
.addAlias("-wb")
.build());
private final CommandFlag REMOVE_PLAYERS_FLAG = flag(CommandFlag.builder("--remove-players")
.addAlias("-r")
.build());
private final WorldTickDeferrer worldTickDeferrer;
private final RegenCommand.Flags flags;
@Inject
RegenCommand(
@NotNull MVCommandManager commandManager,
@NotNull CommandQueueManager commandQueueManager,
@NotNull WorldManager worldManager,
@NotNull PlayerWorldTeleporter playerWorldTeleporter) {
super(commandManager);
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
@NotNull WorldTickDeferrer worldTickDeferrer,
@NotNull Flags flags
) {
this.commandQueueManager = commandQueueManager;
this.worldManager = worldManager;
this.playerWorldTeleporter = playerWorldTeleporter;
this.worldTickDeferrer = worldTickDeferrer;
this.flags = flags;
}
@Subcommand("regen")
@CommandPermission("multiverse.core.regen")
@CommandCompletion("@mvworlds:scope=loaded @flags:groupName=mvregencommand")
@Syntax("<world> --seed [seed] --keep-gamerules")
@CommandCompletion("@mvworlds:scope=loaded @flags:groupName=" + Flags.NAME)
@Syntax("<world> [--seed [seed] --reset-world-config --reset-gamerules --reset-world-border --remove-players]")
@Description("{@@mv-core.regen.description}")
void onRegenCommand(
MVCommandIssuer issuer,
@@ -83,27 +75,30 @@ class RegenCommand extends MultiverseCommand {
LoadedMultiverseWorld world,
@Optional
@Syntax("--seed [seed] --reset-gamerules")
@Syntax("[--seed [seed] --reset-world-config --reset-gamerules --reset-world-border --remove-players]")
@Description("{@@mv-core.regen.other.description}")
String[] flags) {
ParsedCommandFlags parsedFlags = parseFlags(flags);
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
this.commandManager.getCommandQueueManager().addToQueue(new QueuedCommand(
issuer.getIssuer(),
() -> runRegenCommand(issuer, world, parsedFlags),
this.commandManager.formatMessage(
issuer, MessageType.INFO, MVCorei18n.REGEN_PROMPT, "{world}", world.getName())));
commandQueueManager.addToQueue(CommandQueuePayload
.issuer(issuer)
.action(() -> runRegenCommand(issuer, world, parsedFlags))
.prompt(Message.of(MVCorei18n.REGEN_PROMPT, "",
Replace.WORLD.with(world.getName()))));
}
private void runRegenCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, ParsedCommandFlags parsedFlags) {
issuer.sendInfo(MVCorei18n.REGEN_REGENERATING, "{world}", world.getName());
issuer.sendInfo(MVCorei18n.REGEN_REGENERATING, Replace.WORLD.with(world.getName()));
List<Player> worldPlayers = world.getPlayers().getOrElse(Collections.emptyList());
var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
var future = parsedFlags.hasFlag(flags.removePlayers)
? playerWorldTeleporter.removeFromWorld(world)
: Async.completedFuture(Collections.emptyList());
: AsyncAttemptsAggregate.emptySuccess();
future.thenRun(() -> doWorldRegening(issuer, world, parsedFlags, worldPlayers));
// todo: using future will hide stacktrace
future.onSuccess(() -> worldTickDeferrer
.deferWorldTick(() -> doWorldRegening(issuer, world, parsedFlags, worldPlayers)))
.onFailure(() -> issuer.sendError("Failed to teleport one or more players out of the world!"));
}
private void doWorldRegening(
@@ -111,22 +106,80 @@ class RegenCommand extends MultiverseCommand {
LoadedMultiverseWorld world,
ParsedCommandFlags parsedFlags,
List<Player> worldPlayers) {
//todo: Change biome on regen
RegenWorldOptions regenWorldOptions = RegenWorldOptions.world(world)
.randomSeed(parsedFlags.hasFlag(SEED_FLAG))
.seed(parsedFlags.flagValue(SEED_FLAG))
.keepWorldConfig(!parsedFlags.hasFlag(RESET_WORLD_CONFIG_FLAG))
.keepGameRule(!parsedFlags.hasFlag(RESET_GAMERULES_FLAG))
.keepWorldBorder(!parsedFlags.hasFlag(RESET_WORLD_BORDER_FLAG));
.randomSeed(parsedFlags.hasFlag(flags.seed))
.seed(parsedFlags.flagValue(flags.seed))
.keepWorldConfig(!parsedFlags.hasFlag(flags.resetWorldConfig))
.keepGameRule(!parsedFlags.hasFlag(flags.resetGamerules))
.keepWorldBorder(!parsedFlags.hasFlag(flags.resetWorldBorder))
.keepFiles(parsedFlags.flagValue(flags.keepFiles));
worldManager.regenWorld(regenWorldOptions).onSuccess(newWorld -> {
Logging.fine("World regen success: " + newWorld);
issuer.sendInfo(MVCorei18n.REGEN_SUCCESS, "{world}", newWorld.getName());
if (parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)) {
issuer.sendInfo(MVCorei18n.REGEN_SUCCESS, Replace.WORLD.with(newWorld.getName()));
if (parsedFlags.hasFlag(flags.removePlayers)) {
playerWorldTeleporter.teleportPlayersToWorld(worldPlayers, newWorld);
}
}).onFailure(failure -> {
Logging.fine("World regen failure: " + failure);
Logging.warning("World regen failure: " + failure);
issuer.sendError(failure.getFailureMessage());
});
}
@Service
private static final class Flags extends RemovePlayerFlags {
private static final String NAME = "mvregen";
@Inject
private Flags(@NotNull CommandFlagsManager flagsManager) {
super(NAME, flagsManager);
}
private final CommandValueFlag<String> seed = flag(CommandValueFlag.builder("--seed", String.class)
.addAlias("-s")
.completion(input -> Collections.singleton(String.valueOf(ACFUtil.RANDOM.nextLong())))
.optional()
.build());
private final CommandFlag resetWorldConfig = flag(CommandFlag.builder("--reset-world-config")
.addAlias("-wc")
.build());
private final CommandFlag resetGamerules = flag(CommandFlag.builder("--reset-gamerules")
.addAlias("-gm")
.build());
private final CommandFlag resetWorldBorder = flag(CommandFlag.builder("--reset-world-border")
.addAlias("-wb")
.build());
private final CommandValueFlag<List> keepFiles = flag(CommandValueFlag.builder("--keep-files", List.class)
.addAlias("-f")
.completion(input -> List.of("paper-world.yml"))
.defaultValue(Collections.emptyList())
.context(input -> List.of(REPatterns.COMMA.split(input)))
.build());
}
@Service
private static final class LegacyAlias extends RegenCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(
@NotNull CommandQueueManager commandQueueManager,
@NotNull WorldManager worldManager,
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
@NotNull WorldTickDeferrer worldTickDeferrer,
@NotNull Flags flags
) {
super(commandQueueManager, worldManager, playerWorldTeleporter, worldTickDeferrer, flags);
}
@Override
@CommandAlias("mvregen")
void onRegenCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, String[] flags) {
super.onRegenCommand(issuer, world, flags);
}
}
}
@@ -14,30 +14,28 @@ import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.config.MVCoreConfig;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.event.MVConfigReloadEvent;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.world.WorldManager;
@Service
@CommandAlias("mv")
class ReloadCommand extends MultiverseCommand {
class ReloadCommand extends CoreCommand {
private final MVCoreConfig config;
private final CoreConfig config;
private final AnchorManager anchorManager;
private final WorldManager worldManager;
private final PluginManager pluginManager;
@Inject
ReloadCommand(
@NotNull MVCommandManager commandManager,
@NotNull MVCoreConfig config,
@NotNull CoreConfig config,
@NotNull AnchorManager anchorManager,
@NotNull WorldManager worldManager,
@NotNull PluginManager pluginManager) {
super(commandManager);
@NotNull PluginManager pluginManager
) {
this.config = config;
this.anchorManager = anchorManager;
this.worldManager = worldManager;
@@ -50,24 +48,36 @@ class ReloadCommand extends MultiverseCommand {
void onReloadCommand(@NotNull BukkitCommandIssuer issuer) {
issuer.sendInfo(MVCorei18n.RELOAD_RELOADING);
try {
// TODO: Make this all Try<Void>
this.config.load().getOrElseThrow(e -> new RuntimeException("Failed to load config", e));
this.worldManager.initAllWorlds().getOrElseThrow(e -> new RuntimeException("Failed to init worlds", e));
this.anchorManager.loadAnchors();
this.anchorManager.loadAnchors().getOrElseThrow(e -> new RuntimeException("Failed to load anchors", e));
} catch (Exception e) {
e.printStackTrace();
}
List<String> configsLoaded = new ArrayList<>();
configsLoaded.add("Multiverse-Core - config.yml");
configsLoaded.add("Multiverse-Core - worlds2.yml");
configsLoaded.add("Multiverse-Core - worlds.yml");
configsLoaded.add("Multiverse-Core - anchors.yml");
MVConfigReloadEvent configReload = new MVConfigReloadEvent(configsLoaded);
this.pluginManager.callEvent(configReload);
// TODO: replace this sendMessage and format the configsLoaded above, maybe?
configReload.getAllConfigsLoaded().forEach(issuer::sendMessage);
issuer.sendInfo(MVCorei18n.RELOAD_SUCCESS);
}
@Service
private static final class LegacyAlias extends ReloadCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(@NotNull CoreConfig config, @NotNull AnchorManager anchorManager, @NotNull WorldManager worldManager, @NotNull PluginManager pluginManager) {
super(config, anchorManager, worldManager, pluginManager);
}
@Override
@CommandAlias("mvreload|mvr")
void onReloadCommand(@NotNull BukkitCommandIssuer issuer) {
super.onReloadCommand(issuer);
}
}
}
@@ -1,14 +1,10 @@
package org.mvplugins.multiverse.core.commands;
import java.util.Collections;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Conditions;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import com.dumptruckman.minecraft.util.Logging;
@@ -16,75 +12,112 @@ import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.flags.CommandFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.utils.result.Async;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
import org.mvplugins.multiverse.core.world.options.RemoveWorldOptions;
@Service
@CommandAlias("mv")
class RemoveCommand extends MultiverseCommand {
class RemoveCommand extends CoreCommand {
private final WorldManager worldManager;
private final PlayerWorldTeleporter playerWorldTeleporter;
private final CommandFlag REMOVE_PLAYERS_FLAG = flag(CommandFlag.builder("--remove-players")
.addAlias("-r")
.build());
private final Flags flags;
@Inject
RemoveCommand(
@NotNull MVCommandManager commandManager,
@NotNull WorldManager worldManager,
@NotNull PlayerWorldTeleporter playerWorldTeleporter) {
super(commandManager);
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
@NotNull Flags flags
) {
this.worldManager = worldManager;
this.playerWorldTeleporter = playerWorldTeleporter;
this.flags = flags;
}
@Subcommand("remove")
@CommandPermission("multiverse.core.remove")
@CommandCompletion("@mvworlds:scope=both")
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + Flags.NAME)
@Syntax("<world>")
@Description("{@@mv-core.remove.description}")
void onRemoveCommand(
MVCommandIssuer issuer,
@Single
@Conditions("mvworlds:scope=both @flags:groupName=mvremovecommand")
@Syntax("<world>")
@Description("{@@mv-core.remove.world.description}")
MultiverseWorld world,
@Optional
@Syntax("[--remove-players]")
@Description(/* TODO */"")
String[] flags) {
ParsedCommandFlags parsedFlags = parseFlags(flags);
@Description("")
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
var future = parsedFlags.hasFlag(REMOVE_PLAYERS_FLAG)
? worldManager.getLoadedWorld(world)
.map(playerWorldTeleporter::removeFromWorld)
.getOrElse(Async.completedFuture(Collections.emptyList()))
: Async.completedFuture(Collections.emptyList());
var future = parsedFlags.hasFlag(flags.removePlayers)
? worldManager.getLoadedWorld(world).map(playerWorldTeleporter::removeFromWorld).getOrElse(AsyncAttemptsAggregate::emptySuccess)
: AsyncAttemptsAggregate.emptySuccess();
future.thenRun(() -> doWorldRemoving(issuer, world));
future.onSuccess(() -> doWorldRemoving(issuer, world, parsedFlags))
.onFailure(() -> issuer.sendError("Failed to teleport one or more players out of the world!"));
}
private void doWorldRemoving(MVCommandIssuer issuer, MultiverseWorld world) {
worldManager.removeWorld(world)
private void doWorldRemoving(MVCommandIssuer issuer, MultiverseWorld world, ParsedCommandFlags parsedFlags) {
worldManager.removeWorld(RemoveWorldOptions.world(world)
.saveBukkitWorld(!parsedFlags.hasFlag(flags.noSave))
.unloadBukkitWorld(!parsedFlags.hasFlag(flags.noUnloadBukkitWorld)))
.onSuccess(removedWorldName -> {
Logging.fine("World remove success: " + removedWorldName);
issuer.sendInfo(MVCorei18n.REMOVE_SUCCESS, "{world}", removedWorldName);
issuer.sendInfo(MVCorei18n.REMOVE_SUCCESS, Replace.WORLD.with(removedWorldName));
}).onFailure(failure -> {
Logging.fine("World remove failure: " + failure);
issuer.sendError(failure.getFailureMessage());
});
}
@Service
private static final class Flags extends RemovePlayerFlags {
private static final String NAME = "mvremove";
@Inject
private Flags(@NotNull CommandFlagsManager flagsManager) {
super(NAME, flagsManager);
}
private final CommandFlag noUnloadBukkitWorld = flag(CommandFlag.builder("--no-unload-bukkit-world")
.addAlias("-b")
.build());
private final CommandFlag noSave = flag(CommandFlag.builder("--no-save")
.addAlias("-n")
.build());
}
@Service
private static final class LegacyAlias extends RemoveCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(
@NotNull WorldManager worldManager,
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
@NotNull Flags flags
) {
super(worldManager, playerWorldTeleporter, flags);
}
@Override
@CommandAlias("mvremove")
void onRemoveCommand(MVCommandIssuer issuer, MultiverseWorld world, String[] flags) {
super.onRemoveCommand(issuer, world, flags);
}
}
}
@@ -9,18 +9,16 @@ import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
@Service
class RootCommand extends MultiverseCommand {
final class RootCommand extends CoreCommand {
private final Plugin plugin;
@Inject
RootCommand(@NotNull MVCommandManager commandManager, @NotNull MultiverseCore plugin) {
super(commandManager);
RootCommand(@NotNull MultiverseCore plugin) {
this.plugin = plugin;
}
@@ -1,11 +1,9 @@
package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.BukkitCommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Flags;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
@@ -13,59 +11,85 @@ import jakarta.inject.Inject;
import org.bukkit.Location;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.api.BlockSafety;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.context.PlayerLocation;
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
import org.mvplugins.multiverse.core.command.flags.UnsafeFlags;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
import org.mvplugins.multiverse.core.world.WorldManager;
@Service
@CommandAlias("mv")
public class SetSpawnCommand extends MultiverseCommand {
class SetSpawnCommand extends CoreCommand {
private final WorldManager worldManager;
private final BlockSafety blockSafety;
private final UnsafeFlags flags;
@Inject
SetSpawnCommand(@NotNull MVCommandManager commandManager) {
super(commandManager);
SetSpawnCommand(@NotNull WorldManager worldManager, @NotNull BlockSafety blockSafety, @NotNull UnsafeFlags flags) {
this.worldManager = worldManager;
this.blockSafety = blockSafety;
this.flags = flags;
}
@CommandAlias("mvsetspawn")
@Subcommand("setspawn")
@CommandPermission("multiverse.core.setspawn")
@CommandCompletion("@nothing @mvworlds:scope=loaded ") // TODO: Use Brigadier to show <position> above in chat like the vanilla TP command
@Syntax("[location] [world]")
@CommandPermission("multiverse.core.spawn.set")
@CommandCompletion("@flags:groupName=" + UnsafeFlags.NAME + " @flags:resolveUntil=arg1,groupName=" + UnsafeFlags.NAME)
@Syntax("[worldname:x,y,z[,pitch,yaw]] [--unsafe]")
@Description("{@@mv-core.setspawn.description}")
void onSetSpawnCommand(
BukkitCommandIssuer issuer,
MVCommandIssuer issuer,
@Optional
@Flags("resolve=issuerAware")
@Syntax("<location>")
@Syntax("[worldname:x,y,z[,pitch,yaw]]")
@Description("{@@mv-core.setspawn.location.description}")
Location location,
PlayerLocation playerLocation,
@Optional
@Flags("resolve=issuerAware")
@Syntax("<world>")
@Description("{@@mv-core.setspawn.world.description}")
LoadedMultiverseWorld world
) {
@Syntax("[--unsafe]")
@Description("")
String[] flagArray) {
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
Location location = playerLocation.value();
// TODO: Use a flag to do this, no clue how to edit an inbuilt ACF flag though
// Get the Location
if (location == null) {
if (issuer.isPlayer()) {
location = issuer.getPlayer().getLocation();
} else {
issuer.sendMessage("The console must specify a location");
return;
}
if (!parsedFlags.hasFlag(flags.unsafe) && !blockSafety.canSpawnAtLocationSafely(location)) {
issuer.sendMessage(MVCorei18n.SETSPAWN_UNSAFE);
return;
}
issuer.sendMessage("Setting spawn in " + world.getName() + " to " + prettyLocation(location));
world.setSpawnLocation(location);
worldManager.getLoadedWorld(location.getWorld())
.peek(mvWorld -> mvWorld.setSpawnLocation(location)
.flatMap(ignore -> worldManager.saveWorldsConfig())
.onSuccess(ignore -> issuer.sendMessage(MVCorei18n.SETSPAWN_SUCCESS,
Replace.WORLD.with(mvWorld.getName()),
Replace.LOCATION.with(prettyLocation(location))))
.onFailure(e -> issuer.sendMessage(MVCorei18n.SETSPAWN_FAILED,
Replace.WORLD.with(mvWorld.getName()),
Replace.ERROR.with(e))))
.onEmpty(() -> issuer.sendMessage(MVCorei18n.SETSPAWN_NOTMVWORLD,
Replace.WORLD.with(location.getWorld().getName())));
}
private String prettyLocation(Location location) {
return location.getX() + ", " + location.getY() + ", " + location.getZ();
return "%.2f, %.2f, %.2f, pitch:%.2f, yaw:%.2f"
.formatted(location.getX(), location.getY(), location.getZ(), location.getPitch(), location.getYaw());
}
@Service
private static final class LegacyAlias extends SetSpawnCommand implements LegacyAliasCommand {
@Inject
LegacyAlias(@NotNull WorldManager worldManager, @NotNull BlockSafety blockSafety, @NotNull UnsafeFlags flags) {
super(worldManager, blockSafety, flags);
}
@Override
@CommandAlias("mvss")
void onSetSpawnCommand(MVCommandIssuer issuer, PlayerLocation location, String[] flagArray) {
super.onSetSpawnCommand(issuer, location, flagArray);
}
}
}
Loaded 100 of 439 files, more files were not shown because too many files have changed in this diff. Show more