mirror of
https://github.com/Multiverse/Multiverse-Core.git
synced 2026-11-04 16:50:03 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c86c41f98 | ||
|
|
0afd4dbb62 | ||
|
|
f527fbd693 | ||
|
|
b50687db3b | ||
|
|
a28a071e1e | ||
|
|
66dc376633 | ||
|
|
5e8e9380e3 | ||
|
|
4a16fa2297 | ||
|
|
b9e07a85d0 | ||
|
|
53c7459c1d | ||
|
|
112505bc93 | ||
|
|
e27fe73faa | ||
|
|
9bc2b4dfaf | ||
|
|
3ba8beefc5 | ||
|
|
05b6b380e0 | ||
|
|
b58ea2e04b | ||
|
|
f4405874d1 | ||
|
|
247685c93d | ||
|
|
490107b5d5 | ||
|
|
122e35103a | ||
|
|
25eeefbc77 | ||
|
|
6b5ce2aebb | ||
|
|
e9905ca281 | ||
|
|
e002282b13 | ||
|
|
39711527fa | ||
|
|
8d9b29ff87 | ||
|
|
82fe350afa | ||
|
|
a7b4b9bbda | ||
|
|
15f511b660 | ||
|
|
8347cf8fbb | ||
|
|
4c27fea26b | ||
|
|
4111da8fd6 | ||
|
|
167f6f8a37 | ||
|
|
325b5d7643 | ||
|
|
5dfb098d5c | ||
|
|
6c50bfb08e | ||
|
|
37c0580828 | ||
|
|
e1a42cec92 | ||
|
|
a6cc8bd68e | ||
|
|
ee4cd18522 | ||
|
|
b6c309a332 | ||
|
|
87571e7037 | ||
|
|
0822b1460a | ||
|
|
1220998c12 | ||
|
|
e078c5e5dd | ||
|
|
c1ef8f656b | ||
|
|
86e2112d87 | ||
|
|
8a77dc807e | ||
|
|
77382dacf3 | ||
|
|
588a8e1baf | ||
|
|
5bd37d17c3 | ||
|
|
815637e00f | ||
|
|
78194389eb | ||
|
|
2db55fab3b | ||
|
|
c505da76ec | ||
|
|
6152620541 | ||
|
|
cf70b3b839 | ||
|
|
527085713c | ||
|
|
f446c94dbc | ||
|
|
aac0111dde | ||
|
|
f66e673ea9 | ||
|
|
1434ca176a | ||
|
|
212b68437e | ||
|
|
05d01884ad | ||
|
|
65c399740e | ||
|
|
72ee0d5bed | ||
|
|
1afecd40ea | ||
|
|
00944ed3b2 | ||
|
|
5f659a80d8 | ||
|
|
5f9b0af8d1 | ||
|
|
5c44c05f6f | ||
|
|
1699a47011 | ||
|
|
96deee719b | ||
|
|
8a60761098 | ||
|
|
a38b4e03b4 | ||
|
|
db9b725e10 | ||
|
|
d43b23e7ea | ||
|
|
86d77ae796 | ||
|
|
86bf785c84 | ||
|
|
626537290d | ||
|
|
9fa47727f4 | ||
|
|
ae3bc6d201 | ||
|
|
b1d126397e | ||
|
|
8ab9993e97 | ||
|
|
13b949c692 | ||
|
|
2b41c1367e | ||
|
|
fde3f388bd | ||
|
|
eb3968b7d6 | ||
|
|
fed1fdcbea | ||
|
|
bfb6fea5f5 | ||
|
|
5b02452a3d | ||
|
|
4a2bfc74fa | ||
|
|
f0539441cf | ||
|
|
efe490d8c4 | ||
|
|
3074cfab17 | ||
|
|
7a5dd44feb | ||
|
|
88a9ee5b6d | ||
|
|
46f67240e8 | ||
|
|
89e765b2e8 | ||
|
|
b8bdec22d3 | ||
|
|
792913cff7 | ||
|
|
6dcb40fdcb | ||
|
|
2bb601098a | ||
|
|
4bcebd51f5 | ||
|
|
e207eb5d0d | ||
|
|
f181bb7ade | ||
|
|
7207d9303f | ||
|
|
82799cdbe8 | ||
|
|
3629babdbc | ||
|
|
eed3353e2b | ||
|
|
147564caab | ||
|
|
30e50866bd | ||
|
|
5dde7608f2 | ||
|
|
2f9915c62e | ||
|
|
e62e37831b | ||
|
|
ef9934f3c6 | ||
|
|
691bcd0ffa | ||
|
|
148644970d | ||
|
|
ad20ec94c5 | ||
|
|
f7d86884b4 | ||
|
|
0e0e4e0434 | ||
|
|
8e878b1ed0 | ||
|
|
e1c6af2f38 | ||
|
|
b52d65757a | ||
|
|
ff2135c775 | ||
|
|
c4e4e311e2 | ||
|
|
3e4ded52e3 | ||
|
|
4ef8390ea3 | ||
|
|
bca71e1acc | ||
|
|
343da8c301 | ||
|
|
d34f3adccc | ||
|
|
b3862faf88 | ||
|
|
8bffffdc65 | ||
|
|
79a71fa58f | ||
|
|
a41476de89 | ||
|
|
fe75e497e4 | ||
|
|
fdd9257e2c | ||
|
|
55d1347d4f | ||
|
|
29cc97486b | ||
|
|
8d06b92edd | ||
|
|
ce3b6ab700 | ||
|
|
58ac0f505c | ||
|
|
4004554733 | ||
|
|
99037b2af5 | ||
|
|
ac46a5f64a | ||
|
|
4d805b921b | ||
|
|
a612854490 | ||
|
|
e85c14e319 | ||
|
|
3983af7b4c | ||
|
|
8853f88f3a | ||
|
|
08d361e0ec | ||
|
|
f985f2db06 | ||
|
|
c068225950 | ||
|
|
82acca3ff9 | ||
|
|
d712c1eb09 | ||
|
|
25e9213ee9 | ||
|
|
9e79708fec | ||
|
|
65d1141289 | ||
|
|
63b2ffdf72 | ||
|
|
14d8ad7a5c | ||
|
|
33e0df925f | ||
|
|
1883edbb28 | ||
|
|
b7a2bd61ca | ||
|
|
a0673ac109 | ||
|
|
7735a33ccf | ||
|
|
5f24520685 | ||
|
|
608cb8611c | ||
|
|
a965b9ce56 | ||
|
|
981d368058 | ||
|
|
7507099a4e | ||
|
|
29b58b1655 | ||
|
|
a1081c1b6b | ||
|
|
3152b62668 | ||
|
|
0082cc9852 | ||
|
|
f60b656bed | ||
|
|
da5f5de5cd | ||
|
|
e9aacecc2f | ||
|
|
fa3adbe605 | ||
|
|
e33b2f7025 | ||
|
|
a9cc577754 | ||
|
|
02344a31cf | ||
|
|
2f4871ef71 | ||
|
|
9750fad4b7 | ||
|
|
df77372d96 | ||
|
|
039af3c219 | ||
|
|
09ec4f87ef | ||
|
|
f996f22f4b | ||
|
|
b230c5ae9c | ||
|
|
2561107697 | ||
|
|
70babc8656 | ||
|
|
bd09d6c950 | ||
|
|
b94de763ca | ||
|
|
22ca57543f | ||
|
|
25c70170d9 | ||
|
|
9926041c2f | ||
|
|
1c99898d0d | ||
|
|
41e0261eee | ||
|
|
642f3ab1b8 | ||
|
|
57d1844756 | ||
|
|
73e1650fc3 | ||
|
|
800709ca95 | ||
|
|
935cbad21c | ||
|
|
0e2a27b298 | ||
|
|
baaa175423 | ||
|
|
e2b7a31191 | ||
|
|
afa2825bc7 | ||
|
|
21b4f6b087 | ||
|
|
a771652c8f | ||
|
|
e89b4f19ee | ||
|
|
eb8ef32a94 | ||
|
|
f9d783bc5c | ||
|
|
87dd4168cd | ||
|
|
2da6ece606 | ||
|
|
cbb3a7a518 | ||
|
|
57796d295b | ||
|
|
09314e8bce | ||
|
|
907a6dd5c0 | ||
|
|
a50bd807b6 | ||
|
|
02081b4654 | ||
|
|
156ec7911d | ||
|
|
2b95c62a84 | ||
|
|
539d5c43d1 | ||
|
|
1664690472 | ||
|
|
455396713e | ||
|
|
8bc3a593d0 | ||
|
|
5177f290ae | ||
|
|
5118864bb8 | ||
|
|
654da03add | ||
|
|
61229356ad | ||
|
|
fa5aa41547 | ||
|
|
04f07657eb | ||
|
|
eea2af169f | ||
|
|
6d393723ff | ||
|
|
9137ef04d2 | ||
|
|
878c4bea3d | ||
|
|
021d86e21c | ||
|
|
3e9425c8f1 | ||
|
|
cc60b9c938 | ||
|
|
d3cc950a97 | ||
|
|
5e97b1169b | ||
|
|
c3f4f117e5 | ||
|
|
814901b1c1 | ||
|
|
7c6b1d07b0 | ||
|
|
c389a41cdc | ||
|
|
2db9ae7c66 | ||
|
|
b80aea4c38 | ||
|
|
35b129701f | ||
|
|
d92fe17c4f | ||
|
|
488e258e3f | ||
|
|
d096d1a941 | ||
|
|
3ef4271e63 | ||
|
|
0a33d35b2b | ||
|
|
b4fedea1db | ||
|
|
4d92964dda | ||
|
|
3443b175ff | ||
|
|
ad3fe9e114 | ||
|
|
d1660d7e81 | ||
|
|
7750b61103 | ||
|
|
eeb1019bbb | ||
|
|
6bbaad8bdb | ||
|
|
1ef4470979 | ||
|
|
3110f462ac | ||
|
|
170e1ac347 | ||
|
|
86b95034cc | ||
|
|
8f6d4d5960 | ||
|
|
a34d2f810b | ||
|
|
db002ab2b1 | ||
|
|
c9c8a1a9c6 | ||
|
|
cdc22bc533 | ||
|
|
1efe98ab46 | ||
|
|
989242810d | ||
|
|
29d3c0c8c1 | ||
|
|
97cc7e53f0 | ||
|
|
4c4544340a | ||
|
|
755fab05ba | ||
|
|
5f295fd57b | ||
|
|
86b888a68e | ||
|
|
4fd8350a96 | ||
|
|
5702426ddd | ||
|
|
9c562d09ea | ||
|
|
59d1c44c23 | ||
|
|
1082f73be7 | ||
|
|
1372626b50 | ||
|
|
c09dacb8ec | ||
|
|
518d64882c | ||
|
|
4f08b9a446 | ||
|
|
a3b7dd542d | ||
|
|
c68936f06d | ||
|
|
b91214f958 | ||
|
|
ff26ffd9c5 | ||
|
|
058a7c97cb | ||
|
|
a50965509d | ||
|
|
ce263b5b1f | ||
|
|
4ddcb3f0bf | ||
|
|
d76a6a6ca6 | ||
|
|
e9a20254b0 | ||
|
|
42a8d33824 | ||
|
|
d89f00a607 | ||
|
|
41c8c17bc9 | ||
|
|
a3a621945a | ||
|
|
71eedd392d | ||
|
|
ba78b7da3a | ||
|
|
38231ef0d4 | ||
|
|
232d18f8da | ||
|
|
530a8a9c2c | ||
|
|
8a96c7b610 | ||
|
|
bb2da157da | ||
|
|
1c521e0f79 | ||
|
|
aab13c1ec6 | ||
|
|
ae4d33d7b1 | ||
|
|
d5b2cc884b | ||
|
|
50f6152a71 | ||
|
|
c84ae0493c |
No files matched your search
@@ -0,0 +1,2 @@
|
||||
github: Multiverse
|
||||
open_collective: multiverse-plugins
|
||||
@@ -11,9 +11,9 @@ jobs:
|
||||
pull-requests: write
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: dbelyaev/action-checkstyle@v0.8.4
|
||||
- uses: dbelyaev/action-checkstyle@v3
|
||||
continue-on-error: true
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -107,9 +107,9 @@ 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.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
|
||||
game_versions: 26.1.2, 26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 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
|
||||
loaders: bukkit, spigot, paper, purpur
|
||||
dependencies: ${{ inputs.modrinth_dependencies }}
|
||||
|
||||
- name: Upload to DBO
|
||||
@@ -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.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
|
||||
game_versions: 26.1.2, 26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 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.18.2-1.21.5"]}'
|
||||
platform_dependencies: '{"PAPER": ["1.18.2-26.1.2"]}'
|
||||
plugin_dependencies: ${{ inputs.hangar_plugin_dependencies }}
|
||||
@@ -9,10 +9,9 @@
|
||||
|
||||
[](https://github.com/Multiverse/Multiverse-Core/releases/latest)
|
||||
[](https://github.com/Multiverse/Multiverse-Core/releases)
|
||||
[](https://discord.gg/NZtfKky)
|
||||
[](https://patreon.com/dumptruckman)
|
||||
[](LICENSE.md)
|
||||
|
||||
[](https://discord.gg/NZtfKky)
|
||||
[](https://github.com/sponsors/Multiverse)
|
||||
[](https://opencollective.com/multiverse-plugins)
|
||||
|
||||
# About
|
||||
|
||||
@@ -44,7 +43,7 @@ Simply build the source with Gradle:
|
||||
* 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-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!
|
||||
|
||||
|
||||
+8
-8
@@ -1,7 +1,7 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
plugins {
|
||||
id 'org.mvplugins.multiverse-plugin' version '1.2.0'
|
||||
id 'org.mvplugins.multiverse-plugin' version '1.2.2'
|
||||
}
|
||||
|
||||
group = 'org.mvplugins.multiverse.core'
|
||||
@@ -25,9 +25,9 @@ repositories {
|
||||
}
|
||||
|
||||
configure(apiDependencies) {
|
||||
serverApiVersion = '1.19.4-R0.1-SNAPSHOT'
|
||||
serverApiVersion = '1.21.11-R0.1-SNAPSHOT'
|
||||
mockBukkitServerApiVersion = '1.21'
|
||||
mockBukkitVersion = '4.31.1'
|
||||
mockBukkitVersion = '4.100.0'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -48,9 +48,9 @@ dependencies {
|
||||
}
|
||||
|
||||
// Utils
|
||||
shadowed 'io.vavr:vavr:0.10.4'
|
||||
shadowed 'org.glassfish.hk2:hk2-locator:3.0.3'
|
||||
shadowed('org.glassfish.hk2:hk2-inhabitant-generator: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') {
|
||||
@@ -60,8 +60,8 @@ dependencies {
|
||||
shadowed('org.bstats:bstats-bukkit:3.1.0') {
|
||||
exclude group: 'org.bukkit', module: 'bukkit'
|
||||
}
|
||||
shadowed 'net.minidev:json-smart:2.4.9'
|
||||
shadowed 'org.jetbrains:annotations:22.0.0'
|
||||
shadowed 'net.minidev:json-smart:2.5.2'
|
||||
shadowed 'org.jetbrains:annotations:26.0.2'
|
||||
shadowed 'io.papermc:paperlib:1.0.8'
|
||||
|
||||
// Tests
|
||||
|
||||
@@ -15,6 +15,7 @@ import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
@@ -123,9 +124,9 @@ public class MultiverseCore extends MultiverseModule {
|
||||
public void onDisable() {
|
||||
super.onDisable();
|
||||
MultiverseCoreApi.shutdown();
|
||||
saveAllConfigs();
|
||||
shutdownDependencyInjection();
|
||||
PluginServiceLocatorFactory.get().shutdown();
|
||||
Logging.info("- Disabled");
|
||||
Logging.shutdown();
|
||||
}
|
||||
|
||||
@@ -214,23 +215,23 @@ public class MultiverseCore extends MultiverseModule {
|
||||
*/
|
||||
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.");
|
||||
});
|
||||
.andThenTry(() -> worldManagerProvider.get().saveWorldsConfig())
|
||||
.andThenTry(() -> anchorManagerProvider.get().saveAllAnchors())
|
||||
.onFailure(e ->
|
||||
Logging.severe("Failed to save all configs, things may not work as expected. %s",
|
||||
e.getLocalizedMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the enable message.
|
||||
*/
|
||||
private void logEnableMessage() {
|
||||
Logging.config("\u001B[32mVersion %s (API v%s) Enabled - By %s\u001B[39m",
|
||||
Logging.config("\u001B[32mVersion %s (API v%s) Enabled - By %s\u001B[0m",
|
||||
this.getDescription().getVersion(), getVersionAsNumber(), StringFormatter.joinAnd(getDescription().getAuthors()));
|
||||
|
||||
if (configProvider.get().isShowingDonateMessage()) {
|
||||
Logging.config("\u001B[32mHelp dumptruckman keep this project alive. Become a patron! https://www.patreon.com/dumptruckman\u001B[39m");
|
||||
Logging.config("\u001B[32mOne time donations are also appreciated: https://www.paypal.me/dumptruckman\u001B[39m");
|
||||
Logging.config("\u001B[32mLoving Multiverse-Core? Please consider supporting the project with a " +
|
||||
"small donation: https://github.com/sponsors/Multiverse\u001B[0m");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +239,11 @@ public class MultiverseCore extends MultiverseModule {
|
||||
* 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();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -15,7 +16,10 @@ 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.
|
||||
@@ -23,6 +27,7 @@ import java.util.Objects;
|
||||
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) {
|
||||
@@ -30,6 +35,9 @@ public class MultiverseCoreApi {
|
||||
}
|
||||
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() {
|
||||
@@ -37,10 +45,49 @@ public class MultiverseCoreApi {
|
||||
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) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.mvplugins.multiverse.core;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -12,22 +13,33 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
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.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@Service
|
||||
final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
|
||||
private final MultiverseCore plugin;
|
||||
private final CoreConfig coreConfig;
|
||||
private final WorldManager worldManager;
|
||||
private final MVEconomist economist;
|
||||
|
||||
@Inject
|
||||
public PlaceholderExpansionHook(MultiverseCore plugin, WorldManager worldManager, MVEconomist economist) {
|
||||
public PlaceholderExpansionHook(@NotNull MultiverseCore plugin,
|
||||
@NotNull CoreConfig coreConfig,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull MVEconomist economist) {
|
||||
this.plugin = plugin;
|
||||
this.coreConfig = coreConfig;
|
||||
this.worldManager = worldManager;
|
||||
this.economist = economist;
|
||||
}
|
||||
@@ -38,6 +50,13 @@ final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
return super.register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String msg) {
|
||||
if (coreConfig.getWarnInvalidPapiFormat()) {
|
||||
super.warning(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getIdentifier() {
|
||||
return "multiverse-core";
|
||||
@@ -68,101 +87,110 @@ final class PlaceholderExpansionHook extends PlaceholderExpansion {
|
||||
@Override
|
||||
public @Nullable String onRequest(OfflinePlayer offlinePlayer, @NotNull String params) {
|
||||
// Split string in to an Array with underscores
|
||||
String[] paramsArray = REPatterns.UNDERSCORE.split(params, 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.remove(0).toLowerCase(Locale.ENGLISH);
|
||||
|
||||
// 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]);
|
||||
}
|
||||
|
||||
// Fail if world is null
|
||||
return targetWorld.map(world -> getWorldPlaceHolderValue(placeholder, world)).getOrNull();
|
||||
return parseWorldName(offlinePlayer, paramsArray)
|
||||
.flatMap(worldName -> worldManager.getWorld(worldName)
|
||||
.onEmpty(() -> warning("Multiverse World not found: " + worldName)))
|
||||
.flatMap(world -> world.asLoadedWorld()
|
||||
.flatMap(loadedWorld -> getLoadedWorldPlaceHolderValue(placeholder, paramsArray, loadedWorld))
|
||||
.orElse(() -> getWorldPlaceHolderValue(placeholder, paramsArray, world)))
|
||||
.getOrElse(() -> coreConfig.getInvalidPapiFormatReturnsBlank() ? "" : null);
|
||||
}
|
||||
|
||||
private @Nullable String getWorldPlaceHolderValue(@NotNull String placeholder, @NotNull LoadedMultiverseWorld world) {
|
||||
// Switch to find what specific placeholder we want
|
||||
switch (placeholder.toLowerCase()) {
|
||||
case "alias" -> {
|
||||
return world.getAliasOrName();
|
||||
private @NotNull Option<String> parseWorldName(OfflinePlayer offlinePlayer, List<String> paramsArray) {
|
||||
// No world defined, get from player
|
||||
if (paramsArray.isEmpty()) {
|
||||
if (offlinePlayer instanceof Player player) {
|
||||
return Option.of(player.getWorld().getName());
|
||||
} else {
|
||||
warning("You must specify a world name for non-player placeholders");
|
||||
return Option.none();
|
||||
}
|
||||
case "animalspawn" -> {
|
||||
return String.valueOf(world.getEntitySpawnConfig().getSpawnCategoryConfig(SpawnCategory.ANIMAL).isSpawn());
|
||||
}
|
||||
case "autoheal" -> {
|
||||
return String.valueOf(world.getAutoHeal());
|
||||
}
|
||||
case "blacklist" -> {
|
||||
return String.join(", ", world.getWorldBlacklist());
|
||||
}
|
||||
case "currency" -> {
|
||||
return String.valueOf(world.getCurrency());
|
||||
}
|
||||
case "difficulty" -> {
|
||||
return world.getDifficulty().toString();
|
||||
}
|
||||
case "entryfee" -> {
|
||||
return economist.formatPrice(world.getPrice(), world.getCurrency());
|
||||
}
|
||||
case "environment" -> {
|
||||
return world.getEnvironment().toString().toLowerCase();
|
||||
}
|
||||
case "flight" -> {
|
||||
return String.valueOf(world.isAllowFlight());
|
||||
}
|
||||
case "gamemode" -> {
|
||||
return world.getGameMode().toString().toLowerCase();
|
||||
}
|
||||
case "generator" -> {
|
||||
return world.getGenerator();
|
||||
}
|
||||
case "hunger" -> {
|
||||
return String.valueOf(world.isHunger());
|
||||
}
|
||||
case "monstersspawn" -> {
|
||||
return String.valueOf(world.getEntitySpawnConfig().getSpawnCategoryConfig(SpawnCategory.MONSTER).isSpawn());
|
||||
}
|
||||
case "name" -> {
|
||||
return world.getName();
|
||||
}
|
||||
case "playerlimit" -> {
|
||||
return String.valueOf(world.getPlayerLimit());
|
||||
}
|
||||
case "price" -> {
|
||||
return String.valueOf(world.getPrice());
|
||||
}
|
||||
case "pvp" -> {
|
||||
return String.valueOf(world.getPvp());
|
||||
}
|
||||
case "seed" -> {
|
||||
return String.valueOf(world.getSeed());
|
||||
}
|
||||
case "time" -> {
|
||||
return String.valueOf(world.getBukkitWorld().map(World::getTime).getOrElse(0L));
|
||||
}
|
||||
case "type" -> {
|
||||
return world.getBukkitWorld().map(World::getWorldType).map(Enum::name).getOrElse("null");
|
||||
}
|
||||
case "weather" -> {
|
||||
return String.valueOf(world.isAllowWeather());
|
||||
}
|
||||
|
||||
// Try get from params
|
||||
String paramWorldName = paramsArray.get(paramsArray.size() - 1);
|
||||
if (worldManager.isWorld(paramWorldName)) {
|
||||
paramsArray.remove(paramsArray.size() - 1);
|
||||
return Option.of(paramWorldName);
|
||||
}
|
||||
|
||||
// Param not a world, fallback to player
|
||||
if (offlinePlayer instanceof Player player) {
|
||||
return Option.of(player.getWorld().getName());
|
||||
}
|
||||
warning("Multiverse World not found: " + paramWorldName);
|
||||
return Option.none();
|
||||
}
|
||||
|
||||
private @NotNull Option<String> getWorldPlaceHolderValue(@NotNull String placeholder,
|
||||
@NotNull List<String> placeholderParams,
|
||||
@NotNull MultiverseWorld world) {
|
||||
return Option.of(switch (placeholder) {
|
||||
case "alias" -> world.getAliasOrName();
|
||||
case "animalspawn" -> String.valueOf(world.getEntitySpawnConfig()
|
||||
.getSpawnCategoryConfig(SpawnCategory.ANIMAL)
|
||||
.isSpawn());
|
||||
case "autoheal" -> String.valueOf(world.getAutoHeal());
|
||||
case "blacklist" -> String.join(", ", world.getWorldBlacklist());
|
||||
case "currency" -> String.valueOf(world.getCurrency());
|
||||
case "difficulty" -> world.getDifficulty().toString();
|
||||
case "entryfee" -> economist.formatPrice(world.getPrice(), world.getCurrency());
|
||||
case "environment" -> world.getEnvironment().toString().toLowerCase();
|
||||
case "flight" -> String.valueOf(world.isAllowFlight());
|
||||
case "gamemode" -> world.getGameMode().toString().toLowerCase();
|
||||
case "generator" -> world.getGenerator();
|
||||
case "hunger" -> String.valueOf(world.isHunger());
|
||||
case "isloaded" -> String.valueOf(world.isLoaded());
|
||||
case "monstersspawn" -> String.valueOf(world.getEntitySpawnConfig()
|
||||
.getSpawnCategoryConfig(SpawnCategory.MONSTER)
|
||||
.isSpawn());
|
||||
case "name" -> world.getName();
|
||||
case "playerlimit" -> String.valueOf(world.getPlayerLimit());
|
||||
case "price" -> String.valueOf(world.getPrice());
|
||||
case "pvp" -> String.valueOf(world.getPvp());
|
||||
case "seed" -> String.valueOf(world.getSeed());
|
||||
case "weather" -> String.valueOf(world.isAllowWeather());
|
||||
case "playercount", "time", "type" -> {
|
||||
warning("Placeholder '" + placeholder + "' is only available for loaded worlds.");
|
||||
yield null;
|
||||
}
|
||||
default -> {
|
||||
warning("Unknown placeholder: " + placeholder);
|
||||
return null;
|
||||
warning("Unknown Placeholder: " + placeholder);
|
||||
yield null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private @NotNull Option<String> getLoadedWorldPlaceHolderValue(@NotNull String placeholder,
|
||||
@NotNull List<String> placeholderParams,
|
||||
@NotNull LoadedMultiverseWorld world) {
|
||||
return Option.of(switch (placeholder) {
|
||||
case "playercount" -> String.valueOf(world.getBukkitWorld()
|
||||
.map(World::getPlayers)
|
||||
.map(List::size)
|
||||
.getOrElse(-1));
|
||||
case "time" -> {
|
||||
String timeFormat = !placeholderParams.isEmpty() ? placeholderParams.get(0) : "";
|
||||
long time = world.getBukkitWorld().map(World::getTime).getOrElse(0L);
|
||||
yield switch (timeFormat) {
|
||||
case "" -> String.valueOf(time);
|
||||
case "12h" -> MinecraftTimeFormatter.format12h(time);
|
||||
case "24h" -> MinecraftTimeFormatter.format24h(time);
|
||||
default -> MinecraftTimeFormatter.formatTime(time, timeFormat);
|
||||
};
|
||||
}
|
||||
case "type" -> world.getBukkitWorld().map(World::getWorldType).map(Enum::name).getOrElse("null");
|
||||
default -> null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -26,6 +27,7 @@ import org.bukkit.GameRule;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -33,22 +35,25 @@ 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.destination.core.WorldDestination;
|
||||
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;
|
||||
import static org.mvplugins.multiverse.core.utils.StringFormatter.addOnToCommaSeparated;
|
||||
|
||||
@Service
|
||||
public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
@@ -84,7 +89,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
registerAsyncCompletion("commands", this::suggestCommands);
|
||||
registerAsyncCompletion("destinations", this::suggestDestinations);
|
||||
registerStaticCompletion("difficulties", suggestEnums(Difficulty.class));
|
||||
registerStaticCompletion("environments", suggestEnums(World.Environment.class));
|
||||
registerStaticCompletion("environments", List.of("normal", "nether", "the_end")); // Don't tab complete the "custom" environment
|
||||
registerAsyncCompletion("flags", this::suggestFlags);
|
||||
registerStaticCompletion("gamemodes", suggestEnums(GameMode.class));
|
||||
registerStaticCompletion("gamerules", this::suggestGamerules);
|
||||
@@ -96,7 +101,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
registerAsyncCompletion("mvworlds", this::suggestMVWorlds);
|
||||
registerAsyncCompletion("mvworldpropsname", this::suggestMVWorldPropsName);
|
||||
registerAsyncCompletion("mvworldpropsvalue", this::suggestMVWorldPropsValue);
|
||||
registerAsyncCompletion("playersarray", this::suggestPlayersArray);
|
||||
registerCompletion("playersarray", this::suggestPlayersArray); // getting online players cannot be async
|
||||
registerStaticCompletion("propsmodifyaction", suggestEnums(PropertyModifyAction.class));
|
||||
registerStaticCompletion("spawncategories", suggestEnums(SpawnCategory.class));
|
||||
registerAsyncCompletion("spawncategorypropsname", this::suggestSpawnCategoryPropsName);
|
||||
@@ -129,6 +134,22 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
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();
|
||||
@@ -142,7 +163,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
}
|
||||
}
|
||||
if (context.hasConfig("multiple")) {
|
||||
return addonToCommaSeperated(context.getInput(), handler.getCompletions(context));
|
||||
return addOnToCommaSeparated(context.getInput(), handler.getCompletions(context));
|
||||
}
|
||||
return handler.getCompletions(context);
|
||||
}
|
||||
@@ -157,7 +178,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
public <T extends Enum<T>> Collection<String> suggestEnums(Class<T> enumClass) {
|
||||
return EnumSet.allOf(enumClass).stream()
|
||||
.map(Enum::name)
|
||||
.map(String::toLowerCase)
|
||||
.map(name -> name.toLowerCase(Locale.ENGLISH))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -195,27 +216,32 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
}
|
||||
|
||||
private Collection<String> suggestDestinations(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> context.getContextValue(Player[].class))
|
||||
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()))) {
|
||||
CommandSender sender = context.getIssuer().getIssuer();
|
||||
if (context.hasConfig("othersOnly") && (players.length == 1 && players[0].equals(sender))) {
|
||||
return Collections.<String>emptyList();
|
||||
}
|
||||
return suggestDestinationsWithPerms(context.getIssuer().getIssuer(), players, context.getInput());
|
||||
return suggestDestinationsWithPerms(sender, Arrays.asList(players), context.getInput());
|
||||
})
|
||||
.getOrElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
private Collection<String> suggestDestinationsWithPerms(CommandSender teleporter, Player[] players, String deststring) {
|
||||
private Collection<String> suggestDestinationsWithPerms(CommandSender teleporter, List<Entity> teleportees, String deststring) {
|
||||
return destinationsProvider.suggestDestinations(teleporter, deststring).stream()
|
||||
.filter(packet -> !config.getSimplifiedDestinationTabCompletion()
|
||||
|| packet.destination() instanceof WorldDestination
|
||||
|| deststring.startsWith(packet.destination().getIdentifier() + ":"))
|
||||
.filter(packet -> corePermissionsChecker
|
||||
.checkDestinationPacketPermission(teleporter, Arrays.asList(players), packet))
|
||||
.map(packet -> packet.destination() instanceof WorldDestination
|
||||
? packet.destinationString()
|
||||
: packet.destination().getIdentifier() + ":" + packet.destinationString())
|
||||
.checkDestinationPacketPermission(teleporter, teleportees, packet))
|
||||
.map(DestinationSuggestionPacket::parsableString)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -248,7 +274,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
private Collection<String> suggestMVConfigValues(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> context.getContextValue(String.class))
|
||||
.map(propertyName -> config.getStringPropertyHandle()
|
||||
.getSuggestedPropertyValue(propertyName, context.getInput(), PropertyModifyAction.SET))
|
||||
.getSuggestedPropertyValue(propertyName, context.getInput(), PropertyModifyAction.SET, context.getSender()))
|
||||
.getOrElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
@@ -258,19 +284,16 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
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" -> {
|
||||
@@ -283,7 +306,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
|
||||
private Collection<String> suggestMVWorldPropsName(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> {
|
||||
MultiverseWorld world = context.getContextValue(MultiverseWorld.class);
|
||||
MultiverseWorld world = context.getContextValue(MultiverseWorldValue.class).value();
|
||||
PropertyModifyAction action = context.getContextValue(PropertyModifyAction.class);
|
||||
return world.getStringPropertyHandle().getModifiablePropertyNames(action);
|
||||
}).getOrElse(Collections.emptyList());
|
||||
@@ -291,10 +314,10 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
|
||||
private Collection<String> suggestMVWorldPropsValue(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> {
|
||||
MultiverseWorld world = context.getContextValue(MultiverseWorld.class);
|
||||
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);
|
||||
return world.getStringPropertyHandle().getSuggestedPropertyValue(propertyName, context.getInput(), action, context.getSender());
|
||||
}).getOrElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
@@ -311,7 +334,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
matchedPlayers.add(name);
|
||||
}
|
||||
}
|
||||
return addonToCommaSeperated(context.getInput(), matchedPlayers);
|
||||
return addOnToCommaSeparated(context.getInput(), matchedPlayers);
|
||||
}
|
||||
|
||||
private Collection<String> suggestSpawnCategoryPropsName(BukkitCommandCompletionContext context) {
|
||||
@@ -335,7 +358,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
return world.getEntitySpawnConfig()
|
||||
.getSpawnCategoryConfig(spawnCategory)
|
||||
.getStringPropertyHandle()
|
||||
.getSuggestedPropertyValue(propertyName, context.getInput(), action);
|
||||
.getSuggestedPropertyValue(propertyName, context.getInput(), action, context.getSender());
|
||||
}).getOrElse(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,19 @@ 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.Option;
|
||||
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;
|
||||
@@ -20,6 +28,10 @@ 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;
|
||||
@@ -27,6 +39,7 @@ 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;
|
||||
@@ -69,13 +82,16 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
registerContext(GameRule.class, this::parseGameRule);
|
||||
registerContext(GameRuleValue.class, this::parseGameRuleValue);
|
||||
registerContext(GeneratorPlugin.class, this::parseGeneratorPlugin);
|
||||
registerIssuerAwareContext(LoadedMultiverseWorld.class, this::parseLoadedMultiverseWorld);
|
||||
registerIssuerAwareContext(LoadedMultiverseWorld[].class, this::parseLoadedMultiverseWorldArray);
|
||||
registerIssuerAwareContext(MultiverseWorld.class, this::parseMultiverseWorld);
|
||||
registerIssuerAwareContext(MultiverseWorld[].class, this::parseMultiverseWorldArray);
|
||||
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, this::parsePlayer);
|
||||
registerIssuerAwareContext(Player[].class, this::parsePlayerArray);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -100,8 +116,9 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
throw new InvalidCommandArgument("No destination specified.");
|
||||
}
|
||||
|
||||
return destinationsProvider.parseDestination(destination)
|
||||
.getOrThrow(failure -> MVInvalidCommandArgument.of(failure.getFailureMessage()));
|
||||
return destinationsProvider.parseDestination(context.getSender(), destination)
|
||||
.getOrThrow(failure ->
|
||||
new InvalidCommandArgument(failure.getFailureMessage().formatted(context.getIssuer())));
|
||||
}
|
||||
|
||||
private GameRule<?> parseGameRule(BukkitCommandExecutionContext context) {
|
||||
@@ -149,107 +166,42 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
return generatorPlugin;
|
||||
}
|
||||
|
||||
private LoadedMultiverseWorld parseLoadedMultiverseWorld(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
// Get world based on sender only
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return worldManager.getLoadedWorld(context.getIssuer().getPlayer().getWorld()).getOrNull();
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("This command can only be used by a player in a Multiverse World.");
|
||||
}
|
||||
|
||||
String worldName = context.getFirstArg();
|
||||
LoadedMultiverseWorld world = getLoadedMultiverseWorld(worldName);
|
||||
|
||||
// Get world based on input, fallback to sender if input is not a world
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (world != null) {
|
||||
context.popFirstArg();
|
||||
return world;
|
||||
}
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return worldManager.getLoadedWorld(context.getPlayer().getWorld())
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."));
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("World '" + worldName + "' is not a loaded multiverse world. Remember to specify the world name when using this command in console.");
|
||||
}
|
||||
|
||||
// Get world based on input only
|
||||
if (world != null) {
|
||||
context.popFirstArg();
|
||||
return world;
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("World " + worldName + " is not a loaded multiverse world.");
|
||||
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 LoadedMultiverseWorld[] parseLoadedMultiverseWorldArray(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
// Get world based on sender only
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
LoadedMultiverseWorld playerWorld = worldManager.getLoadedWorld(context.getIssuer().getPlayer().getWorld())
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."));
|
||||
return new LoadedMultiverseWorld[]{playerWorld};
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("This command can only be used by a player in a Multiverse World.");
|
||||
}
|
||||
|
||||
String worldStrings = context.getFirstArg();
|
||||
String[] worldNames = worldStrings == null ? new String[0] : REPatterns.COMMA.split(worldStrings);
|
||||
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);
|
||||
}
|
||||
|
||||
// Get world based on input, fallback to sender if input is not a world
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (!worlds.isEmpty()) {
|
||||
context.popFirstArg();
|
||||
return worlds.toArray(new LoadedMultiverseWorld[0]);
|
||||
}
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
LoadedMultiverseWorld playerWorld = worldManager.getLoadedWorld(context.getIssuer().getPlayer().getWorld())
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."));
|
||||
return new LoadedMultiverseWorld[]{playerWorld};
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("Worlds '" + worldStrings + "' is not a loaded multiverse world. Remember to specify the world name when using this command in console.");
|
||||
}
|
||||
|
||||
// Get world based on input only
|
||||
if (!worlds.isEmpty()) {
|
||||
context.popFirstArg();
|
||||
return worlds.toArray(new LoadedMultiverseWorld[0]);
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("World " + worldStrings + " 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
|
||||
@@ -259,107 +211,41 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
: worldManager.getLoadedWorld(worldName).getOrNull();
|
||||
}
|
||||
|
||||
private MultiverseWorld parseMultiverseWorld(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
// Get world based on sender only
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return worldManager.getWorld(context.getIssuer().getPlayer().getWorld()).getOrNull();
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("This command can only be used by a player in a Multiverse World.");
|
||||
}
|
||||
|
||||
String worldName = context.getFirstArg();
|
||||
MultiverseWorld world = getMultiverseWorld(worldName);
|
||||
|
||||
// Get world based on input, fallback to sender if input is not a world
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (world != null) {
|
||||
context.popFirstArg();
|
||||
return world;
|
||||
}
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return worldManager.getWorld(context.getPlayer().getWorld())
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."));
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("World '" + worldName + "' is not a loaded multiverse world. Remember to specify the world name when using this command in console.");
|
||||
}
|
||||
|
||||
// Get world based on input only
|
||||
if (world != null) {
|
||||
context.popFirstArg();
|
||||
return world;
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("World " + worldName + " is not a loaded multiverse world.");
|
||||
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 MultiverseWorld[] parseMultiverseWorldArray(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
// Get world based on sender only
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
MultiverseWorld playerWorld = worldManager.getWorld(context.getIssuer().getPlayer().getWorld())
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."));
|
||||
return new MultiverseWorld[]{playerWorld};
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("This command can only be used by a player in a Multiverse World.");
|
||||
}
|
||||
|
||||
String worldStrings = context.getFirstArg();
|
||||
String[] worldNames = worldStrings == null ? new String[0] : REPatterns.COMMA.split(worldStrings);
|
||||
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 loaded multiverse world.");
|
||||
}
|
||||
worlds.add(world);
|
||||
}
|
||||
|
||||
// Get world based on input, fallback to sender if input is not a world
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (!worlds.isEmpty()) {
|
||||
context.popFirstArg();
|
||||
return worlds.toArray(new MultiverseWorld[0]);
|
||||
}
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
MultiverseWorld playerWorld = worldManager.getWorld(context.getIssuer().getPlayer().getWorld())
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("You are not in a multiverse world. Either specify a multiverse world name or use this command in a multiverse world."));
|
||||
return new MultiverseWorld[]{playerWorld};
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("Worlds '" + worldStrings + "' is not a loaded multiverse world. Remember to specify the world name when using this command in console.");
|
||||
}
|
||||
|
||||
// Get world based on input only
|
||||
if (!worlds.isEmpty()) {
|
||||
context.popFirstArg();
|
||||
return worlds.toArray(new MultiverseWorld[0]);
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("World " + worldStrings + " is not a loaded 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
|
||||
@@ -375,92 +261,122 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
.getOrElseThrow(() -> new InvalidCommandArgument("The anchor '" +anchorName + "' does not exist."));
|
||||
}
|
||||
|
||||
private Player parsePlayer(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
|
||||
// Get player based on sender only
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return context.getIssuer().getPlayer();
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("This command can only be used by a player.");
|
||||
}
|
||||
|
||||
String playerIdentifier = context.getFirstArg();
|
||||
Player player = PlayerFinder.get(playerIdentifier, context.getSender());
|
||||
|
||||
// Get player based on input, fallback to sender if input is not a player
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (player != null) {
|
||||
context.popFirstArg();
|
||||
return player;
|
||||
}
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return context.getIssuer().getPlayer();
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("Invalid player: " + playerIdentifier
|
||||
+ ". Either specify an online player or use this command as a player.");
|
||||
}
|
||||
|
||||
// Get player based on input only
|
||||
if (player != null) {
|
||||
context.popFirstArg();
|
||||
return player;
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("Player " + playerIdentifier + " not found.");
|
||||
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 Player[] parsePlayerArray(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
private IssuerAwareContextBuilder<Player[]> playerArrayContextBuilder() {
|
||||
return new IssuerAwareContextBuilder<Player[]>()
|
||||
.fromPlayer((context, player) -> new Player[]{player})
|
||||
.fromInput((context, input) -> PlayerFinder
|
||||
.tryGetMulti(input, context.getSender())
|
||||
.map(list -> list.toArray(new Player[0]))
|
||||
.map(arr -> (arr.length == 0) ? null : arr)
|
||||
.getOrElseThrow(failure -> {
|
||||
if (failure instanceof MVInvalidCommandArgument mvFailure) {
|
||||
throw mvFailure;
|
||||
}
|
||||
throw new InvalidCommandArgument(failure.getLocalizedMessage() + " "
|
||||
+ Option.of(failure.getCause()).map(Throwable::getLocalizedMessage).getOrElse(""));
|
||||
}))
|
||||
.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) -> {
|
||||
if (PlayerFinder.isSelector(input)) {
|
||||
return Message.of("No player(s) matched selector: " + input + ".");
|
||||
}
|
||||
return Message.of("Player(s) " + input + " not found.");
|
||||
});
|
||||
}
|
||||
|
||||
// Get player based on sender only
|
||||
if (resolve.equals("issuerOnly")) {
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return new Player[]{context.getIssuer().getPlayer()};
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("This command can only be used by a player.");
|
||||
}
|
||||
private PlayerLocation parsePlayerLocation(BukkitCommandExecutionContext context) {
|
||||
Try<Location> location = Try.of(() -> parseLocationFromInput(context.getFirstArg(), context.getSender()));
|
||||
|
||||
String playerIdentifier = context.getFirstArg();
|
||||
Player[] players = PlayerFinder.getMulti(playerIdentifier, context.getSender()).toArray(new Player[0]);
|
||||
|
||||
// Get player based on input, fallback to sender if input is not a player
|
||||
if (resolve.equals("issuerAware")) {
|
||||
if (players.length > 0) {
|
||||
context.popFirstArg();
|
||||
return players;
|
||||
}
|
||||
if (context.getIssuer().isPlayer()) {
|
||||
return new Player[]{context.getIssuer().getPlayer()};
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
throw new InvalidCommandArgument("Invalid player: " + playerIdentifier
|
||||
+ ". Either specify an online player or use this command as a player.");
|
||||
}
|
||||
|
||||
// Get player based on input only
|
||||
if (players.length > 0) {
|
||||
if (location.isSuccess()) {
|
||||
context.popFirstArg();
|
||||
return players;
|
||||
return new PlayerLocation(location.get());
|
||||
}
|
||||
if (context.isOptional()) {
|
||||
return null;
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new InvalidCommandArgument("Player " + playerIdentifier + " not found.");
|
||||
}
|
||||
|
||||
private SpawnCategory[] parseSpawnCategories(BukkitCommandExecutionContext context) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.mvplugins.multiverse.core.command;
|
||||
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import co.aikar.commands.MessageKeys;
|
||||
import co.aikar.commands.MessageType;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -9,6 +8,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
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 BukkitCommandIssuer {
|
||||
|
||||
@@ -24,8 +24,13 @@ public class MVCommandIssuer extends BukkitCommandIssuer {
|
||||
return commandManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageInternal(String message) {
|
||||
ChatTextFormatter.sendFormattedMessage(getIssuer(), message);
|
||||
}
|
||||
|
||||
public void sendError(String message, MessageReplacement... replacements) {
|
||||
sendMessage(MessageType.INFO, message, replacements);
|
||||
sendMessage(MessageType.ERROR, message, replacements);
|
||||
}
|
||||
|
||||
public void sendSyntax(String message, MessageReplacement... replacements) {
|
||||
@@ -107,9 +112,9 @@ public class MVCommandIssuer extends BukkitCommandIssuer {
|
||||
} else {
|
||||
var formatter = getManager().getFormat(messageType);
|
||||
if (formatter != null) {
|
||||
sendMessage(formatter.format(message.formatted()));
|
||||
sendMessage(formatter.format(message.formatted(getManager().getLocales(), this)));
|
||||
} else {
|
||||
sendMessage(message.formatted());
|
||||
sendMessage(message.formatted(getManager().getLocales(), this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ public class MVRootCommand extends BukkitRootCommand {
|
||||
@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);
|
||||
return super.getTabCompletions(sender, alias, quoteFormatedArgs, commandsOnly, isAsync)
|
||||
.stream()
|
||||
.map(StringFormatter::quoteMultiWordString)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
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() {
|
||||
validateRequiredVariables();
|
||||
return context -> resolveValue(context, GenericIssuerAwareValue::new).value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
validateRequiredVariables();
|
||||
return context -> resolveValue(context, createValue);
|
||||
}
|
||||
|
||||
private void validateRequiredVariables() {
|
||||
Objects.requireNonNull(fromPlayer);
|
||||
Objects.requireNonNull(fromInput);
|
||||
Objects.requireNonNull(issuerOnlyFailMessage);
|
||||
Objects.requireNonNull(issuerAwarePlayerFailMessage);
|
||||
Objects.requireNonNull(issuerAwareInputFailMessage);
|
||||
Objects.requireNonNull(inputOnlyFailMessage);
|
||||
}
|
||||
|
||||
private <I extends IssuerAwareValue> I resolveValue(BukkitCommandExecutionContext context, BiFunction<Boolean, T, I> createValue) {
|
||||
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);
|
||||
}
|
||||
|
||||
int maxArgForAware = context.getFlagValue("maxArgForAware", Integer.MAX_VALUE);
|
||||
long argLengthWithoutFlags = context.getArgs().stream()
|
||||
.takeWhile(value -> !value.startsWith("-") && !value.isEmpty()) // ignore the flags
|
||||
.count();
|
||||
|
||||
if (resolve.equals("issuerAware") && argLengthWithoutFlags <= maxArgForAware) {
|
||||
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));
|
||||
}
|
||||
|
||||
private static class GenericIssuerAwareValue<T> extends IssuerAwareValue {
|
||||
private final T value;
|
||||
|
||||
public GenericIssuerAwareValue(boolean byIssuer, T value) {
|
||||
super(byIssuer);
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+36
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.command.flag;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
|
||||
import co.aikar.commands.InvalidCommandArgument;
|
||||
@@ -220,7 +221,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
private void setEnumContext() {
|
||||
this.context = (String value) -> {
|
||||
try {
|
||||
return Enum.valueOf(type, value.toUpperCase());
|
||||
return Enum.valueOf(type, value.toUpperCase(Locale.ENGLISH));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidCommandArgument("Invalid value for argument " + key + ": " + value);
|
||||
}
|
||||
@@ -229,7 +230,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
|
||||
private void setEnumCompletion() {
|
||||
List<String> types = Arrays.stream(type.getEnumConstants())
|
||||
.map(typeClass -> typeClass.name().toLowerCase())
|
||||
.map(typeClass -> typeClass.name().toLowerCase(Locale.ENGLISH))
|
||||
.toList();
|
||||
|
||||
this.completion = (input) -> types;
|
||||
|
||||
@@ -20,7 +20,7 @@ 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.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.options.CloneWorldOptions;
|
||||
|
||||
@@ -48,7 +48,7 @@ class CloneCommand extends CoreCommand {
|
||||
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.clone.world.description}")
|
||||
LoadedMultiverseWorld world,
|
||||
MultiverseWorld world,
|
||||
|
||||
@Syntax("<new-world-name>")
|
||||
@Description("{@@mv-core.clone.newWorld.description}")
|
||||
@@ -114,7 +114,7 @@ class CloneCommand extends CoreCommand {
|
||||
|
||||
@Override
|
||||
@CommandAlias("mvcl|mvclone")
|
||||
void onCloneCommand(MVCommandIssuer issuer, LoadedMultiverseWorld world, String newWorldName, String[] flags) {
|
||||
void onCloneCommand(MVCommandIssuer issuer, MultiverseWorld world, String newWorldName, String[] flags) {
|
||||
super.onCloneCommand(issuer, world, newWorldName, flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,13 +65,12 @@ class ConfigCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
private void updateConfigValue(MVCommandIssuer issuer, String name, String value) {
|
||||
config.getStringPropertyHandle().setPropertyString(name, value)
|
||||
.onSuccess(ignore -> {
|
||||
config.save();
|
||||
issuer.sendMessage(MVCorei18n.CONFIG_SET_SUCCESS,
|
||||
Replace.NAME.with(name),
|
||||
Replace.VALUE.with(value));
|
||||
})
|
||||
var stringPropertyHandle = config.getStringPropertyHandle();
|
||||
stringPropertyHandle.setPropertyString(issuer.getIssuer(), name, value)
|
||||
.andThenTry(config::save)
|
||||
.onSuccess(ignore -> issuer.sendMessage(MVCorei18n.CONFIG_SET_SUCCESS,
|
||||
Replace.NAME.with(name),
|
||||
Replace.VALUE.with(stringPropertyHandle.getProperty(name).getOrNull())))
|
||||
.onFailure(e -> issuer.sendMessage(MVCorei18n.CONFIG_SET_ERROR,
|
||||
Replace.NAME.with(name),
|
||||
Replace.VALUE.with(value),
|
||||
|
||||
@@ -19,7 +19,6 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
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;
|
||||
@@ -27,6 +26,7 @@ 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.StringFormatter;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt.Failure;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
@@ -53,7 +53,7 @@ class CreateCommand extends CoreCommand {
|
||||
@CommandPermission("multiverse.core.create")
|
||||
@CommandCompletion("@empty @environments @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<name> <environment> [--seed <seed> --generator <generator[:id]> --world-type <worldtype> --adjust-spawn "
|
||||
+ "--no-structures --biome <biome>]")
|
||||
+ "--no-structures --biome <biome> --properties <prop1=value1,prop2=value2,...>]")
|
||||
@Description("{@@mv-core.create.description}")
|
||||
void onCreateCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -68,7 +68,7 @@ class CreateCommand extends CoreCommand {
|
||||
|
||||
@Optional
|
||||
@Syntax("[--seed <seed> --generator <generator[:id]> --world-type <worldtype> --adjust-spawn "
|
||||
+ "--no-structures --biome <biome>]")
|
||||
+ "--no-structures --biome <biome> --properties <prop1=value1,prop2=value2,...>]")
|
||||
@Description("{@@mv-core.create.flags.description}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
@@ -78,14 +78,15 @@ class CreateCommand extends CoreCommand {
|
||||
issuer.sendInfo(MVCorei18n.CREATE_LOADING);
|
||||
|
||||
worldManager.createWorld(CreateWorldOptions.worldName(worldName)
|
||||
.biome(parsedFlags.flagValue(flags.biome, ""))
|
||||
.environment(environment)
|
||||
.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)))
|
||||
.biome(parsedFlags.flagValue(flags.biome, ""))
|
||||
.environment(environment)
|
||||
.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))
|
||||
.worldPropertyStrings(StringFormatter.parseCSVMap(parsedFlags.flagValue(flags.properties))))
|
||||
.onSuccess(newWorld -> messageSuccess(issuer, newWorld))
|
||||
.onFailure(failure -> messageFailure(issuer, failure));
|
||||
}
|
||||
@@ -180,6 +181,10 @@ class CreateCommand extends CoreCommand {
|
||||
.addAlias("-b")
|
||||
.completion(input -> biomeProviderFactory.suggestBiomeString(input))
|
||||
.build());
|
||||
|
||||
private final CommandValueFlag<String> properties = flag(CommandValueFlag.builder("--properties", String.class)
|
||||
.addAlias("-p")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Service
|
||||
|
||||
@@ -57,7 +57,7 @@ class DeleteCommand extends CoreCommand {
|
||||
|
||||
@Subcommand("delete")
|
||||
@CommandPermission("multiverse.core.delete")
|
||||
@CommandCompletion("@mvworlds:scope=loaded @flags:groupName=" + RemovePlayerFlags.NAME)
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + RemovePlayerFlags.NAME)
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.delete.description}")
|
||||
void onDeleteCommand(
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -136,6 +137,9 @@ final class DumpsService {
|
||||
}
|
||||
|
||||
private String getPluginList() {
|
||||
return " - " + StringUtils.join(plugin.getServer().getPluginManager().getPlugins(), "\n - ");
|
||||
return " - " + StringUtils.join(Arrays.stream(plugin.getServer().getPluginManager().getPlugins())
|
||||
.map(Object::toString)
|
||||
.sorted()
|
||||
.toList(), "\n - ");
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ final class EntitySpawnConfigCommand extends CoreCommand {
|
||||
void onInfoCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
MultiverseWorld world,
|
||||
|
||||
@@ -91,7 +91,7 @@ final class EntitySpawnConfigCommand extends CoreCommand {
|
||||
void onModifyCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=4")
|
||||
@Syntax("[world]")
|
||||
MultiverseWorld world,
|
||||
|
||||
@@ -113,11 +113,10 @@ final class EntitySpawnConfigCommand extends CoreCommand {
|
||||
.getSpawnCategoryConfig(spawnCategory)
|
||||
.getStringPropertyHandle()
|
||||
.modifyPropertyString(property, value, action)
|
||||
.onSuccess(ignore -> issuer.sendMessage("Successfully set " + property + " to " + value
|
||||
.andThenTry(worldManager::saveWorldsConfig)
|
||||
.onSuccess(ignore -> issuer.sendInfo("Successfully set " + property + " to " + value
|
||||
+ " for " + spawnCategory.name() + " in " + world.getName()))
|
||||
.onFailure(e -> issuer.sendMessage("Unable to set " + property + " to " + value
|
||||
.onFailure(e -> issuer.sendError("Unable to set " + property + " to " + value
|
||||
+ " for " + spawnCategory.name() + " in " + world.getName() + ": " + e.getMessage()));
|
||||
|
||||
worldManager.saveWorldsConfig();
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,6 @@ import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
@Service
|
||||
@Subcommand("gamerule|rule")
|
||||
class GameruleCommand extends CoreCommand {
|
||||
|
||||
private final PageFilterFlags flags;
|
||||
@@ -52,7 +51,7 @@ class GameruleCommand extends CoreCommand {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Subcommand("set")
|
||||
@Subcommand("gamerule|rule set")
|
||||
@CommandPermission("multiverse.core.gamerule.set")
|
||||
@CommandCompletion("@gamerules @gamerulesvalues @mvworlds:multiple|*")
|
||||
@Syntax("<Gamerule> <Gamerule value> [World or *]")
|
||||
@@ -68,7 +67,7 @@ class GameruleCommand extends CoreCommand {
|
||||
@Description("{@@mv-core.gamerule.set.value.description}")
|
||||
GameRuleValue gameRuleValue,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=2")
|
||||
@Syntax("[World or *]")
|
||||
@Description("{@@mv-core.gamerule.set.world.description}")
|
||||
LoadedMultiverseWorld[] worlds) {
|
||||
@@ -103,7 +102,7 @@ class GameruleCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes,unchecked")
|
||||
@Subcommand("reset")
|
||||
@Subcommand("gamerule|rule reset")
|
||||
@CommandPermission("multiverse.core.gamerule.set")
|
||||
@CommandCompletion("@gamerules @mvworlds:multiple|*")
|
||||
@Syntax("<Gamerule> [World or *]")
|
||||
@@ -115,7 +114,7 @@ class GameruleCommand extends CoreCommand {
|
||||
@Description("{@@mv-core.gamerule.reset.gamerule.description}")
|
||||
GameRule gamerule,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=1")
|
||||
@Syntax("[World or *]")
|
||||
@Description("{@@mv-core.gamerule.reset.world.description}")
|
||||
LoadedMultiverseWorld[] worlds) {
|
||||
@@ -145,15 +144,15 @@ class GameruleCommand extends CoreCommand {
|
||||
}
|
||||
}
|
||||
|
||||
@Subcommand("list")
|
||||
@Subcommand("gamerule|rule list")
|
||||
@CommandPermission("multiverse.core.gamerule.list")
|
||||
@CommandCompletion("@mvworlds|@flags:groupName=mvgamerulecommand @flags:groupName=" + PageFilterFlags.NAME)
|
||||
@CommandCompletion("@mvworlds|@flags:groupName=" + PageFilterFlags.NAME + " @flags:groupName=" + PageFilterFlags.NAME)
|
||||
@Syntax("[world] [--page <page>] [--filter <filter>]")
|
||||
@Description("{@@mv-core.gamerule.list.description}")
|
||||
void onGameruleListCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.gamerule.list.description.world}")
|
||||
LoadedMultiverseWorld world,
|
||||
@@ -220,6 +219,11 @@ class GameruleCommand extends CoreCommand {
|
||||
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) {
|
||||
|
||||
@@ -68,7 +68,8 @@ class ImportCommand extends CoreCommand {
|
||||
.biome(parsedFlags.flagValue(flags.biome, ""))
|
||||
.environment(environment)
|
||||
.generator(parsedFlags.flagValue(flags.generator, String.class))
|
||||
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn)))
|
||||
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn))
|
||||
.doFolderCheck(!parsedFlags.hasFlag(flags.skipFolderCheck)))
|
||||
.onSuccess(newWorld -> {
|
||||
Logging.fine("World import success: " + newWorld);
|
||||
issuer.sendInfo(MVCorei18n.IMPORT_SUCCESS, Replace.WORLD.with(newWorld.getName()));
|
||||
@@ -105,6 +106,10 @@ class ImportCommand extends CoreCommand {
|
||||
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
|
||||
|
||||
@@ -61,8 +61,8 @@ class InfoCommand extends CoreCommand {
|
||||
public void onInfoCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("<world>")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
@Description("{@@mv-core.info.description.world}")
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -15,36 +14,49 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
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.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.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.options.LoadWorldOptions;
|
||||
|
||||
@Service
|
||||
class LoadCommand extends CoreCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final LoadCommand.Flags flags;
|
||||
|
||||
@Inject
|
||||
LoadCommand(@NotNull WorldManager worldManager) {
|
||||
LoadCommand(@NotNull WorldManager worldManager, @NotNull Flags flags) {
|
||||
this.worldManager = worldManager;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Subcommand("load")
|
||||
@CommandPermission("multiverse.core.load")
|
||||
@CommandCompletion("@mvworlds:scope=unloaded")
|
||||
@CommandCompletion("@mvworlds:scope=unloaded @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.load.description}")
|
||||
void onLoadCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Single
|
||||
@Conditions("worldname:scope=unloaded")
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.load.world.description}")
|
||||
String worldName) {
|
||||
issuer.sendInfo(MVCorei18n.LOAD_LOADING, Replace.WORLD.with(worldName));
|
||||
worldManager.loadWorld(worldName)
|
||||
MultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--skip-folder-check]")
|
||||
@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, Replace.WORLD.with(newWorld.getName()));
|
||||
@@ -54,17 +66,32 @@ class LoadCommand extends CoreCommand {
|
||||
});
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends FlagBuilder {
|
||||
|
||||
private static final String NAME = "mvload";
|
||||
|
||||
@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) {
|
||||
super(worldManager);
|
||||
LegacyAlias(@NotNull WorldManager worldManager, @NotNull Flags flags) {
|
||||
super(worldManager, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CommandAlias("mvload")
|
||||
void onLoadCommand(MVCommandIssuer issuer, String worldName) {
|
||||
super.onLoadCommand(issuer, worldName);
|
||||
void onLoadCommand(MVCommandIssuer issuer, MultiverseWorld world, String[] flagArray) {
|
||||
super.onLoadCommand(issuer, world, flagArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,27 +10,32 @@ 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.ChatColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
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.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.locale.message.MessageReplacement;
|
||||
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
|
||||
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
|
||||
@@ -40,16 +45,19 @@ class ModifyCommand extends CoreCommand {
|
||||
|
||||
@Subcommand("modify")
|
||||
@CommandPermission("multiverse.core.modify")
|
||||
@CommandCompletion("@mvworlds:scope=both @propsmodifyaction @mvworldpropsname @mvworldpropsvalue")
|
||||
@Syntax("[world] <set|add|remove|reset> <property> <value>")
|
||||
@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("{@@mv-core.modify.description}")
|
||||
void onModifyCommand(// SUPPRESS CHECKSTYLE: ParameterNumber
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=3")
|
||||
@Syntax("[world]")
|
||||
@Description("{@@mv-core.modify.world.description}")
|
||||
@NotNull MultiverseWorld world,
|
||||
@NotNull MultiverseWorldValue worldValue,
|
||||
|
||||
@Syntax("<set|add|remove|reset>")
|
||||
@Description("")
|
||||
@@ -64,6 +72,15 @@ class ModifyCommand extends CoreCommand {
|
||||
@Syntax("[value]")
|
||||
@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()),
|
||||
@@ -77,28 +94,28 @@ class ModifyCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
StringPropertyHandle worldPropertyHandle = world.getStringPropertyHandle();
|
||||
worldPropertyHandle.modifyPropertyString(propertyName, propertyValue, action).onSuccess(ignore -> {
|
||||
issuer.sendMessage(MVCorei18n.MODIFY_SUCCESS,
|
||||
worldPropertyHandle.modifyPropertyString(issuer.getIssuer(), propertyName, propertyValue, action)
|
||||
.andThenTry(worldManager::saveWorldsConfig)
|
||||
.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 -> {
|
||||
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()));
|
||||
}
|
||||
Replace.WORLD.with(world.getName())))
|
||||
.onFailure(exception -> {
|
||||
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()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -111,7 +128,7 @@ class ModifyCommand extends CoreCommand {
|
||||
|
||||
@Override
|
||||
@CommandAlias("mvmodify|mvm")
|
||||
void onModifyCommand(MVCommandIssuer issuer, @NotNull MultiverseWorld world, @NotNull PropertyModifyAction action, @NotNull String propertyName, @Nullable String propertyValue) {
|
||||
void onModifyCommand(MVCommandIssuer issuer, @NotNull MultiverseWorldValue world, @NotNull PropertyModifyAction action, @NotNull String propertyName, @Nullable String propertyValue) {
|
||||
super.onModifyCommand(issuer, world, action, propertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ final class PurgeAllEntitiesCommand extends CoreCommand {
|
||||
void onPurgeAllEntitiesCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=1")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ final class PurgeEntitiesCommand extends CoreCommand {
|
||||
void onPurgeEntityCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
|
||||
@@ -15,6 +15,8 @@ import org.jvnet.hk2.annotations.Service;
|
||||
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;
|
||||
@@ -23,19 +25,20 @@ 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
|
||||
class RemoveCommand extends CoreCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final PlayerWorldTeleporter playerWorldTeleporter;
|
||||
private final RemovePlayerFlags flags;
|
||||
private final Flags flags;
|
||||
|
||||
@Inject
|
||||
RemoveCommand(
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
|
||||
@NotNull RemovePlayerFlags flags
|
||||
@NotNull Flags flags
|
||||
) {
|
||||
this.worldManager = worldManager;
|
||||
this.playerWorldTeleporter = playerWorldTeleporter;
|
||||
@@ -44,7 +47,7 @@ class RemoveCommand extends CoreCommand {
|
||||
|
||||
@Subcommand("remove")
|
||||
@CommandPermission("multiverse.core.remove")
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + RemovePlayerFlags.NAME)
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.remove.description}")
|
||||
void onRemoveCommand(
|
||||
@@ -64,12 +67,14 @@ class RemoveCommand extends CoreCommand {
|
||||
? worldManager.getLoadedWorld(world).map(playerWorldTeleporter::removeFromWorld).getOrElse(AsyncAttemptsAggregate::emptySuccess)
|
||||
: AsyncAttemptsAggregate.emptySuccess();
|
||||
|
||||
future.onSuccess(() -> 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, Replace.WORLD.with(removedWorldName));
|
||||
@@ -79,13 +84,32 @@ class RemoveCommand extends CoreCommand {
|
||||
});
|
||||
}
|
||||
|
||||
@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,
|
||||
RemovePlayerFlags flags
|
||||
@NotNull Flags flags
|
||||
) {
|
||||
super(worldManager, playerWorldTeleporter, flags);
|
||||
}
|
||||
|
||||
@@ -1,76 +1,95 @@
|
||||
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.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.Location;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
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
|
||||
class SetSpawnCommand extends CoreCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final BlockSafety blockSafety;
|
||||
private final UnsafeFlags flags;
|
||||
|
||||
@Inject
|
||||
SetSpawnCommand(@NotNull WorldManager worldManager) {
|
||||
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.spawn.set")
|
||||
@Syntax("[x],[y],[z],[pitch],[yaw]")
|
||||
@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,
|
||||
|
||||
@Syntax("[worldname:x,y,z[,pitch,yaw]]")
|
||||
@Description("{@@mv-core.setspawn.location.description}")
|
||||
PlayerLocation playerLocation,
|
||||
|
||||
@Optional
|
||||
@Syntax("<location>")
|
||||
@Description("{@@mv-core.setspawn.location.description}")
|
||||
Location location) {
|
||||
Option.of(location).orElse(() -> {
|
||||
if (issuer.isPlayer()) {
|
||||
return Option.of(issuer.getPlayer().getLocation());
|
||||
}
|
||||
return Option.none();
|
||||
}).peek(finalLocation ->
|
||||
worldManager.getLoadedWorld(finalLocation.getWorld())
|
||||
.peek(mvWorld -> mvWorld.setSpawnLocation(finalLocation)
|
||||
.onSuccess(ignore -> issuer.sendMessage(
|
||||
"Successfully set spawn in " + mvWorld.getName() + " to "
|
||||
+ prettyLocation(mvWorld.getSpawnLocation())))
|
||||
.onFailure(e -> issuer.sendMessage(e.getLocalizedMessage())))
|
||||
.onEmpty(() -> issuer.sendMessage("That world is not loaded or does not exist!")))
|
||||
.onEmpty(() -> issuer.sendMessage("You must specify a location in the format: worldname:x,y,z"));
|
||||
@Syntax("[--unsafe]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
Location location = playerLocation.value();
|
||||
|
||||
if (!parsedFlags.hasFlag(flags.unsafe) && !blockSafety.canSpawnAtLocationSafely(location)) {
|
||||
issuer.sendMessage(MVCorei18n.SETSPAWN_UNSAFE);
|
||||
return;
|
||||
}
|
||||
|
||||
worldManager.getLoadedWorld(location.getWorld())
|
||||
.peek(mvWorld -> mvWorld.setSpawnLocation(location)
|
||||
.andThenTry(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() + ". pitch:" + location.getPitch()
|
||||
+ ", yaw:" + location.getYaw();
|
||||
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) {
|
||||
super(worldManager);
|
||||
LegacyAlias(@NotNull WorldManager worldManager, @NotNull BlockSafety blockSafety, @NotNull UnsafeFlags flags) {
|
||||
super(worldManager, blockSafety, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CommandAlias("mvss")
|
||||
void onSetSpawnCommand(BukkitCommandIssuer issuer, Location location) {
|
||||
super.onSetSpawnCommand(issuer, location);
|
||||
void onSetSpawnCommand(MVCommandIssuer issuer, PlayerLocation location, String[] flagArray) {
|
||||
super.onSetSpawnCommand(issuer, location, flagArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ final class SpawnCommand extends CoreCommand {
|
||||
@CommandAlias("mvspawn")
|
||||
@Subcommand("spawn")
|
||||
@CommandPermission("@mvspawn")
|
||||
@CommandCompletion("@playersarray:checkPermissions=@mvspawnother|@flags:groupName=" + UnsafeFlags.NAME + ",resolveUntil=arg1"
|
||||
@CommandCompletion("@playersarray:checkPermissions=@mvspawnother|@flags:resolveUntil=arg1,groupName=" + UnsafeFlags.NAME
|
||||
+ " @flags:groupName=" + UnsafeFlags.NAME)
|
||||
@Syntax("[player]")
|
||||
@Description("{@@mv-core.spawn.description}")
|
||||
@@ -108,7 +108,7 @@ final class SpawnCommand extends CoreCommand {
|
||||
safetyTeleporter.to(mvWorld.getSpawnLocation())
|
||||
.by(issuer)
|
||||
.checkSafety(checkSafety)
|
||||
.teleport(entity)
|
||||
.teleportSingle(entity)
|
||||
.onSuccess(() -> issuer.sendInfo(MVCorei18n.SPAWN_SUCCESS,
|
||||
Replace.PLAYER.with(entity.equals(issuer.getPlayer())
|
||||
? Message.of(MVCorei18n.GENERIC_YOU)
|
||||
@@ -119,7 +119,7 @@ final class SpawnCommand extends CoreCommand {
|
||||
? Message.of(MVCorei18n.GENERIC_YOU)
|
||||
: Message.of(entity.getName())),
|
||||
Replace.WORLD.with(mvWorld.getName()),
|
||||
Replace.REASON.with(failure.getFailureMessage())));
|
||||
Replace.REASON.with(failure.get(0).getFailureMessage())));
|
||||
}
|
||||
|
||||
private void handleMultiTeleport(MVCommandIssuer issuer, LoadedMultiverseWorld mvWorld,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
@@ -20,9 +18,11 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.context.issueraware.PlayerArrayValue;
|
||||
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.command.flags.UnsafeFlags;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
@@ -31,7 +31,6 @@ 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.AsyncSafetyTeleporter;
|
||||
import org.mvplugins.multiverse.core.teleportation.TeleportFailureReason;
|
||||
|
||||
@Service
|
||||
final class TeleportCommand extends CoreCommand {
|
||||
@@ -39,14 +38,14 @@ final class TeleportCommand extends CoreCommand {
|
||||
private final CoreConfig config;
|
||||
private final CorePermissionsChecker permissionsChecker;
|
||||
private final AsyncSafetyTeleporter safetyTeleporter;
|
||||
private final UnsafeFlags flags;
|
||||
private final Flags flags;
|
||||
|
||||
@Inject
|
||||
TeleportCommand(
|
||||
@NotNull CoreConfig config,
|
||||
@NotNull CorePermissionsChecker permissionsChecker,
|
||||
@NotNull AsyncSafetyTeleporter safetyTeleporter,
|
||||
@NotNull UnsafeFlags flags
|
||||
@NotNull Flags flags
|
||||
) {
|
||||
this.config = config;
|
||||
this.permissionsChecker = permissionsChecker;
|
||||
@@ -57,19 +56,18 @@ final class TeleportCommand extends CoreCommand {
|
||||
@CommandAlias("mvtp")
|
||||
@Subcommand("teleport|tp")
|
||||
@CommandPermission("@mvteleport")
|
||||
@CommandCompletion(
|
||||
"@destinations:playerOnly|@playersarray:checkPermissions=@mvteleportother "
|
||||
+ "@destinations:othersOnly|@flags:groupName=" + UnsafeFlags.NAME + ",resolveUntil=arg2 "
|
||||
+ "@flags:groupName=" + UnsafeFlags.NAME)
|
||||
@CommandCompletion("@playersarray:checkPermissions=@mvteleportother|@destinations:byIssuerForArg=arg1 "
|
||||
+ "@destinations:notByIssuerForArg=arg1|@flags:byIssuerForArg=arg1,groupName=" + Flags.NAME + " "
|
||||
+ "@flags:notByIssuerForArg=arg1,groupName=" + Flags.NAME)
|
||||
@Syntax("[player] <destination> [--unsafe]")
|
||||
@Description("{@@mv-core.teleport.description}")
|
||||
void onTeleportCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@co.aikar.commands.annotation.Flags("resolve=issuerAware,maxArgForAware=1")
|
||||
@Syntax("[player]")
|
||||
@Description("{@@mv-core.teleport.player.description}")
|
||||
Player[] players,
|
||||
PlayerArrayValue playersValue,
|
||||
|
||||
@Syntax("<destination>")
|
||||
@Description("{@@mv-core.teleport.destination.description}")
|
||||
@@ -79,6 +77,7 @@ final class TeleportCommand extends CoreCommand {
|
||||
@Syntax("[--unsafe]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
Player[] players = playersValue.value();
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
if (players.length == 1) {
|
||||
@@ -105,14 +104,26 @@ final class TeleportCommand extends CoreCommand {
|
||||
safetyTeleporter.to(destination)
|
||||
.by(issuer)
|
||||
.checkSafety(!parsedFlags.hasFlag(flags.unsafe) && destination.checkTeleportSafety())
|
||||
.teleport(player)
|
||||
.onSuccess(() -> issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS,
|
||||
Replace.PLAYER.with(getYouOrName(issuer, player)),
|
||||
Replace.DESTINATION.with(destination.toString())))
|
||||
.onFailure(failure -> issuer.sendError(MVCorei18n.TELEPORT_FAILED,
|
||||
Replace.PLAYER.with(getYouOrName(issuer, player)),
|
||||
Replace.DESTINATION.with(destination.toString()),
|
||||
Replace.REASON.with(failure.getFailureMessage())));
|
||||
.passengerMode(config.getPassengerMode())
|
||||
.teleportSingle(player)
|
||||
.onSuccess(() -> {
|
||||
if (parsedFlags.hasFlag(flags.silent)) {
|
||||
return;
|
||||
}
|
||||
issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS,
|
||||
Replace.PLAYER.with(getYouOrName(issuer, player)),
|
||||
Replace.DESTINATION.with(destination.getDisplayMessage()));
|
||||
})
|
||||
.onFailureCount(reasonsCountMap -> {
|
||||
for (var entry : reasonsCountMap.entrySet()) {
|
||||
Logging.finer("Failed to teleport %s players to %s: %s",
|
||||
entry.getValue(), destination, entry.getKey());
|
||||
issuer.sendError(MVCorei18n.TELEPORT_FAILED,
|
||||
Replace.PLAYER.with(player.getName()),
|
||||
Replace.DESTINATION.with(destination.getDisplayMessage()),
|
||||
Replace.REASON.with(Message.of(entry.getKey())));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Message getYouOrName(MVCommandIssuer issuer, Player player) {
|
||||
@@ -131,19 +142,40 @@ final class TeleportCommand extends CoreCommand {
|
||||
safetyTeleporter.to(destination)
|
||||
.by(issuer)
|
||||
.checkSafety(!parsedFlags.hasFlag(flags.unsafe) && destination.checkTeleportSafety())
|
||||
.passengerMode(config.getPassengerMode())
|
||||
.teleport(List.of(players))
|
||||
.onSuccessCount(successCount -> issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS,
|
||||
Replace.PLAYER.with(successCount + " players"),
|
||||
Replace.DESTINATION.with(destination.toString())))
|
||||
.onSuccessCount(successCount -> {
|
||||
if (parsedFlags.hasFlag(flags.silent)) {
|
||||
return;
|
||||
}
|
||||
issuer.sendInfo(MVCorei18n.TELEPORT_SUCCESS,
|
||||
Replace.PLAYER.with(successCount + " players"),
|
||||
Replace.DESTINATION.with(destination.getDisplayMessage()));
|
||||
})
|
||||
.onFailureCount(reasonsCountMap -> {
|
||||
for (var entry : reasonsCountMap.entrySet()) {
|
||||
Logging.finer("Failed to teleport %s players to %s: %s",
|
||||
entry.getValue(), destination, entry.getKey());
|
||||
issuer.sendError(MVCorei18n.TELEPORT_FAILED,
|
||||
Replace.PLAYER.with(entry.getValue() + " players"),
|
||||
Replace.DESTINATION.with(destination.toString()),
|
||||
Replace.DESTINATION.with(destination.getDisplayMessage()),
|
||||
Replace.REASON.with(Message.of(entry.getKey())));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends UnsafeFlags {
|
||||
|
||||
private static final String NAME = "mvteleport";
|
||||
|
||||
@Inject
|
||||
private Flags(@NotNull CommandFlagsManager flagsManager) {
|
||||
super(NAME, flagsManager);
|
||||
}
|
||||
|
||||
private final CommandFlag silent = flag(CommandFlag.builder("--silent")
|
||||
.addAlias("-s")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -74,12 +74,12 @@ class WhoCommand extends CoreCommand {
|
||||
@Subcommand("who")
|
||||
@CommandPermission("multiverse.core.list.who")
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + PageFilterFlags.NAME)
|
||||
@Syntax("<world> [--page <page>] [--filter <filter>]")
|
||||
@Syntax("[world] [--page <page>] [--filter <filter>]")
|
||||
@Description("{@@mv-core.who.description}")
|
||||
void onWhoCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("<world>")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
@Description("{@@mv-core.who.world.description}")
|
||||
LoadedMultiverseWorld inputtedWorld,
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("[time]")
|
||||
int time,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -54,7 +54,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("[z]")
|
||||
double z,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -79,7 +79,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("<damage>")
|
||||
double damage,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -103,7 +103,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("<distance>")
|
||||
double distance,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -124,7 +124,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
void onWorldBorderGet(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -147,7 +147,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("[time]")
|
||||
int time,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -179,7 +179,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("<distance>")
|
||||
int distance,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -203,7 +203,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("<time>")
|
||||
int time,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
|
||||
@@ -8,15 +8,13 @@ import java.util.Locale;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.queue.ConfirmMode;
|
||||
import org.mvplugins.multiverse.core.config.handle.CommentedConfigurationHandle;
|
||||
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
|
||||
@@ -27,7 +25,8 @@ import org.mvplugins.multiverse.core.config.migration.action.InvertBoolMigratorA
|
||||
import org.mvplugins.multiverse.core.config.migration.action.MoveMigratorAction;
|
||||
import org.mvplugins.multiverse.core.config.migration.VersionMigrator;
|
||||
import org.mvplugins.multiverse.core.config.migration.action.SetMigratorAction;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.teleportation.PassengerMode;
|
||||
import org.mvplugins.multiverse.core.teleportation.PassengerModes;
|
||||
import org.mvplugins.multiverse.core.world.helpers.DimensionFinder.DimensionFormat;
|
||||
|
||||
@Service
|
||||
@@ -187,6 +186,36 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.enforceFlight);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Try<Void> setGamemodeAndFlightEnforceDelay(int delayTicks) {
|
||||
return configHandle.set(configNodes.gamemodeAndFlightEnforceDelay, delayTicks);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public int getGamemodeAndFlightEnforceDelay() {
|
||||
return configHandle.get(configNodes.gamemodeAndFlightEnforceDelay);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setApplyEntitySpawnRate(boolean applyEntitySpawnRate) {
|
||||
return configHandle.set(configNodes.applyEntitySpawnRate, applyEntitySpawnRate);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public boolean getApplyEntitySpawnRate() {
|
||||
return configHandle.get(configNodes.applyEntitySpawnRate);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setApplyEntitySpawnLimit(boolean applyEntitySpawnLimit) {
|
||||
return configHandle.set(configNodes.applyEntitySpawnLimit, applyEntitySpawnLimit);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public boolean getApplyEntitySpawnLimit() {
|
||||
return configHandle.get(configNodes.applyEntitySpawnLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -231,6 +260,31 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.useFinerTeleportPermissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the passenger mode
|
||||
*
|
||||
* @param passengerMode The passenger mode
|
||||
* @return The set result
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public Try<Void> setPassengerMode(PassengerModes passengerMode) {
|
||||
return configHandle.set(configNodes.passengerMode, passengerMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the passenger mode
|
||||
*
|
||||
* @return The passenger mode
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public PassengerMode getPassengerMode() {
|
||||
return configHandle.get(configNodes.passengerMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -449,6 +503,26 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.registerPapiHook);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Try<Void> setWarnInvalidPapiFormat(boolean warnInvalidPapiFormat) {
|
||||
return configHandle.set(configNodes.warnInvalidPapiFormat, warnInvalidPapiFormat);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public boolean getWarnInvalidPapiFormat() {
|
||||
return configHandle.get(configNodes.warnInvalidPapiFormat);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Try<Void> setInvalidPapiFormatReturnsBlank(boolean invalidPapiFormatReturnsBlank) {
|
||||
return configHandle.set(configNodes.invalidPapiFormatReturnsBlank, invalidPapiFormatReturnsBlank);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public boolean getInvalidPapiFormatReturnsBlank() {
|
||||
return configHandle.get(configNodes.invalidPapiFormatReturnsBlank);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -491,6 +565,16 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.resolveAliasName);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public Try<Void> setSimplifiedDestinationTabCompletion(boolean simplifiedDestinationTabCompletion) {
|
||||
return configHandle.set(configNodes.simplifiedDestinationTabCompletion, simplifiedDestinationTabCompletion);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public boolean getSimplifiedDestinationTabCompletion() {
|
||||
return configHandle.get(configNodes.simplifiedDestinationTabCompletion);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -567,6 +651,16 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.eventPriorityPlayerTeleport);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public Try<Void> setEventPriorityPlayerWorldChange(EventPriority eventPriorityPlayerWorldChange) {
|
||||
return configHandle.set(configNodes.eventPriorityPlayerWorldChange, eventPriorityPlayerWorldChange);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public EventPriority getEventPriorityPlayerWorldChange() {
|
||||
return configHandle.get(configNodes.eventPriorityPlayerWorldChange);
|
||||
}
|
||||
|
||||
public Try<Void> setBukkitYmlPath(String bukkitYmlPath) {
|
||||
return configHandle.set(configNodes.bukkitYmlPath, bukkitYmlPath);
|
||||
}
|
||||
@@ -575,6 +669,26 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.bukkitYmlPath);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setServerPropertiesPath(String serverPropertiesPath) {
|
||||
return configHandle.set(configNodes.serverPropertiesPath, serverPropertiesPath);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public String getServerPropertiesPath() {
|
||||
return configHandle.get(configNodes.serverPropertiesPath);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public Try<Void> setAutoDetectGeneratorPlugins(boolean autoDetectGeneratorPlugins) {
|
||||
return configHandle.set(configNodes.autoDetectGeneratorPlugins, autoDetectGeneratorPlugins);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.3")
|
||||
public boolean getAutoDetectGeneratorPlugins() {
|
||||
return configHandle.get(configNodes.autoDetectGeneratorPlugins);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -632,10 +746,11 @@ public final class CoreConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the underlying config file object
|
||||
* Gets the underlying config file object. For internal use only.
|
||||
*
|
||||
* @return The config file
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public FileConfiguration getConfig() {
|
||||
return configHandle.getConfig();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
@@ -19,12 +20,13 @@ import org.mvplugins.multiverse.core.config.node.Node;
|
||||
import org.mvplugins.multiverse.core.config.node.NodeGroup;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeStringParser;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.destination.core.WorldDestination;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.EventPriorityMapper;
|
||||
import org.mvplugins.multiverse.core.event.MVDebugModeEvent;
|
||||
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
|
||||
import org.mvplugins.multiverse.core.permissions.PermissionUtils;
|
||||
import org.mvplugins.multiverse.core.teleportation.PassengerModes;
|
||||
import org.mvplugins.multiverse.core.world.helpers.DimensionFinder.DimensionFormat;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -75,13 +77,10 @@ final class CoreConfigNodes {
|
||||
.comment("# █░▀░█ █▄█ █▄▄ ░█░ █ ▀▄▀ ██▄ █▀▄ ▄█ ██▄ █▄▄ █▄█ █▀▄ ██▄ #")
|
||||
.comment("# #")
|
||||
.comment("# #")
|
||||
.comment("# WIKI: https://github.com/Multiverse/Multiverse-Core/wiki #")
|
||||
.comment("# WIKI: https://mvplugins.org/core/ #")
|
||||
.comment("# DISCORD: https://discord.gg/NZtfKky #")
|
||||
.comment("# BUG REPORTS: https://github.com/Multiverse/Multiverse-Core/issues #")
|
||||
.comment("# #")
|
||||
.comment("# #")
|
||||
.comment("# Each option in this file is documented and explained here: #")
|
||||
.comment("# ==> https://github.com/Multiverse/Multiverse-Core/wiki/config.yml #")
|
||||
.comment("# DONATE: https://github.com/sponsors/Multiverse #")
|
||||
.comment("# #")
|
||||
.comment("# #")
|
||||
.comment("# New options are added to this file automatically. If you manually made changes #")
|
||||
@@ -93,7 +92,6 @@ final class CoreConfigNodes {
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> autoImportDefaultWorlds = node(ConfigNode.builder("world.auto-import-default-worlds", Boolean.class)
|
||||
.comment("")
|
||||
.comment("When enabled, Multiverse will automatically import default worlds defined in the server.properties")
|
||||
.comment("`level-name` property when the Multiverse is enabled or reloaded. This will include the nether and ")
|
||||
.comment("end if the server created them.")
|
||||
@@ -110,6 +108,7 @@ final class CoreConfigNodes {
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> enforceAccess = node(ConfigNode.builder("world.enforce-access", Boolean.class)
|
||||
.comment("")
|
||||
.comment("This setting will prevent players from entering worlds they don't have access to.")
|
||||
.comment("If this is set to false, players will be able to enter any world they want.")
|
||||
.comment("If this is set to true, players will only be able to enter worlds they have")
|
||||
@@ -136,6 +135,42 @@ final class CoreConfigNodes {
|
||||
.name("enforce-flight")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Integer> gamemodeAndFlightEnforceDelay = node(ConfigNode.builder("world.gamemode-and-flight-enforce-delay", Integer.class)
|
||||
.comment("")
|
||||
.comment("Sets the delay in ticks before Multiverse enforces gamemode and flight ability on world change.")
|
||||
.comment("Increase this value if you are experiencing issues with other plugins overriding gamemode or flight ability.")
|
||||
.comment("Or set to 0 to enforce immediately during world change event.")
|
||||
.defaultValue(1)
|
||||
.name("gamemode-and-flight-enforce-delay")
|
||||
.suggester((sender -> List.of("0", "1", "2", "5", "10")))
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> applyEntitySpawnRate = node(ConfigNode.builder("world.apply-entity-spawn-rate", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will apply the world's entity `tick-rate` config in worlds.yml.")
|
||||
.comment("If disabled, the `tick-rate` config in worlds.yml will be ignored.")
|
||||
.comment("Disable this if you want paper-world.yml or another plugin to handle entity spawn rate per world.")
|
||||
.comment("!!!NOTE: This will only apply after a server restart!")
|
||||
.defaultValue(true)
|
||||
.name("apply-entity-spawn-rate")
|
||||
.onChange(((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'apply-entity-spawn-rate' config option will only take " +
|
||||
"full effect after a server restart.")))
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> applyEntitySpawnLimit = node(ConfigNode.builder("world.apply-entity-spawn-limit", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will apply the world's entity `spawn-limit` config when a world is loaded.")
|
||||
.comment("If disabled, the `spawn-limit` config in worlds.yml will be ignored.")
|
||||
.comment("Disable this if you want paper-world.yml or another plugin to handle entity limits per world.")
|
||||
.comment("!!!NOTE: This will only apply after a server restart!")
|
||||
.defaultValue(true)
|
||||
.name("apply-entity-spawn-limit")
|
||||
.onChange(((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'apply-entity-spawn-limit' config option will only take" +
|
||||
" full effect after a server restart.")))
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> autoPurgeEntities = node(ConfigNode.builder("world.auto-purge-entities", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will purge entities on world load based world's entity spawn config.")
|
||||
@@ -183,6 +218,24 @@ final class CoreConfigNodes {
|
||||
.name("use-finer-teleport-permissions")
|
||||
.build());
|
||||
|
||||
final ConfigNode<PassengerModes> passengerMode = node(ConfigNode.builder("teleport.passenger-mode", PassengerModes.class)
|
||||
.comment("")
|
||||
.comment("Configures how passengers and vehicles are handled when an entity is teleported. This config is")
|
||||
.comment("only applied to /mvtp, join-destination and first-spawn-override. It will not touch any other")
|
||||
.comment("teleport by other plugins or commands.")
|
||||
.comment("========= Available options: =========")
|
||||
.comment(" default: Server will handle passengers and vehicles. On papermc 1.21.10+, all will be retained by default.")
|
||||
.comment(" On older versions, all will be dismounted if world changes.")
|
||||
.comment(" dismount_passengers: Passengers will be removed from the parent entity before the teleport.")
|
||||
.comment(" dismount_vehicle: Vehicle will be removed and from the parent entity before the teleport.")
|
||||
.comment(" dismount_all: All passengers and vehicles will be removed from the parent entity before the teleport.")
|
||||
.comment(" retain_passengers: Passengers will teleport together with the parent entity.")
|
||||
.comment(" retain_vehicle: Vehicles will teleport together with the parent entity.")
|
||||
.comment(" retain_all: All passengers and vehicles will teleport together with the parent entity.")
|
||||
.defaultValue(PassengerModes.DEFAULT)
|
||||
.name("passenger-mode")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Integer> concurrentTeleportLimit = node(ConfigNode.builder("teleport.concurrent-teleport-limit", Integer.class)
|
||||
.comment("")
|
||||
.comment("Sets the maximum number of players allowed to be teleported at once with `/mv teleport` command")
|
||||
@@ -237,6 +290,7 @@ final class CoreConfigNodes {
|
||||
.defaultValue("")
|
||||
.name("first-spawn-location")
|
||||
.suggester(this::suggestDestinations)
|
||||
.stringParser(this::parseDestinationString)
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> enableJoinDestination = node(ConfigNode.builder("spawn.enable-join-destination", Boolean.class)
|
||||
@@ -254,6 +308,7 @@ final class CoreConfigNodes {
|
||||
.defaultValue("")
|
||||
.name("join-destination")
|
||||
.suggester(this::suggestDestinations)
|
||||
.stringParser(this::parseDestinationString)
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> defaultRespawnInOverworld = node(ConfigNode.builder("spawn.default-respawn-in-overworld", Boolean.class)
|
||||
@@ -348,12 +403,29 @@ final class CoreConfigNodes {
|
||||
.name("register-papi-hook")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> warnInvalidPapiFormat = node(ConfigNode.builder("messaging.warn-invalid-papi-format", Boolean.class)
|
||||
.comment("")
|
||||
.comment("This config option defines whether or not Multiverse should warn about invalid PlaceholderAPI formats.")
|
||||
.comment("Only applies if PlaceholderAPI is installed, and only to multiverse placeholders.")
|
||||
.defaultValue(true)
|
||||
.name("warn-invalid-papi-format")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> invalidPapiFormatReturnsBlank = node(ConfigNode.builder("messaging.invalid-papi-format-returns-blank", Boolean.class)
|
||||
.comment("")
|
||||
.comment("By default, invalid formats will return the original placeholder string.")
|
||||
.comment("This config option defines whether or not Multiverse should show invalid PlaceholderAPI formats as blank.")
|
||||
.comment("Only applies if PlaceholderAPI is installed, and only to multiverse placeholders.")
|
||||
.defaultValue(false)
|
||||
.name("invalid-papi-format-returns-blank")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Locale> defaultLocale = node(ConfigNode.builder("messaging.default-locale", Locale.class)
|
||||
.comment("")
|
||||
.comment("This config option defines the default language Multiverse should use.")
|
||||
.defaultValue(Locale.ENGLISH)
|
||||
.name("default-locale")
|
||||
.onSetValue((oldValue, newValue) ->
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
commandManager.get().getLocales().setDefaultLocale(newValue))
|
||||
.build());
|
||||
|
||||
@@ -363,9 +435,8 @@ final class CoreConfigNodes {
|
||||
.comment("If the player's language does not have a translation, it will use the default language set above instead.")
|
||||
.defaultValue(true)
|
||||
.name("per-player-locale")
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
commandManager.get().usePerIssuerLocale(newValue);
|
||||
})
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
commandManager.get().usePerIssuerLocale(newValue))
|
||||
.build());
|
||||
|
||||
private final ConfigHeaderNode commandHeader = node(ConfigHeaderNode.builder("command")
|
||||
@@ -381,6 +452,15 @@ final class CoreConfigNodes {
|
||||
.name("resolve-alias-name")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> simplifiedDestinationTabCompletion = node(ConfigNode.builder("command.simplified-destination-tab-completion", Boolean.class)
|
||||
.comment("")
|
||||
.comment("If this is set to true, Multiverse will only suggest simple destination formats in tab completion.")
|
||||
.comment("This means only world names will be suggested without other destination type such as `e:worldname:x,y,z` or `p:playername`.")
|
||||
.comment("Note: This DOES NOT prevent players from using the destinations, as that is controlled by permissions.")
|
||||
.defaultValue(false)
|
||||
.name("simplified-destination-tab-completion")
|
||||
.build());
|
||||
|
||||
final ConfigNode<ConfirmMode> confirmMode = node(ConfigNode.builder("command.confirm-mode", ConfirmMode.class)
|
||||
.comment("")
|
||||
.comment("This config option defines whether `/mv confirm` is needed before running a DANGEROUS action.")
|
||||
@@ -435,8 +515,11 @@ final class CoreConfigNodes {
|
||||
.comment("")
|
||||
.comment("This config option defines the priority for the PlayerPortalEvent.")
|
||||
.name("event-priority-player-portal")
|
||||
.onSetValue((oldValue, newValue) ->
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
eventPriorityMapper.get().setPriority("mvcore-player-portal", newValue))
|
||||
.onChange(((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'event-priority.player-portal' config option will only take " +
|
||||
"full effect after a server restart.")))
|
||||
.build());
|
||||
|
||||
final ConfigNode<EventPriority> eventPriorityPlayerRespawn = node(ConfigNode.builder("event-priority.player-respawn", EventPriority.class)
|
||||
@@ -444,16 +527,23 @@ final class CoreConfigNodes {
|
||||
.name("event-priority-player-respawn")
|
||||
.comment("")
|
||||
.comment("This config option defines the priority for the PlayerRespawnEvent.")
|
||||
.onSetValue((oldValue, newValue) ->
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
eventPriorityMapper.get().setPriority("mvcore-player-respawn", newValue))
|
||||
.onChange((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'event-priority.player-respawn' config option will only take " +
|
||||
"full effect after a server restart."))
|
||||
.build());
|
||||
|
||||
final ConfigNode<EventPriority> eventPriorityPlayerSpawnLocation = node(ConfigNode.builder("event-priority.player-spawn-location", EventPriority.class)
|
||||
.defaultValue(EventPriority.NORMAL)
|
||||
.comment("")
|
||||
.comment("This config option defines the priority for the PlayerSpawnLocationEvent.")
|
||||
.name("event-priority-player-spawn-location").onSetValue((oldValue, newValue) ->
|
||||
.name("event-priority-player-spawn-location")
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
eventPriorityMapper.get().setPriority("mvcore-player-spawn-location", newValue))
|
||||
.onChange((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'event-priority.player-spawn-location' config option will only take " +
|
||||
"full effect after a server restart."))
|
||||
.build());
|
||||
|
||||
final ConfigNode<EventPriority> eventPriorityPlayerTeleport = node(ConfigNode.builder("event-priority.player-teleport", EventPriority.class)
|
||||
@@ -461,8 +551,23 @@ final class CoreConfigNodes {
|
||||
.name("event-priority-player-teleport")
|
||||
.comment("")
|
||||
.comment("This config option defines the priority for the PlayerTeleportEvent.")
|
||||
.onSetValue((oldValue, newValue) ->
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
eventPriorityMapper.get().setPriority("mvcore-player-teleport", newValue))
|
||||
.onChange((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'event-priority.player-teleport' config option will only take " +
|
||||
"full effect after a server restart."))
|
||||
.build());
|
||||
|
||||
final ConfigNode<EventPriority> eventPriorityPlayerWorldChange = node(ConfigNode.builder("event-priority.player-changed-world", EventPriority.class)
|
||||
.defaultValue(EventPriority.NORMAL)
|
||||
.name("event-priority-player-changed-world")
|
||||
.comment("")
|
||||
.comment("This config option defines the priority for the PlayerChangedWorldEvent.")
|
||||
.onLoadAndChange((oldValue, newValue) ->
|
||||
eventPriorityMapper.get().setPriority("mvcore-player-changed-world", newValue))
|
||||
.onChange((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'event-priority.player-changed-world' config option will only take " +
|
||||
"full effect after a server restart."))
|
||||
.build());
|
||||
|
||||
private final ConfigHeaderNode miscHeader = node(ConfigHeaderNode.builder("misc")
|
||||
@@ -475,6 +580,29 @@ final class CoreConfigNodes {
|
||||
.comment("Note: this config option needs a server restart to take effect.")
|
||||
.defaultValue("bukkit.yml")
|
||||
.name("bukkit-yml-path")
|
||||
.onChange(((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'bukkit-yml-path' config option will only take " +
|
||||
"full effect after a server restart.")))
|
||||
.build());
|
||||
|
||||
final ConfigNode<String> serverPropertiesPath = node(ConfigNode.builder("misc.server-properties-path", String.class)
|
||||
.comment("")
|
||||
.comment("Change this if you use a custom path for the server.properties file with `--config` startup flag.")
|
||||
.comment("Note: this config option needs a server restart to take effect.")
|
||||
.defaultValue("server.properties")
|
||||
.name("server-properties-path")
|
||||
.onChange(((sender, oldValue, newValue) ->
|
||||
sender.sendMessage(ChatColor.YELLOW + "'server-properties-path' config option will only take " +
|
||||
"full effect after a server restart.")))
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> autoDetectGeneratorPlugins = node(ConfigNode.builder("misc.auto-detect-generator-plugins", Boolean.class)
|
||||
.comment("")
|
||||
.comment("When enabled, Multiverse will attempt to automatically detect world generator plugins installed on your server.")
|
||||
.comment("This option only affects tab-completion within `/mv create` command and output of `/mv generators` command.")
|
||||
.comment("Disabling this will not have any affect on the usages of world generator plugins itself.")
|
||||
.defaultValue(true)
|
||||
.name("auto-detect-generator-plugins")
|
||||
.build());
|
||||
|
||||
final ConfigNode<Integer> globalDebug = node(ConfigNode.builder("misc.global-debug", Integer.class)
|
||||
@@ -492,7 +620,7 @@ final class CoreConfigNodes {
|
||||
.validator(value -> (value < 0 || value > 3)
|
||||
? Try.failure(new MultiverseException("Debug level must be between 0 and 3."))
|
||||
: Try.success(null))
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
if (newValue != Logging.getDebugLevel()) {
|
||||
Logging.setDebugLevel(newValue);
|
||||
pluginManager.callEvent(new MVDebugModeEvent(newValue));
|
||||
@@ -501,11 +629,12 @@ final class CoreConfigNodes {
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> debugPermissions = node(ConfigNode.builder("misc.debug-permissions", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether console will log every permission check done by all multiverse plugins.")
|
||||
.comment("This will only work if the above 'global-debug' is set to 1 or more.")
|
||||
.defaultValue(false)
|
||||
.name("debug-permissions")
|
||||
.onSetValue((oldValue, newValue) -> PermissionUtils.setDebugPermissions(newValue))
|
||||
.onLoadAndChange((oldValue, newValue) -> PermissionUtils.setDebugPermissions(newValue))
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> silentStart = node(ConfigNode.builder("misc.silent-start", Boolean.class)
|
||||
@@ -513,12 +642,13 @@ final class CoreConfigNodes {
|
||||
.comment("If true, the startup console messages will no longer show.")
|
||||
.defaultValue(false)
|
||||
.name("silent-start")
|
||||
.onSetValue((oldValue, newValue) -> Logging.setShowingConfig(!newValue))
|
||||
.onLoadAndChange((oldValue, newValue) -> Logging.setShowingConfig(!newValue))
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> showDonationMessage = node(ConfigNode.builder("misc.show-donation-message", Boolean.class)
|
||||
.comment("")
|
||||
.comment("If you don't want to donate, you can set this to false and Multiverse will stop nagging you.")
|
||||
.comment("But if you want to donate, here is our Github Sponsors page: https://github.com/sponsors/Multiverse")
|
||||
.defaultValue(true)
|
||||
.name("show-donation-message")
|
||||
.build());
|
||||
@@ -532,15 +662,14 @@ final class CoreConfigNodes {
|
||||
.hidden()
|
||||
.build());
|
||||
|
||||
// todo: Maybe combine with the similar method in MVCommandCompletion but that has permission checking
|
||||
private Collection<String> suggestDestinations(String input) {
|
||||
return destinationsProvider.get().getDestinations().stream()
|
||||
.flatMap(destination -> destination.suggestDestinations(Bukkit.getConsoleSender(), null)
|
||||
.stream()
|
||||
.map(packet -> destination instanceof WorldDestination
|
||||
? packet.destinationString()
|
||||
: destination.getIdentifier() + ":" + packet.destinationString()))
|
||||
.toList();
|
||||
private Collection<String> suggestDestinations(CommandSender sender, String input) {
|
||||
return destinationsProvider.get().suggestDestinationStrings(sender, input);
|
||||
}
|
||||
|
||||
private Try<String> parseDestinationString(CommandSender sender, String input, Class<String> type) {
|
||||
return destinationsProvider.get().parseDestination(sender, input)
|
||||
.map(DestinationInstance::toString)
|
||||
.toTry();
|
||||
}
|
||||
|
||||
private static final class DimensionFormatNodeSerializer implements NodeSerializer<DimensionFormat> {
|
||||
|
||||
+24
-3
@@ -3,19 +3,22 @@ package org.mvplugins.multiverse.core.config.handle;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.migration.ConfigMigrator;
|
||||
import org.mvplugins.multiverse.core.config.node.ListValueNode;
|
||||
import org.mvplugins.multiverse.core.config.node.Node;
|
||||
import org.mvplugins.multiverse.core.config.node.NodeGroup;
|
||||
import org.mvplugins.multiverse.core.config.node.ValueNode;
|
||||
|
||||
@@ -84,7 +87,8 @@ public abstract class BaseConfigurationHandle<C extends ConfigurationSection> {
|
||||
});
|
||||
|
||||
nodeValueMap.forEach((valueNode, value) -> {
|
||||
valueNode.onSetValue(value, value);
|
||||
valueNode.onLoad(value);
|
||||
valueNode.onLoadAndChange(Bukkit.getConsoleSender(), null, value);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,10 +160,27 @@ public abstract class BaseConfigurationHandle<C extends ConfigurationSection> {
|
||||
* @return Empty try if the value was set, try containing an error otherwise.
|
||||
*/
|
||||
public <T> Try<Void> set(@NotNull ValueNode<T> node, T value) {
|
||||
return set(Bukkit.getConsoleSender(), node, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a node, if the validator is not null, it will be tested first.
|
||||
*
|
||||
* @param sender The sender who triggered the change.
|
||||
* @param node The node to set the value of.
|
||||
* @param value The value to set.
|
||||
* @param <T> The type of the node value.
|
||||
* @return Empty try if the value was set, try containing an error otherwise.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public <T> Try<Void> set(@NotNull CommandSender sender, @NotNull ValueNode<T> node, T value) {
|
||||
return node.validate(value).map(ignore -> {
|
||||
T oldValue = get(node);
|
||||
nodeValueMap.put(node, value);
|
||||
node.onSetValue(oldValue, get(node));
|
||||
node.onLoadAndChange(sender, oldValue, value);
|
||||
node.onChange(sender, oldValue, value);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
+129
-29
@@ -5,6 +5,9 @@ import java.util.Collections;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -40,6 +43,7 @@ public class StringPropertyHandle {
|
||||
|
||||
/**
|
||||
* Get property names that can be modified. ADD and REMOVE actions can only be used on list nodes.
|
||||
*
|
||||
* @param action The target action
|
||||
* @return The property names modifiable for the action.
|
||||
*/
|
||||
@@ -71,20 +75,47 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Suggests property values for command auto-complete based on the input and action type.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param input The input value to suggest based on.
|
||||
* @param action The modification action being performed.
|
||||
* @param name The name of the property.
|
||||
* @param input The input value to suggest based on.
|
||||
* @param action The modification action being performed.
|
||||
* @return A collection of suggested values.
|
||||
*/
|
||||
public Collection<String> getSuggestedPropertyValue(
|
||||
@Nullable String name, @Nullable String input, @NotNull PropertyModifyAction action) {
|
||||
@Nullable String name,
|
||||
@Nullable String input,
|
||||
@NotNull PropertyModifyAction action
|
||||
) {
|
||||
return getSuggestedPropertyValue(name, input, action, Bukkit.getConsoleSender());
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggests property values for command auto-complete based on the input and action type.
|
||||
* <br />
|
||||
* Providing a sender gives contextual information such as sender name, permissions, or player location
|
||||
* for better suggestions.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param input The input value to suggest based on.
|
||||
* @param action The modification action being performed.
|
||||
* @param sender The sender context to use.
|
||||
* @return A collection of suggested values
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public Collection<String> getSuggestedPropertyValue(
|
||||
@Nullable String name,
|
||||
@Nullable String input,
|
||||
@NotNull PropertyModifyAction action,
|
||||
@NotNull CommandSender sender
|
||||
) {
|
||||
return switch (action) {
|
||||
case SET -> findNode(name, ValueNode.class)
|
||||
.map(node -> node.suggest(input))
|
||||
.map(node -> node.suggest(sender, input))
|
||||
.getOrElse(Collections.emptyList());
|
||||
|
||||
case ADD -> findNode(name, ListValueNode.class)
|
||||
.map(node -> node.suggestItem(input))
|
||||
.map(node -> node.suggestItem(sender, input))
|
||||
.getOrElse(Collections.emptyList());
|
||||
|
||||
case REMOVE -> findNode(name, ListValueNode.class)
|
||||
@@ -111,7 +142,7 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Sets the value of the specified property name.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param name The name of the property.
|
||||
* @param value The value to set.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
@@ -122,7 +153,7 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Adds a value to a list property name.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param name The name of the property.
|
||||
* @param value The value to add.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
@@ -133,7 +164,7 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Removes a value from a list property name.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param name The name of the property.
|
||||
* @param value The value to remove.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
@@ -154,9 +185,9 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Modifies a property name based on the given action.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param value The new value (if applicable).
|
||||
* @param action The modification action.
|
||||
* @param name The name of the property.
|
||||
* @param value The new value (if applicable).
|
||||
* @param action The modification action.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
public Try<Void> modifyProperty(
|
||||
@@ -173,59 +204,128 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Sets the property value from a string representation.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value to set.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
public Try<Void> setPropertyString(@Nullable String name, @Nullable String value) {
|
||||
return setPropertyString(Bukkit.getConsoleSender(), name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the property value from a string representation with a sender context.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value to set.
|
||||
* @return A Try indicating success or failure.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public Try<Void> setPropertyString(@NotNull CommandSender sender, @Nullable String name, @Nullable String value) {
|
||||
return findNode(name, ValueNode.class)
|
||||
.flatMap(node -> node.parseFromString(value)
|
||||
.flatMap(parsedValue -> handle.set(node, parsedValue)));
|
||||
.flatMap(node -> node.parseFromString(sender, value)
|
||||
.flatMap(parsedValue -> handle.set(sender, node, parsedValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a value to a list property using its string representation.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value to add.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
public Try<Void> addPropertyString(@Nullable String name, @Nullable String value) {
|
||||
return addPropertyString(Bukkit.getConsoleSender(), name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a value to a list property using its string representation with a sender context.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value to add.
|
||||
* @return A Try indicating success or failure.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public Try<Void> addPropertyString(@NotNull CommandSender sender, @Nullable String name, @Nullable String value) {
|
||||
return findNode(name, ListValueNode.class)
|
||||
.flatMap(node -> node.parseItemFromString(value)
|
||||
.flatMap(node -> node.parseItemFromString(sender, value)
|
||||
.flatMap(parsedValue -> handle.add(node, parsedValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a value from a list property using its string representation.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value to remove.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
public Try<Void> removePropertyString(@Nullable String name, @Nullable String value) {
|
||||
return removePropertyString(Bukkit.getConsoleSender(), name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a value from a list property using its string representation with a sender context.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value to remove.
|
||||
* @return A Try indicating success or failure.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public Try<Void> removePropertyString(@NotNull CommandSender sender, @Nullable String name, @Nullable String value) {
|
||||
return findNode(name, ListValueNode.class)
|
||||
.flatMap(node -> node.parseItemFromString(value)
|
||||
.flatMap(node -> node.parseItemFromString(sender, value)
|
||||
.flatMap(parsedValue -> handle.remove(node, parsedValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies a property using a string value based on the given action.
|
||||
*
|
||||
* @param name The name of the property.
|
||||
* @param value The string value (if applicable).
|
||||
* @param action The modification action.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value (if applicable).
|
||||
* @param action The modification action.
|
||||
* @return A Try indicating success or failure.
|
||||
*/
|
||||
public Try<Void> modifyPropertyString(
|
||||
@Nullable String name, @Nullable String value, @NotNull PropertyModifyAction action) {
|
||||
@Nullable String name,
|
||||
@Nullable String value,
|
||||
@NotNull PropertyModifyAction action
|
||||
) {
|
||||
return modifyPropertyString(Bukkit.getConsoleSender(), name, value, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies a property using a string value based on the given action with a sender context.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param name The name of the property.
|
||||
* @param value The string value (if applicable).
|
||||
* @param action The modification action.
|
||||
* @return A Try indicating success or failure.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public Try<Void> modifyPropertyString(
|
||||
@NotNull CommandSender sender,
|
||||
@Nullable String name,
|
||||
@Nullable String value,
|
||||
@NotNull PropertyModifyAction action
|
||||
) {
|
||||
if (action.isRequireValue() && (value == null)) {
|
||||
return Try.failure(new IllegalArgumentException("Value is required for PropertyModifyAction: " + action));
|
||||
}
|
||||
return switch (action) {
|
||||
case SET -> setPropertyString(name, value);
|
||||
case ADD -> addPropertyString(name, value);
|
||||
case REMOVE -> removePropertyString(name, value);
|
||||
case SET -> setPropertyString(sender, name, value);
|
||||
case ADD -> addPropertyString(sender, name, value);
|
||||
case REMOVE -> removePropertyString(sender, name, value);
|
||||
case RESET -> resetProperty(name);
|
||||
default -> Try.failure(new IllegalArgumentException("Unknown action: " + action));
|
||||
};
|
||||
@@ -234,9 +334,9 @@ public class StringPropertyHandle {
|
||||
/**
|
||||
* Finds a configuration node by name and type.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
* @param type The expected class type of the node.
|
||||
* @param <T> The type of node.
|
||||
* @param name The name of the node.
|
||||
* @param type The expected class type of the node.
|
||||
* @param <T> The type of node.
|
||||
* @return A Try containing the found node or a failure if not found.
|
||||
*/
|
||||
private <T extends Node> Try<T> findNode(@Nullable String name, @NotNull Class<T> type) {
|
||||
|
||||
@@ -8,9 +8,17 @@ import java.util.function.Supplier;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeChangeCallback;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeValueCallback;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.SenderNodeChangeCallback;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.SenderNodeStringParser;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.SenderNodeSuggester;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.DefaultSerializerProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.DefaultStringParserProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.DefaultSuggesterProvider;
|
||||
@@ -42,27 +50,34 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
|
||||
protected final @Nullable String name;
|
||||
protected final @NotNull Class<T> type;
|
||||
protected final @NotNull String[] aliases;
|
||||
protected @Nullable Supplier<T> defaultValue;
|
||||
protected @Nullable NodeSuggester suggester;
|
||||
protected @Nullable NodeStringParser<T> stringParser;
|
||||
protected @Nullable NodeSerializer<T> serializer;
|
||||
protected @Nullable Function<T, Try<Void>> validator;
|
||||
protected @Nullable BiConsumer<T, T> onSetValue;
|
||||
protected @Nullable NodeValueCallback<T> onLoad;
|
||||
protected @Nullable NodeChangeCallback<T> onLoadAndChange;
|
||||
protected @Nullable NodeChangeCallback<T> onChange;
|
||||
|
||||
protected ConfigNode(
|
||||
@NotNull String path,
|
||||
@NotNull String[] comments,
|
||||
@Nullable String name,
|
||||
@NotNull Class<T> type,
|
||||
@NotNull String[] aliases,
|
||||
@Nullable Supplier<T> defaultValue,
|
||||
@Nullable NodeSuggester suggester,
|
||||
@Nullable NodeStringParser<T> stringParser,
|
||||
@Nullable NodeSerializer<T> serializer,
|
||||
@Nullable Function<T, Try<Void>> validator,
|
||||
@Nullable BiConsumer<T, T> onSetValue) {
|
||||
@Nullable NodeValueCallback<T> onLoad,
|
||||
@Nullable NodeChangeCallback<T> onLoadAndChange,
|
||||
@Nullable NodeChangeCallback<T> onChange) {
|
||||
super(path, comments);
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.aliases = aliases;
|
||||
this.defaultValue = defaultValue;
|
||||
this.suggester = (suggester != null)
|
||||
? suggester
|
||||
@@ -74,7 +89,9 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
? serializer
|
||||
: DefaultSerializerProvider.getDefaultSerializer(type);
|
||||
this.validator = validator;
|
||||
this.onSetValue = onSetValue;
|
||||
this.onLoad = onLoad;
|
||||
this.onLoadAndChange = onLoadAndChange;
|
||||
this.onChange = onChange;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,6 +110,14 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull String[] getAliases() {
|
||||
return aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -115,6 +140,17 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Collection<String> suggest(@NotNull CommandSender sender, @Nullable String input) {
|
||||
if (suggester != null && suggester instanceof SenderNodeSuggester senderSuggester) {
|
||||
return senderSuggester.suggest(sender, input);
|
||||
}
|
||||
return suggest(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -126,6 +162,17 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return Try.failure(new UnsupportedOperationException("No string parser for type " + type.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Try<T> parseFromString(@NotNull CommandSender sender, @Nullable String input) {
|
||||
if (stringParser != null && stringParser instanceof SenderNodeStringParser<T> senderStringParser) {
|
||||
return senderStringParser.parse(sender, input, type);
|
||||
}
|
||||
return parseFromString(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -148,9 +195,29 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onSetValue(@Nullable T oldValue, @Nullable T newValue) {
|
||||
if (onSetValue != null) {
|
||||
onSetValue.accept(oldValue, newValue);
|
||||
public void onLoad(@Nullable T value) {
|
||||
if (onLoad != null) {
|
||||
onLoad.run(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onLoadAndChange(@NotNull CommandSender sender, @Nullable T oldValue, @Nullable T newValue) {
|
||||
if (onLoadAndChange != null) {
|
||||
onLoadAndChange.run(sender, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onChange(@NotNull CommandSender sender, @Nullable T oldValue, @Nullable T newValue) {
|
||||
if (onChange != null) {
|
||||
onChange.run(sender, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,12 +231,15 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
|
||||
protected @Nullable String name;
|
||||
protected @NotNull final Class<T> type;
|
||||
protected @NotNull String[] aliases = Strings.EMPTY_ARRAY;
|
||||
protected @Nullable Supplier<T> defaultValue;
|
||||
protected @Nullable NodeSuggester suggester;
|
||||
protected @Nullable NodeStringParser<T> stringParser;
|
||||
protected @Nullable NodeSerializer<T> serializer;
|
||||
protected @Nullable Function<T, Try<Void>> validator;
|
||||
protected @Nullable BiConsumer<T, T> onSetValue;
|
||||
protected @Nullable NodeValueCallback<T> onLoad;
|
||||
protected @Nullable NodeChangeCallback<T> onLoadAndChange;
|
||||
protected @Nullable NodeChangeCallback<T> onChange;
|
||||
|
||||
/**
|
||||
* Creates a new builder.
|
||||
@@ -243,6 +313,20 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return name(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the aliases for this node. Aliases are alternative identifiers for referencing the node.
|
||||
*
|
||||
* @param aliases The aliases to set for this node.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public @NotNull B aliases(@NotNull String... aliases) {
|
||||
this.aliases = aliases;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suggester for this node.
|
||||
*
|
||||
@@ -254,6 +338,20 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suggester for this node with sender context.
|
||||
*
|
||||
* @param suggester The suggester for this node.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public @NotNull B suggester(@NotNull SenderNodeSuggester suggester) {
|
||||
this.suggester = suggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for this node.
|
||||
*
|
||||
@@ -265,6 +363,20 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for this node with sender context.
|
||||
*
|
||||
* @param stringParser The string parser for this node.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public @NotNull B stringParser(@NotNull SenderNodeStringParser<T> stringParser) {
|
||||
this.stringParser = stringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the serializer for this node.
|
||||
*
|
||||
@@ -287,15 +399,83 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be performed when the value is loaded.
|
||||
*
|
||||
* @param onLoad The action to be performed.
|
||||
* @return This builder.
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public @NotNull B onLoad(@NotNull NodeValueCallback<T> onLoad) {
|
||||
this.onLoad = this.onLoad == null ? onLoad : this.onLoad.then(onLoad);
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be performed when the value is loaded and changed.
|
||||
*
|
||||
* @param onLoadAndChange The action to be performed.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public @NotNull B onLoadAndChange(@NotNull NodeChangeCallback<T> onLoadAndChange) {
|
||||
this.onLoadAndChange = this.onLoadAndChange == null ? onLoadAndChange : this.onLoadAndChange.then(onLoadAndChange);
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be performed when the value is loaded and changed.
|
||||
*
|
||||
* @param onLoadAndChange The action to be performed.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public @NotNull B onLoadAndChange(@NotNull SenderNodeChangeCallback<T> onLoadAndChange) {
|
||||
return onLoadAndChange((NodeChangeCallback<T>) onLoadAndChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be performed when the value is changed.
|
||||
*
|
||||
* @param onChange The action to be performed.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public @NotNull B onChange(@NotNull NodeChangeCallback<T> onChange) {
|
||||
this.onChange = this.onChange == null ? onChange : this.onChange.then(onChange);
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be performed when the value is changed.
|
||||
*
|
||||
* @param onChange The action to be performed.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public @NotNull B onChange(@NotNull SenderNodeChangeCallback<T> onChange) {
|
||||
return onChange((NodeChangeCallback<T>) onChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be performed when the value is set.
|
||||
*
|
||||
* @param onSetValue The action to be performed.
|
||||
* @return This builder.
|
||||
*
|
||||
* @deprecated Use {@link #onLoadAndChange(NodeChangeCallback)} instead.
|
||||
*/
|
||||
@Deprecated(since = "5.4", forRemoval = true)
|
||||
public @NotNull B onSetValue(@NotNull BiConsumer<T, T> onSetValue) {
|
||||
this.onSetValue = this.onSetValue == null ? onSetValue : this.onSetValue.andThen(onSetValue);
|
||||
return self();
|
||||
return onLoadAndChange(onSetValue::accept);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,7 +484,8 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
@Override
|
||||
public @NotNull ConfigNode<T> build() {
|
||||
return new ConfigNode<>(path, comments.toArray(new String[0]),
|
||||
name, type, defaultValue, suggester, stringParser, serializer, validator, onSetValue);
|
||||
name, type, aliases, defaultValue, suggester, stringParser, serializer, validator,
|
||||
onLoad, onLoadAndChange, onChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@@ -13,13 +12,19 @@ import java.util.stream.Collectors;
|
||||
|
||||
import io.vavr.Value;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.node.functions.DefaultStringParserProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.DefaultSuggesterProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeChangeCallback;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeValueCallback;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeStringParser;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeSuggester;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.SenderNodeStringParser;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.SenderNodeSuggester;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.DefaultSerializerProvider;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
@@ -58,20 +63,23 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
@NotNull String[] comments,
|
||||
@Nullable String name,
|
||||
@NotNull Class<List<I>> type,
|
||||
@NotNull String[] aliases,
|
||||
@Nullable Supplier<List<I>> defaultValueSupplier,
|
||||
@Nullable NodeSuggester suggester,
|
||||
@Nullable NodeStringParser<List<I>> stringParser,
|
||||
@Nullable NodeSerializer<List<I>> serializer,
|
||||
@Nullable Function<List<I>, Try<Void>> validator,
|
||||
@Nullable BiConsumer<List<I>, List<I>> onSetValue,
|
||||
@Nullable NodeValueCallback<List<I>> onLoad,
|
||||
@Nullable NodeChangeCallback<List<I>> onLoadAndChange,
|
||||
@Nullable NodeChangeCallback<List<I>> onChange,
|
||||
@NotNull Class<I> itemType,
|
||||
@Nullable NodeSuggester itemSuggester,
|
||||
@Nullable NodeStringParser<I> itemStringParser,
|
||||
@Nullable NodeSerializer<I> itemSerializer,
|
||||
@Nullable Function<I, Try<Void>> itemValidator,
|
||||
@Nullable BiConsumer<I, I> onSetItemValue) {
|
||||
super(path, comments, name, type, defaultValueSupplier, suggester, stringParser, serializer,
|
||||
validator, onSetValue);
|
||||
super(path, comments, name, type, aliases, defaultValueSupplier, suggester, stringParser, serializer,
|
||||
validator, onLoad, onLoadAndChange, onChange);
|
||||
this.itemType = itemType;
|
||||
this.itemSuggester = itemSuggester != null
|
||||
? itemSuggester
|
||||
@@ -101,8 +109,8 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
if (this.itemSerializer != null && this.serializer == null) {
|
||||
setDefaultSerialiser();
|
||||
}
|
||||
if (this.onSetItemValue != null && this.onSetValue == null) {
|
||||
setDefaultOnSetValue();
|
||||
if (this.onSetItemValue != null && this.onLoadAndChange == null) {
|
||||
setDefaultOnLoadAndChange();
|
||||
}
|
||||
if (this.defaultValue == null) {
|
||||
this.defaultValue = ArrayList::new;
|
||||
@@ -110,12 +118,12 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
}
|
||||
|
||||
private void setDefaultSuggester() {
|
||||
this.suggester = input -> {
|
||||
if (input == null) {
|
||||
return itemSuggester.suggest(null);
|
||||
}
|
||||
return StringFormatter.addonToCommaSeperated(input, itemSuggester.suggest(input));
|
||||
};
|
||||
if (itemSuggester instanceof SenderNodeSuggester senderItemSuggester) {
|
||||
this.suggester = (SenderNodeSuggester)(sender, input) ->
|
||||
StringFormatter.addOnToCommaSeparated(input, senderItemSuggester.suggest(sender, input));
|
||||
} else {
|
||||
this.suggester = input -> StringFormatter.addOnToCommaSeparated(input, itemSuggester.suggest(input));
|
||||
}
|
||||
}
|
||||
|
||||
private void setDefaultStringParser() {
|
||||
@@ -164,8 +172,8 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
};
|
||||
}
|
||||
|
||||
private void setDefaultOnSetValue() {
|
||||
this.onSetValue = (oldValue, newValue) -> {
|
||||
private void setDefaultOnLoadAndChange() {
|
||||
this.onLoadAndChange = (oldValue, newValue) -> {
|
||||
if (oldValue != null) {
|
||||
oldValue.stream()
|
||||
.filter(value -> !newValue.contains(value))
|
||||
@@ -194,6 +202,17 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Collection<String> suggestItem(@NotNull CommandSender sender, @Nullable String input) {
|
||||
if (itemSuggester != null && itemSuggester instanceof SenderNodeSuggester senderSuggester) {
|
||||
return senderSuggester.suggest(sender, input);
|
||||
}
|
||||
return suggestItem(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -205,6 +224,17 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
return Try.failure(new UnsupportedOperationException("No item string parser for type " + itemType));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Try<I> parseItemFromString(@NotNull CommandSender sender, @Nullable String input) {
|
||||
if (itemStringParser != null && itemStringParser instanceof SenderNodeStringParser<I> senderStringParser) {
|
||||
return senderStringParser.parse(sender, input, itemType);
|
||||
}
|
||||
return parseItemFromString(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -267,6 +297,20 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suggester for an individual item in the list with sender context.
|
||||
*
|
||||
* @param itemSuggester The suggester.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public @NotNull B itemSuggester(@NotNull SenderNodeSuggester itemSuggester) {
|
||||
this.itemSuggester = itemSuggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for an individual item in the list.
|
||||
*
|
||||
@@ -278,6 +322,20 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for an individual item in the list with sender context.
|
||||
*
|
||||
* @param itemStringParser The string parser.
|
||||
* @return This builder.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public @NotNull B itemStringParser(@NotNull SenderNodeStringParser<I> itemStringParser) {
|
||||
this.itemStringParser = itemStringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the serializer for an individual item in the list.
|
||||
*
|
||||
@@ -321,12 +379,15 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
comments.toArray(new String[0]),
|
||||
name,
|
||||
type,
|
||||
aliases,
|
||||
defaultValue,
|
||||
suggester,
|
||||
stringParser,
|
||||
serializer,
|
||||
validator,
|
||||
onSetValue,
|
||||
onLoad,
|
||||
onLoadAndChange,
|
||||
onChange,
|
||||
itemType,
|
||||
itemSuggester,
|
||||
itemStringParser,
|
||||
|
||||
@@ -4,6 +4,8 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -31,6 +33,21 @@ public interface ListValueNode<I> extends ValueNode<List<I>> {
|
||||
*/
|
||||
@NotNull Collection<String> suggestItem(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Suggests possible string values for this node. Use contextural information from the sender such as
|
||||
* sender name, permissions, or player location for better suggestions.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The input string.
|
||||
* @return A collection of possible string values
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
default @NotNull Collection<String> suggestItem(@NotNull CommandSender sender, @Nullable String input) {
|
||||
return suggestItem(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link I}. Used for property set by user input.
|
||||
*
|
||||
@@ -39,6 +56,20 @@ public interface ListValueNode<I> extends ValueNode<List<I>> {
|
||||
*/
|
||||
@NotNull Try<I> parseItemFromString(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link I}. Used for property set by user input.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The string to parse.
|
||||
* @return The parsed value, or given exception if parsing failed.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
default @NotNull Try<I> parseItemFromString(@NotNull CommandSender sender, @Nullable String input) {
|
||||
return parseItemFromString(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the serializer for this node.
|
||||
*
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.mvplugins.multiverse.core.config.node;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.github.townyadvanced.commentedconfiguration.setting.CommentedNode;
|
||||
@@ -16,6 +18,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
*/
|
||||
public class NodeGroup implements Collection<Node> {
|
||||
private final Collection<Node> nodes;
|
||||
private final List<String> nodeNames;
|
||||
private final Map<String, Node> nodesMap;
|
||||
|
||||
/**
|
||||
@@ -23,6 +26,7 @@ public class NodeGroup implements Collection<Node> {
|
||||
*/
|
||||
public NodeGroup() {
|
||||
this.nodes = new ArrayList<>();
|
||||
this.nodeNames = new ArrayList<>();
|
||||
this.nodesMap = new HashMap<>();
|
||||
}
|
||||
|
||||
@@ -34,18 +38,27 @@ public class NodeGroup implements Collection<Node> {
|
||||
public NodeGroup(@NotNull Collection<Node> nodes) {
|
||||
this.nodes = nodes;
|
||||
this.nodesMap = new HashMap<>(nodes.size());
|
||||
this.nodeNames = new ArrayList<>(nodes.size());
|
||||
nodes.forEach(this::addNodeIndex);
|
||||
}
|
||||
|
||||
private void addNodeIndex(@NotNull Node node) {
|
||||
if (node instanceof ValueNode) {
|
||||
((ValueNode<?>) node).getName().peek(name -> nodesMap.put(name, node));
|
||||
if (node instanceof ValueNode<?> valueNode) {
|
||||
valueNode.getName().peek(name -> {
|
||||
nodeNames.add(name);
|
||||
nodesMap.put(name, node);
|
||||
Arrays.stream(valueNode.getAliases()).forEach(alias -> nodesMap.put(alias, node));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void removeNodeIndex(@NotNull Node node) {
|
||||
if (node instanceof ValueNode) {
|
||||
((ValueNode<?>) node).getName().peek(nodesMap::remove);
|
||||
if (node instanceof ValueNode<?> valueNode) {
|
||||
valueNode.getName().peek(name -> {
|
||||
nodeNames.remove(name);
|
||||
nodesMap.remove(name);
|
||||
Arrays.stream(valueNode.getAliases()).forEach(alias -> nodesMap.remove(alias, node));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +68,7 @@ public class NodeGroup implements Collection<Node> {
|
||||
* @return The names of all nodes in this group.
|
||||
*/
|
||||
public @NotNull Collection<String> getNames() {
|
||||
return nodesMap.keySet();
|
||||
return nodeNames;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,15 +4,20 @@ import java.util.Collection;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.config.handle.BaseConfigurationHandle;
|
||||
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
|
||||
|
||||
public interface ValueNode<T> extends Node {
|
||||
|
||||
/**
|
||||
* Gets the name of this node. Used for identifying the node from user input.
|
||||
* Gets the name of this node. Used for identifying the node from user input. This must be unique within a node group.
|
||||
*
|
||||
* @return An {@link Option} containing the name of this node, or {@link Option.None} if the node has no name.
|
||||
*/
|
||||
@@ -25,6 +30,19 @@ public interface ValueNode<T> extends Node {
|
||||
*/
|
||||
@NotNull Class<T> getType();
|
||||
|
||||
/**
|
||||
* Gets the aliases of this node. Serves as shorter or legacy alternatives the {@link #getName()} and must be
|
||||
* unique within a node group.
|
||||
*
|
||||
* @return The aliases of this node.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
default @NotNull String[] getAliases() {
|
||||
return Strings.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default value with type {@link T} of the node.
|
||||
*
|
||||
@@ -40,6 +58,19 @@ public interface ValueNode<T> extends Node {
|
||||
*/
|
||||
@NotNull Collection<String> suggest(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Suggests possible string values for this node. Use contextural information from the sender such as
|
||||
* sender name, permissions, or player location for better suggestions.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The input string.
|
||||
* @return A collection of possible string values
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
@NotNull Collection<String> suggest(@NotNull CommandSender sender, @Nullable String input);
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link T}. Used for property set by user input.
|
||||
*
|
||||
@@ -48,6 +79,21 @@ public interface ValueNode<T> extends Node {
|
||||
*/
|
||||
@NotNull Try<T> parseFromString(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link T} with context from the sender.
|
||||
* Used for property set by user input.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The string to parse.
|
||||
* @return The parsed value, or given exception if parsing failed.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
default @NotNull Try<T> parseFromString(@NotNull CommandSender sender, @Nullable String input) {
|
||||
return parseFromString(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the serializer for this node.
|
||||
*
|
||||
@@ -64,11 +110,51 @@ public interface ValueNode<T> extends Node {
|
||||
*/
|
||||
Try<Void> validate(@Nullable T value);
|
||||
|
||||
/**
|
||||
* Called when the value of this node is loaded by {@link BaseConfigurationHandle#load()}.
|
||||
*
|
||||
* @param value The loaded value.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
void onLoad(@Nullable T value);
|
||||
|
||||
/**
|
||||
* Called when the value of this node is loaded or changed.
|
||||
*
|
||||
* @param sender The sender who triggered the change. If triggered by loading or no target sender is specified,
|
||||
* it will be the console sender.
|
||||
* @param oldValue The old value, will always be null on load.
|
||||
* @param newValue The new value.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
void onLoadAndChange(@NotNull CommandSender sender, @Nullable T oldValue, @Nullable T newValue);
|
||||
|
||||
/**
|
||||
* Called when the value of this node is changed by {@link BaseConfigurationHandle#set(ValueNode, Object)}.
|
||||
*
|
||||
* @param sender The sender who changed the value, or console sender if no target sender specified.
|
||||
* @param oldValue The old value.
|
||||
* @param newValue The new value.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
void onChange(@NotNull CommandSender sender, @Nullable T oldValue, @Nullable T newValue);
|
||||
|
||||
/**
|
||||
* Called when the value of this node is set.
|
||||
*
|
||||
* @param oldValue The old value.
|
||||
* @param newValue The new value.
|
||||
*
|
||||
* @deprecated Use {@link #onLoadAndChange(CommandSender, Object, Object)} instead.
|
||||
*/
|
||||
void onSetValue(@Nullable T oldValue, @Nullable T newValue);
|
||||
@Deprecated(since = "5.4", forRemoval = true)
|
||||
default void onSetValue(@Nullable T oldValue, @Nullable T newValue) {
|
||||
onLoadAndChange(Bukkit.getConsoleSender(), oldValue, newValue);
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.config.node.functions;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import co.aikar.commands.ACFUtil;
|
||||
@@ -41,13 +42,13 @@ public final class DefaultStringParserProvider {
|
||||
}
|
||||
|
||||
private static final NodeStringParser<Enum> ENUM_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> Enum.valueOf(type, input.toUpperCase()));
|
||||
() -> Enum.valueOf(type, input.toUpperCase(Locale.ENGLISH)));
|
||||
|
||||
private static final NodeStringParser<String> STRING_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> input);
|
||||
|
||||
private static final NodeStringParser<Boolean> BOOLEAN_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> switch (String.valueOf(input).toLowerCase()) {
|
||||
() -> switch (String.valueOf(input).toLowerCase(Locale.ENGLISH)) {
|
||||
case "t", "true", "on", "y", "yes", "1", "allow" -> true;
|
||||
case "f", "false", "off", "n", "no", "0", "deny" -> false;
|
||||
default -> throw new MultiverseException("Unable to convert '" + input + "' to boolean. Please use 'true' or 'false'");
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.config.node.functions;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
@@ -43,7 +44,7 @@ public final class DefaultSuggesterProvider {
|
||||
private static NodeSuggester enumSuggester(Class<?> clazz) {
|
||||
return input -> Arrays.stream(clazz.getEnumConstants())
|
||||
.map(Object::toString)
|
||||
.map(String::toLowerCase)
|
||||
.map(name -> name.toLowerCase(Locale.ENGLISH))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package org.mvplugins.multiverse.core.config.node.functions;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* A callback interface for handling changes to configuration node values.
|
||||
*
|
||||
* @param <T> The type of the node value.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
@FunctionalInterface
|
||||
public interface NodeChangeCallback<T> {
|
||||
/**
|
||||
* Called when the value of a node changes.
|
||||
*
|
||||
* @param oldValue The old value of the node.
|
||||
* @param newValue The new value of the node.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
void run(T oldValue, T newValue);
|
||||
|
||||
/**
|
||||
* Called when the value of a node changes, with the sender of the change.
|
||||
*
|
||||
* @param sender The sender of the change.
|
||||
* @param oldValue The old value of the node.
|
||||
* @param newValue The new value of the node.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
default void run(CommandSender sender, T oldValue, T newValue) {
|
||||
run(oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chains another {@link NodeChangeCallback} to be executed after this one.
|
||||
*
|
||||
* @param after The callback to execute after this one.
|
||||
* @return A new {@link NodeChangeCallback} that executes both callbacks in order.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
default NodeChangeCallback<T> then(NodeChangeCallback<T> after) {
|
||||
return new SenderNodeChangeCallback<T>() {
|
||||
@Override
|
||||
public void run(CommandSender sender, T oldValue, T newValue) {
|
||||
NodeChangeCallback.this.run(sender, oldValue, newValue);
|
||||
after.run(sender, oldValue, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(T oldValue, T newValue) {
|
||||
NodeChangeCallback.this.run(oldValue, newValue);
|
||||
after.run(oldValue, newValue);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -6,7 +6,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A function that suggests possible values for a node value.
|
||||
* A function that suggests possible values for a node value. These suggestions must be able to be used to parse the
|
||||
* value from string with {@link NodeStringParser}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface NodeSuggester {
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package org.mvplugins.multiverse.core.config.node.functions;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Handler called when an action is performed on a node such as loading.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
@FunctionalInterface
|
||||
public interface NodeValueCallback<T> {
|
||||
/**
|
||||
* Called when an action is performed on a node.
|
||||
*
|
||||
* @param value The value of the node.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
void run(T value);
|
||||
|
||||
/**
|
||||
* Chains another {@link NodeValueCallback} to be executed after this one.
|
||||
*
|
||||
* @param after The callback to execute after this one.
|
||||
* @return A new {@link NodeValueCallback} that executes both callbacks in order.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
default NodeValueCallback<T> then(NodeValueCallback<T> after) {
|
||||
return (T value) -> {
|
||||
this.run(value);
|
||||
after.run(value);
|
||||
};
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package org.mvplugins.multiverse.core.config.node.functions;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* A callback interface for handling changes to configuration node values with contextual information from the sender.
|
||||
*
|
||||
* @param <T> The type of the node value.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
@FunctionalInterface
|
||||
public interface SenderNodeChangeCallback<T> extends NodeChangeCallback<T> {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
void run(CommandSender sender, T oldValue, T newValue);
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
@Override
|
||||
default void run(T oldValue, T newValue) {
|
||||
run(Bukkit.getConsoleSender(), oldValue, newValue);
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package org.mvplugins.multiverse.core.config.node.functions;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A function that parses a string into a node value object of type {@link T} with contextual information from the sender.
|
||||
*
|
||||
* @param <T> The type of the object to parse.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
@FunctionalInterface
|
||||
public interface SenderNodeStringParser<T> extends NodeStringParser<T> {
|
||||
/**
|
||||
* Parses a string into a node value object of type {@link T} with contextual information from the sender.
|
||||
* This ties in with {@link SenderNodeSuggester} that provides suggestions based on the sender context.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param string The string to parse.
|
||||
* @param type The type of the object to parse.
|
||||
* @return The parsed object, or {@link Try.Failure} if the string could not be parsed.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
@NotNull Try<T> parse(@NotNull CommandSender sender, @Nullable String string, @NotNull Class<T> type);
|
||||
|
||||
@Override
|
||||
default @NotNull Try<T> parse(@Nullable String string, @NotNull Class<T> type) {
|
||||
return parse(Bukkit.getConsoleSender(), string, type);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package org.mvplugins.multiverse.core.config.node.functions;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A function that suggests possible values for a node value with sender contextual information.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
@FunctionalInterface
|
||||
public interface SenderNodeSuggester extends NodeSuggester {
|
||||
|
||||
/**
|
||||
* Suggests possible values for a node value. Generated based on the current user input and sender contextual information.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The current partial user input
|
||||
* @return The possible values.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
@NotNull Collection<String> suggest(@NotNull CommandSender sender, @Nullable String input);
|
||||
|
||||
@Override
|
||||
default @NotNull Collection<String> suggest(@Nullable String input) {
|
||||
return suggest(Bukkit.getConsoleSender(), input);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -48,12 +48,12 @@ public final class DefaultSerializerProvider {
|
||||
if (type.isInstance(object)) {
|
||||
return (Enum<?>) object;
|
||||
}
|
||||
return Enum.valueOf(type, String.valueOf(object).toUpperCase());
|
||||
return Enum.valueOf(type, String.valueOf(object).toUpperCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize(Enum object, Class<Enum> type) {
|
||||
return object.name().toLowerCase();
|
||||
return object.name().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -80,7 +80,7 @@ public final class DefaultSerializerProvider {
|
||||
}
|
||||
String input = String.valueOf(object);
|
||||
//todo: this is a copy from string parser
|
||||
return switch (input.toLowerCase()) {
|
||||
return switch (input.toLowerCase(Locale.ENGLISH)) {
|
||||
case "t", "true", "on", "y", "yes", "1", "allow" -> true;
|
||||
case "f", "false", "off", "n", "no", "0", "deny" -> false;
|
||||
default -> throw new RuntimeException("Unable to convert '" + input + "' to boolean.");
|
||||
|
||||
@@ -2,7 +2,9 @@ package org.mvplugins.multiverse.core.destination;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Contract;
|
||||
@@ -11,6 +13,9 @@ import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
|
||||
/**
|
||||
* A destination is a location that can be teleported to.
|
||||
* <br />
|
||||
* Please ensure you implement at least one of {@link #getDestinationInstance(CommandSender, String)} or
|
||||
* {@link #getDestinationInstance(String)} to prevent a stack overflow.
|
||||
*
|
||||
* @param <D> The type of the destination
|
||||
* @param <T> The type of the destination instance
|
||||
@@ -18,13 +23,13 @@ import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
@Contract
|
||||
public interface Destination<D extends Destination<D, T, F>, T extends DestinationInstance<T, D>, F extends FailureReason> {
|
||||
/**
|
||||
* Returns the identifier or prefix that is required for this destination.
|
||||
* Returns the identifier or prefix 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.
|
||||
* hash and cannot have duplicate values. Means that 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.
|
||||
* @return The identifier or prefix required for this destination.
|
||||
*/
|
||||
@NotNull String getIdentifier();
|
||||
|
||||
@@ -33,8 +38,36 @@ public interface Destination<D extends Destination<D, T, F>, T extends Destinati
|
||||
*
|
||||
* @param destinationParams The destination parameters. ex: p:MyPortal:nw
|
||||
* @return The destination instance, or null if the destination parameters are invalid.
|
||||
*
|
||||
* @deprecated Use {@link #getDestinationInstance(CommandSender, String)} instead. This method will no longer be
|
||||
* called by {@link DestinationsProvider}.
|
||||
*/
|
||||
@NotNull Attempt<T, F> getDestinationInstance(@NotNull String destinationParams);
|
||||
@Deprecated(forRemoval = true, since = "5.1")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
default @NotNull Attempt<T, F> getDestinationInstance(@NotNull String destinationParams) {
|
||||
return getDestinationInstance(Bukkit.getConsoleSender(), destinationParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the destination instance for the given destination parameters with sender context. This allows
|
||||
* for shorthands such as getting location or name from sender. If no sender context is available, use
|
||||
* {@link Bukkit#getConsoleSender()} will be defaulted by {@link DestinationsProvider}.
|
||||
* <br />
|
||||
* Note that the resulting {@link DestinationInstance} should be (de)serializable without the original sender context.
|
||||
* <br />
|
||||
* For example, the parsable string with sender context `e:@here` should return a {@link DestinationInstance} that
|
||||
* is serialized to `e:world:x,y,z:p:y`, which can be deserialized without the original sender context.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param destinationParams The destination parameters. ex: p:MyPortal:nw
|
||||
* @return The destination instance, or null if the destination parameters are invalid.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
default Attempt<T, F> getDestinationInstance(@NotNull CommandSender sender, @NotNull String destinationParams) {
|
||||
return getDestinationInstance(destinationParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of possible destinations for the given destination parameters. This packet's destination
|
||||
|
||||
@@ -4,8 +4,9 @@ import io.vavr.control.Option;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.world.location.UnloadedWorldLocation;
|
||||
|
||||
/**
|
||||
@@ -79,6 +80,20 @@ public abstract class DestinationInstance<I extends DestinationInstance<I, T>,
|
||||
*/
|
||||
public abstract @NotNull Option<String> getFinerPermissionSuffix();
|
||||
|
||||
/**
|
||||
* Gets a user-friendly display text for this destination instance. This is used when displaying the destination
|
||||
* to the player. By default, this returns the same as {@link #toString()}. Override this method to provide a more
|
||||
* user-friendly display text with colors, formatting and localization support.
|
||||
*
|
||||
* @return The display message.
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
@NotNull
|
||||
public Message getDisplayMessage() {
|
||||
return Message.of(this.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialises the destination instance to a savable string.
|
||||
*
|
||||
|
||||
+18
@@ -1,5 +1,8 @@
|
||||
package org.mvplugins.multiverse.core.destination;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.mvplugins.multiverse.core.destination.core.WorldDestination;
|
||||
|
||||
/**
|
||||
* Data of a possible destination for tab completion and permission checking
|
||||
*
|
||||
@@ -8,4 +11,19 @@ package org.mvplugins.multiverse.core.destination;
|
||||
* @param finerPermissionSuffix The finer permission suffix
|
||||
*/
|
||||
public record DestinationSuggestionPacket(Destination<?, ?, ?> destination, String destinationString, String finerPermissionSuffix) {
|
||||
|
||||
/**
|
||||
* Gets a parsable string representation of the destination that is most likely valid for
|
||||
* {@link DestinationsProvider#parseDestination(String)}.
|
||||
*
|
||||
* @return The parsable string
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public String parsableString() {
|
||||
return destination instanceof WorldDestination
|
||||
? destinationString
|
||||
: destination.getIdentifier() + ":" + destinationString;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,9 @@ import java.util.Map;
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -50,8 +52,25 @@ public final class DestinationsProvider {
|
||||
* @param destinationString The destination string.
|
||||
* @return The destination object, or null if invalid format.
|
||||
*/
|
||||
@SuppressWarnings("unchecked,rawtypes")
|
||||
public @NotNull Attempt<DestinationInstance<?, ?>, FailureReason> parseDestination(@NotNull String destinationString) {
|
||||
return this.parseDestination(Bukkit.getConsoleSender(), destinationString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a destination string to a destination object with sender context.
|
||||
*
|
||||
* @param sender The target sender context.
|
||||
* @param destinationString The destination string.
|
||||
* @return The destination object, or null if invalid format.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
@SuppressWarnings("unchecked,rawtypes")
|
||||
public @NotNull Attempt<DestinationInstance<?, ?>, FailureReason> parseDestination(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationString
|
||||
) {
|
||||
String[] items = destinationString.split(SEPARATOR, 2);
|
||||
|
||||
String idString = items[0];
|
||||
@@ -73,7 +92,7 @@ public final class DestinationsProvider {
|
||||
replace("{ids}").with(String.join(", ", this.destinationMap.keySet())));
|
||||
}
|
||||
|
||||
return destination.getDestinationInstance(destinationParams);
|
||||
return destination.getDestinationInstance(sender, destinationParams);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,12 +114,36 @@ public final class DestinationsProvider {
|
||||
return this.destinationMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets suggestions for possible parsable destinations.
|
||||
*
|
||||
* @param sender The target sender context.
|
||||
* @param destinationParams The current user input.
|
||||
* @return A collection of destination suggestions.
|
||||
*/
|
||||
public @NotNull Collection<DestinationSuggestionPacket> suggestDestinations(@NotNull CommandSender sender, @Nullable String destinationParams) {
|
||||
return this.getDestinations().stream()
|
||||
.flatMap(destination -> destination.suggestDestinations(sender, destinationParams).stream())
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets suggestions for possible parsable destinations.
|
||||
*
|
||||
* @param sender The target sender context.
|
||||
* @param destinationParams The current user input.
|
||||
* @return A collection of destination suggestions in parsable string format.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public @NotNull Collection<String> suggestDestinationStrings(@NotNull CommandSender sender, @Nullable String destinationParams) {
|
||||
return suggestDestinations(sender, destinationParams)
|
||||
.stream()
|
||||
.map(DestinationSuggestionPacket::parsableString)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public enum ParseFailureReason implements FailureReason {
|
||||
INVALID_DESTINATION_ID(MVCorei18n.DESTINATION_PARSE_FAILUREREASON_INVALIDDESTINATIONID),
|
||||
;
|
||||
|
||||
@@ -45,7 +45,10 @@ public final class AnchorDestination implements Destination<AnchorDestination, A
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Attempt<AnchorDestinationInstance, InstanceFailureReason> getDestinationInstance(@NotNull String destinationParams) {
|
||||
public @NotNull Attempt<AnchorDestinationInstance, InstanceFailureReason> getDestinationInstance(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationParams
|
||||
) {
|
||||
return this.anchorManager.getAnchor(destinationParams)
|
||||
.fold(
|
||||
() -> Attempt.failure(InstanceFailureReason.ANCHOR_NOT_FOUND, replace("{anchor}").with(destinationParams)),
|
||||
|
||||
+10
@@ -7,6 +7,7 @@ import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
/**
|
||||
* Destination instance implementation for the {@link AnchorDestination}.
|
||||
@@ -63,6 +64,15 @@ public final class AnchorDestinationInstance extends DestinationInstance<AnchorD
|
||||
return Option.of(anchorName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Message getDisplayMessage() {
|
||||
//TODO Localize
|
||||
return Message.of("anchor '" + anchorName + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import org.bukkit.Bukkit;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -20,8 +20,7 @@ import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.PlayerFinder;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
import org.mvplugins.multiverse.core.world.helpers.ConcurrentPlayerWorldTracker;
|
||||
|
||||
/**
|
||||
* {@link Destination} implementation for beds.
|
||||
@@ -30,7 +29,11 @@ import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.re
|
||||
public final class BedDestination implements Destination<BedDestination, BedDestinationInstance, BedDestination.InstanceFailureReason> {
|
||||
static final String OWN_BED_STRING = "playerbed";
|
||||
|
||||
BedDestination() {
|
||||
private final ConcurrentPlayerWorldTracker worldTracker;
|
||||
|
||||
@Inject
|
||||
BedDestination(@NotNull ConcurrentPlayerWorldTracker worldTracker) {
|
||||
this.worldTracker = worldTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,7 +48,10 @@ public final class BedDestination implements Destination<BedDestination, BedDest
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Attempt<BedDestinationInstance, InstanceFailureReason> getDestinationInstance(@NotNull String destinationParams) {
|
||||
public @NotNull Attempt<BedDestinationInstance, InstanceFailureReason> getDestinationInstance(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationParams
|
||||
) {
|
||||
Player player = PlayerFinder.get(destinationParams);
|
||||
if (player == null && !OWN_BED_STRING.equals(destinationParams)) {
|
||||
return Attempt.failure(InstanceFailureReason.PLAYER_NOT_FOUND, Replace.PLAYER.with(destinationParams));
|
||||
@@ -59,8 +65,8 @@ public final class BedDestination implements Destination<BedDestination, BedDest
|
||||
@Override
|
||||
public @NotNull Collection<DestinationSuggestionPacket> suggestDestinations(
|
||||
@NotNull CommandSender sender, @Nullable String destinationParams) {
|
||||
List<DestinationSuggestionPacket> collect = Bukkit.getOnlinePlayers().stream()
|
||||
.map(player -> new DestinationSuggestionPacket(this, player.getName(), player.getName()))
|
||||
List<DestinationSuggestionPacket> collect = worldTracker.getOnlinePlayers().stream()
|
||||
.map(player -> new DestinationSuggestionPacket(this, player, player))
|
||||
.collect(Collectors.toList());
|
||||
if (sender instanceof Player) {
|
||||
collect.add(new DestinationSuggestionPacket(this, OWN_BED_STRING, OWN_BED_STRING));
|
||||
|
||||
+11
-1
@@ -9,12 +9,13 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
/**
|
||||
* Destination instance implementation for the {@link BedDestination}.
|
||||
*/
|
||||
public final class BedDestinationInstance extends DestinationInstance<BedDestinationInstance, BedDestination> {
|
||||
private final Player player;
|
||||
private final @Nullable Player player;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -64,6 +65,15 @@ public final class BedDestinationInstance extends DestinationInstance<BedDestina
|
||||
return Option.of(player != null ? player.getName() : BedDestination.OWN_BED_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Message getDisplayMessage() {
|
||||
//TODO Localize
|
||||
return Message.of(player == null ? "your bed/respawn point" : player.getName() + "'s bed/respawn point");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
@@ -51,7 +51,10 @@ public final class CannonDestination implements Destination<CannonDestination, C
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Attempt<CannonDestinationInstance, InstanceFailureReason> getDestinationInstance(@NotNull String destinationParams) {
|
||||
public @NotNull Attempt<CannonDestinationInstance, InstanceFailureReason> getDestinationInstance(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationParams
|
||||
) {
|
||||
String[] params = REPatterns.COLON.split(destinationParams);
|
||||
if (params.length != 5) {
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_FORMAT);
|
||||
|
||||
+78
-33
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.destination.core;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
@@ -8,7 +9,9 @@ import io.vavr.control.Option;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.BlockCommandSender;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -16,15 +19,15 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.destination.Destination;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket;
|
||||
import org.mvplugins.multiverse.core.exceptions.utils.position.PositionParseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.entrycheck.WorldEntryCheckerProvider;
|
||||
import org.mvplugins.multiverse.core.world.location.UnloadedWorldLocation;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.*;
|
||||
|
||||
@@ -60,55 +63,55 @@ public final class ExactDestination implements Destination<ExactDestination, Exa
|
||||
* @return A new {@link ExactDestinationInstance}
|
||||
*/
|
||||
public @NotNull ExactDestinationInstance fromLocation(@NotNull Location location) {
|
||||
return new ExactDestinationInstance(this, new UnloadedWorldLocation(location));
|
||||
return new ExactDestinationInstance(
|
||||
this,
|
||||
worldManager,
|
||||
location.getWorld().getName(),
|
||||
EntityPosition.ofLocation(location)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Attempt<ExactDestinationInstance, InstanceFailureReason> getDestinationInstance(@NotNull String destinationParams) {
|
||||
String[] items = REPatterns.COLON.split(destinationParams);
|
||||
public @NotNull Attempt<ExactDestinationInstance, InstanceFailureReason> getDestinationInstance(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationParams
|
||||
) {
|
||||
String[] items = REPatterns.COLON.split(destinationParams, 2);
|
||||
if (items.length < 2) {
|
||||
if (items[0].equals("@here")) {
|
||||
return getLocationFromSender(sender)
|
||||
.map(location -> Attempt.<ExactDestinationInstance, InstanceFailureReason>success(
|
||||
new ExactDestinationInstance(
|
||||
this,
|
||||
worldManager,
|
||||
location.getWorld().getName(),
|
||||
EntityPosition.ofLocation(location)
|
||||
)
|
||||
))
|
||||
.getOrElse(() -> Attempt.failure(InstanceFailureReason.INVALID_COORDINATES_FORMAT)); // todo: specific failure reason for this case
|
||||
}
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_FORMAT);
|
||||
}
|
||||
|
||||
String worldName = items[0];
|
||||
String coordinates = items[1];
|
||||
String[] coordinatesParams = REPatterns.COMMA.split(coordinates);
|
||||
if (coordinatesParams.length != 3) {
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_COORDINATES_FORMAT);
|
||||
}
|
||||
String positionStr = items[1];
|
||||
|
||||
World world = getLoadedMultiverseWorld(worldName).flatMap(LoadedMultiverseWorld::getBukkitWorld).getOrNull();
|
||||
if (world == null) {
|
||||
return Attempt.failure(InstanceFailureReason.WORLD_NOT_FOUND, Replace.WORLD.with(worldName));
|
||||
}
|
||||
|
||||
UnloadedWorldLocation location;
|
||||
EntityPosition position;
|
||||
try {
|
||||
location = new UnloadedWorldLocation(
|
||||
world,
|
||||
Double.parseDouble(coordinatesParams[0]),
|
||||
Double.parseDouble(coordinatesParams[1]),
|
||||
Double.parseDouble(coordinatesParams[2])
|
||||
);
|
||||
} catch (NumberFormatException e) {
|
||||
position = EntityPosition.fromString(positionStr);
|
||||
} catch (PositionParseException e) {
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_NUMBER_FORMAT, Replace.ERROR.with(e));
|
||||
}
|
||||
|
||||
if (items.length == 4) {
|
||||
String pitch = items[2];
|
||||
String yaw = items[3];
|
||||
try {
|
||||
location.setPitch(Float.parseFloat(pitch));
|
||||
location.setYaw(Float.parseFloat(yaw));
|
||||
} catch (NumberFormatException e) {
|
||||
return Attempt.failure(InstanceFailureReason.INVALID_NUMBER_FORMAT, Replace.ERROR.with(e));
|
||||
}
|
||||
}
|
||||
|
||||
return Attempt.success(new ExactDestinationInstance(this, location));
|
||||
return Attempt.success(new ExactDestinationInstance(this, worldManager, worldName, position));
|
||||
}
|
||||
|
||||
//TODO: Extract to a world finder class
|
||||
@@ -118,19 +121,61 @@ public final class ExactDestination implements Destination<ExactDestination, Exa
|
||||
: worldManager.getLoadedWorld(worldName);
|
||||
}
|
||||
|
||||
private Option<Location> getLocationFromSender(CommandSender sender) {
|
||||
if (sender instanceof Entity entity) {
|
||||
return Option.of(entity.getLocation());
|
||||
}
|
||||
if (sender instanceof BlockCommandSender blockSender) {
|
||||
return Option.of(blockSender.getBlock().getLocation());
|
||||
}
|
||||
return Option.none();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Collection<DestinationSuggestionPacket> suggestDestinations(
|
||||
@NotNull CommandSender sender, @Nullable String destinationParams) {
|
||||
return worldManager.getLoadedWorlds().stream()
|
||||
Stream<DestinationSuggestionPacket> stream = worldManager.getLoadedWorlds().stream()
|
||||
.filter(world -> worldEntryCheckerProvider.forSender(sender)
|
||||
.canAccessWorld(world)
|
||||
.isSuccess())
|
||||
.map(world ->
|
||||
new DestinationSuggestionPacket(this, world.getTabCompleteName() + ":", world.getName()))
|
||||
.toList();
|
||||
new DestinationSuggestionPacket(this, world.getTabCompleteName() + ":", world.getName()));
|
||||
|
||||
Location location = getLocationFromSender(sender).getOrNull();
|
||||
if (location != null) {
|
||||
var herePacket = new DestinationSuggestionPacket(
|
||||
this,
|
||||
"@here",
|
||||
location.getWorld().getName()
|
||||
);
|
||||
var locationPacket = new DestinationSuggestionPacket(
|
||||
this,
|
||||
"%s:%.2f,%.2f,%.2f".formatted(
|
||||
location.getWorld().getName(),
|
||||
location.getX(),
|
||||
location.getY(),
|
||||
location.getZ()
|
||||
),
|
||||
location.getWorld().getName()
|
||||
);
|
||||
var locationPacketPW = new DestinationSuggestionPacket(
|
||||
this,
|
||||
"%s:%.2f,%.2f,%.2f:%.2f:%.2f".formatted(
|
||||
location.getWorld().getName(),
|
||||
location.getX(),
|
||||
location.getY(),
|
||||
location.getZ(),
|
||||
location.getPitch(),
|
||||
location.getYaw()
|
||||
),
|
||||
location.getWorld().getName()
|
||||
);
|
||||
stream = Stream.concat(stream, Stream.of(herePacket, locationPacket, locationPacketPW));
|
||||
}
|
||||
return stream.toList();
|
||||
}
|
||||
|
||||
public enum InstanceFailureReason implements FailureReason {
|
||||
|
||||
+36
-10
@@ -1,6 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.destination.core;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
@@ -8,22 +9,34 @@ import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.world.location.UnloadedWorldLocation;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.position.EntityPosition;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
/**
|
||||
* Destination instance implementation for the {@link ExactDestination}.
|
||||
*/
|
||||
public final class ExactDestinationInstance extends DestinationInstance<ExactDestinationInstance, ExactDestination> {
|
||||
private final UnloadedWorldLocation location;
|
||||
private final WorldManager worldManager;
|
||||
private final String worldName;
|
||||
private final EntityPosition position;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param location The location to teleport to.
|
||||
* @param destination The parent destination.
|
||||
* @param worldName The name of the world.
|
||||
* @param position The position in the world.
|
||||
*/
|
||||
ExactDestinationInstance(@NotNull ExactDestination destination, @NotNull UnloadedWorldLocation location) {
|
||||
ExactDestinationInstance(@NotNull ExactDestination destination,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull String worldName,
|
||||
@NotNull EntityPosition position) {
|
||||
super(destination);
|
||||
this.location = location;
|
||||
this.worldManager = worldManager;
|
||||
this.worldName = worldName;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,10 +44,13 @@ public final class ExactDestinationInstance extends DestinationInstance<ExactDes
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Option<Location> getLocation(@NotNull Entity teleportee) {
|
||||
if (location.getWorld() == null) {
|
||||
World world = Bukkit.getWorld(worldName);
|
||||
if (world == null) {
|
||||
return Option.none();
|
||||
}
|
||||
return Option.of(location.toBukkitLocation());
|
||||
Location destinationLocation = position.toBukkitLocation(teleportee.getLocation());
|
||||
destinationLocation.setWorld(world);
|
||||
return Option.of(destinationLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +74,18 @@ public final class ExactDestinationInstance extends DestinationInstance<ExactDes
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Option<String> getFinerPermissionSuffix() {
|
||||
return Option.of(location.getWorld()).map(World::getName);
|
||||
return Option.of(worldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Message getDisplayMessage() {
|
||||
String displayWorldName = worldManager.getWorld(worldName)
|
||||
.map(MultiverseWorld::getAliasOrName)
|
||||
.getOrElse(worldName);
|
||||
return Message.of(displayWorldName + " at " + position.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +93,6 @@ public final class ExactDestinationInstance extends DestinationInstance<ExactDes
|
||||
*/
|
||||
@Override
|
||||
public @NotNull String serialise() {
|
||||
return location.getWorldName() + ":" + location.getX() + "," + location.getY()
|
||||
+ "," + location.getZ() + ":" + location.getPitch() + ":" + location.getYaw();
|
||||
return worldName + ":" + position.toString();
|
||||
}
|
||||
}
|
||||
+14
-6
@@ -4,7 +4,7 @@ import java.util.Collection;
|
||||
|
||||
import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import org.bukkit.Bukkit;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -14,21 +14,26 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.destination.Destination;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.PlayerFinder;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.FailureReason;
|
||||
import org.mvplugins.multiverse.core.world.helpers.ConcurrentPlayerWorldTracker;
|
||||
|
||||
/**
|
||||
* {@link Destination} implementation for players.s
|
||||
*/
|
||||
@Service
|
||||
public final class PlayerDestination implements Destination<PlayerDestination, PlayerDestinationInstance, PlayerDestination.InstanceFailureReason> {
|
||||
|
||||
private final ConcurrentPlayerWorldTracker playerWorldTracker;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the PlayerDestination.
|
||||
*/
|
||||
PlayerDestination() {
|
||||
@Inject
|
||||
PlayerDestination(@NotNull ConcurrentPlayerWorldTracker playerWorldTracker) {
|
||||
this.playerWorldTracker = playerWorldTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,7 +48,10 @@ public final class PlayerDestination implements Destination<PlayerDestination, P
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Attempt<PlayerDestinationInstance, InstanceFailureReason> getDestinationInstance(@NotNull String destinationParams) {
|
||||
public @NotNull Attempt<PlayerDestinationInstance, InstanceFailureReason> getDestinationInstance(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationParams
|
||||
) {
|
||||
Player player = PlayerFinder.get(destinationParams);
|
||||
if (player == null) {
|
||||
return Attempt.failure(InstanceFailureReason.PLAYER_NOT_FOUND, Replace.PLAYER.with(destinationParams));
|
||||
@@ -57,8 +65,8 @@ public final class PlayerDestination implements Destination<PlayerDestination, P
|
||||
@Override
|
||||
public @NotNull Collection<DestinationSuggestionPacket> suggestDestinations(
|
||||
@NotNull CommandSender sender, @Nullable String destinationParams) {
|
||||
return Bukkit.getOnlinePlayers().stream()
|
||||
.map(p -> new DestinationSuggestionPacket(this, p.getName(), p.getName()))
|
||||
return playerWorldTracker.getOnlinePlayers().stream()
|
||||
.map(player -> new DestinationSuggestionPacket(this, player, player))
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
+9
@@ -9,6 +9,7 @@ import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -66,6 +67,14 @@ public final class PlayerDestinationInstance extends DestinationInstance<PlayerD
|
||||
return Option.of(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Message getDisplayMessage() {
|
||||
return Message.of(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
@@ -60,7 +60,10 @@ public final class WorldDestination implements Destination<WorldDestination, Wor
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Attempt<WorldDestinationInstance, InstanceFailureReason> getDestinationInstance(@NotNull String destinationParams) {
|
||||
public @NotNull Attempt<WorldDestinationInstance, InstanceFailureReason> getDestinationInstance(
|
||||
@NotNull CommandSender sender,
|
||||
@NotNull String destinationParams
|
||||
) {
|
||||
String[] items = REPatterns.COLON.split(destinationParams, 3);
|
||||
String worldName = items[0];
|
||||
MultiverseWorld world = getMultiverseWorld(worldName);
|
||||
|
||||
+9
-4
@@ -8,12 +8,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Destination instance implementation for the {@link WorldDestination}.
|
||||
*/
|
||||
@@ -87,6 +84,14 @@ public final class WorldDestinationInstance extends DestinationInstance<WorldDes
|
||||
return Option.of(world.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Message getDisplayMessage() {
|
||||
return Message.of(world.getAliasOrName());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.google.common.base.Strings;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
|
||||
|
||||
/**
|
||||
* Filter content and text based on regex matching.
|
||||
@@ -68,9 +69,12 @@ public class RegexContentFilter implements ContentFilter {
|
||||
if (!hasValidRegex()) {
|
||||
return false;
|
||||
}
|
||||
String text = ChatColor.stripColor(String.valueOf(value)).toLowerCase();
|
||||
String text = ChatTextFormatter.removeColor(String.valueOf(value));
|
||||
if (text == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return regexPattern.matcher(text).find();
|
||||
return regexPattern.matcher(text.toLowerCase()).find();
|
||||
} catch (PatternSyntaxException ignored) {
|
||||
Logging.warning("Error parsing regex '%s' for input '%s'", regexString, text);
|
||||
return false;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package org.mvplugins.multiverse.core.event.world;
|
||||
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
/**
|
||||
* Called when a world has been successfully cloned.
|
||||
@@ -10,14 +13,24 @@ import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
public final class MVWorldClonedEvent extends MultiverseWorldEvent<LoadedMultiverseWorld> {
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
private final LoadedMultiverseWorld fromWorld;
|
||||
private final MultiverseWorld fromWorld;
|
||||
|
||||
public MVWorldClonedEvent(LoadedMultiverseWorld world, LoadedMultiverseWorld fromWorld) {
|
||||
public MVWorldClonedEvent(LoadedMultiverseWorld world, MultiverseWorld fromWorld) {
|
||||
super(world);
|
||||
this.fromWorld = fromWorld;
|
||||
}
|
||||
|
||||
public LoadedMultiverseWorld getFromWorld() {
|
||||
/**
|
||||
* @deprecated Cloning can be done from unloaded worlds as well. Use {@link #getSourceWorld()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.6")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public @Nullable LoadedMultiverseWorld getFromWorld() {
|
||||
return fromWorld.asLoadedWorld().getOrNull();
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public @NotNull MultiverseWorld getSourceWorld() {
|
||||
return fromWorld;
|
||||
}
|
||||
|
||||
@@ -37,5 +50,4 @@ public final class MVWorldClonedEvent extends MultiverseWorldEvent<LoadedMultive
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.mvplugins.multiverse.core.exceptions.utils.position;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
public class PositionParseException extends MultiverseException {
|
||||
public PositionParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PositionParseException(@Nullable Message message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PositionParseException(@Nullable String message, @Nullable Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public PositionParseException(@Nullable Message message, @Nullable Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import jakarta.inject.Inject;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -15,7 +14,9 @@ import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventClass;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventMethod;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.IgnoreIfCancelled;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.SkipIfEventExist;
|
||||
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.ConcurrentPlayerWorldTracker;
|
||||
|
||||
/**
|
||||
* Multiverse's Listener for players.
|
||||
@@ -24,16 +25,17 @@ import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
final class MVChatListener implements CoreListener {
|
||||
private final CoreConfig config;
|
||||
private final WorldManager worldManager;
|
||||
private final MVPlayerListener playerListener;
|
||||
private final ConcurrentPlayerWorldTracker playerWorldTracker;
|
||||
|
||||
@Inject
|
||||
MVChatListener(
|
||||
CoreConfig config,
|
||||
WorldManager worldManager,
|
||||
MVPlayerListener playerListener) {
|
||||
ConcurrentPlayerWorldTracker playerWorldTracker
|
||||
) {
|
||||
this.config = config;
|
||||
this.worldManager = worldManager;
|
||||
this.playerListener = playerListener;
|
||||
this.playerWorldTracker = playerWorldTracker;
|
||||
}
|
||||
|
||||
@EventClass("io.papermc.paper.event.player.AsyncChatEvent")
|
||||
@@ -87,18 +89,16 @@ final class MVChatListener implements CoreListener {
|
||||
|
||||
String prefixChatFormat = config.getPrefixChatFormat();
|
||||
prefixChatFormat = prefixChatFormat.replace("%world%", worldName).replace("%chat%", chat);
|
||||
prefixChatFormat = ChatColor.translateAlternateColorCodes('&', prefixChatFormat);
|
||||
|
||||
event.setFormat(prefixChatFormat);
|
||||
prefixChatFormat = ChatTextFormatter.colorize(prefixChatFormat);
|
||||
if (prefixChatFormat != null) {
|
||||
event.setFormat(prefixChatFormat);
|
||||
}
|
||||
}
|
||||
|
||||
private String getWorldName(Player player) {
|
||||
String world = playerListener.getPlayerWorld().get(player.getName());
|
||||
if (world == null) {
|
||||
world = player.getWorld().getName();
|
||||
playerListener.getPlayerWorld().put(player.getName(), world);
|
||||
}
|
||||
return this.worldManager.getLoadedWorld(world)
|
||||
String worldName = playerWorldTracker.getPlayerWorld(player.getName())
|
||||
.getOrElse(() -> player.getWorld().getName());
|
||||
return this.worldManager.getLoadedWorld(worldName)
|
||||
.map(mvworld -> mvworld.isHidden() ? "" : mvworld.getAliasOrName())
|
||||
.getOrElse("");
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
|
||||
@@ -43,7 +42,7 @@ final class MVEntityListener implements CoreListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventMethod
|
||||
public void foodLevelChange(FoodLevelChangeEvent event) {
|
||||
void foodLevelChange(FoodLevelChangeEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
@@ -65,7 +64,7 @@ final class MVEntityListener implements CoreListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventMethod
|
||||
public void entityRegainHealth(EntityRegainHealthEvent event) {
|
||||
void entityRegainHealth(EntityRegainHealthEvent event) {
|
||||
if (event.isCancelled() || event.getRegainReason() != RegainReason.REGEN) {
|
||||
return;
|
||||
}
|
||||
@@ -84,7 +83,7 @@ final class MVEntityListener implements CoreListener {
|
||||
* @param event The event.
|
||||
*/
|
||||
@EventMethod
|
||||
public void creatureSpawn(CreatureSpawnEvent event) {
|
||||
void creatureSpawn(CreatureSpawnEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
@@ -92,7 +91,8 @@ final class MVEntityListener implements CoreListener {
|
||||
// Always allow custom command and plugins to spawn creatures
|
||||
if (event.getSpawnReason() == SpawnReason.CUSTOM
|
||||
|| event.getSpawnReason() == SpawnReason.COMMAND
|
||||
|| event.getSpawnReason() == SpawnReason.BREEDING) {
|
||||
|| event.getSpawnReason() == SpawnReason.BREEDING
|
||||
|| event.getSpawnReason() == SpawnReason.SPAWNER_EGG) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ final class MVEntityListener implements CoreListener {
|
||||
* Handle Spawn Category settings for non-creature entities.
|
||||
*/
|
||||
@EventMethod
|
||||
public void entitySpawn(EntitySpawnEvent event) {
|
||||
void entitySpawn(EntitySpawnEvent event) {
|
||||
if (event.getEntity() instanceof LivingEntity) {
|
||||
// Handled by CreatureSpawnEvent
|
||||
return;
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
|
||||
package org.mvplugins.multiverse.core.listeners;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
@@ -23,6 +21,7 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerPortalEvent;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
@@ -32,13 +31,17 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
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.dynamiclistener.annotations.DefaultEventPriority;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventMethod;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventPriorityKey;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.IgnoreIfCancelled;
|
||||
import org.mvplugins.multiverse.core.economy.MVEconomist;
|
||||
import org.mvplugins.multiverse.core.event.MVRespawnEvent;
|
||||
import org.mvplugins.multiverse.core.locale.PluginLocales;
|
||||
import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker;
|
||||
import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter;
|
||||
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
|
||||
import org.mvplugins.multiverse.core.teleportation.TeleportQueue;
|
||||
import org.mvplugins.multiverse.core.utils.result.ResultChain;
|
||||
@@ -49,7 +52,6 @@ import org.mvplugins.multiverse.core.world.entrycheck.EntryFeeResult;
|
||||
import org.mvplugins.multiverse.core.world.entrycheck.WorldEntryCheckerProvider;
|
||||
import org.mvplugins.multiverse.core.world.helpers.DimensionFinder;
|
||||
import org.mvplugins.multiverse.core.world.helpers.EnforcementHandler;
|
||||
import org.spigotmc.event.player.PlayerSpawnLocationEvent;
|
||||
|
||||
/**
|
||||
* Multiverse's Listener for players.
|
||||
@@ -68,8 +70,8 @@ final class MVPlayerListener implements CoreListener {
|
||||
private final DestinationsProvider destinationsProvider;
|
||||
private final EnforcementHandler enforcementHandler;
|
||||
private final DimensionFinder dimensionFinder;
|
||||
|
||||
private final Map<String, String> playerWorld = new ConcurrentHashMap<>();
|
||||
private final CorePermissionsChecker corePermissionsChecker;
|
||||
private final AsyncSafetyTeleporter asyncSafetyTeleporter;
|
||||
|
||||
@Inject
|
||||
MVPlayerListener(
|
||||
@@ -84,7 +86,9 @@ final class MVPlayerListener implements CoreListener {
|
||||
Provider<MVCommandManager> commandManagerProvider,
|
||||
DestinationsProvider destinationsProvider,
|
||||
EnforcementHandler enforcementHandler,
|
||||
DimensionFinder dimensionFinder) {
|
||||
DimensionFinder dimensionFinder,
|
||||
CorePermissionsChecker corePermissionsChecker,
|
||||
AsyncSafetyTeleporter asyncSafetyTeleporter) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.worldManagerProvider = worldManagerProvider;
|
||||
@@ -97,6 +101,8 @@ final class MVPlayerListener implements CoreListener {
|
||||
this.destinationsProvider = destinationsProvider;
|
||||
this.enforcementHandler = enforcementHandler;
|
||||
this.dimensionFinder = dimensionFinder;
|
||||
this.corePermissionsChecker = corePermissionsChecker;
|
||||
this.asyncSafetyTeleporter = asyncSafetyTeleporter;
|
||||
}
|
||||
|
||||
private WorldManager getWorldManager() {
|
||||
@@ -107,19 +113,10 @@ final class MVPlayerListener implements CoreListener {
|
||||
return commandManagerProvider.get();
|
||||
}
|
||||
|
||||
public PluginLocales getLocales() {
|
||||
private PluginLocales getLocales() {
|
||||
return getCommandManager().getLocales();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map of player and the world name they are in.
|
||||
*
|
||||
* @return the playerWorld-map
|
||||
*/
|
||||
public Map<String, String> getPlayerWorld() {
|
||||
return playerWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when a player respawns.
|
||||
*
|
||||
@@ -128,7 +125,7 @@ final class MVPlayerListener implements CoreListener {
|
||||
@EventMethod
|
||||
@EventPriorityKey("mvcore-player-respawn")
|
||||
@DefaultEventPriority(EventPriority.LOW)
|
||||
public void playerRespawn(PlayerRespawnEvent event) {
|
||||
void playerRespawn(PlayerRespawnEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
LoadedMultiverseWorld mvWorld = getWorldManager().getLoadedWorld(player.getWorld()).getOrNull();
|
||||
if (mvWorld == null) {
|
||||
@@ -196,23 +193,19 @@ final class MVPlayerListener implements CoreListener {
|
||||
}
|
||||
|
||||
@EventMethod
|
||||
// TODO: Consider if this key is needed anymore, need to remove from config.yml as well
|
||||
@EventPriorityKey("mvcore-player-spawn-location")
|
||||
void playerSpawnLocation(PlayerSpawnLocationEvent event) {
|
||||
void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
MultiverseWorld world = getWorldManager().getLoadedWorld(player.getWorld()).getOrNull();
|
||||
if (world == null) {
|
||||
Logging.finer("Player joined in a world that is not managed by Multiverse.");
|
||||
return;
|
||||
}
|
||||
if (!player.hasPlayedBefore()) {
|
||||
handleFirstSpawn(event);
|
||||
if (player.hasPlayedBefore()) {
|
||||
handleJoinLocation(player);
|
||||
} else {
|
||||
handleJoinLocation(event);
|
||||
handleFirstSpawn(player);
|
||||
}
|
||||
this.handleGameModeAndFlight(player, event.getSpawnLocation().getWorld());
|
||||
handleGameModeAndFlight(player, player.getWorld());
|
||||
}
|
||||
|
||||
private void handleFirstSpawn(PlayerSpawnLocationEvent event) {
|
||||
private void handleFirstSpawn(Player player) {
|
||||
if (!config.getFirstSpawnOverride()) {
|
||||
Logging.finer("FirstSpawnOverride is disabled");
|
||||
// User has disabled the feature in config
|
||||
@@ -220,16 +213,14 @@ final class MVPlayerListener implements CoreListener {
|
||||
}
|
||||
Logging.fine("Moving NEW player to(firstspawnoverride): %s", config.getFirstSpawnLocation());
|
||||
destinationsProvider.parseDestination(config.getFirstSpawnLocation())
|
||||
.map(destination -> destination.getLocation(event.getPlayer())
|
||||
.peek(event::setSpawnLocation)
|
||||
.onEmpty(() -> Logging.warning("The destination in FirstSpawnLocation in config is invalid")))
|
||||
.peek(destination -> teleportToDestinationOnJoin(player, destination))
|
||||
.onFailure(failure -> {
|
||||
Logging.warning("Invalid destination in FirstSpawnLocation in config: %s");
|
||||
Logging.warning(failure.getFailureMessage().formatted(getLocales()));
|
||||
});
|
||||
}
|
||||
|
||||
private void handleJoinLocation(PlayerSpawnLocationEvent event) {
|
||||
private void handleJoinLocation(Player player) {
|
||||
if (!config.getEnableJoinDestination()) {
|
||||
Logging.finer("JoinDestination is disabled");
|
||||
// User has disabled the feature in config
|
||||
@@ -239,27 +230,36 @@ final class MVPlayerListener implements CoreListener {
|
||||
Logging.warning("Joindestination is enabled but no destination has been specified in config!");
|
||||
return;
|
||||
}
|
||||
if (corePermissionsChecker.hasJoinLocationBypassPermission(player)) {
|
||||
Logging.finer("Player %s has bypass permission for JoinDestination", player.getName());
|
||||
return;
|
||||
}
|
||||
Logging.finer("JoinDestination is " + config.getJoinDestination());
|
||||
destinationsProvider.parseDestination(config.getJoinDestination())
|
||||
.map(destination -> destination.getLocation(event.getPlayer())
|
||||
.peek(event::setSpawnLocation)
|
||||
.onEmpty(() -> Logging.warning("The destination in JoinDestination in config is invalid")))
|
||||
.peek(destination -> teleportToDestinationOnJoin(player, destination))
|
||||
.onFailure(failure -> {
|
||||
Logging.warning("Invalid destination in JoinDestination in config: %s");
|
||||
Logging.warning(failure.getFailureMessage().formatted(getLocales()));
|
||||
});
|
||||
}
|
||||
|
||||
private void teleportToDestinationOnJoin(Player player, DestinationInstance<?, ?> destination) {
|
||||
asyncSafetyTeleporter.to(destination).passengerMode(config.getPassengerMode()).teleportSingle(player)
|
||||
.onSuccess(result -> Logging.fine("Player %s has been teleported on join",
|
||||
player.getName()))
|
||||
.onFailure(failure -> Logging.warning("Failed to teleport player %s on join: %s",
|
||||
player.getName(), failure.get(0)));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when a player changes worlds.
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventMethod
|
||||
@DefaultEventPriority(EventPriority.MONITOR)
|
||||
public void playerChangedWorld(PlayerChangedWorldEvent event) {
|
||||
// Permissions now determine whether or not to handle a gamemode.
|
||||
@EventPriorityKey("mvcore-player-changed-world")
|
||||
@DefaultEventPriority(EventPriority.NORMAL)
|
||||
void playerChangedWorld(PlayerChangedWorldEvent event) {
|
||||
this.handleGameModeAndFlight(event.getPlayer(), event.getPlayer().getWorld());
|
||||
playerWorld.put(event.getPlayer().getName(), event.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,13 +269,29 @@ final class MVPlayerListener implements CoreListener {
|
||||
@EventMethod
|
||||
@EventPriorityKey("mvcore-player-teleport")
|
||||
@DefaultEventPriority(EventPriority.HIGHEST)
|
||||
public void playerTeleport(PlayerTeleportEvent event) {
|
||||
Logging.finer("Got teleport event for player '"
|
||||
+ event.getPlayer().getName() + "' with cause '" + event.getCause() + "'");
|
||||
void playerTeleport(PlayerTeleportEvent event) {
|
||||
Player teleportee = event.getPlayer();
|
||||
|
||||
if (event.isCancelled()) {
|
||||
Logging.finer("Teleport event for player '" + teleportee.getName() + "' was already cancelled.");
|
||||
return;
|
||||
}
|
||||
Player teleportee = event.getPlayer();
|
||||
|
||||
Logging.finer("Got teleport event for player '"
|
||||
+ teleportee.getName() + "' with cause '" + event.getCause() + "'");
|
||||
|
||||
Location fromLocation = event.getFrom();
|
||||
if (fromLocation == null || fromLocation.getWorld() == null) { // should never be null, but just in case
|
||||
Logging.finer("Teleport event for player '" + teleportee.getName() + "' has null from-location or world.");
|
||||
return;
|
||||
}
|
||||
|
||||
Location toLocation = event.getTo();
|
||||
if (toLocation == null || toLocation.getWorld() == null) { // may be null on spigot
|
||||
Logging.finer("Teleport event for player '" + teleportee.getName() + "' has null to-location or world.");
|
||||
return;
|
||||
}
|
||||
|
||||
Option<String> teleporterName = teleportQueue.popFromQueue(teleportee.getName());
|
||||
CommandSender teleporter = teleporterName.map(name -> {
|
||||
if (name.equalsIgnoreCase("CONSOLE")) {
|
||||
@@ -296,17 +312,17 @@ final class MVPlayerListener implements CoreListener {
|
||||
}
|
||||
|
||||
Logging.finer("Teleporter %s is teleporting %s from %s to %s", teleporter.getName(), teleportee.getName(),
|
||||
event.getFrom(), event.getTo());
|
||||
fromLocation, toLocation);
|
||||
|
||||
MultiverseWorld fromWorld = getWorldManager().getLoadedWorld(event.getFrom().getWorld()).getOrNull();
|
||||
LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(event.getTo().getWorld()).getOrNull();
|
||||
MultiverseWorld fromWorld = getWorldManager().getLoadedWorld(fromLocation.getWorld()).getOrNull();
|
||||
LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(toLocation.getWorld()).getOrNull();
|
||||
if (toWorld == null) {
|
||||
Logging.fine("Player '" + teleportee.getName() + "' is teleporting to world '"
|
||||
+ event.getTo().getWorld().getName() + "' which is not managed by Multiverse-Core. No further "
|
||||
+ toLocation.getWorld().getName() + "' which is not managed by Multiverse-Core. No further "
|
||||
+ "actions will be taken by Multiverse-Core.");
|
||||
return;
|
||||
}
|
||||
if (event.getFrom().getWorld().equals(event.getTo().getWorld())) {
|
||||
if (fromLocation.getWorld().equals(toLocation.getWorld())) {
|
||||
// The player is Teleporting to the same world.
|
||||
Logging.finer("Player '" + teleportee.getName() + "' is teleporting to the same world.");
|
||||
this.stateSuccess(teleportee.getName(), toWorld.getName());
|
||||
@@ -340,20 +356,19 @@ final class MVPlayerListener implements CoreListener {
|
||||
*/
|
||||
@EventMethod
|
||||
@DefaultEventPriority(EventPriority.LOWEST)
|
||||
public void playerPortalCheck(PlayerPortalEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
if (event.getFrom().getWorld() == null) {
|
||||
@IgnoreIfCancelled
|
||||
void playerPortalCheck(PlayerPortalEvent event) {
|
||||
Location fromLocation = event.getFrom();
|
||||
if (fromLocation == null || fromLocation.getWorld() == null) {
|
||||
Logging.warning("PlayerPortalEvent's from world is null!");
|
||||
return;
|
||||
}
|
||||
if (event.getFrom().getWorld().getBlockAt(event.getFrom()).getType() == Material.NETHER_PORTAL) {
|
||||
if (fromLocation.getWorld().getBlockAt(fromLocation).getType() == Material.NETHER_PORTAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Player was actually outside of the portal, adjust the from location
|
||||
Location newLocation = blockSafety.findPortalBlockNextTo(event.getFrom());
|
||||
Location newLocation = blockSafety.findPortalBlockNextTo(fromLocation);
|
||||
if (newLocation != null) {
|
||||
event.setFrom(newLocation);
|
||||
}
|
||||
@@ -366,28 +381,33 @@ final class MVPlayerListener implements CoreListener {
|
||||
@EventMethod
|
||||
@EventPriorityKey("mvcore-player-portal")
|
||||
@DefaultEventPriority(EventPriority.HIGH)
|
||||
public void playerPortal(PlayerPortalEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
@IgnoreIfCancelled
|
||||
void playerPortal(PlayerPortalEvent event) {
|
||||
Location fromLocation = event.getFrom();
|
||||
if (fromLocation == null || fromLocation.getWorld() == null) { // should never be null, but just in case
|
||||
Logging.finer("PlayerPortalEvent's from world is null!");
|
||||
return;
|
||||
}
|
||||
if (event.getTo() == null || event.getTo().getWorld() == null) {
|
||||
|
||||
Location toLocation = event.getTo();
|
||||
if (toLocation == null || toLocation.getWorld() == null) { // may be null on spigot
|
||||
Logging.finer("PlayerPortalEvent's to world is null!");
|
||||
return;
|
||||
}
|
||||
if (config.isUsingCustomPortalSearch()) {
|
||||
event.setSearchRadius(config.getCustomPortalSearchRadius());
|
||||
}
|
||||
if (Objects.equals(event.getFrom().getWorld(), event.getTo().getWorld())) {
|
||||
if (Objects.equals(fromLocation.getWorld(), toLocation.getWorld())) {
|
||||
// The player is Portaling to the same world.
|
||||
Logging.finer("Player '" + event.getPlayer().getName() + "' is portaling to the same world.");
|
||||
return;
|
||||
}
|
||||
|
||||
MultiverseWorld fromWorld = getWorldManager().getLoadedWorld(event.getFrom().getWorld()).getOrNull();
|
||||
LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(event.getTo().getWorld()).getOrNull();
|
||||
MultiverseWorld fromWorld = getWorldManager().getLoadedWorld(fromLocation.getWorld()).getOrNull();
|
||||
LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(toLocation.getWorld()).getOrNull();
|
||||
if (toWorld == null) {
|
||||
Logging.fine("Player '" + event.getPlayer().getName() + "' is portaling to world '"
|
||||
+ event.getTo().getWorld().getName() + "' which is not managed by Multiverse-Core. No further "
|
||||
+ toLocation.getWorld().getName() + "' which is not managed by Multiverse-Core. No further "
|
||||
+ "actions will be taken by Multiverse-Core.");
|
||||
return;
|
||||
}
|
||||
@@ -401,21 +421,31 @@ final class MVPlayerListener implements CoreListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the gamemode for the specified {@link Player}.
|
||||
* Handles the gamemode for the specified {@link Player}. Delays the enforcement if configured to do so
|
||||
* to ensure multiverse has final say over other plugins changing gamemodes.
|
||||
*
|
||||
* @param player The {@link Player}.
|
||||
* @param world The {@link World} the player is supposed to be in.
|
||||
*/
|
||||
private void handleGameModeAndFlight(final Player player, World world) {
|
||||
// We perform this task one tick later to MAKE SURE that the player actually reaches the
|
||||
// destination world, otherwise we'd be changing the player mode if they havent moved anywhere.
|
||||
this.server.getScheduler().runTaskLater(this.plugin, () -> {
|
||||
if (!player.isOnline() || !player.getWorld().equals(world)) {
|
||||
return;
|
||||
}
|
||||
Logging.finer("Handling gamemode and flight for player %s in world '%s'", player.getName(), world.getName());
|
||||
enforcementHandler.handleFlightEnforcement(player);
|
||||
enforcementHandler.handleGameModeEnforcement(player);
|
||||
}, 1L);
|
||||
if (config.getGamemodeAndFlightEnforceDelay() <= 0) {
|
||||
doGameModeAndFlightEnforcement(player, world);
|
||||
return;
|
||||
}
|
||||
server.getScheduler().runTaskLater(
|
||||
this.plugin,
|
||||
() -> doGameModeAndFlightEnforcement(player, world),
|
||||
config.getGamemodeAndFlightEnforceDelay()
|
||||
);
|
||||
}
|
||||
|
||||
private void doGameModeAndFlightEnforcement(Player player, World world) {
|
||||
if (!player.isOnline() || !player.getWorld().equals(world)) {
|
||||
Logging.finer("Player %s is no longer online or not in the expected world '%s'", player.getName(), world.getName());
|
||||
return;
|
||||
}
|
||||
Logging.finer("Handling gamemode and flight for player %s in world '%s'", player.getName(), world.getName());
|
||||
enforcementHandler.handleFlightEnforcement(player);
|
||||
enforcementHandler.handleGameModeEnforcement(player);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import jakarta.inject.Inject;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.PortalType;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.entity.EntityPortalEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
@@ -48,7 +47,7 @@ final class MVPortalListener implements CoreListener {
|
||||
*/
|
||||
@EventMethod
|
||||
@IgnoreIfCancelled
|
||||
public void portalCreate(PortalCreateEvent event) {
|
||||
void portalCreate(PortalCreateEvent event) {
|
||||
Logging.fine("Attempting to create portal at '%s' with reason: %s",
|
||||
event.getWorld().getName(), event.getReason());
|
||||
|
||||
@@ -93,7 +92,7 @@ final class MVPortalListener implements CoreListener {
|
||||
*/
|
||||
@EventMethod
|
||||
@IgnoreIfCancelled
|
||||
public void playerInteract(PlayerInteractEvent event) {
|
||||
void playerInteract(PlayerInteractEvent event) {
|
||||
if (isCreateEndPortalInteraction(event)) {
|
||||
return;
|
||||
}
|
||||
@@ -122,7 +121,7 @@ final class MVPortalListener implements CoreListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventMethod
|
||||
public void entityPortal(EntityPortalEvent event) {
|
||||
void entityPortal(EntityPortalEvent event) {
|
||||
if (event.isCancelled() || event.getTo() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ package org.mvplugins.multiverse.core.listeners;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.weather.ThunderChangeEvent;
|
||||
import org.bukkit.event.weather.WeatherChangeEvent;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -35,7 +34,7 @@ final class MVWeatherListener implements CoreListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventMethod
|
||||
public void weatherChange(WeatherChangeEvent event) {
|
||||
void weatherChange(WeatherChangeEvent event) {
|
||||
if (event.isCancelled() || !event.toWeatherState()) {
|
||||
return;
|
||||
}
|
||||
@@ -54,7 +53,7 @@ final class MVWeatherListener implements CoreListener {
|
||||
* @param event The Event that was fired.
|
||||
*/
|
||||
@EventMethod
|
||||
public void thunderChange(ThunderChangeEvent event) {
|
||||
void thunderChange(ThunderChangeEvent event) {
|
||||
if (event.isCancelled() || !event.toThunderState()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.DefaultEventPriority;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.annotations.EventMethod;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.options.LoadWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.reasons.LoadFailureReason;
|
||||
import org.mvplugins.multiverse.core.world.reasons.UnloadFailureReason;
|
||||
@@ -68,7 +69,7 @@ final class MVWorldListener implements CoreListener {
|
||||
worldManager.getUnloadedWorld(event.getWorld().getName())
|
||||
.peek(world -> {
|
||||
Logging.fine("Loading world: " + world.getName());
|
||||
worldManager.loadWorld(world).onFailure(failure -> {
|
||||
worldManager.loadWorld(LoadWorldOptions.world(world)).onFailure(failure -> {
|
||||
if (failure.getFailureReason() != LoadFailureReason.WORLD_ALREADY_LOADING) {
|
||||
Logging.severe("Failed to load world: " + failure);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* All the translation keys for the core plugin
|
||||
*/
|
||||
@@ -149,6 +151,7 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
MODIFY_WORLD_DESCRIPTION,
|
||||
MODIFY_PROPERTY_DESCRIPTION,
|
||||
MODIFY_VALUE_DESCRIPTION,
|
||||
MODIFY_PROPERTYREMOVED,
|
||||
MODIFY_CANNOTHAVEVALUE,
|
||||
MODIFY_SPECIFYVALUE,
|
||||
MODIFY_SUCCESS,
|
||||
@@ -181,6 +184,10 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
SETSPAWN_DESCRIPTION,
|
||||
SETSPAWN_LOCATION_DESCRIPTION,
|
||||
SETSPAWN_WORLD_DESCRIPTION,
|
||||
SETSPAWN_UNSAFE,
|
||||
SETSPAWN_SUCCESS,
|
||||
SETSPAWN_FAILED,
|
||||
SETSPAWN_NOTMVWORLD,
|
||||
|
||||
// /mv spawn
|
||||
SPAWN_DESCRIPTION,
|
||||
@@ -285,6 +292,7 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
CLONEWORLD_WORLDEXISTFOLDER,
|
||||
CLONEWORLD_WORLDEXISTUNLOADED,
|
||||
CLONEWORLD_WORLDEXISTLOADED,
|
||||
CLONEWORLD_FROMWORLDFOLDERINVALID,
|
||||
CLONEWORLD_COPYFAILED,
|
||||
|
||||
CREATEWORLD_INVALIDWORLDNAME,
|
||||
@@ -301,11 +309,13 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
IMPORTWORLD_WORLDEXISTUNLOADED,
|
||||
IMPORTWORLD_WORLDEXISTLOADED,
|
||||
IMPORTWORLD_WORLDFOLDERINVALID,
|
||||
IMPORTWORLD_BUKKITENVIRONMENTMISMATCH,
|
||||
|
||||
LOADWORLD_WORLDALREADYLOADING,
|
||||
LOADWORLD_WORLDNONEXISTENT,
|
||||
LOADWORLD_WORLDEXISTFOLDER,
|
||||
LOADWORLD_WORLDEXISTLOADED,
|
||||
LOADWORLD_BUKKITENVIRONMENTMISMATCH,
|
||||
|
||||
REMOVEWORLD_WORLDNONEXISTENT,
|
||||
|
||||
@@ -337,6 +347,11 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
EXCEPTION_MULTIVERSEWORLD_UNLOADPLAYERSINWORLD,
|
||||
EXCEPTION_MULTIVERSEWORLD_UNLOADERROR,
|
||||
|
||||
// multiverse position parse exception
|
||||
EXCEPTION_POSITIONPARSE_INVALIDDIRECTION,
|
||||
EXCEPTION_POSITIONPARSE_INVALIDCOORDINATES,
|
||||
EXCEPTION_POSITIONPARSE_INVALIDNUMBER,
|
||||
|
||||
// generic
|
||||
GENERIC_SUCCESS,
|
||||
GENERIC_FAILURE,
|
||||
@@ -347,7 +362,8 @@ public enum MVCorei18n implements MessageKeyProvider {
|
||||
|
||||
// END CHECKSTYLE-SUPPRESSION: Javadoc
|
||||
|
||||
private final MessageKey key = MessageKey.of("mv-core." + this.name().replace('_', '.').toLowerCase());
|
||||
private final MessageKey key = MessageKey.of("mv-core." + this.name().replace('_', '.')
|
||||
.toLowerCase(Locale.ENGLISH));
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
|
||||
@@ -27,39 +27,12 @@ public final class LocalizedMessage extends Message implements MessageKeyProvide
|
||||
return messageKeyProvider.getMessageKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String[] getReplacements(@NotNull Locales locales, @Nullable CommandIssuer commandIssuer) {
|
||||
return toReplacementsArray(locales, commandIssuer, replacements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String formatted(@NotNull CommandIssuer commandIssuer) {
|
||||
return formatted(commandIssuer.getManager().getLocales(), commandIssuer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String formatted(@NotNull Locales locales, @Nullable CommandIssuer commandIssuer) {
|
||||
Objects.requireNonNull(locales, "locales must not be null");
|
||||
|
||||
String[] parsedReplacements = getReplacements(locales, commandIssuer);
|
||||
if (parsedReplacements.length == 0) {
|
||||
return locales.getMessage(commandIssuer, getMessageKey());
|
||||
}
|
||||
return ACFUtil.replaceStrings(locales.getMessage(commandIssuer, getMessageKey()), parsedReplacements);
|
||||
}
|
||||
|
||||
private static String[] toReplacementsArray(
|
||||
@NotNull Locales locales,
|
||||
@Nullable CommandIssuer commandIssuer,
|
||||
@NotNull MessageReplacement... replacements) {
|
||||
String[] replacementsArray = new String[replacements.length * 2];
|
||||
int i = 0;
|
||||
for (MessageReplacement replacement : replacements) {
|
||||
replacementsArray[i++] = replacement.getKey();
|
||||
replacementsArray[i++] = replacement.getReplacement().fold(
|
||||
str -> str,
|
||||
message -> message.formatted(locales, commandIssuer));
|
||||
}
|
||||
return replacementsArray;
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@ public sealed class Message permits LocalizedMessage {
|
||||
* @return The replacements
|
||||
*/
|
||||
public @NotNull String[] getReplacements(@NotNull Locales locales, @Nullable CommandIssuer commandIssuer) {
|
||||
return getReplacements();
|
||||
return toReplacementsArray(locales, commandIssuer, replacements);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,7 +152,7 @@ public sealed class Message permits LocalizedMessage {
|
||||
* @return The formatted, localized message
|
||||
*/
|
||||
public @NotNull String formatted(@NotNull CommandIssuer commandIssuer) {
|
||||
return formatted();
|
||||
return formatted(commandIssuer.getManager().getLocales(), commandIssuer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,7 +166,11 @@ public sealed class Message permits LocalizedMessage {
|
||||
* @return The formatted, localized message
|
||||
*/
|
||||
public @NotNull String formatted(@NotNull Locales locales, @Nullable CommandIssuer commandIssuer) {
|
||||
return formatted();
|
||||
String[] parsedReplacements = getReplacements(locales, commandIssuer);
|
||||
if (parsedReplacements.length == 0) {
|
||||
return raw();
|
||||
}
|
||||
return ACFUtil.replaceStrings(message, parsedReplacements);
|
||||
}
|
||||
|
||||
private static String[] toReplacementsArray(@NotNull MessageReplacement... replacements) {
|
||||
@@ -178,4 +182,19 @@ public sealed class Message permits LocalizedMessage {
|
||||
}
|
||||
return replacementsArray;
|
||||
}
|
||||
|
||||
private static String[] toReplacementsArray(
|
||||
@NotNull Locales locales,
|
||||
@Nullable CommandIssuer commandIssuer,
|
||||
@NotNull MessageReplacement... replacements) {
|
||||
String[] replacementsArray = new String[replacements.length * 2];
|
||||
int i = 0;
|
||||
for (MessageReplacement replacement : replacements) {
|
||||
replacementsArray[i++] = replacement.getKey();
|
||||
replacementsArray[i++] = replacement.getReplacement().fold(
|
||||
str -> str,
|
||||
message -> message.formatted(locales, commandIssuer));
|
||||
}
|
||||
return replacementsArray;
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,7 @@ public final class MessageReplacement {
|
||||
DESTINATION(replace("{destination}")),
|
||||
ERROR(replace("{error}")),
|
||||
GAMERULE(replace("{gamerule}")),
|
||||
LOCATION(replace("{location}")),
|
||||
NAME(replace("{name}")),
|
||||
PLAYER(replace("{player}")),
|
||||
REASON(replace("{reason}")),
|
||||
|
||||
@@ -10,18 +10,16 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jvnet.hk2.annotations.Contract;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.MultiverseCoreApi;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.MultiverseCommand;
|
||||
import org.mvplugins.multiverse.core.commands.CoreCommand;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.DynamicListener;
|
||||
import org.mvplugins.multiverse.core.dynamiclistener.DynamicListenerRegistration;
|
||||
import org.mvplugins.multiverse.core.inject.PluginServiceLocator;
|
||||
import org.mvplugins.multiverse.core.inject.PluginServiceLocatorFactory;
|
||||
import org.mvplugins.multiverse.core.inject.binder.PluginBinder;
|
||||
import org.mvplugins.multiverse.core.listeners.CoreListener;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Common plugin class for all Multiverse plugins.
|
||||
*/
|
||||
@@ -108,8 +106,8 @@ public abstract class MultiverseModule extends JavaPlugin {
|
||||
*
|
||||
* @deprecated Use {@link #registerDynamicListeners(Class)} with the new DynamicListener API.
|
||||
*/
|
||||
@Deprecated(since = "5.0.0", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0.0")
|
||||
@Deprecated(since = "5.0", forRemoval = true)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
protected void registerEvents(Class<? extends Listener> listenerClass) {
|
||||
var pluginManager = getServer().getPluginManager();
|
||||
Try.run(() -> serviceLocator.getAllServices(listenerClass).forEach(
|
||||
@@ -151,7 +149,7 @@ public abstract class MultiverseModule extends JavaPlugin {
|
||||
.andThen(pluginLocales -> {
|
||||
pluginLocales.addFileResClassLoader(this);
|
||||
pluginLocales.addBundleClassLoader(this.getClassLoader());
|
||||
pluginLocales.addMessageBundles(this.getDescription().getName().toLowerCase());
|
||||
pluginLocales.addMessageBundles(this.getName().toLowerCase(Locale.ENGLISH));
|
||||
})
|
||||
.onFailure(e -> {
|
||||
Logging.severe("Failed to register locales");
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.mvplugins.multiverse.core.permissions;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
@@ -12,9 +13,15 @@ import org.mvplugins.multiverse.core.destination.Destination;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
|
||||
import static org.mvplugins.multiverse.core.permissions.PermissionUtils.concatPermission;
|
||||
import static org.mvplugins.multiverse.core.permissions.PermissionUtils.registerPermissionWithWildcards;
|
||||
|
||||
@Service
|
||||
public final class CorePermissions {
|
||||
/**
|
||||
* Permission to bypass the join location.
|
||||
*/
|
||||
static final String JOINLOCATION_BYPASS = "mv.bypass.joinlocation";
|
||||
|
||||
/**
|
||||
* Permission to access a world.
|
||||
*/
|
||||
@@ -52,24 +59,36 @@ public final class CorePermissions {
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void registerBasePermissions() {
|
||||
Try.run(() -> {
|
||||
registerPermissionWithWildcards(new Permission(JOINLOCATION_BYPASS, PermissionDefault.FALSE));
|
||||
}).onSuccess(ignore -> {
|
||||
Logging.fine("Successfully registered base permissions");
|
||||
}).onFailure(e -> {
|
||||
Logging.fine("Failed to register base permissions: %s", e.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
public Try<Void> addWorldPermissions(@NotNull MultiverseWorld world) {
|
||||
return Try.run(() -> {
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(WORLD_ACCESS, world.getName()), PermissionDefault.OP));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(WORLD_EXEMPT, world.getName()), PermissionDefault.OP));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(GAMEMODE_BYPASS, world.getName()), PermissionDefault.FALSE));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(PLAYERLIMIT_BYPASS, world.getName()), PermissionDefault.FALSE));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(SPAWN, world.getName()), PermissionDefault.OP));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(SPAWN, "self", world.getName()), PermissionDefault.OP));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(SPAWN, "other", world.getName()), PermissionDefault.OP));
|
||||
Logging.fine("Successfully registered permissions for world %s", world.getName());
|
||||
});
|
||||
}).onFailure(e -> Logging.fine("Failed to register permissions for world %s: %s",
|
||||
world.getName(), e.getMessage()));
|
||||
}
|
||||
|
||||
public Try<Void> removeWorldPermissions(@NotNull MultiverseWorld world) {
|
||||
@@ -78,20 +97,23 @@ public final class CorePermissions {
|
||||
pluginManager.removePermission(concatPermission(WORLD_EXEMPT, world.getName()));
|
||||
pluginManager.removePermission(concatPermission(GAMEMODE_BYPASS, world.getName()));
|
||||
pluginManager.removePermission(concatPermission(PLAYERLIMIT_BYPASS, world.getName()));
|
||||
pluginManager.removePermission(concatPermission(SPAWN, world.getName()));
|
||||
pluginManager.removePermission(concatPermission(SPAWN, "self", world.getName()));
|
||||
pluginManager.removePermission(concatPermission(SPAWN, "other", world.getName()));
|
||||
Logging.fine("Successfully removed permissions for world %s", world.getName());
|
||||
});
|
||||
}).onFailure(e -> Logging.fine("Failed to remove permissions for world %s: %s",
|
||||
world.getName(), e.getMessage()));
|
||||
}
|
||||
|
||||
public Try<Void> addDestinationPermissions(@NotNull Destination destination) {
|
||||
return Try.run(() -> {
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(TELEPORT, "self", destination.getIdentifier()), PermissionDefault.OP));
|
||||
pluginManager.addPermission(new Permission(
|
||||
registerPermissionWithWildcards(new Permission(
|
||||
concatPermission(TELEPORT, "other", destination.getIdentifier()), PermissionDefault.OP));
|
||||
Logging.fine("Successfully registered permissions for destination %s", destination.getIdentifier());
|
||||
});
|
||||
}).onFailure(e -> Logging.fine("Failed to register permissions for destination %s: %s",
|
||||
destination.getIdentifier(), e.getMessage()));
|
||||
}
|
||||
|
||||
public Try<Void> removeDestinationPermissions(@NotNull Destination destination) {
|
||||
@@ -99,6 +121,7 @@ public final class CorePermissions {
|
||||
pluginManager.removePermission(concatPermission(TELEPORT, "self", destination.getIdentifier()));
|
||||
pluginManager.removePermission(concatPermission(TELEPORT, "other", destination.getIdentifier()));
|
||||
Logging.fine("Successfully removed permissions for destination %s", destination.getIdentifier());
|
||||
});
|
||||
}).onFailure(e -> Logging.fine("Failed to remove permissions for destination %s: %s",
|
||||
destination.getIdentifier(), e.getMessage()));
|
||||
}
|
||||
}
|
||||
+16
-2
@@ -3,6 +3,7 @@ package org.mvplugins.multiverse.core.permissions;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
@@ -50,6 +51,19 @@ public final class CorePermissionsChecker {
|
||||
this.worldManager = worldManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the sender has permission to bypass the join location restriction.
|
||||
*
|
||||
* @param sender The command sender.
|
||||
* @return True if the sender has bypass permission, false otherwise.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.2")
|
||||
public boolean hasJoinLocationBypassPermission(@NotNull CommandSender sender) {
|
||||
return hasPermission(sender, CorePermissions.JOINLOCATION_BYPASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the sender has permission to access the specified world.
|
||||
*
|
||||
@@ -349,8 +363,8 @@ public final class CorePermissionsChecker {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public static Scope getApplicableScope(CommandSender sender, Entity entity) {
|
||||
if (sender instanceof Entity senderEntity && senderEntity.equals(entity)) {
|
||||
public static Scope getApplicableScope(CommandSender teleporter, Entity entity) {
|
||||
if (teleporter instanceof Entity senderEntity && senderEntity.equals(entity)) {
|
||||
return Scope.SELF;
|
||||
}
|
||||
return Scope.OTHER;
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package org.mvplugins.multiverse.core.permissions;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class PermissionUtils {
|
||||
|
||||
@@ -20,6 +26,37 @@ public final class PermissionUtils {
|
||||
PermissionUtils.debugPermissions = debugPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a permission along with all its wildcard parents.
|
||||
* <br />
|
||||
* For example, registering "mv.bypass.joinlocation" will also register "mv.*" and "mv.bypass.*" as parents.
|
||||
*
|
||||
* @param permission The permission to register.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static void registerPermissionWithWildcards(Permission permission) {
|
||||
Bukkit.getServer().getPluginManager().addPermission(permission);
|
||||
String[] split = permission.getName().split("\\.");
|
||||
StringBuilder prefix = new StringBuilder();
|
||||
// Skip the last element since it's the actual permission
|
||||
Arrays.stream(Arrays.copyOfRange(split, 0, split.length - 1)).forEach(s -> {
|
||||
prefix.append(s).append(".");
|
||||
Permission perm = getOrAddPermission(prefix + "*");
|
||||
permission.addParent(perm, true);
|
||||
});
|
||||
}
|
||||
|
||||
private static Permission getOrAddPermission(String permission) {
|
||||
Permission perm = Bukkit.getServer().getPluginManager().getPermission(permission);
|
||||
if (perm == null) {
|
||||
perm = new Permission(permission, PermissionDefault.FALSE);
|
||||
Bukkit.getServer().getPluginManager().addPermission(perm);
|
||||
}
|
||||
return perm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins permissions with a dot.
|
||||
*
|
||||
|
||||
+110
-19
@@ -11,6 +11,7 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
@@ -20,6 +21,7 @@ import org.mvplugins.multiverse.core.utils.result.AsyncAttempt;
|
||||
import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -35,6 +37,7 @@ public final class AsyncSafetyTeleporterAction {
|
||||
|
||||
private final @NotNull Either<Location, DestinationInstance<?, ?>> locationOrDestination;
|
||||
private boolean checkSafety;
|
||||
private PassengerMode passengerMode = PassengerModes.DEFAULT;
|
||||
private @Nullable CommandSender teleporter = null;
|
||||
|
||||
AsyncSafetyTeleporterAction(
|
||||
@@ -58,18 +61,32 @@ public final class AsyncSafetyTeleporterAction {
|
||||
* Sets whether to check for safe location before teleport.
|
||||
*
|
||||
* @param checkSafety Whether to check for safe location
|
||||
* @return A new {@link AsyncSafetyTeleporterAction} to be chained
|
||||
* @return The same {@link AsyncSafetyTeleporterAction} to be chained
|
||||
*/
|
||||
public AsyncSafetyTeleporterAction checkSafety(boolean checkSafety) {
|
||||
this.checkSafety = checkSafety;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the passenger mode
|
||||
*
|
||||
* @param passengerMode The passenger mode
|
||||
* @return The same {@link AsyncSafetyTeleporterAction} to be chained
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public AsyncSafetyTeleporterAction passengerMode(@NotNull PassengerMode passengerMode) {
|
||||
this.passengerMode = passengerMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the teleporter.
|
||||
*
|
||||
* @param issuer The issuer
|
||||
* @return A new {@link AsyncSafetyTeleporterAction} to be chained
|
||||
* @return The same {@link AsyncSafetyTeleporterAction} to be chained
|
||||
*/
|
||||
public AsyncSafetyTeleporterAction by(@NotNull BukkitCommandIssuer issuer) {
|
||||
return by(issuer.getIssuer());
|
||||
@@ -79,7 +96,7 @@ public final class AsyncSafetyTeleporterAction {
|
||||
* Sets the teleporter.
|
||||
*
|
||||
* @param teleporter The teleporter
|
||||
* @return A new {@link AsyncSafetyTeleporterAction} to be chained
|
||||
* @return The same {@link AsyncSafetyTeleporterAction} to be chained
|
||||
*/
|
||||
public AsyncSafetyTeleporterAction by(@NotNull CommandSender teleporter) {
|
||||
this.teleporter = teleporter;
|
||||
@@ -94,7 +111,36 @@ public final class AsyncSafetyTeleporterAction {
|
||||
* @return A list of async futures that represent the teleportation result of each entity
|
||||
*/
|
||||
public <T extends Entity> AsyncAttemptsAggregate<Void, TeleportFailureReason> teleport(@NotNull List<T> teleportees) {
|
||||
return AsyncAttemptsAggregate.allOf(teleportees.stream().map(this::teleport).toList());
|
||||
return AsyncAttemptsAggregate.allOfAggregate(teleportees.stream().map(this::teleportSingle).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports one parent entity. Multiple entities may be teleported depending on {@link #passengerMode(PassengerMode)}.
|
||||
*
|
||||
* @param teleportee The entity to teleport
|
||||
* @return A list of async future that represents the teleportation result
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public AsyncAttemptsAggregate<Void, TeleportFailureReason> teleportSingle(@NotNull Entity teleportee) {
|
||||
var localTeleporter = this.teleporter == null ? teleportee : this.teleporter;
|
||||
|
||||
return getLocation(teleportee).mapAttempt(this::doSafetyCheck)
|
||||
.onSuccess(() -> {
|
||||
if (teleportee instanceof Player player) {
|
||||
this.teleportQueue.addToQueue(localTeleporter, player);
|
||||
}
|
||||
})
|
||||
.transform(
|
||||
location -> doAsyncTeleport(teleportee, location),
|
||||
failure -> AsyncAttemptsAggregate.allOf(AsyncAttempt.failure(failure))
|
||||
)
|
||||
.thenRun(() -> {
|
||||
if (teleportee instanceof Player player) {
|
||||
this.teleportQueue.popFromQueue(player.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,21 +148,14 @@ public final class AsyncSafetyTeleporterAction {
|
||||
*
|
||||
* @param teleportee The entity to teleport
|
||||
* @return An async future that represents the teleportation result
|
||||
*
|
||||
* @deprecated Use {@link #teleportSingle(Entity)} instead, as teleport of single entity may result in multiple
|
||||
* teleports due to vehicle and passengers based on {@link #passengerMode(PassengerMode)}.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.1")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public AsyncAttempt<Void, TeleportFailureReason> teleport(@NotNull Entity teleportee) {
|
||||
var localTeleporter = this.teleporter == null ? teleportee : this.teleporter;
|
||||
return AsyncAttempt.fromAttempt(getLocation(teleportee).mapAttempt(this::doSafetyCheck))
|
||||
.onSuccess(() -> {
|
||||
if (teleportee instanceof Player player) {
|
||||
this.teleportQueue.addToQueue(localTeleporter, player);
|
||||
}
|
||||
})
|
||||
.mapAsyncAttempt(location -> doAsyncTeleport(teleportee, location))
|
||||
.thenRun(() -> {
|
||||
if (teleportee instanceof Player player) {
|
||||
this.teleportQueue.popFromQueue(player.getName());
|
||||
}
|
||||
});
|
||||
return teleportSingle(teleportee).getAttempts().get(0);
|
||||
}
|
||||
|
||||
private Attempt<Location, TeleportFailureReason> getLocation(@NotNull Entity teleportee) {
|
||||
@@ -160,9 +199,61 @@ public final class AsyncSafetyTeleporterAction {
|
||||
return Attempt.success(safeLocation);
|
||||
}
|
||||
|
||||
private AsyncAttempt<Void, TeleportFailureReason> doAsyncTeleport(
|
||||
private AsyncAttemptsAggregate<Void, TeleportFailureReason> doAsyncTeleport(
|
||||
@NotNull Entity teleportee,
|
||||
@NotNull Location location) {
|
||||
@NotNull Location location
|
||||
) {
|
||||
if (passengerMode.isDismountVehicle() && teleportee.isInsideVehicle()) {
|
||||
Entity vehicle = teleportee.getVehicle();
|
||||
if (vehicle != null) {
|
||||
return doVehicleTeleport(teleportee, location, vehicle);
|
||||
}
|
||||
}
|
||||
|
||||
List<Entity> passengers = teleportee.getPassengers();
|
||||
if (passengerMode.isDismountPassengers() && !passengers.isEmpty()) {
|
||||
passengers.forEach(teleportee::removePassenger);
|
||||
if (passengerMode.isPassengersFollow()) {
|
||||
return doPassengersTeleport(teleportee, location, passengers);
|
||||
}
|
||||
}
|
||||
|
||||
return AsyncAttemptsAggregate.allOf(doSingleTeleport(teleportee, location));
|
||||
}
|
||||
|
||||
private AsyncAttemptsAggregate<Void, TeleportFailureReason> doVehicleTeleport(
|
||||
@NotNull Entity teleportee,
|
||||
@NotNull Location location,
|
||||
@NotNull Entity vehicle
|
||||
) {
|
||||
if (passengerMode.isVehicleFollow()) {
|
||||
return doPassengersTeleport(vehicle, location, vehicle.getPassengers());
|
||||
}
|
||||
teleportee.leaveVehicle();
|
||||
return doAsyncTeleport(teleportee, location);
|
||||
}
|
||||
|
||||
private AsyncAttemptsAggregate<Void, TeleportFailureReason> doPassengersTeleport(
|
||||
@NotNull Entity teleportee,
|
||||
@NotNull Location location,
|
||||
@NotNull List<Entity> passengers
|
||||
) {
|
||||
List<Entity> toTeleport = new ArrayList<>(passengers);
|
||||
toTeleport.addFirst(teleportee);
|
||||
|
||||
return AsyncAttemptsAggregate.allOfAggregate(toTeleport.stream()
|
||||
.map(passenger -> doAsyncTeleport(passenger, location))
|
||||
.toList())
|
||||
.onSuccess(() -> Bukkit.getScheduler().runTask(multiverseCore, () -> {
|
||||
passengers.forEach(teleportee::addPassenger);
|
||||
Logging.finer("Mounted %d passengers to %s", passengers.size(), teleportee.getName());
|
||||
}));
|
||||
}
|
||||
|
||||
private AsyncAttempt<Void, TeleportFailureReason> doSingleTeleport(
|
||||
@NotNull Entity teleportee,
|
||||
@NotNull Location location
|
||||
) {
|
||||
return AsyncAttempt.of(PaperLib.teleportAsync(teleportee, location), exception -> {
|
||||
Logging.warning("Failed to teleport %s to %s: %s",
|
||||
teleportee.getName(), location, exception.getMessage());
|
||||
|
||||
@@ -132,6 +132,10 @@ public final class BlockSafety {
|
||||
*/
|
||||
public boolean canSpawnAtBlockSafely(@NotNull Block block) {
|
||||
Logging.finest("Checking spawn safety for location: %s, %s, %s", block.getX(), block.getY(), block.getZ());
|
||||
if (!block.getWorld().getWorldBorder().isInside(block.getLocation())) {
|
||||
Logging.finest("Location is outside world border.");
|
||||
return false;
|
||||
}
|
||||
if (isUnsafeSpawnBody(block)) {
|
||||
// Player body will be stuck in solid
|
||||
Logging.finest("Unsafe location for player's body: " + block);
|
||||
|
||||
@@ -228,8 +228,8 @@ public final class LocationManipulation {
|
||||
if (orientation == null) {
|
||||
return 0;
|
||||
}
|
||||
if (ORIENTATION_INTS.containsKey(orientation.toLowerCase())) {
|
||||
return ORIENTATION_INTS.get(orientation.toLowerCase());
|
||||
if (ORIENTATION_INTS.containsKey(orientation.toLowerCase(Locale.ENGLISH))) {
|
||||
return ORIENTATION_INTS.get(orientation.toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.mvplugins.multiverse.core.teleportation;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Defines how passengers and vehicles on an entity should be handled when the entity is teleported.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public interface PassengerMode {
|
||||
/**
|
||||
* Defines whether the passengers should be removed from the parent entity when the parent entity is teleported.
|
||||
* <br/>
|
||||
* Teleports between worlds may fail if passengers are not removed.
|
||||
*
|
||||
* @return Whether the passengers should be removed
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
boolean isDismountPassengers();
|
||||
|
||||
/**
|
||||
* Defines whether the passengers should follow the parent entity when the parent entity is teleported.
|
||||
* <br/>
|
||||
* This only applies of passengers are removed from the parent entity, i.e. {@link #isDismountPassengers()} is true.
|
||||
*
|
||||
* @return Whether the passengers should follow
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
boolean isPassengersFollow();
|
||||
|
||||
/**
|
||||
* Defines whether the entity should dismount from the vehicle when the entity is teleported.
|
||||
* <br/>
|
||||
* Teleport between worlds may fail if entity is not dismounted.
|
||||
*
|
||||
* @return Whether the entity should dismount
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
boolean isDismountVehicle();
|
||||
|
||||
/**
|
||||
* Defines whether the vehicle and other passengers on it should follow the target entity on the vehicle when
|
||||
* the target entity is teleported.
|
||||
* <br/>
|
||||
* This only applies if the entity is dismounted from the vehicle, i.e. {@link #isDismountVehicle()} is true.
|
||||
*
|
||||
* @return Whether the vehicle should follow
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
boolean isVehicleFollow();
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package org.mvplugins.multiverse.core.teleportation;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Enum shorthands for {@link PassengerMode} to define common passenger modes configurations.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public enum PassengerModes implements PassengerMode {
|
||||
/**
|
||||
* All passengers and vehicles are handled by the server. This usually means that entities with passengers
|
||||
* or in vehicles will not be able to teleport to a different world until they manually dismount.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
DEFAULT(false, false, false, false),
|
||||
|
||||
/**
|
||||
* All passengers will be removed from the parent entity before the teleport. Passengers will be left behind.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
DISMOUNT_PASSENGERS(true, false, false, false),
|
||||
|
||||
/**
|
||||
* Parent entity will be dismounted from the vehicle before the teleport. Vehicle will be left behind.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
DISMOUNT_VEHICLE(true, false, true, false),
|
||||
|
||||
/**
|
||||
* All passengers and vehicles will be removed from the parent entity before the teleport. Passengers and vehicles
|
||||
* will be left behind.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
DISMOUNT_ALL(true, false, true, false),
|
||||
|
||||
/**
|
||||
* All passengers will teleport together with the parent entity.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
RETAIN_PASSENGERS(true, true, false, false),
|
||||
|
||||
/**
|
||||
* All vehicles will teleport together with the parent entity.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
RETAIN_VEHICLE(false, false, true, true),
|
||||
|
||||
/**
|
||||
* All passengers and vehicles will teleport together with the parent entity.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
RETAIN_ALL(true, true, true, true),
|
||||
;
|
||||
|
||||
private final boolean dismountPassengers;
|
||||
private final boolean passengersFollow;
|
||||
private final boolean dismountVehicle;
|
||||
private final boolean vehicleFollow;
|
||||
|
||||
PassengerModes(boolean dismountPassengers, boolean mountPassengers, boolean dismountVehicle, boolean mountVehicle) {
|
||||
this.dismountPassengers = dismountPassengers;
|
||||
this.passengersFollow = mountPassengers;
|
||||
this.dismountVehicle = dismountVehicle;
|
||||
this.vehicleFollow = mountVehicle;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isDismountPassengers() {
|
||||
return dismountPassengers;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isPassengersFollow() {
|
||||
return passengersFollow;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isDismountVehicle() {
|
||||
return dismountVehicle;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isVehicleFollow() {
|
||||
return vehicleFollow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A map with case-insensitive String keys. All keys are stored in lower-case form.
|
||||
*
|
||||
* @param <T> the type of mapped values
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public class CaseInsensitiveStringMap<T> implements Map<String, T> {
|
||||
|
||||
private final Map<String, T> map;
|
||||
|
||||
public CaseInsensitiveStringMap() {
|
||||
map = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return map.containsKey(normalizeKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return map.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(Object key) {
|
||||
return map.get(normalizeKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable T put(String key, T value) {
|
||||
return map.put(normalizeKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(Object key) {
|
||||
return map.remove(normalizeKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(@NonNull Map<? extends String, ? extends T> m) {
|
||||
m.forEach((key, value) -> map.put(normalizeKey(key), value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Set<String> keySet() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Collection<T> values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Set<Entry<String, T>> entrySet() {
|
||||
return map.entrySet();
|
||||
}
|
||||
|
||||
private String normalizeKey(Object key) {
|
||||
return String.valueOf(key).toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
@@ -25,6 +26,7 @@ import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
|
||||
@@ -77,7 +79,7 @@ public final class FileUtils {
|
||||
*/
|
||||
public @Nullable File getServerProperties() {
|
||||
if (this.serverProperties == null) {
|
||||
this.serverProperties = findFileFromServerDirectory("server.properties");
|
||||
this.serverProperties = findFileFromServerDirectory(config.getServerPropertiesPath());
|
||||
Logging.finer("server.properties: %s", this.serverProperties);
|
||||
}
|
||||
return this.serverProperties;
|
||||
@@ -210,16 +212,19 @@ public final class FileUtils {
|
||||
|
||||
private final Path sourceDir;
|
||||
private final Path targetDir;
|
||||
private final List<String> excludeFiles;
|
||||
private final List<Path> excludeFiles;
|
||||
|
||||
private CopyDirFileVisitor(@NotNull Path sourceDir, @NotNull Path targetDir, @NotNull List<String> excludeFiles) {
|
||||
this.sourceDir = sourceDir;
|
||||
this.targetDir = targetDir;
|
||||
this.excludeFiles = excludeFiles;
|
||||
this.excludeFiles = excludeFiles.stream()
|
||||
.map(sourceDir::resolve)
|
||||
.toList();
|
||||
Logging.finest(this.excludeFiles.stream().map(Path::toString).collect(Collectors.joining(", ", "Exclude files: [", "]")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FileVisitResult preVisitDirectory(Path dir, @NotNull BasicFileAttributes attrs) throws IOException {
|
||||
public @NotNull FileVisitResult preVisitDirectory(@NonNull Path dir, @NotNull BasicFileAttributes attrs) throws IOException {
|
||||
Path newDir = targetDir.resolve(sourceDir.relativize(dir));
|
||||
if (!Files.isDirectory(newDir)) {
|
||||
Files.createDirectory(newDir);
|
||||
@@ -228,10 +233,10 @@ public final class FileUtils {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FileVisitResult visitFile(Path file, @NotNull BasicFileAttributes attrs) throws IOException {
|
||||
public @NotNull FileVisitResult visitFile(@NonNull Path file, @NotNull BasicFileAttributes attrs) throws IOException {
|
||||
// Pass files that are set to ignore
|
||||
if (excludeFiles.contains(file.getFileName().toString())) {
|
||||
Logging.finest("Ignoring file: " + file.getFileName());
|
||||
if (excludeFiles.contains(file)) {
|
||||
Logging.finest("Ignoring file: " + file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
// Copy the files
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* Utility class for formatting Minecraft time to real-world time format.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public final class MinecraftTimeFormatter {
|
||||
|
||||
private static final double TIME_MULTIPLIER = 3.6;
|
||||
private static final long DAY_SECONDS = 24 * 60 * 60;
|
||||
private static final long START_OFFSET = 6 * 60 * 60;
|
||||
|
||||
/**
|
||||
* Formats Minecraft time to 12-hour format.
|
||||
*
|
||||
* @param time The Minecraft time to format.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static String format12h(long time) {
|
||||
return formatTime(time, "hh:mm a");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats Minecraft time to 24-hour format.
|
||||
*
|
||||
* @param time The Minecraft time to format.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static String format24h(long time) {
|
||||
return formatTime(time, "HH:mm");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats Minecraft time to the specified format.
|
||||
* See <a href="https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html">DateTimeFormatter documentation</a>
|
||||
* for available patterns.
|
||||
*
|
||||
* @param time The Minecraft time to format.
|
||||
* @param format The format string for the time.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static String formatTime(long time, String format) {
|
||||
// Convert Minecraft time to real-world time
|
||||
long realTime = (long) ((time * TIME_MULTIPLIER) + START_OFFSET) % DAY_SECONDS; // Minecraft ticks to seconds
|
||||
|
||||
// Convert seconds to LocalTime
|
||||
LocalTime localTime = LocalTime.ofSecondOfDay(realTime);
|
||||
|
||||
return Try.of(() -> DateTimeFormatter.ofPattern(format))
|
||||
.map(localTime::format)
|
||||
.getOrElse("invalid time format: " + format);
|
||||
}
|
||||
|
||||
private MinecraftTimeFormatter() {
|
||||
// No instantiation
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import com.google.common.base.Strings;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -76,22 +78,33 @@ public final class PlayerFinder {
|
||||
public static @NotNull List<Player> getMulti(@Nullable String playerIdentifiers,
|
||||
@NotNull CommandSender sender
|
||||
) {
|
||||
List<Player> playerResults = new ArrayList<>();
|
||||
return tryGetMulti(playerIdentifiers, sender)
|
||||
.getOrElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple {@link Player} based on many identifiers of name UUID or selector.
|
||||
*
|
||||
* @param playerIdentifiers An identifier of multiple names, UUIDs or selectors, separated by comma.
|
||||
* @param sender Target sender for selector.
|
||||
* @return A list of all the {@link Player} found.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static @NotNull Try<@NotNull List<Player>> tryGetMulti(@Nullable String playerIdentifiers,
|
||||
@NotNull CommandSender sender
|
||||
) {
|
||||
if (playerIdentifiers == null || Strings.isNullOrEmpty(playerIdentifiers)) {
|
||||
return playerResults;
|
||||
return Try.success(Collections.emptyList());
|
||||
}
|
||||
|
||||
// TODO: Currently just assume entire string is a selector. Add support for comma seperated mixture of names, uuids and selectors
|
||||
if (isSelector(playerIdentifiers)) {
|
||||
Logging.finer("Using selector: %s", playerIdentifiers);
|
||||
List<Player> targetPlayers = getMultiBySelector(playerIdentifiers, sender);
|
||||
if (targetPlayers != null) {
|
||||
playerResults.addAll(targetPlayers);
|
||||
}
|
||||
return playerResults;
|
||||
return tryGetMultiBySelector(playerIdentifiers, sender);
|
||||
}
|
||||
|
||||
List<Player> playerResults = new ArrayList<>();
|
||||
String[] playerIdentifierArray = REPatterns.COMMA.split(playerIdentifiers);
|
||||
for (String playerIdentifier : playerIdentifierArray) {
|
||||
Player targetPlayer = getByName(playerIdentifier);
|
||||
@@ -104,15 +117,25 @@ public final class PlayerFinder {
|
||||
playerResults.add(targetPlayer);
|
||||
continue;
|
||||
}
|
||||
List<Player> targetPlayers = getMultiBySelector(playerIdentifier, sender);
|
||||
if (targetPlayers != null) {
|
||||
playerResults.addAll(targetPlayers);
|
||||
Try<@NotNull List<Player>> selectorParseResult = tryGetMultiBySelector(playerIdentifier, sender);
|
||||
if (selectorParseResult.isFailure()) {
|
||||
return Try.failure(selectorParseResult.getCause());
|
||||
}
|
||||
playerResults.addAll(selectorParseResult.getOrElse(Collections.emptyList()));
|
||||
}
|
||||
return playerResults;
|
||||
return Try.success(playerResults);
|
||||
}
|
||||
|
||||
private static boolean isSelector(@NotNull String playerIdentifier) {
|
||||
/**
|
||||
* Check if the player identifier is a selector.
|
||||
*
|
||||
* @param playerIdentifier An identifier of name, UUID or selector.
|
||||
* @return True if the identifier is a selector, else false.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static boolean isSelector(@NotNull String playerIdentifier) {
|
||||
return VANILLA_SELECTORS.stream().anyMatch(playerIdentifier::startsWith);
|
||||
}
|
||||
|
||||
@@ -167,7 +190,7 @@ public final class PlayerFinder {
|
||||
@NotNull CommandSender sender
|
||||
) {
|
||||
List<Player> matchedPlayers = getMultiBySelector(playerSelector, sender);
|
||||
if (matchedPlayers == null || matchedPlayers.isEmpty()) {
|
||||
if (matchedPlayers.isEmpty()) {
|
||||
Logging.fine("No player found with selector '%s' for %s.", playerSelector, sender.getName());
|
||||
return null;
|
||||
}
|
||||
@@ -186,22 +209,36 @@ public final class PlayerFinder {
|
||||
* @param sender Target sender for selector.
|
||||
* @return A list of all the {@link Player} found.
|
||||
*/
|
||||
public static @Nullable List<Player> getMultiBySelector(@NotNull String playerSelector,
|
||||
public static @NotNull List<Player> getMultiBySelector(@NotNull String playerSelector,
|
||||
@NotNull CommandSender sender
|
||||
) {
|
||||
return tryGetMultiBySelector(playerSelector, sender)
|
||||
.onFailure(throwable -> Logging.warning(
|
||||
"Error selecting entities with selector '%s' for %s: %s",
|
||||
playerSelector, sender.getName(), throwable.getMessage()
|
||||
))
|
||||
.getOrElse(Collections::emptyList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple {@link Player} based on <a href="https://minecraft.gamepedia.com/Commands#Target_selectors">vanilla selectors</a>.
|
||||
*
|
||||
* @param playerSelector A target selector, usually starts with an '@'.
|
||||
* @param sender Target sender for selector.
|
||||
* @return A list of all the {@link Player} found.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static @NotNull Try<@NotNull List<Player>> tryGetMultiBySelector(@NotNull String playerSelector,
|
||||
@NotNull CommandSender sender
|
||||
) {
|
||||
if (playerSelector.charAt(0) != '@') {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Bukkit.selectEntities(sender, playerSelector).stream()
|
||||
.filter(e -> e instanceof Player)
|
||||
.map(e -> ((Player) e))
|
||||
.collect(Collectors.toList());
|
||||
} catch (IllegalArgumentException e) {
|
||||
Logging.warning("An error occurred while parsing selector '%s' for %s. Is it is the correct format?",
|
||||
playerSelector, sender.getName());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
return Try.success(Collections.emptyList());
|
||||
}
|
||||
return Try.of(() -> Bukkit.selectEntities(sender, playerSelector).stream()
|
||||
.filter(e -> e instanceof Player)
|
||||
.map(e -> ((Player) e))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -16,7 +18,17 @@ public class REPatterns {
|
||||
public static final Pattern COMMA = get(",");
|
||||
public static final Pattern DOT = get("\\.");
|
||||
public static final Pattern EQUALS = get("=");
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static final Pattern HYPHEN = get("-");
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static final Pattern NAMESPACE = get("^[a-z0-9_.-]+$");
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static final Pattern NAMESPACE_KEY = get("^[a-z0-9/_.-]+$");
|
||||
public static final Pattern SEMICOLON = get(";");
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static final Pattern SPACE = get(" ");
|
||||
public static final Pattern UNDERSCORE = get("_");
|
||||
public static final Pattern UUID = get("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
public static final Pattern YAML_KEY = get("^[a-zA-Z0-9\\[\\]_-]+$");
|
||||
}
|
||||
@@ -2,13 +2,17 @@ package org.mvplugins.multiverse.core.utils;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -30,15 +34,15 @@ public final class StringFormatter {
|
||||
* @param list the list of strings to join. If the list is empty, an empty string is returned.
|
||||
* @return the concatenated string
|
||||
*/
|
||||
public static @NotNull String joinAnd(List<String> list) {
|
||||
public static @NotNull String joinAnd(@Nullable List<String> list) {
|
||||
return join(list, ", ", " and ");
|
||||
}
|
||||
|
||||
public static @NotNull String join(Collection list, String separator) {
|
||||
public static @NotNull String join(@Nullable Collection<?> list, @NotNull String separator) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return list.stream().map(String::valueOf).collect(Collectors.joining(separator)).toString();
|
||||
return list.stream().map(String::valueOf).collect(Collectors.joining(separator));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +54,7 @@ public final class StringFormatter {
|
||||
* @param lastSeparator the separator to use before the last element. For example, " and ".
|
||||
* @return the concatenated string
|
||||
*/
|
||||
public static @NotNull String join(List<String> list, String separator, String lastSeparator) {
|
||||
public static @NotNull String join(@Nullable List<String> list, @NotNull String separator, @NotNull String lastSeparator) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
@@ -71,11 +75,30 @@ public final class StringFormatter {
|
||||
|
||||
/**
|
||||
* Appends a list of suggestions to the end of the input string, separated by commas.
|
||||
*
|
||||
* @param input The current input
|
||||
* @param addons The autocomplete suggestions
|
||||
* @return A collection of suggestions with the next suggestion appended
|
||||
*
|
||||
* @deprecated Method name has a spelling error. Use {@link #addOnToCommaSeparated(String, Collection)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.5")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
public static Collection<String> addonToCommaSeperated(@Nullable String input, @NotNull Collection<String> addons) {
|
||||
return addOnToCommaSeparated(input, addons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a list of suggestions to the end of the input string, separated by commas.
|
||||
*
|
||||
* @param input The current input
|
||||
* @param addons The autocomplete suggestions
|
||||
* @return A collection of suggestions with the next suggestion appended
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public static Collection<String> addOnToCommaSeparated(@Nullable String input, @NotNull Collection<String> addons) {
|
||||
if (Strings.isNullOrEmpty(input)) {
|
||||
return addons;
|
||||
}
|
||||
@@ -94,7 +117,7 @@ public final class StringFormatter {
|
||||
* @param args The args to parse
|
||||
* @return The parsed args
|
||||
*/
|
||||
public static Collection<String> parseQuotesInArgs(String[] args) {
|
||||
public static @NotNull Collection<String> parseQuotesInArgs(@NotNull String[] args) {
|
||||
List<String> result = new ArrayList<>(args.length);
|
||||
StringBuilder current = new StringBuilder();
|
||||
boolean inQuotes = false;
|
||||
@@ -139,7 +162,28 @@ public final class StringFormatter {
|
||||
* @param input The string to add quotes to
|
||||
* @return The quoted string
|
||||
*/
|
||||
public static String quoteMultiWordString(String input) {
|
||||
return input.contains(" ") ? "\"" + input + "\"" : input;
|
||||
@Contract("null -> null")
|
||||
public static @Nullable String quoteMultiWordString(@Nullable String input) {
|
||||
return input != null && input.contains(" ") ? "\"" + input + "\"" : input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a CSV string of key=value pairs into a map.
|
||||
* E.g. "key1=value1,key2=value2" -> {key1=value1, key2=value2}
|
||||
*
|
||||
* @param input The CSV string to parse
|
||||
* @return The parsed map
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.5")
|
||||
public static @Unmodifiable Map<String, String> parseCSVMap(@Nullable String input) {
|
||||
if (Strings.isNullOrEmpty(input)) {
|
||||
return Map.of();
|
||||
}
|
||||
return REPatterns.COMMA.splitAsStream(input)
|
||||
.map(s -> REPatterns.EQUALS.split(s, 2))
|
||||
.filter(parts -> parts.length == 2)
|
||||
.collect(Collectors.toUnmodifiableMap(parts -> parts[0], parts -> parts[1]));
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package org.mvplugins.multiverse.core.utils.compatibility;
|
||||
|
||||
import io.vavr.control.Option;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.utils.ReflectHelper;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Compatibility class used to handle API changes in {@link Bukkit} class.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public final class BukkitCompatibility {
|
||||
|
||||
private static final Option<Method> GET_LEVEL_DIRECTORY_METHOD;
|
||||
private static final Option<Method> GET_WORLD_NAMESPACED_KEY_METHOD;
|
||||
|
||||
static {
|
||||
GET_LEVEL_DIRECTORY_METHOD = Option.of(ReflectHelper.getMethod(Server.class, "getLevelDirectory"));
|
||||
GET_WORLD_NAMESPACED_KEY_METHOD = Option.of(ReflectHelper.getMethod(Bukkit.class, "getWorld", NamespacedKey.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the server is using the new dimension storage system introduced in PaperMC 26.1.
|
||||
* <br />
|
||||
* This is based of whether getLevelDirectory method exists in the server class,which is the main API change for
|
||||
* the new dimension storage system.
|
||||
*
|
||||
* @return True if the server is using the new dimension storage system, else false.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
public static boolean isUsingNewDimensionStorage() {
|
||||
return GET_LEVEL_DIRECTORY_METHOD
|
||||
.flatMap(method -> Option.of(ReflectHelper.invokeMethod(Bukkit.getServer(), method)))
|
||||
.isDefined();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the folder where all the worlds will be store. Before 26.1, all worlds are stored in the root directory
|
||||
* of the server, which can be obtained by {@link Server#getWorldContainer()}.
|
||||
* <br />
|
||||
* After 26.1, PaperMC changed all worlds are stored in the "[level]/dimensions/minecraft" folder under the world
|
||||
* level directory, which needs to be manually parsed.
|
||||
*
|
||||
* @return The location where all the worlds folders should be, depending on server's mc version.
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
@NotNull
|
||||
public static Path getWorldFoldersDirectory() {
|
||||
Server server = Bukkit.getServer();
|
||||
return GET_LEVEL_DIRECTORY_METHOD.map(method -> ReflectHelper.invokeMethod(server, method))
|
||||
.filter(Path.class::isInstance)
|
||||
.map(Path.class::cast)
|
||||
.map(path -> path.resolve("dimensions/minecraft"))
|
||||
.getOrElse(() -> server.getWorldContainer().toPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the world with the given name or namespaced key (e.g. minecraft:overworld) exists,
|
||||
* and return it if it does.
|
||||
* <br />
|
||||
* Note that some default world names have different namespaced key matched with them.
|
||||
* E.g.: world -> minecraft:overworld, world_nether -> minecraft:the_nether, world_the_end -> minecraft:the_end.
|
||||
*
|
||||
* @param nameOrKey Either a name or namespaced key string representation.
|
||||
* @return The world if it exists
|
||||
*
|
||||
* @since 5.6
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.6")
|
||||
@NotNull
|
||||
public static Option<World> getWorldByNameOrKey(@NotNull String nameOrKey) {
|
||||
return Option.of(Bukkit.getWorld(nameOrKey))
|
||||
.orElse(() -> GET_WORLD_NAMESPACED_KEY_METHOD
|
||||
.map(method -> ReflectHelper.invokeMethod(null, method, NamespacedKey.fromString(nameOrKey)))
|
||||
.filter(World.class::isInstance)
|
||||
.map(World.class::cast));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Contains classes to handle compatibility due to differing API methods across different server versions and the
|
||||
* API gaps between PaperMC and Spigot.
|
||||
*/
|
||||
package org.mvplugins.multiverse.core.utils.compatibility;
|
||||
Loaded 100 of 162 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user