mirror of
https://github.com/Multiverse/Multiverse-Core.git
synced 2026-11-04 08:42:11 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34185b90a9 | ||
|
|
5d57bbfda9 | ||
|
|
a78459ab77 | ||
|
|
0ad795b27f | ||
|
|
2765cf6f3e | ||
|
|
7a9b21f9a0 | ||
|
|
f55ceda42c | ||
|
|
893c69a6e1 | ||
|
|
6bb93766d4 | ||
|
|
fe49efe276 | ||
|
|
8de11b59a1 | ||
|
|
744450cbbb | ||
|
|
ecf107563d | ||
|
|
f7ea31d949 | ||
|
|
1bf4b99ecf | ||
|
|
4de4e5a68e | ||
|
|
f6a8a6b3b4 | ||
|
|
d485238c56 | ||
|
|
5234445746 | ||
|
|
0b626d9eaf | ||
|
|
2ff321e475 | ||
|
|
58dbe55be4 | ||
|
|
a2045b52bd | ||
|
|
396ece4d27 | ||
|
|
e25ac0e531 | ||
|
|
7cf53ee7a8 | ||
|
|
4a46c13754 | ||
|
|
5449ade6f3 | ||
|
|
a9f8deeb01 | ||
|
|
1ce7eb6d3e | ||
|
|
d11d664020 | ||
|
|
abd86f81e5 | ||
|
|
0faec79250 | ||
|
|
9264f5e2ce | ||
|
|
892ce24845 | ||
|
|
091eec96b9 | ||
|
|
37ecceb179 | ||
|
|
7697065519 | ||
|
|
ad7b9638f1 | ||
|
|
37ba5ecc04 | ||
|
|
2445464ab8 | ||
|
|
7fad9aa2e4 | ||
|
|
a8c0b221cd | ||
|
|
826fe4c6d0 | ||
|
|
28240c3649 | ||
|
|
c8e4c3239a | ||
|
|
b23735dc72 | ||
|
|
97cbb4576d | ||
|
|
1ccfa2b24b | ||
|
|
d50cd6cce5 | ||
|
|
b500956555 | ||
|
|
de80667bb4 | ||
|
|
68bbb6ab90 | ||
|
|
16738f181a | ||
|
|
9dddbdcd97 | ||
|
|
0c829f5a4e | ||
|
|
6f91d290db | ||
|
|
cf65076f69 | ||
|
|
25daacadba | ||
|
|
8d9daa4bdf | ||
|
|
d2c35a029d | ||
|
|
61f4d2308c | ||
|
|
767b7af5e1 | ||
|
|
3e46f198f5 | ||
|
|
9f06740873 | ||
|
|
5648211722 | ||
|
|
ed96764474 | ||
|
|
15453e6443 | ||
|
|
0df35f0e26 | ||
|
|
0424ac81bd | ||
|
|
4ee6901df1 | ||
|
|
f6b3ed0f8b | ||
|
|
afce49b8d8 | ||
|
|
b9271195e9 | ||
|
|
c76faf0353 | ||
|
|
a27b2186c5 | ||
|
|
97d4f6cea2 | ||
|
|
e4f3b4612c | ||
|
|
5c822340c9 | ||
|
|
665bd1c9ae | ||
|
|
2bc3965283 | ||
|
|
54dd5bfb34 | ||
|
|
c2c1cc4f0a | ||
|
|
b8364a00b5 | ||
|
|
6e0ebf752b | ||
|
|
ebb809f899 | ||
|
|
8b1aea5d99 | ||
|
|
4f92268ba2 | ||
|
|
39ac80be2c | ||
|
|
5f43d756e5 | ||
|
|
50efb7e262 | ||
|
|
142ef02ca0 | ||
|
|
54900f55a9 | ||
|
|
dbc91e401c | ||
|
|
e32b478cef | ||
|
|
e82bc005f2 | ||
|
|
504f17eb1a | ||
|
|
a54a3b6152 | ||
|
|
0fdc74ab56 | ||
|
|
e29b851c61 | ||
|
|
534f2743c4 | ||
|
|
c486f0306e | ||
|
|
392d66ac85 | ||
|
|
6d88c6c8c4 | ||
|
|
2e6dc23b1b | ||
|
|
bf05abc3ca | ||
|
|
d65cde74e0 | ||
|
|
357532d500 | ||
|
|
a59ec16737 | ||
|
|
cab6358f03 | ||
|
|
5ad47da967 | ||
|
|
603ab5bc0e | ||
|
|
5e7637bc42 | ||
|
|
bc07b24360 | ||
|
|
5451e55f17 | ||
|
|
fd8d0261aa | ||
|
|
a7d067222a | ||
|
|
0b18c8d832 | ||
|
|
7937d889da | ||
|
|
867462b6ed | ||
|
|
ed923491b5 | ||
|
|
dfeb72e501 | ||
|
|
35133ee647 | ||
|
|
3e62084b23 | ||
|
|
944acd8387 | ||
|
|
9460f0b2b1 | ||
|
|
9ed58375d4 | ||
|
|
f470df6f2c | ||
|
|
c291df201f | ||
|
|
58402b010d | ||
|
|
8060514e47 | ||
|
|
ac86b691ba | ||
|
|
ee40eb5822 | ||
|
|
3052489952 | ||
|
|
a6cbe44411 | ||
|
|
65db598fd8 | ||
|
|
272e118eed | ||
|
|
9643cce272 | ||
|
|
372b600d68 | ||
|
|
c9db931ad5 | ||
|
|
8a1bbebb0b | ||
|
|
192ee269fa | ||
|
|
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 | ||
|
|
3356724481 | ||
|
|
8dcdf68b9f | ||
|
|
ae0f25a0f8 | ||
|
|
148f513bc2 | ||
|
|
29f60bcde5 | ||
|
|
e9ddb24706 | ||
|
|
71e815a9bd | ||
|
|
a55ce680df | ||
|
|
3e47460c3b | ||
|
|
abd6983eee | ||
|
|
ce263b5b1f | ||
|
|
7f15a28a69 | ||
|
|
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
|
||||
@@ -0,0 +1,77 @@
|
||||
name: 'Generic: Artifact Comment'
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
plugin_name:
|
||||
description: 'Plugin name used by Generic: Test'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
comment-artifact:
|
||||
if: >-
|
||||
github.event_name == 'workflow_run' &&
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
# Run separately so fork builds can post comments without a write token.
|
||||
# Do not check out or execute code from the pull request in this workflow.
|
||||
- name: Find pull request and artifact
|
||||
id: artifact
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
PLUGIN_NAME: ${{ inputs.plugin_name }}
|
||||
with:
|
||||
script: |
|
||||
const run = context.payload.workflow_run;
|
||||
// Fork runs can have an empty pull_requests array in the event.
|
||||
const pulls = await github.paginate(github.rest.pulls.list, {
|
||||
...context.repo,
|
||||
state: 'open',
|
||||
head: `${run.head_repository.owner.login}:${run.head_branch}`,
|
||||
per_page: 100,
|
||||
});
|
||||
const pull = pulls.find(pr =>
|
||||
pr.head.repo?.id === run.head_repository.id &&
|
||||
pr.head.ref === run.head_branch &&
|
||||
pr.head.sha === run.head_sha
|
||||
);
|
||||
if (!pull) {
|
||||
core.info('No open pull request matches this build; skipping comment.');
|
||||
return;
|
||||
}
|
||||
|
||||
const artifacts = await github.paginate(github.rest.actions.listWorkflowRunArtifacts, {
|
||||
...context.repo,
|
||||
run_id: run.id,
|
||||
per_page: 100,
|
||||
});
|
||||
const artifact = artifacts.find(item =>
|
||||
item.name === `${process.env.PLUGIN_NAME}-pr${pull.number}` && !item.expired
|
||||
);
|
||||
if (!artifact) {
|
||||
core.info('No build artifact is available; skipping comment.');
|
||||
return;
|
||||
}
|
||||
|
||||
core.setOutput('number', pull.number);
|
||||
core.setOutput('url', `${run.html_url}/artifacts/${artifact.id}`);
|
||||
|
||||
- name: Comment artifact
|
||||
if: steps.artifact.outputs.number != ''
|
||||
uses: marocchino/sticky-pull-request-comment@v3
|
||||
with:
|
||||
number: ${{ steps.artifact.outputs.number }}
|
||||
header: build-artifact-${{ inputs.plugin_name }}
|
||||
message: |
|
||||
### ✅ `${{ inputs.plugin_name }}` test build ready
|
||||
|
||||
Last updated for: [${{ github.event.workflow_run.head_sha }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.event.workflow_run.head_sha }})
|
||||
**Download:** [${{ inputs.plugin_name }}-pr${{ steps.artifact.outputs.number }}.zip](${{ steps.artifact.outputs.url }})
|
||||
|
||||
View [CI build and checks](${{ github.event.workflow_run.html_url }}) for more details.
|
||||
@@ -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.3, 26.2, 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.3, 26.2, 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.3"]}'
|
||||
plugin_dependencies: ${{ inputs.hangar_plugin_dependencies }}
|
||||
@@ -36,13 +36,3 @@ jobs:
|
||||
with:
|
||||
name: ${{ inputs.plugin_name }}-pr${{ github.event.pull_request.number }}
|
||||
path: build/libs/${{ inputs.plugin_name }}-pr${{ github.event.pull_request.number }}.jar
|
||||
|
||||
comment-artifact:
|
||||
needs: test-and-artifact
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: benwoo1110/artifact-comment-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,15 @@
|
||||
name: 'PR: Artifact Comment'
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['PR: Test']
|
||||
types: [completed]
|
||||
|
||||
jobs:
|
||||
comment-artifact:
|
||||
permissions:
|
||||
actions: read
|
||||
pull-requests: write
|
||||
uses: ./.github/workflows/generic.artifact_comment.yml
|
||||
with:
|
||||
plugin_name: multiverse-core
|
||||
@@ -46,3 +46,6 @@ debug.txt
|
||||
|
||||
# Gradle
|
||||
.gradle
|
||||
|
||||
# Kotlin generated files
|
||||
/.kotlin/
|
||||
@@ -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!
|
||||
|
||||
|
||||
+15
-15
@@ -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'
|
||||
@@ -9,13 +9,13 @@ description = 'Multiverse-Core'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = 'aikar repo'
|
||||
url = uri('https://repo.aikar.co/content/groups/aikar/')
|
||||
name = 'jitpack repo'
|
||||
url = uri('https://jitpack.io')
|
||||
}
|
||||
|
||||
maven {
|
||||
name = 'glaremasters repo'
|
||||
url = 'https://repo.glaremasters.me/repository/towny/'
|
||||
name = 'aikar repo'
|
||||
url = uri('https://repo.aikar.co/content/groups/aikar/')
|
||||
}
|
||||
|
||||
maven {
|
||||
@@ -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.110.0'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -37,31 +37,31 @@ dependencies {
|
||||
}
|
||||
|
||||
// PlaceholderAPI
|
||||
externalPlugin 'me.clip:placeholderapi:2.11.6'
|
||||
externalPlugin 'me.clip:placeholderapi:2.12.3'
|
||||
|
||||
// Command Framework
|
||||
shadowed 'co.aikar:acf-paper:0.5.1-SNAPSHOT'
|
||||
|
||||
// Config
|
||||
shadowed('io.github.townyadvanced.commentedconfiguration:CommentedConfiguration:1.0.1') {
|
||||
shadowed('com.github.TownyAdvanced:CommentedConfiguration:1.0.3') {
|
||||
exclude group: 'org.spigotmc', module: 'spigot-api'
|
||||
}
|
||||
|
||||
// Utils
|
||||
shadowed 'io.vavr:vavr:0.10.4'
|
||||
shadowed 'org.glassfish.hk2:hk2-locator:3.0.3'
|
||||
shadowed('org.glassfish.hk2:hk2-inhabitant-generator:3.0.3') {
|
||||
shadowed 'io.vavr:vavr:1.0.1'
|
||||
shadowed 'org.glassfish.hk2:hk2-locator:3.1.1'
|
||||
shadowed('org.glassfish.hk2:hk2-inhabitant-generator:3.1.1') {
|
||||
exclude group: 'org.apache.maven', module: 'maven-core'
|
||||
}
|
||||
shadowed('com.dumptruckman.minecraft:Logging:1.1.1') {
|
||||
exclude group: 'junit', module: 'junit'
|
||||
}
|
||||
shadowed 'de.themoep.idconverter:mappings:1.2-SNAPSHOT'
|
||||
shadowed('org.bstats:bstats-bukkit:3.1.0') {
|
||||
shadowed('org.bstats:bstats-bukkit:3.2.1') {
|
||||
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.6.0'
|
||||
shadowed 'org.jetbrains:annotations:26.1.0'
|
||||
shadowed 'io.papermc:paperlib:1.0.8'
|
||||
|
||||
// Tests
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.mvplugins.multiverse.core;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
@@ -10,7 +11,7 @@ import jakarta.inject.Inject;
|
||||
import org.apache.commons.lang3.text.WordUtils;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.AdvancedPie;
|
||||
import org.bstats.charts.MultiLineChart;
|
||||
import org.bstats.charts.SingleLineChart;
|
||||
import org.bukkit.World;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
@@ -79,18 +80,15 @@ final class BstatsMetricsConfigurator {
|
||||
}
|
||||
|
||||
private void addWorldCountMetric() {
|
||||
addMultiLineMetric("world_count", map -> {
|
||||
map.put("Loaded worlds", worldManager.getLoadedWorlds().size());
|
||||
map.put("Total number of worlds", worldManager.getWorlds().size());
|
||||
});
|
||||
addLineMetric("worlds", () -> worldManager.getWorlds().size());
|
||||
}
|
||||
|
||||
private void addAdvancedPieMetric(String chartId, Consumer<Map<String, Integer>> metricsFunc) {
|
||||
metrics.addCustomChart(createAdvancedPieChart(chartId, metricsFunc));
|
||||
}
|
||||
|
||||
private void addMultiLineMetric(String chartId, Consumer<Map<String, Integer>> metricsFunc) {
|
||||
metrics.addCustomChart(createMultiLineChart(chartId, metricsFunc));
|
||||
private void addLineMetric(String chartId, Callable<Integer> metricsFunc) {
|
||||
metrics.addCustomChart(createLineChart(chartId, metricsFunc));
|
||||
}
|
||||
|
||||
private void incrementCount(Map<String, Integer> map, String key) {
|
||||
@@ -104,9 +102,7 @@ final class BstatsMetricsConfigurator {
|
||||
return new AdvancedPie(chartId, () -> map);
|
||||
}
|
||||
|
||||
private MultiLineChart createMultiLineChart(String chartId, Consumer<Map<String, Integer>> metricsFunc) {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
metricsFunc.accept(map);
|
||||
return new MultiLineChart(chartId, () -> map);
|
||||
private SingleLineChart createLineChart(String chartId, Callable<Integer> metricsFunc) {
|
||||
return new SingleLineChart(chartId, metricsFunc);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -29,6 +30,7 @@ import org.mvplugins.multiverse.core.inject.PluginServiceLocatorFactory;
|
||||
import org.mvplugins.multiverse.core.module.MultiverseModule;
|
||||
import org.mvplugins.multiverse.core.utils.StringFormatter;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.entity.SpawnCategoryMapper;
|
||||
import org.mvplugins.multiverse.core.world.location.NullSpawnLocation;
|
||||
import org.mvplugins.multiverse.core.world.location.SpawnLocation;
|
||||
import org.mvplugins.multiverse.core.world.location.UnloadedWorldLocation;
|
||||
@@ -96,12 +98,16 @@ public class MultiverseCore extends MultiverseModule {
|
||||
}
|
||||
Logging.setShowingConfig(shouldShowConfig());
|
||||
|
||||
// Build it here so our logger can work, and failure messages will be logged
|
||||
SpawnCategoryMapper.buildSpawnCategoryMap();
|
||||
|
||||
// Initialize the worlds
|
||||
worldManagerProvider.get().initAllWorlds().andThenTry(() -> {
|
||||
Try.run(() -> {
|
||||
setUpLocales();
|
||||
worldManagerProvider.get().initAllWorlds();
|
||||
loadEconomist(); // Setup economy here so vault is loaded
|
||||
loadAnchors();
|
||||
registerDynamicListeners(CoreListener.class);
|
||||
setUpLocales();
|
||||
registerCommands(CoreCommand.class);
|
||||
registerDestinations();
|
||||
setupMetrics();
|
||||
@@ -123,9 +129,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 +220,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("Version %s (API v%s) Enabled - By %s",
|
||||
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("Help dumptruckman keep this project alive. Become a patron! https://www.patreon.com/dumptruckman");
|
||||
Logging.config("One time donations are also appreciated: https://www.paypal.me/dumptruckman");
|
||||
Logging.config("\u001B[32mLoving Multiverse-Core? Please consider supporting the project with a " +
|
||||
"small donation: https://github.com/sponsors/Multiverse\u001B[0m");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +244,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();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
package org.mvplugins.multiverse.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.ServicePriority;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.anchor.AnchorManager;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
@@ -15,14 +24,13 @@ 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.Objects;
|
||||
|
||||
/**
|
||||
* Provides access to the MultiverseCore API.
|
||||
*/
|
||||
public class MultiverseCoreApi {
|
||||
public final class MultiverseCoreApi {
|
||||
|
||||
private static MultiverseCoreApi instance;
|
||||
private static final List<Consumer<MultiverseCoreApi>> WHEN_LOADED_CALLBACKS = new ArrayList<>();
|
||||
|
||||
static void init(@NotNull MultiverseCore multiverseCore) {
|
||||
if (instance != null) {
|
||||
@@ -30,17 +38,72 @@ public class MultiverseCoreApi {
|
||||
}
|
||||
instance = new MultiverseCoreApi(multiverseCore.getServiceLocator());
|
||||
Bukkit.getServicesManager().register(MultiverseCoreApi.class, instance, multiverseCore, ServicePriority.Normal);
|
||||
|
||||
List<Consumer<MultiverseCoreApi>> callbacks = List.copyOf(WHEN_LOADED_CALLBACKS);
|
||||
WHEN_LOADED_CALLBACKS.clear();
|
||||
MultiverseCoreApi loadedApi = instance;
|
||||
callbacks.forEach(callback -> runLoadCallback(callback, loadedApi));
|
||||
}
|
||||
|
||||
private static void runLoadCallback(
|
||||
@NotNull Consumer<MultiverseCoreApi> callback,
|
||||
@NotNull MultiverseCoreApi loadedApi) {
|
||||
Try.run(() -> callback.accept(loadedApi))
|
||||
.onFailure(exception -> Logging.warning(
|
||||
"A Multiverse-Core API load callback failed: %s", exception.getMessage()));
|
||||
}
|
||||
|
||||
static void shutdown() {
|
||||
if (instance == null) {
|
||||
return;
|
||||
}
|
||||
Bukkit.getServicesManager().unregister(instance);
|
||||
instance = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook your plugin into the MultiverseCoreApi here to ensure you only start using the API after it has been initialized.
|
||||
* Use this if you know your plugin may load before Multiverse-Core is fully initialized.
|
||||
* <br/>
|
||||
* This handy method removes the need for you to check with plugin manager or listen to plugin enable event.
|
||||
* <br/>
|
||||
* Callback will be called immediately if the MultiverseCoreApi has already been initialized.
|
||||
*
|
||||
* @param consumer The callback to execute when the MultiverseCoreApi has been initialized.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
public static void whenLoaded(@NotNull Consumer<MultiverseCoreApi> consumer) {
|
||||
if (instance != null) {
|
||||
consumer.accept(instance);
|
||||
} else {
|
||||
WHEN_LOADED_CALLBACKS.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,124 @@ 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(
|
||||
StringFormatter.parseQuotesInArgs(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());
|
||||
}
|
||||
|
||||
// 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 "key" -> String.valueOf(world.getKey());
|
||||
case "meta" -> {
|
||||
if (placeholderParams.isEmpty()) {
|
||||
warning("No meta key specified.");
|
||||
yield null;
|
||||
}
|
||||
String metaKey = String.join("_", placeholderParams);
|
||||
yield world.getMeta(metaKey)
|
||||
.getOrElse(() -> {
|
||||
warning("Meta key '" + metaKey + "' not found for world '" + world.getName() + "'.");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
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());
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.mvplugins.multiverse.core.command;
|
||||
|
||||
import co.aikar.commands.CommandCompletionContext;
|
||||
import co.aikar.commands.CommandCompletionFilter;
|
||||
import co.aikar.commands.apachecommonslang.ApacheCommonsLangUtil;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utility filters for command completion matching.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public final class MVCommandCompletionFilters {
|
||||
|
||||
/**
|
||||
* Matches namespaced keys (for example, {@code minecraft:stone}) using namespace-aware checks.
|
||||
* <p>
|
||||
* This filter accepts completions that:
|
||||
* <ul>
|
||||
* <li>start with the current input,</li>
|
||||
* <li>have a namespace starting with the input, or</li>
|
||||
* <li>have a key value containing the input.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static final CommandCompletionFilter NAMESPACED_KEY = (context, completion) -> {
|
||||
String[] split = completion.split(":", 2);
|
||||
if (split.length < 2) {
|
||||
return ApacheCommonsLangUtil.startsWithIgnoreCase(completion, context.getInput());
|
||||
}
|
||||
String lowerCase = context.getInput().toLowerCase(Locale.ROOT);
|
||||
return ApacheCommonsLangUtil.startsWithIgnoreCase(completion, context.getInput())
|
||||
|| split[0].toLowerCase(Locale.ROOT).startsWith(lowerCase)
|
||||
|| split[1].toLowerCase(Locale.ROOT).contains(lowerCase);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the namespaced-key completion filter with a generic type-safe signature.
|
||||
* Use this method instead of {@link MVCommandCompletionFilters#NAMESPACED_KEY}
|
||||
* to avoid raw type unchecked warnings.
|
||||
*
|
||||
* @param <C> completion context type
|
||||
* @return the namespaced key filter
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static <C extends CommandCompletionContext> CommandCompletionFilter<C> namespacedKey() {
|
||||
return NAMESPACED_KEY;
|
||||
}
|
||||
|
||||
private MVCommandCompletionFilters() {
|
||||
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,12 @@ 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;
|
||||
|
||||
import co.aikar.commands.BukkitCommandCompletionContext;
|
||||
import co.aikar.commands.CommandCompletionFilter;
|
||||
import co.aikar.commands.CommandIssuer;
|
||||
import co.aikar.commands.PaperCommandCompletions;
|
||||
import co.aikar.commands.RegisteredCommand;
|
||||
@@ -26,6 +28,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 +36,26 @@ 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 org.mvplugins.multiverse.core.world.helpers.PotentialWorldFinder;
|
||||
|
||||
import static org.mvplugins.multiverse.core.utils.StringFormatter.addonToCommaSeperated;
|
||||
import static org.mvplugins.multiverse.core.utils.StringFormatter.addOnToCommaSeparated;
|
||||
|
||||
@Service
|
||||
public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
@@ -60,6 +67,7 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
private final CorePermissionsChecker corePermissionsChecker;
|
||||
private final AnchorManager anchorManager;
|
||||
private final GeneratorProvider generatorProvider;
|
||||
private final PotentialWorldFinder potentialWorldFinder;
|
||||
|
||||
@Inject
|
||||
MVCommandCompletions(
|
||||
@@ -69,7 +77,8 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
@NotNull CoreConfig config,
|
||||
@NotNull CorePermissionsChecker corePermissionsChecker,
|
||||
@NotNull AnchorManager anchorManager,
|
||||
@NotNull GeneratorProvider generatorProvider
|
||||
@NotNull GeneratorProvider generatorProvider,
|
||||
@NotNull PotentialWorldFinder potentialWorldFinder
|
||||
) {
|
||||
super(mvCommandManager);
|
||||
this.commandManager = mvCommandManager;
|
||||
@@ -79,27 +88,29 @@ public class MVCommandCompletions extends PaperCommandCompletions {
|
||||
this.corePermissionsChecker = corePermissionsChecker;
|
||||
this.anchorManager = anchorManager;
|
||||
this.generatorProvider = generatorProvider;
|
||||
this.potentialWorldFinder = potentialWorldFinder;
|
||||
|
||||
registerAsyncCompletion("anchornames", this::suggestAnchorNames);
|
||||
registerAsyncCompletion("anchornames", this::suggestAnchorNames, CommandCompletionFilter.contains());
|
||||
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);
|
||||
registerStaticCompletion("gamerules", this::suggestGamerules, MVCommandCompletionFilters.namespacedKey());
|
||||
registerAsyncCompletion("gamerulesvalues", this::suggestGamerulesValues);
|
||||
registerAsyncCompletion("generatorplugins", this::suggestGeneratorPlugins);
|
||||
registerStaticCompletion("materials", suggestEnums(Material.class));
|
||||
registerStaticCompletion("mvconfigs", config.getStringPropertyHandle().getAllPropertyNames());
|
||||
registerAsyncCompletion("generatorplugins", this::suggestGeneratorPlugins, CommandCompletionFilter.contains());
|
||||
registerStaticCompletion("materials", suggestEnums(Material.class), CommandCompletionFilter.contains());
|
||||
registerStaticCompletion("mvconfigs", config.getStringPropertyHandle().getAllPropertyNames(), CommandCompletionFilter.contains());
|
||||
registerAsyncCompletion("mvconfigvalues", this::suggestMVConfigValues);
|
||||
registerAsyncCompletion("mvworlds", this::suggestMVWorlds);
|
||||
registerAsyncCompletion("mvworldpropsname", this::suggestMVWorldPropsName);
|
||||
registerAsyncCompletion("mvworlds", this::suggestMVWorlds, MVCommandCompletionFilters.namespacedKey());
|
||||
registerAsyncCompletion("mvworldmetakey", this::suggestMVWorldMetaKey, CommandCompletionFilter.contains());
|
||||
registerAsyncCompletion("mvworldpropsname", this::suggestMVWorldPropsName, CommandCompletionFilter.contains());
|
||||
registerAsyncCompletion("mvworldpropsvalue", this::suggestMVWorldPropsValue);
|
||||
registerAsyncCompletion("playersarray", this::suggestPlayersArray);
|
||||
registerCompletion("playersarray", this::suggestPlayersArray, CommandCompletionFilter.contains()); // getting online players cannot be async
|
||||
registerStaticCompletion("propsmodifyaction", suggestEnums(PropertyModifyAction.class));
|
||||
registerStaticCompletion("spawncategories", suggestEnums(SpawnCategory.class));
|
||||
registerAsyncCompletion("spawncategorypropsname", this::suggestSpawnCategoryPropsName);
|
||||
registerAsyncCompletion("spawncategorypropsname", this::suggestSpawnCategoryPropsName, CommandCompletionFilter.contains());
|
||||
registerAsyncCompletion("spawncategorypropsvalue", this::suggestSpawnCategoryPropsValue);
|
||||
|
||||
setDefaultCompletion("destinations", DestinationInstance.class);
|
||||
@@ -129,6 +140,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 +169,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 +184,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 +222,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 +280,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,32 +290,36 @@ 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" -> {
|
||||
return worldManager.getPotentialWorlds();
|
||||
return potentialWorldFinder.findPotentialWorlds();
|
||||
}
|
||||
}
|
||||
Logging.severe("Invalid MVWorld scope: " + scope);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private Collection<String> suggestMVWorldMetaKey(BukkitCommandCompletionContext context) {
|
||||
return Try.of(() -> context.getContextValue(MultiverseWorld.class))
|
||||
.map(MultiverseWorld::getAllMeta)
|
||||
.map(Map::keySet)
|
||||
.getOrElse(Collections.emptySet());
|
||||
}
|
||||
|
||||
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 +327,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 +347,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 +371,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,8 +39,12 @@ 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.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.PlayerFinder;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
import org.mvplugins.multiverse.core.utils.tick.TickDuration;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
@@ -69,14 +85,18 @@ 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);
|
||||
registerContext(TickDuration.class, this::parseTickDuration);
|
||||
}
|
||||
|
||||
private MVCommandIssuer parseMVCommandIssuer(BukkitCommandExecutionContext context) {
|
||||
@@ -100,8 +120,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 +170,95 @@ 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 Message loadedMultiverseWorldPlayerOnlyMessage() {
|
||||
return Message.of(MVCorei18n.COMMANDS_ERROR_LOADEDMULTIVERSEWORLD_PLAYERSONLY);
|
||||
}
|
||||
|
||||
private LoadedMultiverseWorld[] parseLoadedMultiverseWorldArray(BukkitCommandExecutionContext context) {
|
||||
String resolve = context.getFlagValue("resolve", "");
|
||||
private Message loadedMultiverseWorldIssuerMessage() {
|
||||
return Message.of(MVCorei18n.COMMANDS_ERROR_LOADEDMULTIVERSEWORLD_ISSUER);
|
||||
}
|
||||
|
||||
// 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.");
|
||||
}
|
||||
private Message loadedMultiverseWorldInputConsoleMessage(String worldName) {
|
||||
return Message.of(MVCorei18n.COMMANDS_ERROR_LOADEDMULTIVERSEWORLD_INPUTCONSOLE,
|
||||
Replace.WORLD.with(worldName));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
private Message loadedMultiverseWorldInputMessage(String worldName) {
|
||||
return Message.of(MVCorei18n.COMMANDS_ERROR_LOADEDMULTIVERSEWORLD_INPUT, Replace.WORLD.with(worldName));
|
||||
}
|
||||
|
||||
// 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.");
|
||||
}
|
||||
private Message multiverseWorldPlayerOnlyMessage() {
|
||||
return Message.of(MVCorei18n.COMMANDS_ERROR_MULTIVERSEWORLD_PLAYERSONLY);
|
||||
}
|
||||
|
||||
// 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 Message multiverseWorldIssuerMessage() {
|
||||
return Message.of(MVCorei18n.COMMANDS_ERROR_MULTIVERSEWORLD_ISSUER);
|
||||
}
|
||||
|
||||
private Message multiverseWorldInputConsoleMessage(String worldName) {
|
||||
return Message.of(MVCorei18n.COMMANDS_ERROR_MULTIVERSEWORLD_INPUTCONSOLE, Replace.WORLD.with(worldName));
|
||||
}
|
||||
|
||||
private Message multiverseWorldInputMessage(String worldName) {
|
||||
return Message.of(MVCorei18n.COMMANDS_ERROR_MULTIVERSEWORLD_INPUT, Replace.WORLD.with(worldName));
|
||||
}
|
||||
|
||||
private Message playerOnlyMessage() {
|
||||
return Message.of(MVCorei18n.COMMANDS_ERROR_PLAYERSONLY);
|
||||
}
|
||||
|
||||
private Message playerInputIssuerMessage(String playerName) {
|
||||
return Message.of(MVCorei18n.COMMANDS_ERROR_PLAYER_ISSUERINPUT, Replace.PLAYER.with(playerName));
|
||||
}
|
||||
|
||||
private Message playerInputMessage(String playerName) {
|
||||
return Message.of(MVCorei18n.COMMANDS_ERROR_PLAYER_INPUT, Replace.PLAYER.with(playerName));
|
||||
}
|
||||
|
||||
private Message playerSelectorMessage(String selector) {
|
||||
return Message.of(MVCorei18n.COMMANDS_ERROR_PLAYER_SELECTOR, Replace.PLAYER.with(selector));
|
||||
}
|
||||
|
||||
private Message playersInputMessage(String playerName) {
|
||||
return Message.of(MVCorei18n.COMMANDS_ERROR_PLAYERS_INPUT, Replace.PLAYER.with(playerName));
|
||||
}
|
||||
|
||||
private IssuerAwareContextBuilder<LoadedMultiverseWorld> loadedMultiverseWorldContextBuilder() {
|
||||
return new IssuerAwareContextBuilder<LoadedMultiverseWorld>()
|
||||
.fromPlayer((context, player) -> worldManager.getLoadedWorld(player.getWorld()).getOrNull())
|
||||
.fromInput((context, input) -> getLoadedMultiverseWorld(input))
|
||||
.issuerOnlyFailMessage((context) -> loadedMultiverseWorldPlayerOnlyMessage())
|
||||
.issuerAwarePlayerFailMessage((context, player) -> loadedMultiverseWorldIssuerMessage())
|
||||
.issuerAwareInputFailMessage((context, input) -> loadedMultiverseWorldInputConsoleMessage(input))
|
||||
.inputOnlyFailMessage((context, input) -> loadedMultiverseWorldInputMessage(input));
|
||||
}
|
||||
|
||||
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(
|
||||
loadedMultiverseWorldInputMessage(worldName).formatted(context.getIssuer()));
|
||||
}
|
||||
worlds.add(world);
|
||||
}
|
||||
return worlds.isEmpty() ? null : worlds.toArray(new LoadedMultiverseWorld[0]);
|
||||
})
|
||||
.issuerOnlyFailMessage((context) -> loadedMultiverseWorldPlayerOnlyMessage())
|
||||
.issuerAwarePlayerFailMessage((context, player) -> loadedMultiverseWorldIssuerMessage())
|
||||
.issuerAwareInputFailMessage((context, input) -> loadedMultiverseWorldInputConsoleMessage(input))
|
||||
.inputOnlyFailMessage((context, input) -> loadedMultiverseWorldInputMessage(input));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -259,107 +268,42 @@ 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) -> multiverseWorldPlayerOnlyMessage())
|
||||
.issuerAwarePlayerFailMessage((context, player) -> multiverseWorldIssuerMessage())
|
||||
.issuerAwareInputFailMessage((context, input) -> multiverseWorldInputConsoleMessage(input))
|
||||
.inputOnlyFailMessage((context, input) -> multiverseWorldInputMessage(input));
|
||||
}
|
||||
|
||||
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(
|
||||
multiverseWorldInputMessage(worldName).formatted(context.getIssuer()));
|
||||
}
|
||||
worlds.add(world);
|
||||
}
|
||||
return worlds.isEmpty() ? null : worlds.toArray(new MultiverseWorld[0]);
|
||||
})
|
||||
.issuerOnlyFailMessage((context) -> multiverseWorldPlayerOnlyMessage())
|
||||
.issuerAwarePlayerFailMessage((context, player) -> multiverseWorldIssuerMessage())
|
||||
.issuerAwareInputFailMessage((context, input) -> multiverseWorldInputConsoleMessage(input))
|
||||
.inputOnlyFailMessage((context, input) -> multiverseWorldInputMessage(input));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -375,92 +319,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) -> playerOnlyMessage())
|
||||
.issuerAwareInputFailMessage((context, input) -> playerInputIssuerMessage(input))
|
||||
.inputOnlyFailMessage((context, input) -> playerInputMessage(input));
|
||||
}
|
||||
|
||||
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) -> playerOnlyMessage())
|
||||
.issuerAwareInputFailMessage((context, input) -> playerInputIssuerMessage(input))
|
||||
.inputOnlyFailMessage((context, input) -> {
|
||||
if (PlayerFinder.isSelector(input)) {
|
||||
return playerSelectorMessage(input);
|
||||
}
|
||||
return playersInputMessage(input);
|
||||
});
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -477,4 +451,13 @@ public class MVCommandContexts extends PaperCommandContexts {
|
||||
}
|
||||
return categories.toArray(new SpawnCategory[0]);
|
||||
}
|
||||
|
||||
private TickDuration parseTickDuration(BukkitCommandExecutionContext context) {
|
||||
String arg = context.popFirstArg();
|
||||
return TickDuration.parseString(arg)
|
||||
.getOrElseThrow(failure ->
|
||||
new InvalidCommandArgument("Invalid time duration format: \"" +arg + "\". Use a number " +
|
||||
"followed by an optional suffix (s for seconds, d for game days) or just a number " +
|
||||
"for ticks. Examples: 100, 5s, 2d"));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import co.aikar.commands.LogLevel;
|
||||
import co.aikar.commands.PaperCommandManager;
|
||||
import co.aikar.commands.RootCommand;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -24,6 +26,7 @@ import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.queue.CommandQueueManager;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.locale.PluginLocales;
|
||||
import org.mvplugins.multiverse.core.locale.message.LocalizedMessage;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.WorldNameChecker;
|
||||
|
||||
@@ -62,13 +65,22 @@ public class MVCommandManager extends PaperCommandManager {
|
||||
this.config = config;
|
||||
this.pluginLocales = new PluginLocales(this);
|
||||
this.locales = this.pluginLocales;
|
||||
this.pluginLocales.loadLanguages();
|
||||
|
||||
MVCommandConditions.load(this, worldManager, worldNameChecker);
|
||||
this.enableUnstableAPI("help");
|
||||
this.setDefaultExceptionHandler(new MVDefaultExceptionHandler());
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
LocalizedMessage.setDefaultLocalesManager(getLocales());
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
private void preDestroy() {
|
||||
LocalizedMessage.setDefaultLocalesManager(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a list of commands and handles {@link LegacyAliasCommand} based on config option.
|
||||
* @param commands The commands to register
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
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.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* 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(MVCorei18n.COMMANDS_ERROR_PLAYERSONLY);
|
||||
private BiFunction<BukkitCommandExecutionContext, Player, Message> issuerAwarePlayerFailMessage =
|
||||
(context, player) -> Message.of(MVCorei18n.COMMANDS_ERROR_RESOLVE_PLAYER,
|
||||
Replace.PLAYER.with(player.getName()));
|
||||
private BiFunction<BukkitCommandExecutionContext, String, Message> issuerAwareInputFailMessage =
|
||||
(context, input) -> Message.of(MVCorei18n.COMMANDS_ERROR_RESOLVE_INPUT,
|
||||
replace("{input}").with(input));
|
||||
private BiFunction<BukkitCommandExecutionContext, String, Message> inputOnlyFailMessage =
|
||||
(context, input) -> Message.of(MVCorei18n.COMMANDS_ERROR_RESOLVE_INPUT,
|
||||
replace("{input}").with(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,9 +3,12 @@ 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 java.util.function.Supplier;
|
||||
|
||||
import co.aikar.commands.InvalidCommandArgument;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -39,34 +42,34 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
|
||||
private final Class<T> type;
|
||||
private final boolean optional;
|
||||
private final T defaultValue;
|
||||
private final Supplier<? extends T> defaultValueSupplier;
|
||||
private final Function<String, T> context;
|
||||
private final Function<String, Collection<String>> completion;
|
||||
|
||||
/**
|
||||
* Creates a new flag.
|
||||
*
|
||||
* @param key The key for the new flag.
|
||||
* @param aliases The aliases that also refer to this flag.
|
||||
* @param type The type of the value.
|
||||
* @param optional Allow for flag without value.
|
||||
* @param defaultValue The default value if optional is true and user does not specify a value.
|
||||
* @param context Function to parse string into value type.
|
||||
* @param completion Function to get completion for this flag.
|
||||
* @param key The key for the new flag.
|
||||
* @param aliases The aliases that also refer to this flag.
|
||||
* @param type The type of the value.
|
||||
* @param optional Allow for flag without value.
|
||||
* @param defaultValueSupplier The default value if optional is true and user does not specify a value.
|
||||
* @param context Function to parse string into value type.
|
||||
* @param completion Function to get completion for this flag.
|
||||
*/
|
||||
protected CommandValueFlag(
|
||||
@NotNull String key,
|
||||
@NotNull List<String> aliases,
|
||||
@NotNull Class<T> type,
|
||||
boolean optional,
|
||||
@Nullable T defaultValue,
|
||||
@Nullable Supplier<? extends T> defaultValueSupplier,
|
||||
@Nullable Function<String, T> context,
|
||||
@Nullable Function<String, Collection<String>> completion
|
||||
) {
|
||||
super(key, aliases);
|
||||
this.type = type;
|
||||
this.optional = optional;
|
||||
this.defaultValue = defaultValue;
|
||||
this.defaultValueSupplier = defaultValueSupplier;
|
||||
this.context = context;
|
||||
this.completion = completion;
|
||||
}
|
||||
@@ -95,7 +98,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
* @return The default value.
|
||||
*/
|
||||
public @Nullable T getDefaultValue() {
|
||||
return defaultValue;
|
||||
return defaultValueSupplier == null ? null : defaultValueSupplier.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +128,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
public static class Builder<T, S extends Builder<T, S>> extends CommandFlag.Builder<S> {
|
||||
protected final Class<T> type;
|
||||
protected boolean optional = false;
|
||||
protected T defaultValue = null;
|
||||
protected Supplier<? extends T> defaultValueSupplier = null;
|
||||
protected Function<String, T> context = null;
|
||||
protected Function<String, Collection<String>> completion = null;
|
||||
|
||||
@@ -157,7 +160,21 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
* @return The builder.
|
||||
*/
|
||||
public @NotNull S defaultValue(@NotNull T defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
return defaultValue(() -> defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default value supplier. Used if optional is true and user does not specify a value.
|
||||
* Supplier is only called when command is executed with the flag is present in input.
|
||||
*
|
||||
* @param defaultValueSupplier The default value supplier
|
||||
* @return The builder
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull S defaultValue(@NotNull Supplier<? extends T> defaultValueSupplier) {
|
||||
this.defaultValueSupplier = defaultValueSupplier;
|
||||
return (S) this;
|
||||
}
|
||||
|
||||
@@ -193,7 +210,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
if (context == null && !String.class.equals(type)) {
|
||||
throw new IllegalStateException("Context is required for non-string value flags");
|
||||
}
|
||||
return new CommandValueFlag<>(key, aliases, type, optional, defaultValue, context, completion);
|
||||
return new CommandValueFlag<>(key, aliases, type, optional, defaultValueSupplier, context, completion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +223,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
public static class EnumBuilder<T extends Enum<T>, S extends EnumBuilder<T, S>> extends CommandFlag.Builder<S> {
|
||||
protected final Class<T> type;
|
||||
protected boolean optional = false;
|
||||
protected T defaultValue = null;
|
||||
protected Supplier<? extends T> defaultValueSupplier = null;
|
||||
protected Function<String, T> context = null;
|
||||
protected Function<String, Collection<String>> completion = null;
|
||||
|
||||
@@ -220,7 +237,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 +246,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;
|
||||
@@ -252,7 +269,21 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
* @return The builder.
|
||||
*/
|
||||
public @NotNull S defaultValue(@NotNull T defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
return defaultValue(() -> defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default value to supply. Used if optional is true and user does not specify a value.
|
||||
* Supplier is only called when command is executed with the flag is present in input.
|
||||
*
|
||||
* @param defaultValueSupplier The default value supplier.
|
||||
* @return The builder.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull S defaultValue(@NotNull Supplier<? extends T> defaultValueSupplier) {
|
||||
this.defaultValueSupplier = defaultValueSupplier;
|
||||
return (S) this;
|
||||
}
|
||||
|
||||
@@ -263,7 +294,7 @@ public class CommandValueFlag<T> extends CommandFlag {
|
||||
*/
|
||||
@Override
|
||||
public @NotNull CommandValueFlag<T> build() {
|
||||
return new CommandValueFlag<>(key, aliases, type, optional, defaultValue, context, completion);
|
||||
return new CommandValueFlag<>(key, aliases, type, optional, defaultValueSupplier, context, completion);
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package org.mvplugins.multiverse.core.command.flags;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
|
||||
import org.mvplugins.multiverse.core.destination.core.WorldDestination;
|
||||
import org.mvplugins.multiverse.core.exceptions.command.MVInvalidCommandArgument;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Service
|
||||
public class RemovePlayerDestinationFlags extends FlagBuilder {
|
||||
|
||||
public static final String NAME = "removeplayer";
|
||||
|
||||
private WorldManager worldManager;
|
||||
private DestinationsProvider destinationsProvider;
|
||||
private WorldDestination worldDestination;
|
||||
|
||||
protected RemovePlayerDestinationFlags(
|
||||
@NotNull String name,
|
||||
@NotNull CommandFlagsManager flagsManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull DestinationsProvider destinationsProvider,
|
||||
@NotNull WorldDestination worldDestination
|
||||
) {
|
||||
super(name, flagsManager);
|
||||
this.worldManager = worldManager;
|
||||
this.destinationsProvider = destinationsProvider;
|
||||
this.worldDestination = worldDestination;
|
||||
}
|
||||
|
||||
@Inject
|
||||
private RemovePlayerDestinationFlags(
|
||||
@NotNull CommandFlagsManager flagsManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull DestinationsProvider destinationsProvider,
|
||||
@NotNull WorldDestination worldDestination
|
||||
) {
|
||||
super(NAME, flagsManager);
|
||||
this.destinationsProvider = destinationsProvider;
|
||||
this.worldManager = worldManager;
|
||||
this.worldDestination = worldDestination;
|
||||
}
|
||||
|
||||
public final CommandValueFlag<DestinationInstance> removePlayers = flag(CommandValueFlag.builder("--remove-players", DestinationInstance.class)
|
||||
.addAlias("-r")
|
||||
.defaultValue(() -> worldManager.getDefaultWorld()
|
||||
.map(defaultWorld -> worldDestination.fromWorld(defaultWorld))
|
||||
.getOrElseThrow(() -> MVInvalidCommandArgument.of(
|
||||
Message.of(MVCorei18n.COMMANDS_ERROR_REMOVEPLAYERS_NODEFAULT))))
|
||||
.completion(input -> destinationsProvider.suggestDestinationStrings(Bukkit.getConsoleSender(), input))
|
||||
.context(input -> destinationsProvider.parseDestination(input)
|
||||
.getOrThrow(failure ->
|
||||
MVInvalidCommandArgument.of(failure.getFailureMessage())))
|
||||
.optional()
|
||||
.build());
|
||||
}
|
||||
@@ -1,12 +1,19 @@
|
||||
package org.mvplugins.multiverse.core.command.flags;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
|
||||
|
||||
/**
|
||||
* @deprecated The --remove-players flag is being removed in favor of a more flexible system that allows for specifying
|
||||
* a destination for players to be teleported to when a world is unloaded. See {@link RemovePlayerDestinationFlags}.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
@Service
|
||||
public class RemovePlayerFlags extends FlagBuilder {
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.block.data.type.CommandBlock;
|
||||
import org.bukkit.command.BlockCommandSender;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -28,6 +27,7 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.config.CoreConfig;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.*;
|
||||
@@ -79,8 +79,9 @@ public class CommandQueueManager {
|
||||
if (config.getUseConfirmOtp()) {
|
||||
confirmCommand += " " + payload.otp();
|
||||
}
|
||||
payload.issuer().sendMessage(String.format("Run %s%s %sto continue. This will expire in %s seconds.",
|
||||
ChatColor.GREEN, confirmCommand, ChatColor.WHITE, config.getConfirmTimeout()));
|
||||
payload.issuer().sendMessage(MVCorei18n.QUEUECOMMAND_PROMPT,
|
||||
replace("{command}").with(confirmCommand),
|
||||
replace("{timeout}").with(config.getConfirmTimeout()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +128,7 @@ public class CommandQueueManager {
|
||||
// Payload already removed
|
||||
return;
|
||||
}
|
||||
payload.issuer().sendMessage("Your queued command has expired.");
|
||||
payload.issuer().sendMessage(MVCorei18n.QUEUECOMMAND_EXPIRED);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.bukkit.scheduler.BukkitTask;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
/**
|
||||
@@ -23,12 +24,10 @@ public class CommandQueuePayload {
|
||||
return new CommandQueuePayload(issuer);
|
||||
}
|
||||
|
||||
private static final String DEFAULT_PROMPT_MESSAGE = "The command you are trying to run is deemed dangerous."; // todo: localize
|
||||
|
||||
private final MVCommandIssuer issuer;
|
||||
private String otp;
|
||||
private Runnable action = () -> {};
|
||||
private Message prompt = Message.of(DEFAULT_PROMPT_MESSAGE);
|
||||
private Message prompt = Message.of(MVCorei18n.QUEUECOMMAND_DEFAULTPROMPT);
|
||||
private BukkitTask expireTask;
|
||||
|
||||
protected CommandQueuePayload(@NotNull MVCommandIssuer issuer) {
|
||||
|
||||
@@ -12,6 +12,9 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.anchor.AnchorManager;
|
||||
import org.mvplugins.multiverse.core.anchor.MultiverseAnchor;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
@Service
|
||||
final class AnchorDeleteCommand extends CoreCommand {
|
||||
@@ -35,9 +38,9 @@ final class AnchorDeleteCommand extends CoreCommand {
|
||||
@Description("")
|
||||
MultiverseAnchor anchor) {
|
||||
if (anchorManager.deleteAnchor(anchor).isSuccess()) {
|
||||
issuer.sendMessage("&aAnchor &f" + anchor.getName() + "&a deleted.");
|
||||
issuer.sendMessage(MVCorei18n.ANCHOR_DELETE_SUCCESS, replace("{anchor}").with(anchor.getName()));
|
||||
} else {
|
||||
issuer.sendMessage("&cFailed to delete anchor.");
|
||||
issuer.sendError(MVCorei18n.ANCHOR_DELETE_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,15 +15,18 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.anchor.AnchorManager;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.PageFilterFlags;
|
||||
import org.mvplugins.multiverse.core.display.ContentDisplay;
|
||||
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
|
||||
import org.mvplugins.multiverse.core.display.parsers.ListContentProvider;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
@Service
|
||||
final class AnchorListCommand extends CoreCommand {
|
||||
|
||||
@@ -58,18 +61,19 @@ final class AnchorListCommand extends CoreCommand {
|
||||
ContentDisplay.create()
|
||||
.addContent(ListContentProvider.forContent(getAnchors(issuer.getPlayer())))
|
||||
.withSendHandler(PagedSendHandler.create()
|
||||
.withHeader("&3==== [ Multiverse Anchors ] ====")
|
||||
.withHeader(Message.of(MVCorei18n.ANCHOR_LIST_HEADER))
|
||||
.doPagination(true)
|
||||
.withTargetPage(parsedFlags.flagValue(flags.page, 1))
|
||||
.withFilter(parsedFlags.flagValue(flags.filter, DefaultContentFilter.get())))
|
||||
.send(issuer);
|
||||
}
|
||||
|
||||
private List<String> getAnchors(Player player) {
|
||||
private List<? extends Message> getAnchors(Player player) {
|
||||
return anchorManager.getAnchors(player).stream()
|
||||
.map(anchor ->
|
||||
"&a%s&7 - &f%s".formatted(
|
||||
anchor.getName(), locationManipulation.locationToString(anchor.getLocation())))
|
||||
Message.of(MVCorei18n.ANCHOR_LIST_ENTRY,
|
||||
replace("{anchor}").with(anchor.getName()),
|
||||
replace("{location}").with(locationManipulation.locationToString(anchor.getLocation()))))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,11 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.anchor.AnchorManager;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
@Service
|
||||
final class AnchorSetCommand extends CoreCommand {
|
||||
|
||||
@@ -80,10 +83,12 @@ final class AnchorSetCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
private void sendAnchorSetSuccessMessage(MVCommandIssuer issuer, String anchorName, String locationString) {
|
||||
issuer.sendMessage("&aAnchor &f" + anchorName + "&a set to &f" + locationString);
|
||||
issuer.sendMessage(MVCorei18n.ANCHOR_SET_SUCCESS,
|
||||
replace("{anchor}").with(anchorName),
|
||||
replace("{location}").with(locationString));
|
||||
}
|
||||
|
||||
private void sendAnchorSetFailedMessage(MVCommandIssuer issuer, String anchorName) {
|
||||
issuer.sendMessage("&cFailed to set anchor &f" + anchorName + ".");
|
||||
issuer.sendMessage(MVCorei18n.ANCHOR_SET_FAILURE, replace("{anchor}").with(anchorName));
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ class CheckCommand extends CoreCommand {
|
||||
replace("{location}").with(destination.getLocation(player)
|
||||
.map(locationManipulation::locationToString)
|
||||
.map(Message::of)
|
||||
.getOrElse(() -> Message.of(MVCorei18n.GENERIC_NULL, "Null!"))));
|
||||
.getOrElse(() -> Message.of(MVCorei18n.GENERIC_NULL))));
|
||||
|
||||
// TODO: Show permission required for this particular destination
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -41,21 +41,21 @@ class CloneCommand extends CoreCommand {
|
||||
@Subcommand("clone")
|
||||
@CommandPermission("multiverse.core.clone")
|
||||
@CommandCompletion("@mvworlds:scope=loaded @empty @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world> <new-world-name> [--reset-world-config --reset-gamerules --reset-world-border]")
|
||||
@Syntax("<world> <new-world-name> [--reset-world-config --reset-gamerules --reset-world-border --no-save]")
|
||||
@Description("{@@mv-core.clone.description}")
|
||||
void onCloneCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("<world>")
|
||||
@Description("{@@mv-core.clone.world.description}")
|
||||
LoadedMultiverseWorld world,
|
||||
MultiverseWorld world,
|
||||
|
||||
@Syntax("<new-world-name>")
|
||||
@Description("{@@mv-core.clone.newWorld.description}")
|
||||
@Description("{@@mv-core.clone.newworld.description}")
|
||||
String newWorldName,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--reset-world-config --reset-gamerules --reset-world-border]")
|
||||
@Syntax("[--reset-world-config --reset-gamerules --reset-world-border --no-save]")
|
||||
@Description("{@@mv-core.regen.other.description}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
@@ -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),
|
||||
|
||||
@@ -11,6 +11,7 @@ import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
@@ -19,14 +20,16 @@ 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;
|
||||
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.exceptions.command.MVInvalidCommandArgument;
|
||||
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.position.EntityPosition;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt.Failure;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
@@ -52,8 +55,9 @@ class CreateCommand extends CoreCommand {
|
||||
@Subcommand("create")
|
||||
@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>]")
|
||||
@Syntax("<name> <environment> [--seed <seed> --generator <generator[:id]> --world-type <worldtype> " +
|
||||
"--no-adjust-spawn --no-structures --generate-bonus-chest --force-spawn-position <x,y,z:pitch:yaw> " +
|
||||
"--biome <biome> --generator-settings <json-settings> --properties <prop1=value1,prop2=value2,...>]")
|
||||
@Description("{@@mv-core.create.description}")
|
||||
void onCreateCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -67,8 +71,9 @@ class CreateCommand extends CoreCommand {
|
||||
World.Environment environment,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--seed <seed> --generator <generator[:id]> --world-type <worldtype> --adjust-spawn "
|
||||
+ "--no-structures --biome <biome>]")
|
||||
@Syntax("[--seed <seed> --generator <generator[:id]> --world-type <worldtype> --no-adjust-spawn " +
|
||||
"--no-structures --generate-bonus-chest --force-spawn-position <x,y,z:pitch:yaw> --biome <biome> " +
|
||||
"--generator-settings <json-settings> --properties <prop1=value1,prop2=value2,...>]")
|
||||
@Description("{@@mv-core.create.flags.description}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
@@ -78,14 +83,17 @@ 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, ""))
|
||||
.bonusChest(parsedFlags.hasFlag(flags.bonusChest))
|
||||
.environment(environment)
|
||||
.forcedSpawnPosition(parsedFlags.flagValue(flags.forceSpawnPosition))
|
||||
.generator(parsedFlags.flagValue(flags.generator, ""))
|
||||
.generatorSettings(parsedFlags.flagValue(flags.generatorSettings, ""))
|
||||
.generateStructures(!parsedFlags.hasFlag(flags.noStructures))
|
||||
.seed(parsedFlags.flagValue(flags.seed))
|
||||
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn))
|
||||
.worldPropertyStrings(StringFormatter.parseCSVMap(parsedFlags.flagValue(flags.properties)))
|
||||
.worldType(parsedFlags.flagValue(flags.worldType, WorldType.NORMAL)))
|
||||
.onSuccess(newWorld -> messageSuccess(issuer, newWorld))
|
||||
.onFailure(failure -> messageFailure(issuer, failure));
|
||||
}
|
||||
@@ -180,6 +188,19 @@ 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());
|
||||
|
||||
private final CommandFlag bonusChest = flag(CommandFlag.builder("--generate-bonus-chest")
|
||||
.addAlias("-c")
|
||||
.build());
|
||||
|
||||
private final CommandValueFlag<EntityPosition> forceSpawnPosition = flag(CommandValueFlag.builder("--force-spawn-position", EntityPosition.class)
|
||||
.addAlias("-f")
|
||||
.context(input -> Try.of(() -> EntityPosition.fromString(input)).getOrElseThrow(MVInvalidCommandArgument::causeBy))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Service
|
||||
|
||||
@@ -15,11 +15,11 @@ 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.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerDestinationFlags;
|
||||
import org.mvplugins.multiverse.core.command.queue.CommandQueueManager;
|
||||
import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload;
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
@@ -31,6 +31,8 @@ import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
|
||||
import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
class DeleteCommand extends CoreCommand {
|
||||
|
||||
@@ -38,7 +40,7 @@ class DeleteCommand extends CoreCommand {
|
||||
private final WorldManager worldManager;
|
||||
private final PlayerWorldTeleporter playerWorldTeleporter;
|
||||
private final WorldTickDeferrer worldTickDeferrer;
|
||||
private final RemovePlayerFlags flags;
|
||||
private final RemovePlayerDestinationFlags flags;
|
||||
|
||||
@Inject
|
||||
DeleteCommand(
|
||||
@@ -46,7 +48,7 @@ class DeleteCommand extends CoreCommand {
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
|
||||
@NotNull WorldTickDeferrer worldTickDeferrer,
|
||||
@NotNull RemovePlayerFlags flags
|
||||
@NotNull RemovePlayerDestinationFlags flags
|
||||
) {
|
||||
this.commandQueueManager = commandQueueManager;
|
||||
this.worldManager = worldManager;
|
||||
@@ -57,8 +59,8 @@ class DeleteCommand extends CoreCommand {
|
||||
|
||||
@Subcommand("delete")
|
||||
@CommandPermission("multiverse.core.delete")
|
||||
@CommandCompletion("@mvworlds:scope=loaded @flags:groupName=" + RemovePlayerFlags.NAME)
|
||||
@Syntax("<world>")
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + RemovePlayerDestinationFlags.NAME)
|
||||
@Syntax("<world> [--remove-players [destination]]")
|
||||
@Description("{@@mv-core.delete.description}")
|
||||
void onDeleteCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -69,7 +71,7 @@ class DeleteCommand extends CoreCommand {
|
||||
MultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--remove-players]")
|
||||
@Syntax("[--remove-players [destination]]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
@@ -77,21 +79,22 @@ class DeleteCommand extends CoreCommand {
|
||||
commandQueueManager.addToQueue(CommandQueuePayload
|
||||
.issuer(issuer)
|
||||
.action(() -> runDeleteCommand(issuer, world, parsedFlags))
|
||||
.prompt(Message.of(MVCorei18n.DELETE_PROMPT, "",
|
||||
.prompt(Message.of(MVCorei18n.DELETE_PROMPT,
|
||||
Replace.WORLD.with(world.getName()))));
|
||||
}
|
||||
|
||||
private void runDeleteCommand(MVCommandIssuer issuer, MultiverseWorld world, ParsedCommandFlags parsedFlags) {
|
||||
issuer.sendInfo(MVCorei18n.DELETE_DELETING, Replace.WORLD.with(world.getName()));
|
||||
|
||||
var future = parsedFlags.hasFlag(flags.removePlayers)
|
||||
DestinationInstance<?, ?> removeToDestination = parsedFlags.flagValue(flags.removePlayers);
|
||||
var future = Objects.nonNull(removeToDestination)
|
||||
&& world.isLoaded()
|
||||
&& world instanceof LoadedMultiverseWorld loadedWorld
|
||||
? playerWorldTeleporter.removeFromWorld(loadedWorld)
|
||||
? playerWorldTeleporter.transferAllFromWorldToDestination(loadedWorld, removeToDestination)
|
||||
: AsyncAttemptsAggregate.emptySuccess();
|
||||
|
||||
future.onSuccess(() -> worldTickDeferrer.deferWorldTick(() -> doWorldDeleting(issuer, world)))
|
||||
.onFailure(() -> issuer.sendError("Failed to teleport one or more players out of the world!"));
|
||||
.onFailure(() -> issuer.sendError(MVCorei18n.GENERIC_TELEPORTPLAYERS_FAILED));
|
||||
}
|
||||
|
||||
private void doWorldDeleting(MVCommandIssuer issuer, MultiverseWorld world) {
|
||||
@@ -113,7 +116,7 @@ class DeleteCommand extends CoreCommand {
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull PlayerWorldTeleporter playerWorldTeleporter,
|
||||
@NotNull WorldTickDeferrer worldTickDeferrer,
|
||||
@NotNull RemovePlayerFlags flags) {
|
||||
@NotNull RemovePlayerDestinationFlags flags) {
|
||||
super(commandQueueManager, worldManager, playerWorldTeleporter, worldTickDeferrer, flags);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,13 +12,10 @@ import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
|
||||
import org.mvplugins.multiverse.core.commands.DumpsLogPoster.LogsType;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.commands.DumpsLogPoster.UploadType;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
|
||||
@Service
|
||||
final class DumpsCommand extends CoreCommand {
|
||||
@@ -35,22 +32,17 @@ final class DumpsCommand extends CoreCommand {
|
||||
@Subcommand("dumps")
|
||||
@CommandPermission("multiverse.core.dumps")
|
||||
@CommandCompletion("@flags:groupName=" + Flags.NAME)
|
||||
@Syntax("[--logs <mclogs | append>] [--upload <pastesdev | pastegg>] [--paranoid]")
|
||||
@Syntax("[--upload <pastesdev|mclogs>]")
|
||||
@Description("{@@mv-core.dumps.description}")
|
||||
void onDumpsCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--logs <mclogs | append>] [--upload <pastesdev | pastegg>] [--paranoid]")
|
||||
@Syntax("[--upload <pastesdev|mclogs>]")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
// Grab all our flags
|
||||
boolean paranoid = parsedFlags.hasFlag(flags.paranoid);
|
||||
LogsType logsType = parsedFlags.flagValue(flags.logs, LogsType.MCLOGS);
|
||||
UploadType servicesType = parsedFlags.flagValue(flags.upload, UploadType.PASTESDEV);
|
||||
|
||||
dumpsService.postLogs(issuer, logsType, servicesType, paranoid);
|
||||
dumpsService.postLogs(issuer, servicesType);
|
||||
}
|
||||
|
||||
@Service
|
||||
@@ -63,19 +55,9 @@ final class DumpsCommand extends CoreCommand {
|
||||
super(NAME, flagsManager);
|
||||
}
|
||||
|
||||
private final CommandValueFlag<LogsType> logs = flag(CommandValueFlag
|
||||
.enumBuilder("--logs", LogsType.class)
|
||||
.addAlias("-l")
|
||||
.build());
|
||||
|
||||
private final CommandValueFlag<UploadType> upload = flag(CommandValueFlag
|
||||
.enumBuilder("--upload", UploadType.class)
|
||||
.addAlias("-u")
|
||||
.build());
|
||||
|
||||
// Does not upload logs or plugin list (except if --logs mclogs is there)
|
||||
private final CommandFlag paranoid = flag(CommandFlag.builder("--paranoid")
|
||||
.addAlias("-p")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Option;
|
||||
import net.minidev.json.JSONArray;
|
||||
import net.minidev.json.JSONObject;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
@@ -18,8 +26,10 @@ final class DumpsLogPoster extends BukkitRunnable {
|
||||
|
||||
enum UploadType {
|
||||
// BEGIN CHECKSTYLE-SUPPRESSION: JavadocVariable
|
||||
PASTEGG(PasteServiceType.PASTEGG, "paste.gg"),
|
||||
PASTESDEV(PasteServiceType.PASTESDEV, "pastes.dev");
|
||||
// PASTEBIN(PasteServiceType.PASTEBIN, "pastebin.com"),
|
||||
PASTESDEV(PasteServiceType.PASTESDEV, "pastes.dev"),
|
||||
MCLOGS(PasteServiceType.MCLOGS, "mclo.gs"),
|
||||
;
|
||||
// END CHECKSTYLE-SUPPRESSION: JavadocVariable
|
||||
|
||||
private final PasteServiceType pasteServiceType;
|
||||
@@ -31,34 +41,21 @@ final class DumpsLogPoster extends BukkitRunnable {
|
||||
}
|
||||
}
|
||||
|
||||
enum LogsType {
|
||||
/**
|
||||
* Appends the log file to the debug info.
|
||||
*/
|
||||
APPEND,
|
||||
/**
|
||||
* Uploads the log file to a paste service.
|
||||
*/
|
||||
MCLOGS
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final MultiverseCore plugin;
|
||||
private final MVCommandIssuer issuer;
|
||||
private final LogsType logsType;
|
||||
private final UploadType uploadType;
|
||||
private final boolean paranoid;
|
||||
private final String logs;
|
||||
private final MVDumpsDebugInfoEvent versionEvent;
|
||||
|
||||
DumpsLogPoster(@NotNull MVCommandIssuer issuer,
|
||||
@NotNull DumpsLogPoster.LogsType logsType,
|
||||
DumpsLogPoster(@NotNull MultiverseCore core,
|
||||
@NotNull MVCommandIssuer issuer,
|
||||
@NotNull DumpsLogPoster.UploadType uploadType,
|
||||
boolean paranoid,
|
||||
@NotNull String logs,
|
||||
@NotNull MVDumpsDebugInfoEvent versionEvent) {
|
||||
this.plugin = core;
|
||||
this.issuer = issuer;
|
||||
this.logsType = logsType;
|
||||
this.uploadType = uploadType;
|
||||
this.paranoid = paranoid;
|
||||
this.logs = logs;
|
||||
this.versionEvent = versionEvent;
|
||||
}
|
||||
@@ -69,24 +66,12 @@ final class DumpsLogPoster extends BukkitRunnable {
|
||||
handleVersionEvent();
|
||||
}
|
||||
|
||||
@SuppressWarnings("checkstyle:MissingSwitchDefault")
|
||||
private void handleLogs() {
|
||||
Logging.finer("Logs type is: " + logsType);
|
||||
|
||||
if (!paranoid) {
|
||||
switch (logsType) {
|
||||
case MCLOGS -> sendDumpsUrl("Logs", postRawDataToMcLogs(logs));
|
||||
case APPEND -> versionEvent.putDetailedDebugInfo("latest.log", logs);
|
||||
default -> Logging.finer("Not uploading logs.");
|
||||
}
|
||||
} else {
|
||||
Logging.finer("Paranoid mode is on, not uploading logs.");
|
||||
}
|
||||
versionEvent.putDetailedDebugInfo("latest.log", logs);
|
||||
}
|
||||
|
||||
private void handleVersionEvent() {
|
||||
Logging.finer("Upload service is: " + uploadType);
|
||||
|
||||
final Map<String, String> files = versionEvent.getDetailedDebugInfo();
|
||||
sendDumpsUrl(uploadType.serviceName, postFilesToService(files));
|
||||
}
|
||||
@@ -95,37 +80,77 @@ final class DumpsLogPoster extends BukkitRunnable {
|
||||
issuer.sendInfo(MVCorei18n.DUMPS_URL_LIST, "{service}", service, "{link}", url);
|
||||
}
|
||||
|
||||
private String postRawDataToMcLogs(@NotNull String rawPasteData) {
|
||||
PasteService pasteService = PasteServiceFactory.getService(PasteServiceType.MCLOGS, true);
|
||||
|
||||
return pasteToService(() -> pasteService.postData(rawPasteData));
|
||||
}
|
||||
|
||||
private String postFilesToService(@NotNull Map<String, String> pasteFiles) {
|
||||
PasteService pasteService = PasteServiceFactory.getService(uploadType.pasteServiceType, true);
|
||||
|
||||
return pasteToService(() -> {
|
||||
if (pasteService.supportsMultiFile()) {
|
||||
return pasteService.postData(pasteFiles);
|
||||
} else {
|
||||
return pasteService.postData(this.encodeAsString(pasteFiles));
|
||||
}
|
||||
});
|
||||
PasteService pasteService = PasteServiceFactory.getService(uploadType.pasteServiceType, false);
|
||||
return pasteToService(() -> pasteService.postData(encodePasteAsJson(pasteFiles)));
|
||||
}
|
||||
|
||||
private String encodeAsString(Map<String, String> files) {
|
||||
StringBuilder uploadData = new StringBuilder();
|
||||
for (Map.Entry<String, String> entry : files.entrySet()) {
|
||||
String file = entry.getKey();
|
||||
String data = entry.getValue();
|
||||
uploadData.append("# ---------- ")
|
||||
.append(file)
|
||||
.append(" ----------\n\n")
|
||||
.append(data)
|
||||
.append("\n\n");
|
||||
private String encodePasteAsJson(Map<String, String> files) {
|
||||
JSONObject rootObject = new JSONObject();
|
||||
JSONArray plugins = new JSONArray();
|
||||
JSONArray filesObject = new JSONArray();
|
||||
|
||||
for (Plugin plugin : plugin.getServer().getPluginManager().getPlugins()) {
|
||||
plugins.add(new JSONObject(Map.of(
|
||||
"name", plugin.getName(),
|
||||
"version", plugin.getDescription().getVersion(),
|
||||
"description", Option.of(plugin.getDescription().getDescription()).getOrElse(""),
|
||||
"authors", plugin.getDescription().getAuthors(),
|
||||
"website", Option.of(plugin.getDescription().getWebsite()).getOrElse(""),
|
||||
"enabled", plugin.isEnabled()
|
||||
)));
|
||||
}
|
||||
|
||||
return uploadData.toString();
|
||||
for (Map.Entry<String, String> entry : files.entrySet()) {
|
||||
String fileType = "text";
|
||||
if (entry.getKey().endsWith(".yml") || entry.getKey().endsWith(".yaml")) {
|
||||
fileType = "yaml";
|
||||
} else if (entry.getKey().endsWith(".json")) {
|
||||
fileType = "json";
|
||||
} else if (entry.getKey().endsWith(".md")) {
|
||||
fileType = "markdown";
|
||||
} else if (entry.getKey().endsWith(".log")) {
|
||||
fileType = "accesslog";
|
||||
}
|
||||
|
||||
filesObject.add(new JSONObject(Map.of(
|
||||
"name", entry.getKey(),
|
||||
"data", entry.getValue(),
|
||||
"type", fileType
|
||||
)));
|
||||
}
|
||||
rootObject.put("server", new JSONObject(Map.of(
|
||||
"name", plugin.getServer().getName(),
|
||||
"version", plugin.getServer().getVersion(),
|
||||
"bukkitVersion", plugin.getServer().getBukkitVersion(),
|
||||
"onlineMode", plugin.getServer().getOnlineMode(),
|
||||
"javaVersion", Runtime.version().toString(),
|
||||
"operatingSystem", System.getProperty("os.name") + " " + System.getProperty("os.version") + " " + System.getProperty("os.arch"),
|
||||
"uptime", calculateUptimeString(),
|
||||
"memory", "%d free / %d total / %d max".formatted(
|
||||
toMb(Runtime.getRuntime().freeMemory()),
|
||||
toMb(Runtime.getRuntime().totalMemory()),
|
||||
toMb(Runtime.getRuntime().maxMemory()))
|
||||
)));
|
||||
|
||||
rootObject.put("plugins", plugins);
|
||||
rootObject.put("files", filesObject);
|
||||
rootObject.put("createdAt", Instant.now().toString());
|
||||
|
||||
return rootObject.toJSONString();
|
||||
}
|
||||
|
||||
private long toMb(long value) {
|
||||
return value / 1024 / 1024;
|
||||
}
|
||||
|
||||
private String calculateUptimeString() {
|
||||
long uptimeMs = System.currentTimeMillis() - ManagementFactory.getRuntimeMXBean().getStartTime();
|
||||
long totalSeconds = uptimeMs / 1000;
|
||||
long hours = totalSeconds / 3600;
|
||||
long minutes = (totalSeconds % 3600) / 60;
|
||||
long seconds = totalSeconds % 60;
|
||||
return String.format("%d hrs, %d mins, %d sec", hours, minutes, seconds);
|
||||
}
|
||||
|
||||
private String pasteToService(LogPaster paster) {
|
||||
|
||||
@@ -8,13 +8,12 @@ import java.nio.file.Path;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
import org.mvplugins.multiverse.core.MultiverseCore;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.module.MultiverseModulesRegistry;
|
||||
import org.mvplugins.multiverse.core.commands.DumpsLogPoster.LogsType;
|
||||
import org.mvplugins.multiverse.core.commands.DumpsLogPoster.UploadType;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent;
|
||||
@@ -38,16 +37,12 @@ final class DumpsService {
|
||||
this.fileUtils = fileUtils;
|
||||
}
|
||||
|
||||
void postLogs(MVCommandIssuer issuer, LogsType logsType, UploadType servicesType, boolean paranoid) {
|
||||
void postLogs(MVCommandIssuer issuer, UploadType servicesType) {
|
||||
issuer.sendInfo(MVCorei18n.DUMPS_STARTING);
|
||||
|
||||
// Initialise and add info to the debug event
|
||||
MVDumpsDebugInfoEvent versionEvent = createAndCallDebugInfoEvent();
|
||||
|
||||
// Add plugin list if user isn't paranoid
|
||||
if (!paranoid) {
|
||||
versionEvent.putDetailedDebugInfo("plugins.md", "# Plugins\n\n" + getPluginList());
|
||||
}
|
||||
|
||||
new DumpsLogPoster(issuer, logsType, servicesType, paranoid, getLogs(), versionEvent)
|
||||
new DumpsLogPoster(plugin, issuer, servicesType, getLogs(), versionEvent)
|
||||
.runTaskAsynchronously(plugin);
|
||||
}
|
||||
|
||||
@@ -72,18 +67,18 @@ final class DumpsService {
|
||||
private @NotNull String readLogsFromFile(Path logsPath) {
|
||||
String logs = "Could not read log";
|
||||
|
||||
// Try reading as ANSI encoded
|
||||
// Try reading as UTF-8 first
|
||||
try {
|
||||
logs = Files.readString(logsPath, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
Logging.severe("Could not read logs/latest.log. See below for stack trace");
|
||||
Logging.severe("Could not read logs/latest.log as UTF-8. Trying ISO-8859-1. See below for stack trace");
|
||||
e.printStackTrace();
|
||||
|
||||
try {
|
||||
logs = Files.readString(logsPath, StandardCharsets.UTF_8);
|
||||
logs = Files.readString(logsPath, StandardCharsets.ISO_8859_1);
|
||||
} catch (IOException ex) {
|
||||
// It is some other strange encoding
|
||||
Logging.severe("Could not read ./logs/latest.log. See below for stack trace");
|
||||
Logging.severe("Could not read ./logs/latest.log as ISO-8859-1. See below for stack trace");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -91,15 +86,6 @@ final class DumpsService {
|
||||
return logs;
|
||||
}
|
||||
|
||||
private String getDebugInfoString() {
|
||||
return "# Multiverse-Core Version info" + "\n\n"
|
||||
+ " - Multiverse-Core Version: " + this.plugin.getDescription().getVersion() + '\n'
|
||||
+ " - Bukkit Version: " + this.plugin.getServer().getVersion() + '\n'
|
||||
+ " - Loaded Worlds: " + worldManager.getLoadedWorlds() + '\n'
|
||||
+ " - Multiverse Plugins Loaded: " + StringFormatter.joinAnd(MultiverseModulesRegistry.get().getRegisteredPlugins()) + '\n'
|
||||
+ " - Multiverse Plugins Count: " + MultiverseModulesRegistry.get().getPluginCount() + '\n';
|
||||
}
|
||||
|
||||
private MVDumpsDebugInfoEvent createAndCallDebugInfoEvent() {
|
||||
MVDumpsDebugInfoEvent event = new MVDumpsDebugInfoEvent();
|
||||
addDebugInfoToEvent(event);
|
||||
@@ -111,31 +97,63 @@ final class DumpsService {
|
||||
// Add the legacy file, but as markdown, so it's readable
|
||||
event.putDetailedDebugInfo("version.md", this.getDebugInfoString());
|
||||
|
||||
// add config.yml
|
||||
File configFile = new File(plugin.getDataFolder(), "config.yml");
|
||||
event.putDetailedDebugInfo("Multiverse-Core/config.yml", configFile);
|
||||
|
||||
// add worlds.yml
|
||||
File worldsFile = new File(plugin.getDataFolder(), "worlds.yml");
|
||||
event.putDetailedDebugInfo("Multiverse-Core/worlds.yml", worldsFile);
|
||||
|
||||
// Add bukkit.yml if we found it
|
||||
if (fileUtils.getBukkitConfig() != null) {
|
||||
event.putDetailedDebugInfo(fileUtils.getBukkitConfig().getPath(), fileUtils.getBukkitConfig());
|
||||
} else {
|
||||
Logging.warning("/mv dumps could not find bukkit.yml. Not including file");
|
||||
}
|
||||
|
||||
// Add server.properties if we found it
|
||||
if (fileUtils.getServerProperties() != null) {
|
||||
event.putDetailedDebugInfo(fileUtils.getServerProperties().getPath(), fileUtils.getServerProperties());
|
||||
event.putDetailedDebugInfo(fileUtils.getServerProperties().getName(), fileUtils.getServerProperties());
|
||||
} else {
|
||||
Logging.warning("/mv dumps could not find server.properties. Not including file");
|
||||
}
|
||||
|
||||
// Add bukkit.yml if we found it
|
||||
if (fileUtils.getBukkitConfig() != null) {
|
||||
event.putDetailedDebugInfo(fileUtils.getBukkitConfig().getName(), fileUtils.getBukkitConfig());
|
||||
} else {
|
||||
Logging.warning("/mv dumps could not find bukkit.yml. Not including file");
|
||||
}
|
||||
|
||||
// Add spigot.yml
|
||||
File spigotYml = fileUtils.getServerFolder().toPath().resolve("spigot.yml").toFile();
|
||||
if (spigotYml.isFile()) {
|
||||
event.putDetailedDebugInfo(spigotYml.getName(), spigotYml);
|
||||
} else {
|
||||
Logging.warning("/mv dumps could not find spigot.yml. Not including file");
|
||||
}
|
||||
|
||||
// Add paper-global.yml
|
||||
File paperGlobalYml = fileUtils.getServerFolder().toPath().resolve("config/paper-global.yml").toFile();
|
||||
if (paperGlobalYml.isFile()) {
|
||||
event.putDetailedDebugInfo(paperGlobalYml.getName(), paperGlobalYml);
|
||||
} else {
|
||||
Logging.warning("/mv dumps could not find paper-global.yml. Not including file");
|
||||
}
|
||||
|
||||
// add paper-world-defaults.yml
|
||||
File paperWorldDefaultsYml = fileUtils.getServerFolder().toPath().resolve("config/paper-world-defaults.yml").toFile();
|
||||
if (paperWorldDefaultsYml.isFile()) {
|
||||
event.putDetailedDebugInfo(paperWorldDefaultsYml.getName(), paperWorldDefaultsYml);
|
||||
} else {
|
||||
Logging.warning("/mv dumps could not find paper-global.yml. Not including file");
|
||||
}
|
||||
|
||||
// add multiverse-core config.yml
|
||||
File configFile = new File(plugin.getDataFolder(), "config.yml");
|
||||
event.putDetailedDebugInfo("multiverse-core/config.yml", configFile);
|
||||
|
||||
// add multiverse-core worlds.yml
|
||||
File worldsFile = new File(plugin.getDataFolder(), "worlds.yml");
|
||||
event.putDetailedDebugInfo("multiverse-core/worlds.yml", worldsFile);
|
||||
|
||||
// add multiverse-core anchors.yml
|
||||
File anchorsFile = new File(plugin.getDataFolder(), "anchors.yml");
|
||||
event.putDetailedDebugInfo("multiverse-core/anchors.yml", anchorsFile);
|
||||
}
|
||||
|
||||
private String getPluginList() {
|
||||
return " - " + StringUtils.join(plugin.getServer().getPluginManager().getPlugins(), "\n - ");
|
||||
private String getDebugInfoString() {
|
||||
return "# Multiverse-Core Version info" + "\n\n"
|
||||
+ " - Multiverse-Core Version: " + this.plugin.getDescription().getVersion() + '\n'
|
||||
+ " - Bukkit Version: " + this.plugin.getServer().getVersion() + '\n'
|
||||
+ " - Loaded Worlds: " + worldManager.getLoadedWorlds() + '\n'
|
||||
+ " - Multiverse Plugins Loaded: " + StringFormatter.joinAnd(MultiverseModulesRegistry.get().getRegisteredPlugins()) + '\n'
|
||||
+ " - Multiverse Plugins Count: " + MultiverseModulesRegistry.get().getPluginCount() + '\n';
|
||||
}
|
||||
}
|
||||
+30
-15
@@ -21,7 +21,9 @@ import org.mvplugins.multiverse.core.display.ContentDisplay;
|
||||
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
|
||||
import org.mvplugins.multiverse.core.display.parsers.ListContentProvider;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.StringFormatter;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
@@ -31,6 +33,8 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
@Service
|
||||
@Subcommand("entity-spawn-config")
|
||||
final class EntitySpawnConfigCommand extends CoreCommand {
|
||||
@@ -52,7 +56,7 @@ final class EntitySpawnConfigCommand extends CoreCommand {
|
||||
void onInfoCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
MultiverseWorld world,
|
||||
|
||||
@@ -64,21 +68,26 @@ final class EntitySpawnConfigCommand extends CoreCommand {
|
||||
ContentDisplay.create()
|
||||
.addContent(ListContentProvider.forContent(getEntitySpawnConfigList(world)))
|
||||
.withSendHandler(PagedSendHandler.create()
|
||||
.withHeader(Message.of("==== [ Entity Spawn Config '" + world.getName() + "' ] ===="))
|
||||
.withHeader(Message.of(MVCorei18n.ENTITYSPAWNCONFIG_INFO_HEADER,
|
||||
Replace.WORLD.with(world.getName())))
|
||||
.withLinesPerPage(8)
|
||||
.withTargetPage(parsedFlags.flagValue(flags.page, 1))
|
||||
.withFilter(parsedFlags.flagValue(flags.filter, DefaultContentFilter.get())))
|
||||
.send(issuer);
|
||||
}
|
||||
|
||||
private List<String> getEntitySpawnConfigList(MultiverseWorld world) {
|
||||
List<String> list = new ArrayList<>();
|
||||
private List<Message> getEntitySpawnConfigList(MultiverseWorld world) {
|
||||
List<Message> list = new ArrayList<>();
|
||||
Arrays.stream(SpawnCategory.values()).forEach(spawnCategory -> {
|
||||
list.add(spawnCategory.name() + ": ");
|
||||
list.add(Message.of(MVCorei18n.ENTITYSPAWNCONFIG_INFO_CATEGORY,
|
||||
replace("{category}").with(spawnCategory.name())));
|
||||
SpawnCategoryConfig spawnCategoryConfig = world.getEntitySpawnConfig().getSpawnCategoryConfig(spawnCategory);
|
||||
list.add(" spawn: " + spawnCategoryConfig.isSpawn());
|
||||
list.add(" tick-rate: " + spawnCategoryConfig.getTickRate());
|
||||
list.add(" exceptions: " + StringFormatter.join(spawnCategoryConfig.getExceptions(), ", "));
|
||||
list.add(Message.of(MVCorei18n.ENTITYSPAWNCONFIG_INFO_SPAWN,
|
||||
replace("{spawn}").with(spawnCategoryConfig.isSpawn())));
|
||||
list.add(Message.of(MVCorei18n.ENTITYSPAWNCONFIG_INFO_TICKRATE,
|
||||
replace("{tickRate}").with(spawnCategoryConfig.getTickRate())));
|
||||
list.add(Message.of(MVCorei18n.ENTITYSPAWNCONFIG_INFO_EXCEPTIONS,
|
||||
replace("{exceptions}").with(StringFormatter.join(spawnCategoryConfig.getExceptions(), ", "))));
|
||||
});
|
||||
return list;
|
||||
}
|
||||
@@ -91,7 +100,7 @@ final class EntitySpawnConfigCommand extends CoreCommand {
|
||||
void onModifyCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=4")
|
||||
@Syntax("[world]")
|
||||
MultiverseWorld world,
|
||||
|
||||
@@ -113,11 +122,17 @@ final class EntitySpawnConfigCommand extends CoreCommand {
|
||||
.getSpawnCategoryConfig(spawnCategory)
|
||||
.getStringPropertyHandle()
|
||||
.modifyPropertyString(property, value, action)
|
||||
.onSuccess(ignore -> issuer.sendMessage("Successfully set " + property + " to " + value
|
||||
+ " for " + spawnCategory.name() + " in " + world.getName()))
|
||||
.onFailure(e -> issuer.sendMessage("Unable to set " + property + " to " + value
|
||||
+ " for " + spawnCategory.name() + " in " + world.getName() + ": " + e.getMessage()));
|
||||
|
||||
worldManager.saveWorldsConfig();
|
||||
.andThenTry(worldManager::saveWorldsConfig)
|
||||
.onSuccess(ignore -> issuer.sendInfo(MVCorei18n.ENTITYSPAWNCONFIG_MODIFY_SUCCESS,
|
||||
replace("{property}").with(property),
|
||||
Replace.VALUE.with(value),
|
||||
replace("{category}").with(spawnCategory.name()),
|
||||
Replace.WORLD.with(world.getName())))
|
||||
.onFailure(e -> issuer.sendError(MVCorei18n.ENTITYSPAWNCONFIG_MODIFY_FAILURE,
|
||||
replace("{property}").with(property),
|
||||
Replace.VALUE.with(value),
|
||||
replace("{category}").with(spawnCategory.name()),
|
||||
Replace.WORLD.with(world.getName()),
|
||||
Replace.ERROR.with(e)));
|
||||
}
|
||||
}
|
||||
@@ -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,24 +51,24 @@ 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 *]")
|
||||
@Syntax("<gamerule> <value> [worlds|*]")
|
||||
@Description("{@@mv-core.gamerule.set.description}")
|
||||
void onGameruleSetCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("<Gamerule>")
|
||||
@Syntax("<gamerule>")
|
||||
@Description("{@@mv-core.gamerule.set.gamerule.description}")
|
||||
GameRule gamerule,
|
||||
|
||||
@Syntax("<Value>")
|
||||
@Syntax("<value>")
|
||||
@Description("{@@mv-core.gamerule.set.value.description}")
|
||||
GameRuleValue gameRuleValue,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("[World or *]")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=2")
|
||||
@Syntax("[worlds|*]")
|
||||
@Description("{@@mv-core.gamerule.set.world.description}")
|
||||
LoadedMultiverseWorld[] worlds) {
|
||||
Object value = gameRuleValue.value();
|
||||
@@ -103,20 +102,20 @@ 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 *]")
|
||||
@Syntax("<gamerule> [worlds|*]")
|
||||
@Description("{@@mv-core.gamerule.reset.description}")
|
||||
void onGameruleResetCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("<Gamerule>")
|
||||
@Syntax("<gamerule>")
|
||||
@Description("{@@mv-core.gamerule.reset.gamerule.description}")
|
||||
GameRule gamerule,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Syntax("[World or *]")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=1")
|
||||
@Syntax("[worlds|*]")
|
||||
@Description("{@@mv-core.gamerule.reset.world.description}")
|
||||
LoadedMultiverseWorld[] worlds) {
|
||||
AtomicBoolean success = new AtomicBoolean(true);
|
||||
@@ -145,16 +144,16 @@ 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")
|
||||
@Syntax("<world>")
|
||||
@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) {
|
||||
|
||||
@@ -10,7 +10,6 @@ import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
|
||||
@@ -23,10 +22,13 @@ import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
|
||||
import org.mvplugins.multiverse.core.display.parsers.ListContentProvider;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.StringFormatter;
|
||||
import org.mvplugins.multiverse.core.world.generators.GeneratorPlugin;
|
||||
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* List all gamerules in your current or specified world.
|
||||
*/
|
||||
@@ -68,7 +70,7 @@ class GeneratorsCommand extends CoreCommand {
|
||||
ContentDisplay.create()
|
||||
.addContent(ListContentProvider.forContent(generators))
|
||||
.withSendHandler(PagedSendHandler.create()
|
||||
.withHeader("%s====[ Multiverse Generator List ]====", ChatColor.AQUA)
|
||||
.withHeader(Message.of(MVCorei18n.GENERATORS_HEADER))
|
||||
.doPagination(true)
|
||||
.withTargetPage(parsedFlags.flagValue(flags.page, 1))
|
||||
.withFilter(parsedFlags.flagValue(flags.filter, DefaultContentFilter.get())))
|
||||
@@ -85,10 +87,13 @@ class GeneratorsCommand extends CoreCommand {
|
||||
@Syntax("<generator>")
|
||||
GeneratorPlugin generatorPlugin
|
||||
) {
|
||||
issuer.sendMessage(ChatColor.RESET + "Generator Plugin: " + generatorPlugin.getPluginName());
|
||||
issuer.sendMessage(ChatColor.RESET + "Example usages: ");
|
||||
issuer.sendMessage(ChatColor.RESET + StringFormatter.join(generatorPlugin.getExampleUsages(), "\n"));
|
||||
issuer.sendMessage(ChatColor.RESET + "Link to more info: " + generatorPlugin.getInfoLink());
|
||||
issuer.sendMessage(MVCorei18n.GENERATORS_INFO_PLUGIN,
|
||||
replace("{plugin}").with(generatorPlugin.getPluginName()));
|
||||
issuer.sendMessage(MVCorei18n.GENERATORS_INFO_EXAMPLEUSAGES);
|
||||
issuer.sendMessage(MVCorei18n.GENERATORS_INFO_USAGES,
|
||||
replace("{usages}").with(StringFormatter.join(generatorPlugin.getExampleUsages(), "\n")));
|
||||
issuer.sendMessage(MVCorei18n.GENERATORS_INFO_INFOLINK,
|
||||
replace("{link}").with(generatorPlugin.getInfoLink()));
|
||||
}
|
||||
|
||||
@Service
|
||||
|
||||
@@ -43,22 +43,24 @@ class ImportCommand extends CoreCommand {
|
||||
@Subcommand("import")
|
||||
@CommandPermission("multiverse.core.import")
|
||||
@CommandCompletion("@mvworlds:scope=potential @environments @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<name> <environment> [--generator <generator[:id]> --adjust-spawn --biome <biome>]")
|
||||
@Syntax("<name> <environment> [--generator <generator[:id]> --no-adjust-spawn --biome <biome> " +
|
||||
"--skip-folder-check --generator-settings <json-settings>]")
|
||||
@Description("{@@mv-core.import.description}")
|
||||
void onImportCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Conditions("worldname:scope=new")
|
||||
// @Conditions("worldname:scope=new")
|
||||
@Syntax("<name>")
|
||||
@Description("{@@mv-core.import.name.description}")
|
||||
String worldName,
|
||||
|
||||
@Syntax("<env>")
|
||||
@Syntax("<environment>")
|
||||
@Description("{@@mv-core.import.env.description}")
|
||||
World.Environment environment,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--generator <generator[:id]> --adjust-spawn --biome <biome>]")
|
||||
@Syntax("[--generator <generator[:id]> --no-adjust-spawn --biome <biome> " +
|
||||
"--skip-folder-check --generator-settings <json-settings>]")
|
||||
@Description("{@@mv-core.import.other.description}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
@@ -67,8 +69,10 @@ class ImportCommand extends CoreCommand {
|
||||
worldManager.importWorld(ImportWorldOptions.worldName(worldName)
|
||||
.biome(parsedFlags.flagValue(flags.biome, ""))
|
||||
.environment(environment)
|
||||
.generator(parsedFlags.flagValue(flags.generator, String.class))
|
||||
.useSpawnAdjust(!parsedFlags.hasFlag(flags.noAdjustSpawn)))
|
||||
.generator(parsedFlags.flagValue(flags.generator))
|
||||
.generatorSettings(parsedFlags.flagValue(flags.generatorSettings, ""))
|
||||
.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 +109,15 @@ 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());
|
||||
|
||||
private final CommandValueFlag<String> generatorSettings = flag(CommandValueFlag
|
||||
.builder("--generator-settings", String.class)
|
||||
.addAlias("-gs")
|
||||
.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,
|
||||
|
||||
@@ -87,6 +87,7 @@ class InfoCommand extends CoreCommand {
|
||||
private Map<String, String> getInfo(LoadedMultiverseWorld world) {
|
||||
Map<String, String> outMap = new LinkedHashMap<>();
|
||||
|
||||
outMap.put("World Key", world.getKey().toString());
|
||||
outMap.put("World Name", world.getName());
|
||||
outMap.put("World Alias", world.getAlias());
|
||||
outMap.put("World UID", world.getUID().toString());
|
||||
@@ -103,6 +104,7 @@ class InfoCommand extends CoreCommand {
|
||||
outMap.put("World Type", world.getWorldType().map(WorldType::getName).getOrNull());
|
||||
outMap.put("Biome", world.getBiome());
|
||||
outMap.put("Generator", world.getGenerator());
|
||||
outMap.put("Generator Settings", world.getGeneratorSettings());
|
||||
outMap.put("Generate Structures", world.canGenerateStructures().map(String::valueOf).getOrNull());
|
||||
|
||||
outMap.put("Auto Load", String.valueOf(world.isAutoLoad()));
|
||||
@@ -112,13 +114,12 @@ class InfoCommand extends CoreCommand {
|
||||
outMap.put("World Scale", String.valueOf(world.getScale()));
|
||||
outMap.put("Weather Enabled", String.valueOf(world.isAllowWeather()));
|
||||
outMap.put("Allow Flight", String.valueOf(world.isAllowFlight()));
|
||||
outMap.put("Grant Advancements", String.valueOf(world.isAllowAdvancementGrant()));
|
||||
outMap.put("Auto Heal", String.valueOf(world.getAutoHeal()));
|
||||
outMap.put("Hunger Depletes", String.valueOf(world.isHunger()));
|
||||
outMap.put("Keep Spawn In Memory", String.valueOf(world.isKeepSpawnInMemory()));
|
||||
outMap.put("PVP Enabled", String.valueOf(world.getPvp()));
|
||||
outMap.put("Portal Form", String.valueOf(world.getPortalForm()));
|
||||
outMap.put("Player Limit", String.valueOf(world.getPlayerLimit()));
|
||||
// getAnimalSpawningInfo(outMap, world);
|
||||
// getMonsterSpawningInfo(outMap, world);
|
||||
outMap.put("World Blacklist", String.join(", ", world.getWorldBlacklist()));
|
||||
|
||||
return outMap;
|
||||
@@ -135,30 +136,6 @@ class InfoCommand extends CoreCommand {
|
||||
}
|
||||
}
|
||||
|
||||
// private void getAnimalSpawningInfo(Map<String, String> outMap, MultiverseWorld world) {
|
||||
// if (world.isSpawningAnimals()) {
|
||||
// outMap.put("Spawning Animals", "ALL");
|
||||
// } else {
|
||||
// if (!world.getSpawningAnimalsExceptions().isEmpty()) {
|
||||
// outMap.put("Spawning Animals", world.getSpawningAnimalsExceptions().toString());
|
||||
// } else {
|
||||
// outMap.put("Spawning Animals", "NONE");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void getMonsterSpawningInfo(Map<String, String> outMap, MultiverseWorld world) {
|
||||
// if (world.isSpawningMonsters()) {
|
||||
// outMap.put("Spawning Monsters", "ALL");
|
||||
// } else {
|
||||
// if (!world.getSpawningMonstersExceptions().isEmpty()) {
|
||||
// outMap.put("Spawning Monsters", world.getSpawningMonstersExceptions().toString());
|
||||
// } else {
|
||||
// outMap.put("Spawning Monsters", "NONE");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@Service
|
||||
private static final class LegacyAlias extends InfoCommand implements LegacyAliasCommand {
|
||||
@Inject
|
||||
|
||||
@@ -54,12 +54,12 @@ class ListCommand extends CoreCommand {
|
||||
@Subcommand("list")
|
||||
@CommandPermission("multiverse.core.list.worlds")
|
||||
@CommandCompletion("@flags:groupName=" + Flags.NAME)
|
||||
@Syntax("--filter [filter] --page [page] --raw")
|
||||
@Syntax("[--filter <filter>] [--page <page>] [--raw]")
|
||||
@Description("{{@mv-core.list.description}}")
|
||||
public void onListCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("[--filter <filter>] [--page <page>]")
|
||||
@Syntax("[--filter <filter>] [--page <page>] [--raw]")
|
||||
@Description("Filters the list of worlds by the given regex and displays the given page.")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
@@ -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")
|
||||
@Syntax("<world>")
|
||||
@CommandCompletion("@mvworlds:scope=unloaded @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world> [--skip-folder-check]")
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import co.aikar.commands.annotation.CommandCompletion;
|
||||
import co.aikar.commands.annotation.CommandPermission;
|
||||
import co.aikar.commands.annotation.Description;
|
||||
import co.aikar.commands.annotation.Flags;
|
||||
import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.Single;
|
||||
import co.aikar.commands.annotation.Subcommand;
|
||||
import co.aikar.commands.annotation.Syntax;
|
||||
import jakarta.inject.Inject;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.PageFilterFlags;
|
||||
import org.mvplugins.multiverse.core.display.ContentDisplay;
|
||||
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
|
||||
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
|
||||
import org.mvplugins.multiverse.core.display.parsers.MapContentProvider;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
@Service
|
||||
@Subcommand("meta")
|
||||
final class MetaCommand extends CoreCommand {
|
||||
|
||||
private final WorldManager worldManager;
|
||||
private final PageFilterFlags flags;
|
||||
|
||||
@Inject
|
||||
MetaCommand(@NotNull WorldManager worldManager, @NotNull PageFilterFlags flags) {
|
||||
this.worldManager = worldManager;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Subcommand("info")
|
||||
@CommandPermission("multiverse.core.meta.info")
|
||||
@CommandCompletion("@mvworlds:scope=both|@flags:resolveUntil=arg1,groupName=" + PageFilterFlags.NAME + " " +
|
||||
"@flags:groupName=" + PageFilterFlags.NAME)
|
||||
@Syntax("[world] [--page <page>] [--filter <filter>]")
|
||||
@Description("{@@mv-core.meta.info.description}")
|
||||
void infoCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
@Description("{@@mv-core.meta.info.world.description}")
|
||||
MultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--page <page>] [--filter <filter>]")
|
||||
String[] flagArray
|
||||
) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
ContentDisplay.create()
|
||||
.addContent(MapContentProvider.forContent(world.getAllMeta())
|
||||
.withKeyColor(ChatColor.AQUA)
|
||||
.withValueColor(ChatColor.WHITE))
|
||||
.withSendHandler(PagedSendHandler.create()
|
||||
.withHeader(Message.of(MVCorei18n.META_INFO_HEADER, replace("{world}").with(world.getName())))
|
||||
.noContentMessage(Message.of(MVCorei18n.META_INFO_NOCONTENT, replace("{world}").with(world.getName())))
|
||||
.withTargetPage(parsedFlags.flagValue(flags.page, 1))
|
||||
.withFilter(parsedFlags.flagValue(flags.filter, DefaultContentFilter.get())))
|
||||
.send(issuer);
|
||||
}
|
||||
|
||||
@Subcommand("modify")
|
||||
@CommandPermission("multiverse.core.meta.modify")
|
||||
@CommandCompletion("@mvworlds:scope=both set|remove @mvworldmetakey @empty")
|
||||
@Syntax("[world] <set|remove> <key> [value]")
|
||||
void modifyCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware,maxArgForAware=4")
|
||||
@Syntax("[world]")
|
||||
MultiverseWorld world,
|
||||
|
||||
@Syntax("<set|remove>")
|
||||
String action,
|
||||
|
||||
@Syntax("<key>")
|
||||
String key,
|
||||
|
||||
@Optional
|
||||
@Single
|
||||
@Syntax("[value]")
|
||||
String value
|
||||
) {
|
||||
switch (action.toLowerCase(Locale.ROOT)) {
|
||||
case "set" -> world.setMeta(key, value)
|
||||
.onSuccess(ignore -> issuer.sendInfo(MVCorei18n.META_MODIFY_SET_SUCCESS,
|
||||
replace("{key}").with(key),
|
||||
replace("{value}").with(value),
|
||||
Replace.WORLD.with(world.getName())))
|
||||
.onFailure(throwable -> issuer.sendError(MVCorei18n.META_MODIFY_SET_FAILURE,
|
||||
replace("{key}").with(key),
|
||||
replace("{value}").with(value),
|
||||
Replace.WORLD.with(world.getName()),
|
||||
Replace.ERROR.with(throwable)));
|
||||
case "remove" -> world.removeMeta(key)
|
||||
.onSuccess(ignore -> issuer.sendInfo(MVCorei18n.META_MODIFY_REMOVE_SUCCESS,
|
||||
replace("{key}").with(key),
|
||||
Replace.WORLD.with(world.getName())))
|
||||
.onFailure(throwable -> issuer.sendError(MVCorei18n.META_MODIFY_REMOVE_FAILURE,
|
||||
replace("{key}").with(key),
|
||||
Replace.WORLD.with(world.getName()),
|
||||
Replace.ERROR.with(throwable)));
|
||||
default -> issuer.sendError(MVCorei18n.META_MODIFY_INVALIDACTION, replace("{action}").with(action));
|
||||
}
|
||||
worldManager.saveWorldsConfig();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
} 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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,16 @@ import jakarta.inject.Inject;
|
||||
import org.bukkit.entity.SpawnCategory;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.StringFormatter;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.entity.EntityPurger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
@Service
|
||||
final class PurgeAllEntitiesCommand extends CoreCommand {
|
||||
|
||||
@@ -33,7 +37,7 @@ final class PurgeAllEntitiesCommand extends CoreCommand {
|
||||
void onPurgeAllEntitiesCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=1")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
@@ -43,12 +47,16 @@ final class PurgeAllEntitiesCommand extends CoreCommand {
|
||||
) {
|
||||
if (spawnCategories == null || spawnCategories.length == 0) {
|
||||
int purgeCount = entityPurger.purgeAllEntities(world);
|
||||
issuer.sendMessage("Successfully purged " + purgeCount + " entities in world " + world.getName() + ".");
|
||||
issuer.sendMessage(MVCorei18n.PURGEALLENTITIES_SUCCESS,
|
||||
Replace.COUNT.with(purgeCount),
|
||||
Replace.WORLD.with(world.getName()));
|
||||
return;
|
||||
}
|
||||
|
||||
int purgeCount = entityPurger.purgeEntities(world, spawnCategories);
|
||||
issuer.sendMessage("Successfully purged " + purgeCount + " entities in world " + world.getName() +
|
||||
" for spawn categories " + StringFormatter.join(List.of(spawnCategories), ", ") + ".");
|
||||
issuer.sendMessage(MVCorei18n.PURGEALLENTITIES_SUCCESS_CATEGORIES,
|
||||
Replace.COUNT.with(purgeCount),
|
||||
Replace.WORLD.with(world.getName()),
|
||||
replace("{categories}").with(StringFormatter.join(List.of(spawnCategories), ", ")));
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import co.aikar.commands.annotation.Syntax;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.entity.EntityPurger;
|
||||
|
||||
@@ -28,11 +30,13 @@ final class PurgeEntitiesCommand extends CoreCommand {
|
||||
void onPurgeEntityCommand(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
int purgeCount = entityPurger.purgeEntities(world);
|
||||
issuer.sendMessage("Successfully purged " + purgeCount + " entities in world " + world.getName() + ".");
|
||||
issuer.sendMessage(MVCorei18n.PURGEENTITIES_SUCCESS,
|
||||
Replace.COUNT.with(purgeCount),
|
||||
Replace.WORLD.with(world.getName()));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package org.mvplugins.multiverse.core.commands;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import co.aikar.commands.ACFUtil;
|
||||
import co.aikar.commands.annotation.CommandAlias;
|
||||
@@ -23,9 +24,12 @@ import org.mvplugins.multiverse.core.command.flag.CommandFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
|
||||
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
|
||||
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
|
||||
import org.mvplugins.multiverse.core.command.flags.RemovePlayerDestinationFlags;
|
||||
import org.mvplugins.multiverse.core.command.queue.CommandQueueManager;
|
||||
import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload;
|
||||
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.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
@@ -65,7 +69,8 @@ class RegenCommand extends CoreCommand {
|
||||
@Subcommand("regen")
|
||||
@CommandPermission("multiverse.core.regen")
|
||||
@CommandCompletion("@mvworlds:scope=loaded @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world> [--seed [seed] --reset-world-config --reset-gamerules --reset-world-border --remove-players]")
|
||||
@Syntax("<world> [--seed [seed]] [--reset-world-config] [--reset-gamerules] [--reset-world-border] " +
|
||||
"[--keep-files <files>] [--remove-players [destination]]")
|
||||
@Description("{@@mv-core.regen.description}")
|
||||
void onRegenCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -75,7 +80,8 @@ class RegenCommand extends CoreCommand {
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--seed [seed] --reset-world-config --reset-gamerules --reset-world-border --remove-players]")
|
||||
@Syntax("[--seed [seed]] [--reset-world-config] [--reset-gamerules] [--reset-world-border] " +
|
||||
"[--keep-files <files>] [--remove-players [destination]]")
|
||||
@Description("{@@mv-core.regen.other.description}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
@@ -83,7 +89,7 @@ class RegenCommand extends CoreCommand {
|
||||
commandQueueManager.addToQueue(CommandQueuePayload
|
||||
.issuer(issuer)
|
||||
.action(() -> runRegenCommand(issuer, world, parsedFlags))
|
||||
.prompt(Message.of(MVCorei18n.REGEN_PROMPT, "",
|
||||
.prompt(Message.of(MVCorei18n.REGEN_PROMPT,
|
||||
Replace.WORLD.with(world.getName()))));
|
||||
}
|
||||
|
||||
@@ -91,14 +97,15 @@ class RegenCommand extends CoreCommand {
|
||||
issuer.sendInfo(MVCorei18n.REGEN_REGENERATING, Replace.WORLD.with(world.getName()));
|
||||
List<Player> worldPlayers = world.getPlayers().getOrElse(Collections.emptyList());
|
||||
|
||||
var future = parsedFlags.hasFlag(flags.removePlayers)
|
||||
? playerWorldTeleporter.removeFromWorld(world)
|
||||
DestinationInstance<?, ?> removeToDestination = parsedFlags.flagValue(flags.removePlayers);
|
||||
var future = Objects.nonNull(removeToDestination)
|
||||
? playerWorldTeleporter.transferAllFromWorldToDestination(world, removeToDestination)
|
||||
: AsyncAttemptsAggregate.emptySuccess();
|
||||
|
||||
// todo: using future will hide stacktrace
|
||||
future.onSuccess(() -> worldTickDeferrer
|
||||
.deferWorldTick(() -> doWorldRegening(issuer, world, parsedFlags, worldPlayers)))
|
||||
.onFailure(() -> issuer.sendError("Failed to teleport one or more players out of the world!"));
|
||||
.onFailure(() -> issuer.sendError(MVCorei18n.GENERIC_TELEPORTPLAYERS_FAILED));
|
||||
}
|
||||
|
||||
private void doWorldRegening(
|
||||
@@ -128,13 +135,18 @@ class RegenCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends RemovePlayerFlags {
|
||||
private static final class Flags extends RemovePlayerDestinationFlags {
|
||||
|
||||
private static final String NAME = "mvregen";
|
||||
|
||||
@Inject
|
||||
private Flags(@NotNull CommandFlagsManager flagsManager) {
|
||||
super(NAME, flagsManager);
|
||||
private Flags(
|
||||
@NotNull CommandFlagsManager flagsManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull DestinationsProvider destinationsProvider,
|
||||
@NotNull WorldDestination worldDestination
|
||||
) {
|
||||
super(NAME, flagsManager, worldManager, destinationsProvider, worldDestination);
|
||||
}
|
||||
|
||||
private final CommandValueFlag<String> seed = flag(CommandValueFlag.builder("--seed", String.class)
|
||||
|
||||
@@ -14,28 +14,35 @@ 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.command.flags.RemovePlayerDestinationFlags;
|
||||
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.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate;
|
||||
import org.mvplugins.multiverse.core.world.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
|
||||
import org.mvplugins.multiverse.core.world.options.RemoveWorldOptions;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@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,8 +51,8 @@ class RemoveCommand extends CoreCommand {
|
||||
|
||||
@Subcommand("remove")
|
||||
@CommandPermission("multiverse.core.remove")
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + RemovePlayerFlags.NAME)
|
||||
@Syntax("<world>")
|
||||
@CommandCompletion("@mvworlds:scope=both @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world> [--remove-players [destination]] [--no-unload-bukkit-world] [--no-save]")
|
||||
@Description("{@@mv-core.remove.description}")
|
||||
void onRemoveCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -55,21 +62,26 @@ class RemoveCommand extends CoreCommand {
|
||||
MultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--remove-players]")
|
||||
@Syntax("[--remove-players [destination]] [--no-unload-bukkit-world] [--no-save]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
var future = parsedFlags.hasFlag(flags.removePlayers)
|
||||
? worldManager.getLoadedWorld(world).map(playerWorldTeleporter::removeFromWorld).getOrElse(AsyncAttemptsAggregate::emptySuccess)
|
||||
DestinationInstance<?, ?> removeToDestination = parsedFlags.flagValue(flags.removePlayers);
|
||||
var future = Objects.nonNull(removeToDestination)
|
||||
? world.asLoadedWorld()
|
||||
.map(loadedWorld -> playerWorldTeleporter.transferAllFromWorldToDestination(loadedWorld, removeToDestination))
|
||||
.getOrElse(AsyncAttemptsAggregate::emptySuccess)
|
||||
: AsyncAttemptsAggregate.emptySuccess();
|
||||
|
||||
future.onSuccess(() -> doWorldRemoving(issuer, world))
|
||||
.onFailure(() -> issuer.sendError("Failed to teleport one or more players out of the world!"));
|
||||
future.onSuccess(() -> doWorldRemoving(issuer, world, parsedFlags))
|
||||
.onFailure(() -> issuer.sendError(MVCorei18n.GENERIC_TELEPORTPLAYERS_FAILED));
|
||||
}
|
||||
|
||||
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 +91,37 @@ class RemoveCommand extends CoreCommand {
|
||||
});
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends RemovePlayerDestinationFlags {
|
||||
|
||||
private static final String NAME = "mvremove";
|
||||
|
||||
@Inject
|
||||
private Flags(
|
||||
@NotNull CommandFlagsManager flagsManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull DestinationsProvider destinationsProvider,
|
||||
@NotNull WorldDestination worldDestination
|
||||
) {
|
||||
super(NAME, flagsManager, worldManager, destinationsProvider, worldDestination);
|
||||
}
|
||||
|
||||
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[,yaw,pitch]] [--unsafe]")
|
||||
@Description("{@@mv-core.setspawn.description}")
|
||||
void onSetSpawnCommand(
|
||||
BukkitCommandIssuer issuer,
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("[worldname:x,y,z[,yaw,pitch]]")
|
||||
@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,9 +60,9 @@ 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]")
|
||||
@Syntax("[player] [--unsafe]")
|
||||
@Description("{@@mv-core.spawn.description}")
|
||||
void onSpawnTpCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -88,11 +88,11 @@ final class SpawnCommand extends CoreCommand {
|
||||
List<Entity> entities, boolean checkSafety) {
|
||||
LoadedMultiverseWorld mvWorld = worldManager.getLoadedWorld(world).getOrNull();
|
||||
if (mvWorld == null) {
|
||||
issuer.sendMessage("The world '" + world.getName() + "' is not a multiverse world!");
|
||||
issuer.sendMessage(MVCorei18n.SPAWN_NOTMVWORLD, Replace.WORLD.with(world.getName()));
|
||||
return;
|
||||
}
|
||||
if (!permissionsChecker.checkSpawnPermission(issuer.getIssuer(), entities, mvWorld)) {
|
||||
issuer.sendMessage("You do not have permission to use this command in this world!");
|
||||
issuer.sendMessage(MVCorei18n.SPAWN_NOPERMISSION);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -129,16 +129,18 @@ final class SpawnCommand extends CoreCommand {
|
||||
.checkSafety(checkSafety)
|
||||
.teleport(entities)
|
||||
.onSuccessCount(successCount -> issuer.sendMessage(MVCorei18n.SPAWN_SUCCESS,
|
||||
Replace.PLAYER.with(successCount + " players"), //todo: replace this with localised "{count} players"
|
||||
Replace.PLAYER.with(Message.of(MVCorei18n.GENERIC_PLAYERCOUNT,
|
||||
Replace.COUNT.with(successCount))),
|
||||
Replace.WORLD.with(mvWorld.getName())))
|
||||
.onFailureCount(reasonsCountMap -> {
|
||||
for (var entry : reasonsCountMap.entrySet()) {
|
||||
Logging.finer("Failed to teleport %s players to %s: %s",
|
||||
entry.getValue(), mvWorld.getName(), entry.getKey());
|
||||
issuer.sendError(MVCorei18n.SPAWN_FAILED,
|
||||
Replace.PLAYER.with(entry.getValue() + " players"),
|
||||
Replace.PLAYER.with(Message.of(MVCorei18n.GENERIC_PLAYERCOUNT,
|
||||
Replace.COUNT.with(entry.getValue()))),
|
||||
Replace.WORLD.with(mvWorld.getName()),
|
||||
Replace.REASON.with(entry.getKey().getMessageKey()));
|
||||
Replace.REASON.with(Message.of(entry.getKey())));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,28 +56,28 @@ 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)
|
||||
@Syntax("[player] <destination> [--unsafe]")
|
||||
@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 --silent]")
|
||||
@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}")
|
||||
DestinationInstance<?, ?> destination,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--unsafe]")
|
||||
@Syntax("[--unsafe --silent]")
|
||||
@Description("")
|
||||
String[] flagArray) {
|
||||
Player[] players = playersValue.value();
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
if (players.length == 1) {
|
||||
@@ -95,24 +94,35 @@ final class TeleportCommand extends CoreCommand {
|
||||
DestinationInstance<?, ?> destination,
|
||||
ParsedCommandFlags parsedFlags) {
|
||||
if (!permissionsChecker.checkTeleportPermission(issuer.getIssuer(), player, destination)) {
|
||||
// TODO localize
|
||||
issuer.sendMessage(player == issuer.getPlayer()
|
||||
? "You do not have permission to teleport yourself!"
|
||||
: "You do not have permission to teleport other players!");
|
||||
issuer.sendError(player == issuer.getPlayer()
|
||||
? MVCorei18n.TELEPORT_NOPERMISSION_SELF
|
||||
: MVCorei18n.TELEPORT_NOPERMISSION_OTHER);
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -123,27 +133,49 @@ final class TeleportCommand extends CoreCommand {
|
||||
DestinationInstance<?, ?> destination,
|
||||
ParsedCommandFlags parsedFlags) {
|
||||
if (!permissionsChecker.checkTeleportPermission(issuer.getIssuer(), Arrays.asList(players), destination)) {
|
||||
// TODO localize
|
||||
issuer.sendMessage("You do not have permission to teleport all these players!");
|
||||
issuer.sendError(MVCorei18n.TELEPORT_NOPERMISSION_ALL);
|
||||
return;
|
||||
}
|
||||
|
||||
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(Message.of(MVCorei18n.GENERIC_PLAYERCOUNT,
|
||||
Replace.COUNT.with(successCount))),
|
||||
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.PLAYER.with(Message.of(MVCorei18n.GENERIC_PLAYERCOUNT,
|
||||
Replace.COUNT.with(entry.getValue()))),
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,13 @@ 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.command.flags.RemovePlayerDestinationFlags;
|
||||
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.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate;
|
||||
@@ -27,6 +29,8 @@ import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
|
||||
import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
class UnloadCommand extends CoreCommand {
|
||||
|
||||
@@ -48,7 +52,7 @@ class UnloadCommand extends CoreCommand {
|
||||
@Subcommand("unload")
|
||||
@CommandPermission("multiverse.core.unload")
|
||||
@CommandCompletion("@mvworlds @flags:groupName=" + Flags.NAME)
|
||||
@Syntax("<world>")
|
||||
@Syntax("<world> [--remove-players [destination]] [--no-unload-bukkit-world] [--no-save]")
|
||||
@Description("{@@mv-core.unload.description}")
|
||||
void onUnloadCommand(
|
||||
MVCommandIssuer issuer,
|
||||
@@ -58,19 +62,20 @@ class UnloadCommand extends CoreCommand {
|
||||
LoadedMultiverseWorld world,
|
||||
|
||||
@Optional
|
||||
@Syntax("[--remove-players] [--no-save]")
|
||||
@Syntax("[--remove-players [destination]] [--no-unload-bukkit-world] [--no-save]")
|
||||
@Description("{@@mv-core.gamerules.description.page}")
|
||||
String[] flagArray) {
|
||||
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
|
||||
|
||||
issuer.sendInfo(MVCorei18n.UNLOAD_UNLOADING, Replace.WORLD.with(world.getAliasOrName()));
|
||||
|
||||
var future = parsedFlags.hasFlag(flags.removePlayers)
|
||||
? playerWorldTeleporter.removeFromWorld(world)
|
||||
DestinationInstance<?, ?> removeToDestination = parsedFlags.flagValue(flags.removePlayers);
|
||||
var future = Objects.nonNull(removeToDestination)
|
||||
? playerWorldTeleporter.transferAllFromWorldToDestination(world, removeToDestination)
|
||||
: AsyncAttemptsAggregate.emptySuccess();
|
||||
|
||||
future.onSuccess(() -> doWorldUnloading(issuer, world, parsedFlags))
|
||||
.onFailure(() -> issuer.sendError("Failed to teleport one or more players out of the world!"));
|
||||
.onFailure(() -> issuer.sendError(MVCorei18n.GENERIC_TELEPORTPLAYERS_FAILED));
|
||||
}
|
||||
|
||||
private void doWorldUnloading(MVCommandIssuer issuer, LoadedMultiverseWorld world, ParsedCommandFlags parsedFlags) {
|
||||
@@ -88,13 +93,18 @@ class UnloadCommand extends CoreCommand {
|
||||
}
|
||||
|
||||
@Service
|
||||
private static final class Flags extends RemovePlayerFlags {
|
||||
private static final class Flags extends RemovePlayerDestinationFlags {
|
||||
|
||||
private static final String NAME = "mvunload";
|
||||
|
||||
@Inject
|
||||
private Flags(@NotNull CommandFlagsManager flagsManager) {
|
||||
super(NAME, flagsManager);
|
||||
private Flags(
|
||||
@NotNull CommandFlagsManager flagsManager,
|
||||
@NotNull WorldManager worldManager,
|
||||
@NotNull DestinationsProvider destinationsProvider,
|
||||
@NotNull WorldDestination worldDestination
|
||||
) {
|
||||
super(NAME, flagsManager, worldManager, destinationsProvider, worldDestination);
|
||||
}
|
||||
|
||||
private final CommandFlag noUnloadBukkitWorld = flag(CommandFlag.builder("--no-unload-bukkit-world")
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -12,8 +12,11 @@ import org.jvnet.hk2.annotations.Service;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.utils.compatibility.WorldBorderCompatibility;
|
||||
import org.mvplugins.multiverse.core.utils.tick.TickDuration;
|
||||
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
|
||||
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
@@ -33,28 +36,27 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Optional
|
||||
@Default("0")
|
||||
@Syntax("[time]")
|
||||
int time,
|
||||
TickDuration duration,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
worldBorderAction(issuer, world, worldBorder -> {
|
||||
onWorldBorderSet(issuer, worldBorder.getSize() + size, time, world);
|
||||
});
|
||||
worldBorderAction(issuer, world, worldBorder ->
|
||||
onWorldBorderSet(issuer, worldBorder.getSize() + size, duration, world));
|
||||
}
|
||||
|
||||
@Subcommand("center")
|
||||
void onWorldBorderCenter(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("[x]")
|
||||
@Syntax("<x>")
|
||||
double x,
|
||||
|
||||
@Syntax("[z]")
|
||||
@Syntax("<z>")
|
||||
double z,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -79,7 +81,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("<damage>")
|
||||
double damage,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -103,7 +105,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("<distance>")
|
||||
double distance,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -124,7 +126,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
void onWorldBorderGet(
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -145,9 +147,9 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Optional
|
||||
@Default("0")
|
||||
@Syntax("[time]")
|
||||
int time,
|
||||
TickDuration duration,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -158,15 +160,18 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
Replace.WORLD.with(world.getAliasOrName()));
|
||||
return;
|
||||
}
|
||||
worldBorder.setSize(size, time);
|
||||
if (time <= 0) {
|
||||
if (!WorldBorderCompatibility.supportsChangeSizeInTicks() && !duration.isExactTo(ChronoUnit.SECONDS)) {
|
||||
setRoundOffError(issuer, duration);
|
||||
}
|
||||
WorldBorderCompatibility.changeSizeDuration(worldBorder, size, duration);
|
||||
if (duration.toTicks() <= 0) {
|
||||
issuer.sendMessage(MVCorei18n.WORLDBORDER_SET_IMMEDIATE,
|
||||
replace("{size}").with(worldBorder.getSize()),
|
||||
Replace.WORLD.with(world.getAliasOrName()));
|
||||
} else {
|
||||
issuer.sendMessage(previousSize > size ? MVCorei18n.WORLDBORDER_SET_GROWING : MVCorei18n.WORLDBORDER_SET_SHRINKING,
|
||||
replace("{size}").with(size),
|
||||
replace("{time}").with(time),
|
||||
replace("{time}").with(String.format("%.2f", duration.toSeconds())),
|
||||
Replace.WORLD.with(world.getAliasOrName()));
|
||||
}
|
||||
});
|
||||
@@ -179,7 +184,7 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
@Syntax("<distance>")
|
||||
int distance,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
@@ -201,27 +206,36 @@ final class WorldBorderCommand extends CoreCommand {
|
||||
MVCommandIssuer issuer,
|
||||
|
||||
@Syntax("<time>")
|
||||
int time,
|
||||
TickDuration duration,
|
||||
|
||||
@Flags("resolve=issuerAware")
|
||||
@Flags("resolve=issuerAware,maxArgForAware=0")
|
||||
@Syntax("[world]")
|
||||
LoadedMultiverseWorld world
|
||||
) {
|
||||
worldBorderAction(issuer, world, worldBorder -> {
|
||||
if (worldBorder.getWarningTime() == time) {
|
||||
if (WorldBorderCompatibility.getWarningTimeTicks(worldBorder) == duration.toTicks()) {
|
||||
issuer.sendMessage(MVCorei18n.WORLDBORDER_WARNINGTIME_NOTHINGCHANGED,
|
||||
Replace.WORLD.with(world.getAliasOrName()));
|
||||
return;
|
||||
}
|
||||
worldBorder.setWarningTime(time);
|
||||
if (!WorldBorderCompatibility.supportsSetWarningTimeInTicks() && !duration.isExactTo(ChronoUnit.SECONDS)) {
|
||||
setRoundOffError(issuer, duration);
|
||||
}
|
||||
WorldBorderCompatibility.setWarningTimeDuration(worldBorder, duration);
|
||||
issuer.sendMessage(MVCorei18n.WORLDBORDER_WARNINGTIME_SUCCESS,
|
||||
replace("{time}").with(worldBorder.getWarningTime()),
|
||||
replace("{time}").with(String.format("%.2f", WorldBorderCompatibility.getWarningTime(worldBorder))),
|
||||
Replace.WORLD.with(world.getAliasOrName()));
|
||||
});
|
||||
}
|
||||
|
||||
private void setRoundOffError(MVCommandIssuer issuer, TickDuration duration) {
|
||||
issuer.sendError(MVCorei18n.WORLDBORDER_ROUNDOFF_WARNING,
|
||||
replace("{fromTime}").with(String.format("%.2f", duration.toSeconds())),
|
||||
replace("{toTime}").with(Math.round(duration.toSeconds())));
|
||||
}
|
||||
|
||||
private void worldBorderAction(MVCommandIssuer issuer, LoadedMultiverseWorld world, Consumer<WorldBorder> worldBorderAction) {
|
||||
Try.run(() -> world.getWorldBorder().peek(worldBorderAction))
|
||||
.onFailure(error -> issuer.sendError(error.getLocalizedMessage()));
|
||||
.onFailure(error -> issuer.sendError(MVCorei18n.GENERIC_ERROR_DETAILS, Replace.ERROR.with(error)));
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
*/
|
||||
@@ -217,6 +246,16 @@ public final class CoreConfig {
|
||||
return configHandle.get(configNodes.endWorldNameFormat);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public Try<Void> setWarnAliasConflicts(boolean warnAliasConflicts) {
|
||||
return configHandle.set(configNodes.warnAliasConflicts, warnAliasConflicts);
|
||||
}
|
||||
|
||||
@ApiStatus.AvailableSince("5.8")
|
||||
public boolean getWarnAliasConflicts() {
|
||||
return configHandle.get(configNodes.warnAliasConflicts);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -231,6 +270,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 +513,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 +575,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 +661,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 +679,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 +756,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,15 @@ 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.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
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 +79,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 +94,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 +110,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 +137,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.")
|
||||
@@ -163,6 +200,15 @@ final class CoreConfigNodes {
|
||||
.stringParser(DimensionFormatNodeStringParser.INSTANCE)
|
||||
.build());
|
||||
|
||||
final ConfigNode<Boolean> warnAliasConflicts = node(ConfigNode.builder("world.warn-alias-conflicts", Boolean.class)
|
||||
.comment("")
|
||||
.comment("Sets whether Multiverse will warn about alias duplicates when adding worlds or modifying aliases.")
|
||||
.comment("Although not enforced, it is highly recommended to not have multiple worlds with the same alias or")
|
||||
.comment("alias matching another world's name as it can cause confusion during world listing and selection in commands.")
|
||||
.defaultValue(true)
|
||||
.name("warn-alias-conflicts")
|
||||
.build());
|
||||
|
||||
private final ConfigHeaderNode teleportHeader = node(ConfigHeaderNode.builder("teleport")
|
||||
.comment("")
|
||||
.comment("")
|
||||
@@ -183,6 +229,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 +301,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 +319,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)
|
||||
@@ -316,7 +382,8 @@ final class CoreConfigNodes {
|
||||
.defaultValue(128)
|
||||
.name("custom-portal-search-radius")
|
||||
.validator(value -> value < 0
|
||||
? Try.failure(new MultiverseException("The value must be greater than or equal to 0.", null))
|
||||
? Try.failure(new MultiverseException(
|
||||
Message.of(MVCorei18n.CORE_CONFIG_CUSTOMPORTALSEARCHRADIUS_NONNEGATIVE), null))
|
||||
: Try.success(null))
|
||||
.build());
|
||||
|
||||
@@ -348,12 +415,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 +447,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 +464,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.")
|
||||
@@ -407,7 +499,8 @@ final class CoreConfigNodes {
|
||||
.defaultValue(30)
|
||||
.name("confirm-timeout")
|
||||
.validator(value -> (value <= 0)
|
||||
? Try.failure(new MultiverseException("Confirm timeout must be a positive number!"))
|
||||
? Try.failure(new MultiverseException(
|
||||
Message.of(MVCorei18n.CORE_CONFIG_CONFIRMTIMEOUT_POSITIVE)))
|
||||
: Try.success(null))
|
||||
.build());
|
||||
|
||||
@@ -435,8 +528,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 +540,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 +564,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 +593,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)
|
||||
@@ -490,9 +631,10 @@ final class CoreConfigNodes {
|
||||
.name("global-debug")
|
||||
.suggester(input -> List.of("0", "1", "2", "3"))
|
||||
.validator(value -> (value < 0 || value > 3)
|
||||
? Try.failure(new MultiverseException("Debug level must be between 0 and 3."))
|
||||
? Try.failure(new MultiverseException(
|
||||
Message.of(MVCorei18n.CORE_CONFIG_DEBUGLEVEL_RANGE)))
|
||||
: Try.success(null))
|
||||
.onSetValue((oldValue, newValue) -> {
|
||||
.onLoadAndChange((oldValue, newValue) -> {
|
||||
if (newValue != Logging.getDebugLevel()) {
|
||||
Logging.setDebugLevel(newValue);
|
||||
pluginManager.callEvent(new MVDebugModeEvent(newValue));
|
||||
@@ -501,11 +643,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 +656,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 +676,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> {
|
||||
|
||||
+51
-19
@@ -6,16 +6,18 @@ import java.util.Map;
|
||||
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.MapValueNode;
|
||||
import org.mvplugins.multiverse.core.config.node.NodeGroup;
|
||||
import org.mvplugins.multiverse.core.config.node.ValueNode;
|
||||
|
||||
@@ -84,22 +86,19 @@ public abstract class BaseConfigurationHandle<C extends ConfigurationSection> {
|
||||
});
|
||||
|
||||
nodeValueMap.forEach((valueNode, value) -> {
|
||||
valueNode.onSetValue(value, value);
|
||||
valueNode.onLoad(value);
|
||||
valueNode.onLoadAndChange(Bukkit.getConsoleSender(), null, value);
|
||||
});
|
||||
}
|
||||
|
||||
protected <T> T deserializeNodeFromConfig(ValueNode<T> node) {
|
||||
if (node.getSerializer() == null) {
|
||||
return Option.of(config.getObject(node.getPath(), node.getType())).getOrElse(node::getDefaultValue);
|
||||
}
|
||||
return Try.of(() -> {
|
||||
var value = config.get(node.getPath());
|
||||
if (value == null) {
|
||||
return node.getDefaultValue();
|
||||
}
|
||||
return node.getSerializer().deserialize(value, node.getType());
|
||||
}).flatMap(value -> node.validate(value).map(ignore -> value))
|
||||
.onFailure(e -> Logging.warning("Failed to deserialize node %s: %s", node.getPath(), e.getMessage()))
|
||||
return value == null ? node.getDefaultValue() : node.deserialize(value);
|
||||
})
|
||||
.flatMap(value -> node.validate(value).map(ignore -> value))
|
||||
.onFailure(e -> Logging.warning("Failed to deserialize node %s: %s",
|
||||
node.getPath(), e.getMessage()))
|
||||
.getOrElse(node::getDefaultValue);
|
||||
}
|
||||
|
||||
@@ -120,12 +119,7 @@ public abstract class BaseConfigurationHandle<C extends ConfigurationSection> {
|
||||
if (value == null) {
|
||||
value = node.getDefaultValue();
|
||||
}
|
||||
if (node.getSerializer() != null) {
|
||||
var serialized = node.getSerializer().serialize(value, node.getType());
|
||||
config.set(node.getPath(), serialized);
|
||||
} else {
|
||||
config.set(node.getPath(), value);
|
||||
}
|
||||
config.set(node.getPath(), node.serialize(value));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,10 +150,36 @@ 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);
|
||||
}
|
||||
|
||||
public <K, V> Try<Void> set(@NotNull MapValueNode<K, V> node, K key, V value) {
|
||||
return node.validateEntry(key, value).map(ignore -> {
|
||||
Map<K,V> map = get(node);
|
||||
map.put(key, value);
|
||||
// node.onSetEntryValue(null, key, null, value);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
});
|
||||
}
|
||||
@@ -200,6 +220,18 @@ public abstract class BaseConfigurationHandle<C extends ConfigurationSection> {
|
||||
});
|
||||
}
|
||||
|
||||
public <K, V> Try<Void> remove(@NotNull MapValueNode<K, V> node, K key) {
|
||||
return node.validateKey(key).map(ignore -> {
|
||||
Map<K,V> map = get(node);
|
||||
V value = map.remove(key);
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("Cannot remove entry as it is already not in the map!");
|
||||
}
|
||||
// node.onSetEntryValue(key, value, null);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default value of a node.
|
||||
*
|
||||
|
||||
@@ -112,10 +112,5 @@ public abstract class FileConfigurationHandle<C extends FileConfiguration> exten
|
||||
* @return The configuration handle.
|
||||
*/
|
||||
public abstract @NotNull FileConfigurationHandle<C> build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected B self() {
|
||||
return (B) this;
|
||||
}
|
||||
}
|
||||
}
|
||||
+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}
|
||||
*/
|
||||
@@ -133,6 +180,24 @@ public class ConfigNode<T> extends ConfigHeaderNode implements ValueNode<T> {
|
||||
return serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public T deserialize(@Nullable Object object) {
|
||||
return serializer == null
|
||||
? (type.isInstance(object)) ? type.cast(object) : getDefaultValue()
|
||||
: serializer.deserialize(object, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Object serialize(T value) {
|
||||
return serializer == null ? value : serializer.serialize(value, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -148,9 +213,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 +249,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 +331,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 +356,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 +381,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 +417,84 @@ 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)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
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 +503,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}
|
||||
*/
|
||||
@@ -253,7 +283,7 @@ public class ListConfigNode<I> extends ConfigNode<List<I>> implements ListValueN
|
||||
//noinspection unchecked
|
||||
super(path, (Class<List<I>>) (Object) List.class);
|
||||
this.itemType = itemType;
|
||||
this.defaultValue = () -> (List<I>) new ArrayList<>();
|
||||
this.defaultValue = ArrayList::new;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,607 @@
|
||||
package org.mvplugins.multiverse.core.config.node;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
import io.vavr.control.Try;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
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.NodeStringParser;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeSuggester;
|
||||
import org.mvplugins.multiverse.core.config.node.functions.NodeValueCallback;
|
||||
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.exceptions.MultiverseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* A config node that contains key-value mappings.
|
||||
*
|
||||
* @param <K> The key type.
|
||||
* @param <V> The value type.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public class MapConfigNode<K, V> extends ConfigNode<Map<K, V>> implements MapValueNode<K, V> {
|
||||
|
||||
/**
|
||||
* Creates a new builder for a {@link MapConfigNode}.
|
||||
*
|
||||
* @param path The path of the node.
|
||||
* @param keyType The map key type.
|
||||
* @param valueType The map value type.
|
||||
* @param <K> The key type.
|
||||
* @param <V> The value type.
|
||||
* @return The new builder.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static @NotNull <K, V> Builder<K, V> mapBuilder(
|
||||
@NotNull String path,
|
||||
@NotNull Class<K> keyType,
|
||||
@NotNull Class<V> valueType) {
|
||||
return new Builder<>(path, keyType, valueType);
|
||||
}
|
||||
|
||||
protected final @NotNull Class<K> keyType;
|
||||
protected final @NotNull Class<V> valueType;
|
||||
protected final @Nullable NodeSuggester keySuggester;
|
||||
protected final @Nullable NodeSuggester valueSuggester;
|
||||
protected final @Nullable NodeStringParser<K> keyStringParser;
|
||||
protected final @Nullable NodeStringParser<V> valueStringParser;
|
||||
protected final @Nullable NodeSerializer<K> keySerializer;
|
||||
protected final @Nullable NodeSerializer<V> valueSerializer;
|
||||
protected final @Nullable Function<K, Try<Void>> keyValidator;
|
||||
protected final @Nullable Function<V, Try<Void>> valueValidator;
|
||||
|
||||
protected MapConfigNode(
|
||||
@NotNull String path,
|
||||
@NotNull String[] comments,
|
||||
@Nullable String name,
|
||||
@NotNull Class<Map<K, V>> type,
|
||||
@NotNull String[] aliases,
|
||||
@Nullable Supplier<Map<K, V>> defaultValueSupplier,
|
||||
@Nullable NodeSuggester suggester,
|
||||
@Nullable NodeStringParser<Map<K, V>> stringParser,
|
||||
@Nullable NodeSerializer<Map<K, V>> serializer,
|
||||
@Nullable Function<Map<K, V>, Try<Void>> validator,
|
||||
@Nullable NodeValueCallback<Map<K, V>> onLoad,
|
||||
@Nullable NodeChangeCallback<Map<K, V>> onLoadAndChange,
|
||||
@Nullable NodeChangeCallback<Map<K, V>> onChange,
|
||||
@NotNull Class<K> keyType,
|
||||
@NotNull Class<V> valueType,
|
||||
@Nullable NodeSuggester keySuggester,
|
||||
@Nullable NodeSuggester valueSuggester,
|
||||
@Nullable NodeStringParser<K> keyStringParser,
|
||||
@Nullable NodeStringParser<V> valueStringParser,
|
||||
@Nullable NodeSerializer<K> keySerializer,
|
||||
@Nullable NodeSerializer<V> valueSerializer,
|
||||
@Nullable Function<K, Try<Void>> keyValidator,
|
||||
@Nullable Function<V, Try<Void>> valueValidator) {
|
||||
super(path, comments, name, type, aliases, defaultValueSupplier, suggester, stringParser, serializer,
|
||||
validator, onLoad, onLoadAndChange, onChange);
|
||||
this.keyType = keyType;
|
||||
this.valueType = valueType;
|
||||
this.keySuggester = keySuggester != null
|
||||
? keySuggester
|
||||
: DefaultSuggesterProvider.getDefaultSuggester(keyType);
|
||||
this.valueSuggester = valueSuggester != null
|
||||
? valueSuggester
|
||||
: DefaultSuggesterProvider.getDefaultSuggester(valueType);
|
||||
this.keyStringParser = keyStringParser != null
|
||||
? keyStringParser
|
||||
: DefaultStringParserProvider.getDefaultStringParser(keyType);
|
||||
this.valueStringParser = valueStringParser != null
|
||||
? valueStringParser
|
||||
: DefaultStringParserProvider.getDefaultStringParser(valueType);
|
||||
this.keySerializer = keySerializer != null
|
||||
? keySerializer
|
||||
: DefaultSerializerProvider.getDefaultSerializer(keyType);
|
||||
this.valueSerializer = valueSerializer != null
|
||||
? valueSerializer
|
||||
: DefaultSerializerProvider.getDefaultSerializer(valueType);
|
||||
this.keyValidator = keyValidator != null
|
||||
? keyValidator
|
||||
: defaultYamlKeyValidator();
|
||||
this.valueValidator = valueValidator;
|
||||
|
||||
setDefaults();
|
||||
}
|
||||
|
||||
private Function<K, Try<Void>> defaultYamlKeyValidator() {
|
||||
return key -> Try.of(() -> {
|
||||
if (!REPatterns.YAML_KEY.matcher(String.valueOf(serializeKey(key))).matches()) {
|
||||
throw new MultiverseException(Message.of(MVCorei18n.CONFIG_NODE_INVALIDYAMLKEY,
|
||||
replace("{key}").with(key)));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void setDefaults() {
|
||||
if ((this.keySuggester != null || this.valueSuggester != null) && this.suggester == null) {
|
||||
setDefaultSuggester();
|
||||
}
|
||||
if (this.keyStringParser != null && this.valueStringParser != null && this.stringParser == null) {
|
||||
setDefaultStringParser();
|
||||
}
|
||||
if ((this.keyValidator != null || this.valueValidator != null) && this.validator == null) {
|
||||
setDefaultValidator();
|
||||
}
|
||||
if ((this.keySerializer != null || this.valueSerializer != null) && this.serializer == null) {
|
||||
setDefaultSerializer();
|
||||
}
|
||||
if (this.defaultValue == null) {
|
||||
this.defaultValue = LinkedHashMap::new;
|
||||
}
|
||||
}
|
||||
|
||||
private void setDefaultSuggester() {
|
||||
if (this.keySuggester instanceof SenderNodeSuggester || this.valueSuggester instanceof SenderNodeSuggester) {
|
||||
this.suggester = (SenderNodeSuggester) this::suggestEntries;
|
||||
} else {
|
||||
this.suggester = input -> suggestEntries(null, input);
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull Collection<String> suggestEntries(@Nullable CommandSender sender, @Nullable String input) {
|
||||
String content = input == null ? "" : input;
|
||||
int lastComma = content.lastIndexOf(',');
|
||||
String prefix = lastComma == -1 ? "" : content.substring(0, lastComma + 1);
|
||||
String currentEntry = lastComma == -1 ? content : content.substring(lastComma + 1);
|
||||
|
||||
String[] keyValue = REPatterns.EQUALS.split(currentEntry, 2);
|
||||
if (keyValue.length == 2 && valueSuggester != null) {
|
||||
String keyPart = keyValue[0];
|
||||
String valuePart = keyValue[1];
|
||||
return suggestFrom(valueSuggester, sender, valuePart).stream()
|
||||
.map(suggestedValue -> prefix + keyPart + "=" + suggestedValue)
|
||||
.toList();
|
||||
}
|
||||
|
||||
if (keySuggester == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return suggestFrom(keySuggester, sender, currentEntry).stream()
|
||||
.map(suggestedKey -> prefix + suggestedKey + "=")
|
||||
.toList();
|
||||
}
|
||||
|
||||
private @NotNull Collection<String> suggestFrom(
|
||||
@NotNull NodeSuggester suggester,
|
||||
@Nullable CommandSender sender,
|
||||
@Nullable String input) {
|
||||
if (sender != null && suggester instanceof SenderNodeSuggester senderSuggester) {
|
||||
return senderSuggester.suggest(sender, input);
|
||||
}
|
||||
return suggester.suggest(input);
|
||||
}
|
||||
|
||||
private void setDefaultStringParser() {
|
||||
this.stringParser = (input, type) -> Try.of(() -> {
|
||||
Map<K, V> parsed = new LinkedHashMap<>();
|
||||
if (input == null || input.isBlank()) {
|
||||
return parsed;
|
||||
}
|
||||
for (String entry : REPatterns.SEMICOLON.split(input)) {
|
||||
String[] keyValue = REPatterns.EQUALS.split(entry, 2);
|
||||
if (keyValue.length != 2) {
|
||||
throw new IllegalArgumentException("Invalid map entry '" + entry + "'. Expected format key=value");
|
||||
}
|
||||
NodeStringParser<K> keyParser = Objects.requireNonNull(keyStringParser, "keyStringParser");
|
||||
NodeStringParser<V> valueParser = Objects.requireNonNull(valueStringParser, "valueStringParser");
|
||||
K parsedKey = keyParser.parse(keyValue[0].trim(), keyType).get();
|
||||
V parsedValue = valueParser.parse(keyValue[1].trim(), valueType).get();
|
||||
parsed.put(parsedKey, parsedValue);
|
||||
}
|
||||
return parsed;
|
||||
});
|
||||
}
|
||||
|
||||
private void setDefaultValidator() {
|
||||
this.validator = value -> Try.of(() -> {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
for (Map.Entry<K, V> entry : value.entrySet()) {
|
||||
if (keyValidator != null) {
|
||||
keyValidator.apply(entry.getKey()).get();
|
||||
}
|
||||
if (valueValidator != null) {
|
||||
valueValidator.apply(entry.getValue()).get();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void setDefaultSerializer() {
|
||||
this.serializer = new NodeSerializer<>() {
|
||||
@Override
|
||||
public Map<K, V> deserialize(Object object, Class<Map<K, V>> type) {
|
||||
if (object == null) {
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
if (object instanceof Map<?, ?> rawMap) {
|
||||
Map<K, V> result = new LinkedHashMap<>();
|
||||
rawMap.forEach((key, value) -> {
|
||||
Map.Entry<K, V> entry = deserializeEntry(key, value);
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
if (object instanceof ConfigurationSection section) {
|
||||
Map<K, V> result = new LinkedHashMap<>();
|
||||
for (String key : section.getKeys(false)) {
|
||||
Object rawValue = section.get(key);
|
||||
Map.Entry<K, V> entry = deserializeEntry(key, rawValue);
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Invalid map object '" + object + "'. Expected map or section.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object serialize(Map<K, V> object, Class<Map<K, V>> type) {
|
||||
Map<Object, Object> serialized = new LinkedHashMap<>();
|
||||
if (object == null) {
|
||||
return serialized;
|
||||
}
|
||||
object.forEach((key, value) -> {
|
||||
Map.Entry<Object, Object> entry = serializeEntry(key, value);
|
||||
serialized.put(entry.getKey(), entry.getValue());
|
||||
});
|
||||
return serialized;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Try<Void> validateKey(@Nullable K key) {
|
||||
return keyValidator == null ? Try.success(null) : keyValidator.apply(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Try<Void> validateValue(@Nullable V value) {
|
||||
return valueValidator == null ? Try.success(null) : valueValidator.apply(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Try<Void> validateEntry(@Nullable K key, @Nullable V value) {
|
||||
return validateKey(key).flatMap(ignored -> validateValue(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Map.Entry<K, V> deserializeEntry(@Nullable Object key, @Nullable Object value) {
|
||||
K deserializedKey = keySerializer == null
|
||||
? keyType.isInstance(key) ? keyType.cast(key) : null
|
||||
: keySerializer.deserialize(key, keyType);
|
||||
V deserializedValue = valueSerializer == null
|
||||
? keyType.isInstance(value) ? valueType.cast(value) : null
|
||||
: valueSerializer.deserialize(value, valueType);
|
||||
return new SimpleEntry<>(deserializedKey, deserializedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Map.Entry<Object, Object> serializeEntry(@Nullable K key, @Nullable V value) {
|
||||
return new SimpleEntry<>(serializeKey(key), serializeValue(value));
|
||||
}
|
||||
|
||||
private @Nullable Object serializeKey(@Nullable K key) {
|
||||
return keySerializer == null ? key : keySerializer.serialize(key, keyType);
|
||||
}
|
||||
|
||||
private @Nullable Object serializeValue(@Nullable V value) {
|
||||
return valueSerializer == null ? value : valueSerializer.serialize(value, valueType);
|
||||
}
|
||||
|
||||
private record SimpleEntry<K, V>(K key, V value) implements Map.Entry<K, V> {
|
||||
@Override
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V setValue(V value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link MapConfigNode} instances.
|
||||
*
|
||||
* <p>This builder extends {@link ConfigNode.Builder} with additional configuration specific to map entries:
|
||||
* entry-level suggesters, string parsers, serializers and validators for both keys and values. When any
|
||||
* entry-level component is omitted the builder provides a sensible default (for example default parsers,
|
||||
* suggesters, serializers or an empty map default value).
|
||||
*
|
||||
* @param <K> The map key type.
|
||||
* @param <V> The map value type.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public static class Builder<K, V> extends ConfigNode.Builder<Map<K, V>, Builder<K, V>> {
|
||||
|
||||
protected final @NotNull Class<K> keyType;
|
||||
protected final @NotNull Class<V> valueType;
|
||||
protected @Nullable NodeSuggester keySuggester;
|
||||
protected @Nullable NodeSuggester valueSuggester;
|
||||
protected @Nullable NodeStringParser<K> keyStringParser;
|
||||
protected @Nullable NodeStringParser<V> valueStringParser;
|
||||
protected @Nullable NodeSerializer<K> keySerializer;
|
||||
protected @Nullable NodeSerializer<V> valueSerializer;
|
||||
protected @Nullable Function<K, Try<Void>> keyValidator;
|
||||
protected @Nullable Function<V, Try<Void>> valueValidator;
|
||||
|
||||
/**
|
||||
* Creates a new builder for a map config node.
|
||||
*
|
||||
* @param path The configuration path for the node.
|
||||
* @param keyType The runtime type of the map keys.
|
||||
* @param valueType The runtime type of the map values.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
protected Builder(@NotNull String path, @NotNull Class<K> keyType, @NotNull Class<V> valueType) {
|
||||
//noinspection unchecked
|
||||
super(path, (Class<Map<K, V>>) (Object) Map.class);
|
||||
this.keyType = keyType;
|
||||
this.valueType = valueType;
|
||||
this.defaultValue = LinkedHashMap::new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a suggester for map keys. The suggester is used when computing tab-completion suggestions for the
|
||||
* key part of an entry.
|
||||
*
|
||||
* @param keySuggester The suggester to use for keys.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> keySuggester(@NotNull NodeSuggester keySuggester) {
|
||||
this.keySuggester = keySuggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a sender-aware suggester for map keys. Use this when suggestions depend on the command sender
|
||||
* (permissions, location, etc.).
|
||||
*
|
||||
* @param keySuggester The sender-aware suggester.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> keySuggester(@NotNull SenderNodeSuggester keySuggester) {
|
||||
this.keySuggester = keySuggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a suggester for map values. The suggester is used when computing tab-completion suggestions for the
|
||||
* value part of an entry.
|
||||
*
|
||||
* @param valueSuggester The suggester to use for values.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> valueSuggester(@NotNull NodeSuggester valueSuggester) {
|
||||
this.valueSuggester = valueSuggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a sender-aware suggester for map values. Use this when value suggestions depend on the
|
||||
* command sender.
|
||||
*
|
||||
* @param valueSuggester The sender-aware suggester.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> valueSuggester(@NotNull SenderNodeSuggester valueSuggester) {
|
||||
this.valueSuggester = valueSuggester;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for keys. The parser is used when parsing user-provided key strings into key
|
||||
* instances (e.g. when parsing a map from a single-line string representation).
|
||||
*
|
||||
* @param keyStringParser The parser to use for keys.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> keyStringParser(@NotNull NodeStringParser<K> keyStringParser) {
|
||||
this.keyStringParser = keyStringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a sender-aware string parser for keys. Use this when parsing depends on the sender context.
|
||||
*
|
||||
* @param keyStringParser The sender-aware key parser.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> keyStringParser(@NotNull SenderNodeStringParser<K> keyStringParser) {
|
||||
this.keyStringParser = keyStringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string parser for values. The parser converts the textual part after '=' in an entry into the
|
||||
* value type.
|
||||
*
|
||||
* @param valueStringParser The parser to use for values.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> valueStringParser(@NotNull NodeStringParser<V> valueStringParser) {
|
||||
this.valueStringParser = valueStringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a sender-aware string parser for values.
|
||||
*
|
||||
* @param valueStringParser The sender-aware value parser.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> valueStringParser(@NotNull SenderNodeStringParser<V> valueStringParser) {
|
||||
this.valueStringParser = valueStringParser;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the serializer for keys. Serializers convert keys to/from persisted forms stored in the configuration
|
||||
* backend.
|
||||
*
|
||||
* @param keySerializer The key serializer.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> keySerializer(@NotNull NodeSerializer<K> keySerializer) {
|
||||
this.keySerializer = keySerializer;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the serializer for values.
|
||||
*
|
||||
* @param valueSerializer The value serializer.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> valueSerializer(@NotNull NodeSerializer<V> valueSerializer) {
|
||||
this.valueSerializer = valueSerializer;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a validator for keys. The validator should return a successful {@link Try} for valid keys or a failed
|
||||
* {@link Try} describing the validation error for invalid keys.
|
||||
*
|
||||
* @param keyValidator The key validator.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> keyValidator(@NotNull Function<K, Try<Void>> keyValidator) {
|
||||
this.keyValidator = keyValidator;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a validator for values.
|
||||
*
|
||||
* @param valueValidator The value validator.
|
||||
* @return This builder for chaining.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull Builder<K, V> valueValidator(@NotNull Function<V, Try<Void>> valueValidator) {
|
||||
this.valueValidator = valueValidator;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull MapConfigNode<K, V> build() {
|
||||
return new MapConfigNode<>(
|
||||
path,
|
||||
comments.toArray(new String[0]),
|
||||
name,
|
||||
type,
|
||||
aliases,
|
||||
defaultValue,
|
||||
suggester,
|
||||
stringParser,
|
||||
serializer,
|
||||
validator,
|
||||
onLoad,
|
||||
onLoadAndChange,
|
||||
onChange,
|
||||
keyType,
|
||||
valueType,
|
||||
keySuggester,
|
||||
valueSuggester,
|
||||
keyStringParser,
|
||||
valueStringParser,
|
||||
keySerializer,
|
||||
valueSerializer,
|
||||
keyValidator,
|
||||
valueValidator);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.mvplugins.multiverse.core.config.node;
|
||||
|
||||
import io.vavr.control.Try;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@link ValueNode} specialization that stores a {@link Map} of key-value pairs.
|
||||
*
|
||||
* @param <K> The key type.
|
||||
* @param <V> The value type.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public interface MapValueNode<K, V> extends ValueNode<Map<K, V>> {
|
||||
|
||||
/**
|
||||
* Validates a single map key.
|
||||
* <br />
|
||||
* Implementations should return a successful {@link Try} when the key is acceptable, or a failed
|
||||
* {@link Try} when the key is invalid.
|
||||
*
|
||||
* @param key The key to validate, or {@code null} if the key is absent.
|
||||
* @return A successful {@link Try} if the key is valid, or a failed {@link Try} if it is not.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull Try<Void> validateKey(@Nullable K key);
|
||||
|
||||
/**
|
||||
* Validates a single map value.
|
||||
* <br />
|
||||
* Implementations should return a successful {@link Try} when the value is acceptable, or a failed
|
||||
* {@link Try} when the value is invalid.
|
||||
*
|
||||
* @param value The value to validate, or {@code null} if the value is absent.
|
||||
* @return A successful {@link Try} if the value is valid, or a failed {@link Try} if it is not.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull Try<Void> validateValue(@Nullable V value);
|
||||
|
||||
/**
|
||||
* Validates a key-value entry.
|
||||
* <br />
|
||||
* This is typically implemented by composing {@link #validateKey(Object)} and {@link #validateValue(Object)}.
|
||||
* If either part fails, the entry is considered invalid.
|
||||
*
|
||||
* @param key The entry key, or {@code null} if the key is absent.
|
||||
* @param value The entry value, or {@code null} if the value is absent.
|
||||
* @return A successful {@link Try} if both parts are valid, or a failed {@link Try} if either part is invalid.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull Try<Void> validateEntry(@Nullable K key, @Nullable V value);
|
||||
|
||||
/**
|
||||
* Deserializes a raw key-value pair into a map entry.
|
||||
* <br />
|
||||
* This method converts a single persisted entry, not the full map. If either component cannot be converted,
|
||||
* implementations may return {@code null} for that side of the entry.
|
||||
*
|
||||
* @param key The raw key object, or {@code null} if no key is present.
|
||||
* @param value The raw value object, or {@code null} if no value is present.
|
||||
* @return A map entry containing the deserialized key and value.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull Map.Entry<K, V> deserializeEntry(@Nullable Object key, @Nullable Object value);
|
||||
|
||||
/**
|
||||
* Serializes a map entry into raw configuration objects.
|
||||
* <br />
|
||||
* This method converts a single entry to a persisted representation, not the full map. The returned entry
|
||||
* contains the serialized key and serialized value.
|
||||
*
|
||||
* @param key The entry key, or {@code null} if the key is absent.
|
||||
* @param value The entry value, or {@code null} if the value is absent.
|
||||
* @return A map entry containing the serialized key and value.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@NotNull Map.Entry<Object, Object> serializeEntry(@Nullable K key, @Nullable V value);
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -26,49 +31,188 @@ public interface ValueNode<T> extends Node {
|
||||
@NotNull Class<T> getType();
|
||||
|
||||
/**
|
||||
* Gets the default value with type {@link T} of the node.
|
||||
* 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 default value of the node.
|
||||
* @return The aliases of this node, or an empty array if the node has no aliases.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.1")
|
||||
default @NotNull String[] getAliases() {
|
||||
return Strings.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default value for this node.
|
||||
*
|
||||
* <p>The returned value is used when no explicit value is present or when deserialization falls back to the
|
||||
* node's default. Implementations may return {@code null} to indicate that no default value is available.
|
||||
*
|
||||
* @return The default value of the node, or {@code null} if none is configured.
|
||||
*/
|
||||
@Nullable T getDefaultValue();
|
||||
|
||||
/**
|
||||
* Suggests possible string values for this node. Generated based on the current user input.
|
||||
* Suggests possible string values for this node.
|
||||
*
|
||||
* @param input The current partial user input
|
||||
* <p>The returned values are based on the current partial user input and should be suitable for command-line
|
||||
* tab completion or other interactive hints.
|
||||
*
|
||||
* @param input The current partial user input, or {@code null} if no input has been typed yet.
|
||||
* @return A collection of possible string values.
|
||||
*/
|
||||
@NotNull Collection<String> suggest(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link T}. Used for property set by user input.
|
||||
* Suggests possible string values for this node using sender context.
|
||||
*
|
||||
* @param input The string to parse.
|
||||
* @return The parsed value, or given exception if parsing failed.
|
||||
* <p>Implementations may use the sender's name, permissions, or player location to tailor the returned
|
||||
* suggestions. If no sender-specific logic is available, implementations should fall back to
|
||||
* {@link #suggest(String)}.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The current partial user input, or {@code null} if no input has been typed yet.
|
||||
* @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}.
|
||||
*
|
||||
* <p>This is typically used for values entered by a user or read from configuration text.
|
||||
*
|
||||
* @param input The string to parse, or {@code null} if the source value is absent.
|
||||
* @return The parsed value, or a failed {@link Try} containing the parsing error.
|
||||
*/
|
||||
@NotNull Try<T> parseFromString(@Nullable String input);
|
||||
|
||||
/**
|
||||
* Parses the given string into a value of type {@link T} with context from the sender.
|
||||
*
|
||||
* <p>Sender-aware implementations may use permissions or other sender-specific information when converting the
|
||||
* input. If no sender-aware parser is available, implementations should fall back to
|
||||
* {@link #parseFromString(String)}.
|
||||
*
|
||||
* @param sender The sender context.
|
||||
* @param input The string to parse, or {@code null} if the source value is absent.
|
||||
* @return The parsed value, or a failed {@link Try} containing the parsing error.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return The serializer for this node.
|
||||
* <p>This method exists for backward compatibility while the preferred API is
|
||||
* {@link #serialize(Object)} and {@link #deserialize(Object)}.
|
||||
*
|
||||
* @return The serializer for this node, or {@code null} if none is configured.
|
||||
*
|
||||
* @deprecated Use {@link #serialize(Object)} and {@link #deserialize(Object)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.7")
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
@Nullable NodeSerializer<T> getSerializer();
|
||||
|
||||
/**
|
||||
* Deserializes a raw configuration object into a value of type {@link T}.
|
||||
*
|
||||
* <p>The input is typically a value read from YAML or another configuration source. Implementations should
|
||||
* return a sensible fallback, such as {@link #getDefaultValue()}, when the object cannot be converted.
|
||||
*
|
||||
* @param object The raw object to deserialize.
|
||||
* @return The deserialized value, or {@code null} if the object cannot be converted and no fallback exists.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Nullable T deserialize(@Nullable Object object);
|
||||
|
||||
/**
|
||||
* Serializes a value of type {@link T} into a configuration-friendly object.
|
||||
*
|
||||
* <p>The returned object should be suitable for persistence in a configuration file.
|
||||
*
|
||||
* @param value The value to serialize.
|
||||
* @return The serialized representation, or {@code null} if the value cannot be serialized.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
@Nullable Object serialize(@Nullable T value);
|
||||
|
||||
/**
|
||||
* Validates the value of this node.
|
||||
*
|
||||
* @param value The value to validate.
|
||||
* @return An empty {@link Try} if the value is valid, or a {@link Try} containing an exception if the value is
|
||||
* invalid.
|
||||
* @return A successful {@link Try} if the value is valid, or a failed {@link Try} containing the validation
|
||||
* exception if the value is invalid.
|
||||
*/
|
||||
Try<Void> validate(@Nullable T value);
|
||||
|
||||
/**
|
||||
* Called when the value of this node is loaded by {@link BaseConfigurationHandle#load()}.
|
||||
*
|
||||
* <p>This callback is invoked after the value has been deserialized and validated during a configuration load.
|
||||
*
|
||||
* @param value The loaded value, or {@code null} if the configured value could not be resolved.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.4")
|
||||
void onLoad(@Nullable T value);
|
||||
|
||||
/**
|
||||
* Called when the value of this node is loaded or changed.
|
||||
*
|
||||
* <p>This callback is shared by both initial load and subsequent updates. When invoked during a load, the
|
||||
* {@code oldValue} parameter will be {@code null} and the sender will be the console sender.
|
||||
*
|
||||
* @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 previous value, or {@code null} when called during 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)}.
|
||||
*
|
||||
* <p>This callback is only invoked for explicit updates after the value already exists.
|
||||
*
|
||||
* @param sender The sender who changed the value, or the console sender if no target sender was 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.
|
||||
*
|
||||
* <p>This is the legacy, senderless form of {@link #onLoadAndChange(CommandSender, Object, Object)}.
|
||||
*
|
||||
* @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)
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "6.0")
|
||||
default void onSetValue(@Nullable T oldValue, @Nullable T newValue) {
|
||||
onLoadAndChange(Bukkit.getConsoleSender(), oldValue, newValue);
|
||||
}
|
||||
}
|
||||
+21
-7
@@ -1,12 +1,17 @@
|
||||
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;
|
||||
import io.vavr.control.Option;
|
||||
import io.vavr.control.Try;
|
||||
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* Provides default string parsers for common types.
|
||||
@@ -41,33 +46,42 @@ 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'");
|
||||
default -> throw new MultiverseException(Message.of(MVCorei18n.CONFIG_STRING_PARSER_INVALIDBOOLEAN,
|
||||
replace("{input}").with(input)));
|
||||
});
|
||||
|
||||
private static final NodeStringParser<Integer> INTEGER_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> ACFUtil.parseInt(input))
|
||||
.flatMap(number -> Option.of(number).toTry(() -> new MultiverseException("Unable to convert '" + input + "' to number. (integer)")));
|
||||
.flatMap(number -> Option.of(number).toTry(() -> new MultiverseException(Message.of(
|
||||
MVCorei18n.CONFIG_STRING_PARSER_INVALIDINTEGER,
|
||||
replace("{input}").with(input)))));
|
||||
|
||||
private static final NodeStringParser<Double> DOUBLE_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> ACFUtil.parseDouble(input))
|
||||
.flatMap(number -> Option.of(number).toTry(() -> new MultiverseException("Unable to convert '" + input + "' to number. (double)")));
|
||||
.flatMap(number -> Option.of(number).toTry(() -> new MultiverseException(Message.of(
|
||||
MVCorei18n.CONFIG_STRING_PARSER_INVALIDDOUBLE,
|
||||
replace("{input}").with(input)))));
|
||||
|
||||
private static final NodeStringParser<Float> FLOAT_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> ACFUtil.parseFloat(input))
|
||||
.flatMap(number -> Option.of(number).toTry(() -> new MultiverseException("Unable to convert '" + input + "' to number. (float)")));
|
||||
.flatMap(number -> Option.of(number).toTry(() -> new MultiverseException(Message.of(
|
||||
MVCorei18n.CONFIG_STRING_PARSER_INVALIDFLOAT,
|
||||
replace("{input}").with(input)))));
|
||||
|
||||
private static final NodeStringParser<Long> LONG_STRING_PARSER = (input, type) -> Try.of(
|
||||
() -> ACFUtil.parseLong(input))
|
||||
.flatMap(number -> Option.of(number).toTry(() -> new MultiverseException("Unable to convert '" + input + "' to number. (long)")));
|
||||
.flatMap(number -> Option.of(number).toTry(() -> new MultiverseException(Message.of(
|
||||
MVCorei18n.CONFIG_STRING_PARSER_INVALIDLONG,
|
||||
replace("{input}").with(input)))));
|
||||
|
||||
static {
|
||||
addDefaultStringParser(String.class, STRING_STRING_PARSER);
|
||||
|
||||
+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)),
|
||||
|
||||
+12
@@ -7,6 +7,10 @@ import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
|
||||
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
|
||||
|
||||
/**
|
||||
* Destination instance implementation for the {@link AnchorDestination}.
|
||||
@@ -63,6 +67,14 @@ public final class AnchorDestinationInstance extends DestinationInstance<AnchorD
|
||||
return Option.of(anchorName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Message getDisplayMessage() {
|
||||
return Message.of(MVCorei18n.DESTINATION_ANCHOR_DISPLAY, replace("{anchor}").with(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));
|
||||
|
||||
+14
-1
@@ -9,12 +9,15 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.mvplugins.multiverse.core.destination.DestinationInstance;
|
||||
import org.mvplugins.multiverse.core.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.Message;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
|
||||
/**
|
||||
* 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 +67,16 @@ public final class BedDestinationInstance extends DestinationInstance<BedDestina
|
||||
return Option.of(player != null ? player.getName() : BedDestination.OWN_BED_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Message getDisplayMessage() {
|
||||
return player == null
|
||||
? Message.of(MVCorei18n.DESTINATION_BED_DISPLAY_OWN)
|
||||
: Message.of(MVCorei18n.DESTINATION_BED_DISPLAY_OTHER, Replace.PLAYER.with(player.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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);
|
||||
|
||||
+83
-36
@@ -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,57 @@ 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 worldNameOrAlias = items[0];
|
||||
String positionStr = items[1];
|
||||
|
||||
World world = getLoadedMultiverseWorld(worldName).flatMap(LoadedMultiverseWorld::getBukkitWorld).getOrNull();
|
||||
World world = getLoadedMultiverseWorld(worldNameOrAlias)
|
||||
.flatMap(LoadedMultiverseWorld::getBukkitWorld)
|
||||
.getOrNull();
|
||||
if (world == null) {
|
||||
return Attempt.failure(InstanceFailureReason.WORLD_NOT_FOUND, Replace.WORLD.with(worldName));
|
||||
return Attempt.failure(InstanceFailureReason.WORLD_NOT_FOUND, Replace.WORLD.with(worldNameOrAlias));
|
||||
}
|
||||
|
||||
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, world.getName(), position));
|
||||
}
|
||||
|
||||
//TODO: Extract to a world finder class
|
||||
@@ -118,19 +123,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}
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ import co.aikar.locales.MessageKey;
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import jakarta.inject.Inject;
|
||||
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;
|
||||
@@ -14,13 +15,11 @@ 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.locale.MVCorei18n;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement;
|
||||
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
|
||||
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
|
||||
import org.mvplugins.multiverse.core.utils.REPatterns;
|
||||
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.MultiverseWorld;
|
||||
import org.mvplugins.multiverse.core.world.WorldManager;
|
||||
import org.mvplugins.multiverse.core.world.entrycheck.WorldEntryCheckerProvider;
|
||||
@@ -56,11 +55,27 @@ public final class WorldDestination implements Destination<WorldDestination, Wor
|
||||
return "w";
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly create a world destination instance for a Multiverse World without any string parsing validation needed.
|
||||
*
|
||||
* @param multiverseWorld The destination's target world.
|
||||
* @return The destination instance with the target world.
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
@ApiStatus.AvailableSince("5.7")
|
||||
public @NotNull WorldDestinationInstance fromWorld(MultiverseWorld multiverseWorld) {
|
||||
return new WorldDestinationInstance(this, multiverseWorld, null, -1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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;
|
||||
|
||||
@@ -2,8 +2,6 @@ package org.mvplugins.multiverse.core.display.handlers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import co.aikar.commands.BukkitCommandIssuer;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
|
||||
@@ -69,12 +67,12 @@ public class PagedSendHandler extends BaseSendHandler<PagedSendHandler> {
|
||||
private void sendPaged(@NotNull MVCommandIssuer issuer, @NotNull List<String> content) {
|
||||
int totalPages = (content.size() + linesPerPage - 1) / linesPerPage; // Basically just divide round up
|
||||
if (targetPage < 1 || targetPage > totalPages) {
|
||||
issuer.sendMessage(String.format("%sInvalid page number. Please enter a page number between 1 and %s", ChatColor.RED, totalPages));
|
||||
issuer.sendMessage(MVCorei18n.CONTENTDISPLAY_INVALIDPAGE, replace("{total}").with(totalPages));
|
||||
return;
|
||||
}
|
||||
|
||||
if (filter.needToFilter()) {
|
||||
issuer.sendMessage("{page} {filter}",
|
||||
issuer.sendMessage(MVCorei18n.CONTENTDISPLAY_PAGEFILTER,
|
||||
replace("{page}").with(Message.of(MVCorei18n.CONTENTDISPLAY_PAGE,
|
||||
replace("{current}").with(targetPage),
|
||||
replace("{total}").with(totalPages))),
|
||||
|
||||
+22
-19
@@ -35,7 +35,7 @@ public final class DynamicListenerRegistration {
|
||||
private static final boolean hasEventExecutorCreate;
|
||||
|
||||
static {
|
||||
hasEventExecutorCreate = ReflectHelper.getMethod(EventExecutor.class, "create", Method.class, Class.class) != null;
|
||||
hasEventExecutorCreate = ReflectHelper.hasMethod(EventExecutor.class, "create", Method.class, Class.class);
|
||||
}
|
||||
|
||||
private final EventPriorityMapper eventPriorityMapper;
|
||||
@@ -98,18 +98,22 @@ public final class DynamicListenerRegistration {
|
||||
}
|
||||
|
||||
method.setAccessible(true);
|
||||
Object methodOutput = ReflectHelper.invokeMethod(listener, method);
|
||||
if (!(methodOutput instanceof EventRunnable eventRunnable)) {
|
||||
Logging.warning("Event method %s in %s did not return a SingleEventListener",
|
||||
method.getName(), listener.getClass().getName());
|
||||
return;
|
||||
}
|
||||
EventExecutor executor = new EventRunnableExecutor<>(eventClass, eventRunnable);
|
||||
EventPriority priority = getDynamicEventPriority(method);
|
||||
boolean ignoreCancelled = isIgnoreIfCancelled(method);
|
||||
|
||||
Logging.finest("Registering dynamic event for %s with priority %s", eventClass.getName(), priority);
|
||||
Bukkit.getPluginManager().registerEvent(eventClass, listener, priority, executor, plugin, ignoreCancelled);
|
||||
ReflectHelper.tryInvokeMethod(listener, method)
|
||||
.onFailure(e -> Logging.warning("Failed to invoke event method %s in %s: $s",
|
||||
method.getName(), listener.getClass().getName(), e.getMessage()))
|
||||
.filter(EventRunnable.class::isInstance)
|
||||
.onFailure(e -> Logging.warning("Event method %s in %s did not return an EventRunnable instance!",
|
||||
method.getName(), listener.getClass().getName()))
|
||||
.map(EventRunnable.class::cast)
|
||||
.map(eventRunnable -> new EventRunnableExecutor<>(eventClass, eventRunnable))
|
||||
.peek(executor -> {
|
||||
EventPriority priority = getDynamicEventPriority(method);
|
||||
boolean ignoreCancelled = isIgnoreIfCancelled(method);
|
||||
Bukkit.getPluginManager().registerEvent(eventClass, listener, priority, executor, plugin, ignoreCancelled);
|
||||
Logging.finest("Registered dynamic event: %s, priority: %s, ignore if cancelled: %s",
|
||||
eventClass.getName(), priority, ignoreCancelled);
|
||||
});
|
||||
}
|
||||
|
||||
private Class<? extends Event> getEventClass(Method method) {
|
||||
@@ -129,13 +133,12 @@ public final class DynamicListenerRegistration {
|
||||
}
|
||||
|
||||
private Class<? extends Event> getEventClassFromString(String className) {
|
||||
Class<?> annotatedClass = ReflectHelper.getClass(className);
|
||||
if (annotatedClass == null || !Event.class.isAssignableFrom(annotatedClass)) {
|
||||
// Usually means the server software used did not implement the event
|
||||
Logging.fine("Event class does not exist: %s", className);
|
||||
return null;
|
||||
}
|
||||
return annotatedClass.asSubclass(Event.class);
|
||||
return ReflectHelper.tryGetClass(className)
|
||||
.onFailure(throwable -> Logging.fine("Failed to find event class: %s", className))
|
||||
.filter(Event.class::isAssignableFrom)
|
||||
.onFailure(throwable -> Logging.warning("Class is not an Event: %s", className))
|
||||
.map(clazz -> (Class<? extends Event>) clazz.asSubclass(Event.class))
|
||||
.getOrNull();
|
||||
}
|
||||
|
||||
private EventPriority getDynamicEventPriority(Method method) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.dumptruckman.minecraft.util.Logging;
|
||||
@@ -24,7 +25,7 @@ public final class MVDumpsDebugInfoEvent extends Event {
|
||||
|
||||
public MVDumpsDebugInfoEvent() {
|
||||
debugInfoBuilder = new StringBuilder();
|
||||
detailedDebugInfo = new HashMap<>();
|
||||
detailedDebugInfo = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Loaded 100 of 232 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user