Compare commits
602 Commits
Minecraft-
...
497c6879e0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
497c6879e0 | ||
![]() |
7b27dfaf5e | ||
![]() |
f9b75c4a3a | ||
![]() |
0509303fd3 | ||
![]() |
f486a251f3 | ||
![]() |
5a1e342e0d | ||
![]() |
d9bbdc3281 | ||
![]() |
cfe00fa47c | ||
![]() |
d68ebd1eaf | ||
![]() |
a7cd79eb41 | ||
![]() |
9e83ee6f0c | ||
![]() |
7c81d91740 | ||
![]() |
5b126b7f4d | ||
![]() |
9fe7d21f4b | ||
![]() |
94ea0271ba | ||
![]() |
3af672d2f2 | ||
![]() |
0dd7b98428 | ||
![]() |
a793692a2c | ||
![]() |
23fb838227 | ||
![]() |
2d6d89d668 | ||
![]() |
0199cb90ff | ||
![]() |
958cef5084 | ||
![]() |
9f5ace9025 | ||
![]() |
3a6e2631bf | ||
![]() |
c7adcf9fdf | ||
![]() |
da3616e636 | ||
![]() |
b371fe67a5 | ||
![]() |
6324c7d527 | ||
![]() |
6263fe283b | ||
![]() |
9a7617f9b8 | ||
![]() |
9a71358dfa | ||
![]() |
a96a2e80a1 | ||
![]() |
68200133b6 | ||
![]() |
188d502c59 | ||
![]() |
84ac683c1d | ||
![]() |
b418c94215 | ||
![]() |
38e593a698 | ||
![]() |
38028e8e90 | ||
![]() |
3db27052a1 | ||
![]() |
e24f9223df | ||
![]() |
9e5ed82c99 | ||
![]() |
606fa278c4 | ||
![]() |
7dd549ff1e | ||
![]() |
3c12b04c98 | ||
![]() |
5545850f9d | ||
![]() |
2f909b44d7 | ||
![]() |
ff155ebbb4 | ||
![]() |
a0a4fa0e56 | ||
![]() |
1b76a26691 | ||
![]() |
bd7bd2739a | ||
![]() |
a7ad407f4b | ||
![]() |
931ff0fde6 | ||
![]() |
dfd847f705 | ||
![]() |
a1fee720b9 | ||
![]() |
963854f8d5 | ||
![]() |
2ef5e7004b | ||
![]() |
2e6f0dd442 | ||
![]() |
7790783949 | ||
![]() |
f4534c8273 | ||
![]() |
76673f02a4 | ||
![]() |
b47ae0944c | ||
![]() |
f9712cbc7c | ||
![]() |
1b6d845530 | ||
![]() |
19424aba9d | ||
![]() |
71ac9b34fa | ||
![]() |
7651d4a249 | ||
![]() |
f8e0bccdf0 | ||
![]() |
a5b6eb6afa | ||
![]() |
41471da9db | ||
![]() |
e71767688d | ||
![]() |
5467e3a842 | ||
![]() |
511017ab35 | ||
![]() |
c3e8cfac79 | ||
![]() |
bf2b3c68f8 | ||
![]() |
68e74a8c03 | ||
![]() |
5b4a540440 | ||
![]() |
88da5c05c7 | ||
![]() |
2d369e8945 | ||
![]() |
02548c4b9b | ||
![]() |
71990e3ccc | ||
![]() |
5e7dcc48b9 | ||
![]() |
5cdba87b87 | ||
![]() |
696315615d | ||
![]() |
dd3f820040 | ||
![]() |
78ca16dfe3 | ||
![]() |
adc32d5a5c | ||
![]() |
12e4514813 | ||
![]() |
587fb37bdf | ||
![]() |
d221e52929 | ||
![]() |
e151a6cf92 | ||
![]() |
9ced5ce131 | ||
![]() |
c8e876bfe2 | ||
![]() |
2a716bbc7f | ||
![]() |
00590b6c0d | ||
![]() |
2ff4be7846 | ||
![]() |
ff5727c5ef | ||
![]() |
e46bc343e4 | ||
![]() |
5972fd2353 | ||
![]() |
8c0e4b1d33 | ||
![]() |
a737a754d1 | ||
![]() |
fc8685a042 | ||
![]() |
cc4765b4fe | ||
![]() |
eccdf87f22 | ||
![]() |
862bb2ac72 | ||
![]() |
34d416a4e8 | ||
![]() |
410f64bc9f | ||
![]() |
978e68fc74 | ||
![]() |
a17d8f8a66 | ||
![]() |
7e47490e70 | ||
![]() |
f4f94d3b56 | ||
![]() |
eae9d45c8a | ||
![]() |
d2d157c1fe | ||
![]() |
9c95d4ba43 | ||
![]() |
6cbd7404f4 | ||
![]() |
ad8a8ef5a9 | ||
![]() |
e6766a1ee2 | ||
![]() |
b4ccdaa51c | ||
![]() |
3a11656909 | ||
![]() |
2479fab632 | ||
![]() |
51eb1ac623 | ||
![]() |
879f37f046 | ||
![]() |
f2aadd6014 | ||
![]() |
1ad81504ca | ||
![]() |
425ee4e142 | ||
![]() |
42d8300bb7 | ||
![]() |
a9d75c5255 | ||
![]() |
98afd548d1 | ||
![]() |
7fc256dba7 | ||
![]() |
21b23624ad | ||
![]() |
1ace5c0c8b | ||
![]() |
bee99beab1 | ||
![]() |
8b363d3d1f | ||
![]() |
c7b0c3cd48 | ||
![]() |
c0c9b28582 | ||
![]() |
c3fffbc919 | ||
![]() |
6613aaea95 | ||
![]() |
53ce6b93a2 | ||
![]() |
d8e293842f | ||
![]() |
5cf869df1a | ||
![]() |
f26f7d8809 | ||
![]() |
c5a90475af | ||
![]() |
3008d7ef2f | ||
![]() |
1823f86dbb | ||
![]() |
06bf088d27 | ||
![]() |
9953698a7c | ||
![]() |
bda1605627 | ||
![]() |
2e0e88db0d | ||
![]() |
96482cc0cf | ||
![]() |
a283aaf724 | ||
![]() |
5db276eb52 | ||
![]() |
c866619f56 | ||
![]() |
b9da505efe | ||
![]() |
061a7c67bd | ||
![]() |
6f7331e852 | ||
![]() |
1b489bcc11 | ||
![]() |
da27924a49 | ||
![]() |
15b39887c5 | ||
![]() |
f9583a7652 | ||
![]() |
cb738188de | ||
![]() |
a8b2f5268d | ||
![]() |
ad50fc9ad3 | ||
![]() |
a25c2b325b | ||
![]() |
c57bf61114 | ||
![]() |
b7935d4b14 | ||
![]() |
00982f3620 | ||
![]() |
088b2045d0 | ||
![]() |
633ff1cfc8 | ||
![]() |
6cda6b6c10 | ||
![]() |
90573625f1 | ||
![]() |
d49e97c423 | ||
![]() |
39a80e414e | ||
![]() |
ab9153ddc3 | ||
![]() |
7ec1f487c1 | ||
![]() |
c96628b72e | ||
![]() |
e5ded9a2fb | ||
![]() |
5823f47467 | ||
![]() |
a0b7f09252 | ||
![]() |
b60a30c705 | ||
![]() |
4fc1a9e770 | ||
![]() |
f0908b663f | ||
![]() |
5fa596fee9 | ||
![]() |
ada1b95ffc | ||
![]() |
72b3bdf676 | ||
![]() |
71d1246374 | ||
![]() |
ac371bb596 | ||
![]() |
830ee8f27d | ||
![]() |
425dd45109 | ||
![]() |
6a039de8db | ||
![]() |
8d783aa172 | ||
![]() |
a4e5f5005b | ||
![]() |
a7c6edeb63 | ||
![]() |
4f23b49fef | ||
![]() |
cfcc8b1a6f | ||
![]() |
ebec582ce2 | ||
![]() |
3d701fbe0e | ||
![]() |
e95da11115 | ||
![]() |
9f6a798ea6 | ||
![]() |
36c8df4d2f | ||
![]() |
baf2f60850 | ||
![]() |
9ac39005f8 | ||
![]() |
9c078b78c3 | ||
![]() |
281aecef4c | ||
![]() |
4199b0ca64 | ||
![]() |
6973e099fd | ||
![]() |
8fffa206e4 | ||
c987ee199d | |||
![]() |
15204131c9 | ||
![]() |
23661737ef | ||
![]() |
5ab5a846aa | ||
![]() |
e93c762f16 | ||
![]() |
023f407b0d | ||
![]() |
64e4f4658a | ||
![]() |
aa22fe68e5 | ||
![]() |
15b514130e | ||
![]() |
a0f9333a13 | ||
![]() |
287e28a722 | ||
![]() |
c1522ab94c | ||
![]() |
0af4bfdbdf | ||
![]() |
94c4fcbad7 | ||
![]() |
a99f62f693 | ||
![]() |
fd4864d475 | ||
![]() |
c5610a6a13 | ||
![]() |
bcc3460dda | ||
![]() |
4794fccfb8 | ||
![]() |
637e7e93e0 | ||
![]() |
2e4b08e5ab | ||
![]() |
a64c34d29e | ||
![]() |
1d40b8a88a | ||
![]() |
26f538d193 | ||
![]() |
afcfac31a9 | ||
![]() |
6cff5a955c | ||
![]() |
e21b0b3773 | ||
![]() |
d65ee874e4 | ||
![]() |
c803f42a84 | ||
![]() |
3409fe6dd3 | ||
![]() |
4786c0986b | ||
![]() |
a7180850e0 | ||
![]() |
e1084bd913 | ||
![]() |
c5f839c9ad | ||
![]() |
87cb3b90ea | ||
![]() |
198004da86 | ||
![]() |
b41e9be4c9 | ||
![]() |
739b496bf6 | ||
![]() |
75af27acf1 | ||
![]() |
d0fd673b60 | ||
![]() |
2f54c94372 | ||
![]() |
67c2dfd884 | ||
![]() |
eeb3c6d3bf | ||
![]() |
727281e69e | ||
![]() |
4e99a32537 | ||
![]() |
3373eb864d | ||
![]() |
b85df4f2a1 | ||
![]() |
855d152503 | ||
![]() |
a1969b2fe6 | ||
![]() |
b91d4d3003 | ||
![]() |
aa66633df8 | ||
![]() |
f1c32f84f4 | ||
![]() |
cb3f87bb27 | ||
![]() |
697f0875e6 | ||
![]() |
6ad26cc8fa | ||
![]() |
c2cc33c6d7 | ||
![]() |
8ea25a8fc7 | ||
![]() |
4363315ec5 | ||
![]() |
c46b14b92c | ||
![]() |
f41b1fc821 | ||
![]() |
e6b0d43d66 | ||
![]() |
a52ea50006 | ||
![]() |
17d5dd3f94 | ||
![]() |
9e8ab747e4 | ||
![]() |
cdacc0b1be | ||
![]() |
b4b998b2e5 | ||
![]() |
a3ab2bf58e | ||
![]() |
adee7bd283 | ||
![]() |
7bd8a0276c | ||
![]() |
0cf27a0981 | ||
![]() |
bf673c5d8b | ||
![]() |
2235a32316 | ||
![]() |
1dee049007 | ||
![]() |
e9ba95b9dc | ||
![]() |
d3bd785289 | ||
![]() |
3ce4132c58 | ||
![]() |
ce2dcaf71d | ||
![]() |
cf72c3a788 | ||
![]() |
cd7a3ab2b2 | ||
![]() |
0a4b9b4984 | ||
![]() |
70370faf5d | ||
![]() |
24a53a671c | ||
![]() |
503b4827d9 | ||
![]() |
eeb374798b | ||
![]() |
3f6aa0336c | ||
![]() |
78a8495399 | ||
![]() |
636c020772 | ||
![]() |
a4512e50fb | ||
![]() |
f510989c1f | ||
![]() |
129884f44d | ||
![]() |
4bb0fb67a8 | ||
![]() |
68cc325ace | ||
![]() |
3d3a5aefa2 | ||
![]() |
2c6a21d503 | ||
![]() |
b7e7274b98 | ||
![]() |
b70cb01413 | ||
![]() |
701391f232 | ||
![]() |
85ea4c165b | ||
![]() |
22d2cd3388 | ||
![]() |
d8c222ae79 | ||
![]() |
d20e622b7b | ||
![]() |
6c8a0ccecb | ||
![]() |
2f547f73f7 | ||
![]() |
5f29e939b0 | ||
![]() |
465215686b | ||
![]() |
d2ceccd646 | ||
![]() |
7ed4c41d39 | ||
![]() |
065893b523 | ||
![]() |
4204fa2966 | ||
![]() |
1f24591a0d | ||
![]() |
4cccf53775 | ||
![]() |
70038c9144 | ||
![]() |
39ef20b298 | ||
![]() |
74a6aa32a2 | ||
![]() |
c7984070a2 | ||
![]() |
9e76966e0f | ||
![]() |
450c33db64 | ||
![]() |
34febec65f | ||
![]() |
5c6bc183fd | ||
![]() |
7669801e69 | ||
![]() |
941d7f7262 | ||
![]() |
fe2a39e4f1 | ||
![]() |
8eb5683783 | ||
![]() |
8fda060611 | ||
![]() |
3ec223ec94 | ||
![]() |
086eb847ec | ||
![]() |
7d68335c1d | ||
![]() |
9bce83704a | ||
![]() |
191afb6a6c | ||
![]() |
4ca942b169 | ||
![]() |
4cef6d1c25 | ||
![]() |
af10f82d14 | ||
![]() |
3f01748d75 | ||
![]() |
5aaccd2e9e | ||
![]() |
771f1735e5 | ||
![]() |
4428409d41 | ||
![]() |
52a125dded | ||
![]() |
caeabb5b62 | ||
![]() |
e2bc7ed797 | ||
![]() |
9133a6f511 | ||
![]() |
6d6fbb5efa | ||
![]() |
0d6f3ee374 | ||
![]() |
28c82238d0 | ||
![]() |
1a06ebeee0 | ||
![]() |
0dd538f9ff | ||
![]() |
96b1fb1f0e | ||
![]() |
219d55dfda | ||
![]() |
29c093f83f | ||
![]() |
7496b0a2c8 | ||
![]() |
3889f8683d | ||
![]() |
712a60ffd2 | ||
![]() |
8b5a89bf12 | ||
![]() |
9a2acc826e | ||
![]() |
4fa1d82b81 | ||
![]() |
a12bb4cead | ||
![]() |
5ef5dd2c09 | ||
![]() |
7dd09289ee | ||
![]() |
a9a4c900e4 | ||
![]() |
d689ba5904 | ||
![]() |
a47b803385 | ||
![]() |
14fbe6178f | ||
![]() |
02a65e34cf | ||
![]() |
7793894621 | ||
![]() |
e5b96b2f17 | ||
![]() |
865a346903 | ||
![]() |
12a99bd291 | ||
![]() |
afef0ec1fe | ||
![]() |
378aaadb68 | ||
![]() |
b0737ba230 | ||
![]() |
6890993c28 | ||
![]() |
783979b6b9 | ||
![]() |
c26705e6b1 | ||
![]() |
6c44ccd597 | ||
![]() |
ed6b03d24a | ||
![]() |
27f926cfc7 | ||
![]() |
cb4108c1b4 | ||
![]() |
1c5bff7ed7 | ||
![]() |
bba5098ff1 | ||
![]() |
41f8eb68c9 | ||
![]() |
9886021428 | ||
![]() |
ba0739798a | ||
![]() |
16b3490576 | ||
![]() |
1bb826109c | ||
![]() |
9ea82e9541 | ||
![]() |
4c47549253 | ||
![]() |
715ec07a28 | ||
![]() |
6fadb4250c | ||
![]() |
d2cf50f9ee | ||
![]() |
a710698277 | ||
![]() |
176b75b97e | ||
![]() |
c9f22868b3 | ||
![]() |
95ed7a5775 | ||
![]() |
7ce9ae50e7 | ||
![]() |
671c4d1341 | ||
![]() |
0a95af5dc1 | ||
![]() |
5cdb181cc5 | ||
![]() |
77b0a38c26 | ||
![]() |
ab810744ec | ||
![]() |
b1cc72e212 | ||
![]() |
ceb9ea1e52 | ||
![]() |
fa542c70df | ||
![]() |
c416c44f43 | ||
![]() |
d591d0ed29 | ||
![]() |
7410ce9077 | ||
![]() |
ff42394bdb | ||
![]() |
7af538793c | ||
![]() |
730715e68b | ||
![]() |
76dc32ee32 | ||
![]() |
c0704194ec | ||
![]() |
2356dbcae8 | ||
![]() |
68103e9a8d | ||
![]() |
968916c0b8 | ||
![]() |
f54f0e3f6a | ||
![]() |
88bacf12a3 | ||
![]() |
e93323ddbc | ||
![]() |
fde2c3fadf | ||
![]() |
79cd077a60 | ||
![]() |
cbfdf64a15 | ||
![]() |
dce4ea193a | ||
![]() |
7241eb37c9 | ||
![]() |
d4bbe0d8db | ||
![]() |
e690a7b389 | ||
![]() |
272258cf5a | ||
![]() |
d7eef6ff2e | ||
![]() |
7ee0b6dccb | ||
![]() |
7653a5f0f8 | ||
![]() |
74e077e0fb | ||
![]() |
a3b44aa612 | ||
![]() |
e23195f5f2 | ||
![]() |
ca8f31bdc7 | ||
![]() |
2d7c74eae5 | ||
![]() |
9b2bb07d89 | ||
![]() |
6b88b63941 | ||
![]() |
c0356eb72d | ||
![]() |
aef386178a | ||
![]() |
22bd43f725 | ||
![]() |
d600c9a526 | ||
![]() |
6b7046c8b7 | ||
![]() |
050d935891 | ||
![]() |
3508bf6c85 | ||
![]() |
dda0638869 | ||
![]() |
9fd98436af | ||
![]() |
eb288a80c3 | ||
![]() |
ed23e3b3d1 | ||
![]() |
1dbfcfb0b5 | ||
![]() |
4c84f37fd2 | ||
![]() |
cccbb3c889 | ||
![]() |
2e826a15e7 | ||
![]() |
fbc5f514e2 | ||
![]() |
3e65ee2f54 | ||
![]() |
0fc5694b6a | ||
![]() |
4e2897710b | ||
![]() |
9a7bf0a361 | ||
![]() |
ec4279eeb4 | ||
![]() |
8d49424226 | ||
![]() |
69bbc3a71e | ||
![]() |
af8d1af635 | ||
![]() |
23554239d0 | ||
![]() |
61cb2df9f3 | ||
![]() |
0eaabdf5ca | ||
![]() |
3db9fb1b69 | ||
![]() |
ef326dba19 | ||
![]() |
d7010d629d | ||
![]() |
7068013be8 | ||
![]() |
bd5a7e5b26 | ||
![]() |
fd675022c0 | ||
![]() |
a1f9c2e7d4 | ||
![]() |
db266a8484 | ||
![]() |
828e45651e | ||
![]() |
1039554039 | ||
![]() |
dbf20957a9 | ||
![]() |
da88d5c502 | ||
![]() |
2ae8ba0afc | ||
![]() |
017f3a2424 | ||
![]() |
caeb7246f0 | ||
![]() |
9b3b42316f | ||
![]() |
daac8d85e2 | ||
![]() |
a5ffeae757 | ||
![]() |
93819212b8 | ||
![]() |
6958943123 | ||
![]() |
73a81ff243 | ||
![]() |
eab710b0aa | ||
![]() |
a6483db3df | ||
![]() |
ff891c000e | ||
![]() |
e26b93728c | ||
![]() |
4db53525bf | ||
![]() |
09ee2b1644 | ||
![]() |
16d261553c | ||
![]() |
5bc189fbb7 | ||
![]() |
806a6dfaca | ||
![]() |
bfab8a1d9c | ||
![]() |
53cc3242e1 | ||
![]() |
cb60d08ee7 | ||
![]() |
6908e700e6 | ||
![]() |
01f44483df | ||
![]() |
950d504bd8 | ||
![]() |
ff7ea427ef | ||
![]() |
0b8ab8eccc | ||
![]() |
db1516ba00 | ||
![]() |
81de9d5a63 | ||
![]() |
c1bdbef9cf | ||
![]() |
6104354fa1 | ||
![]() |
b728aea382 | ||
![]() |
21411af74d | ||
![]() |
129693f533 | ||
![]() |
4fa9961c15 | ||
![]() |
3ee7fdd90e | ||
![]() |
71b00d644f | ||
![]() |
ea6680281f | ||
![]() |
2171ca9f51 | ||
![]() |
4d004d5fed | ||
![]() |
8574688be7 | ||
![]() |
f3e5f34aeb | ||
![]() |
9a4150cd47 | ||
![]() |
72002ed3bd | ||
![]() |
95a269df7a | ||
![]() |
9ecdde2292 | ||
![]() |
1ad81564ad | ||
![]() |
ee256d0a8d | ||
![]() |
987f2d0eb2 | ||
![]() |
908b7f7374 | ||
![]() |
18f57f24fa | ||
![]() |
812141f400 | ||
![]() |
75b7fdac58 | ||
![]() |
5c551fd899 | ||
![]() |
24a65d8fa9 | ||
![]() |
65aa7609d7 | ||
![]() |
0581e49d49 | ||
![]() |
5c809c2499 | ||
![]() |
98e3c70460 | ||
![]() |
6563a9241b | ||
![]() |
356ca08337 | ||
![]() |
b86a33d058 | ||
![]() |
504d3c0529 | ||
![]() |
11c7b246e0 | ||
![]() |
37b3cb4a30 | ||
![]() |
d6772cf1e4 | ||
![]() |
e7be3c6b1e | ||
![]() |
3a0b0aa116 | ||
![]() |
a605c1acbc | ||
![]() |
b374a69b2c | ||
![]() |
b5121db886 | ||
![]() |
a05e695db9 | ||
![]() |
c43f25e23b | ||
![]() |
e5ac567c79 | ||
![]() |
5dc0a8b5c9 | ||
![]() |
eca99576a0 | ||
![]() |
1250088f98 | ||
![]() |
507a98f28f | ||
![]() |
a8c529eca5 | ||
![]() |
c5ac5a0d17 | ||
![]() |
e3869fea18 | ||
![]() |
6f6cb58d8b | ||
![]() |
3fe72154a3 | ||
![]() |
97eef62684 | ||
![]() |
e4cf010bda | ||
![]() |
ec48077dbe | ||
![]() |
2df29701ed | ||
![]() |
d9a8311b8e | ||
![]() |
d14b96d55e | ||
![]() |
41621193ec | ||
![]() |
a12ac37cc3 | ||
![]() |
4c7c64c9b8 | ||
![]() |
80b3135a93 | ||
![]() |
1cd3e42182 | ||
![]() |
2e8ed1cfba | ||
![]() |
b9a98c88ba | ||
![]() |
0b554be10a | ||
![]() |
4872075bf7 | ||
![]() |
f070e2d064 | ||
![]() |
8f2877e806 | ||
![]() |
b6b015fe1f | ||
![]() |
7179dd4c0d | ||
![]() |
1dda27e19b | ||
![]() |
d1a1e87ab5 | ||
![]() |
6b4e285186 | ||
![]() |
891ad8711d | ||
![]() |
540e924bfb | ||
![]() |
f265f7c594 | ||
![]() |
aaddc9fcfd | ||
![]() |
f53e66e2c0 | ||
![]() |
859d176c93 | ||
![]() |
52d66897e4 | ||
![]() |
8b327708ee | ||
![]() |
903ada06f0 | ||
![]() |
a7664a5559 | ||
![]() |
0294fc5f20 | ||
![]() |
46e7f2dfc9 | ||
![]() |
fc64a6c2ff | ||
![]() |
b6671cd00c | ||
![]() |
7926230682 | ||
![]() |
dd66e3068a | ||
![]() |
04a6eff14c | ||
![]() |
05de455a9c | ||
![]() |
12a7b7afc3 | ||
![]() |
dfaa687f71 | ||
![]() |
219819b738 |
63
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
63
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: Bug inside BungeeCord
|
||||
description: Create a bug report about a problem inside BungeeCord.
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
#### Report a bug inside bungeecord
|
||||
Issues happening with forks of BungeeCord should **not** be reported here.
|
||||
- type: input
|
||||
id: bungee-version
|
||||
attributes:
|
||||
label: Bungeecord version
|
||||
description: The output of the /bungee command (or just the bungee build number) (execute in bungeecord console for easy text copy)
|
||||
placeholder: e.g. git:BungeeCord-Bootstrap:1.xx-SNAPSHOT:xxxxxxx:xxxx
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: server-version
|
||||
attributes:
|
||||
label: Server version
|
||||
description: The output of the /version command (execute in server console for easy text copy)
|
||||
placeholder: "e.g. git-Spigot-xxxxxxx-xxxxxxx (MC: 1.x.x)"
|
||||
- type: input
|
||||
id: client-version
|
||||
attributes:
|
||||
label: Client version
|
||||
description: Minecraft Client Version
|
||||
placeholder: e.g. 1.18.2
|
||||
- type: textarea
|
||||
id: bungee-plugins
|
||||
attributes:
|
||||
label: Bungeecord plugins
|
||||
description: Please list all BungeeCord plugins you are using.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: the-bug
|
||||
attributes:
|
||||
label: The bug
|
||||
description: Please describe the bug. Include **details** you find neccessary. If you just have a question, please ask it in [SpigotMC Forums](https://www.spigotmc.org) and not here.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Log output (links)
|
||||
description: Please put your log output inbetween three backticks (```` ``` ````). Upload your log files to [gist.github.com](https://gist.github.com) and put them in here.
|
||||
placeholder: |
|
||||
```
|
||||
log output
|
||||
```
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: Checking
|
||||
options:
|
||||
- label: I am using BungeeCord and **not a fork**. Issues with forks should not be reported here.
|
||||
required: true
|
||||
- label: I think this is **not** an issue with a bungeecord plugin.
|
||||
required: true
|
||||
- label: I have not read these checkboxes and therefore I just ticked them all.
|
||||
- label: This is not a question or plugin creation help request.
|
||||
required: true
|
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Configuration help
|
||||
url: https://www.spigotmc.org/forums/bungeecord-help.70/create-thread
|
||||
about: Help for configuring bungeecord will only be answered in spigotmc.org forums.
|
||||
- name: I have a problem with a bungee plugin
|
||||
url: https://www.spigotmc.org/forums/bungeecord-plugin-help.71/create-thread
|
||||
about: Help about plugins can be recieved in spigotmc.org forums.
|
||||
- name: Questions and discussions
|
||||
url: https://www.spigotmc.org/forums/bungeecord-discussion.21/create-thread
|
||||
about: spigotmc.org forums are the best place to ask your questions regarding bungeecord.
|
||||
- name: Plugin creation help
|
||||
url: https://www.spigotmc.org/forums/bungeecord-plugin-development.23/create-thread
|
||||
about: Plugin creation help for bungee plugins can be recieved in spigotmc.org forums.
|
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Feature request
|
||||
description: Suggest a feature which bungeecord should include.
|
||||
body:
|
||||
- type: textarea
|
||||
id: the-feature
|
||||
attributes:
|
||||
label: Feature description
|
||||
description: Please describe your feature or improvement. Please include **details**.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: goal
|
||||
attributes:
|
||||
label: Goal of the feature
|
||||
description: What is the goal of your feature?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Unfitting alternatives
|
||||
description: What alternatives have you considered and why are they not sufficient for your use case?
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: Checking
|
||||
options:
|
||||
- label: This is not a question or plugin creation help request.
|
||||
required: true
|
||||
- label: This is a **feature or improvement request**.
|
||||
required: true
|
||||
- label: I have not read these checkboxes and therefore I just ticked them all.
|
||||
- label: I did not use this form to report a bug.
|
||||
required: true
|
28
.github/dependabot.yml
vendored
Normal file
28
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: "maven"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 50
|
||||
ignore:
|
||||
# Synchronised with Minecraft
|
||||
- dependency-name: "com.google.code.gson:gson"
|
||||
# 9.x has performance issues (see, eg, checkstyle/checkstyle#10934) and 10.x is incompatible
|
||||
- dependency-name: "com.puppycrawl.tools:checkstyle"
|
||||
# Newer versions have issues, see #1909 and #2050
|
||||
- dependency-name: "jline:jline"
|
||||
# Later versions of these Maven dependencies are incompatible and require careful management - see SPIGOT-7400
|
||||
- dependency-name: "org.apache.maven.resolver:maven-resolver-connector-basic"
|
||||
- dependency-name: "org.apache.maven.resolver:maven-resolver-transport-http"
|
||||
- dependency-name: "org.apache.maven:maven-resolver-provider"
|
||||
# Used with maven-resolver dependencies; 2.0 update breaks other providers
|
||||
- dependency-name: "org.slf4j:slf4j-api"
|
||||
update-types: ["version-update:semver-major"]
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 50
|
23
.github/workflows/maven.yml
vendored
Normal file
23
.github/workflows/maven.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Maven Build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java: [8, 11, 17, 21]
|
||||
|
||||
name: Java ${{ matrix.java }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: ${{ matrix.java }}
|
||||
- run: java -version && mvn --version
|
||||
- run: mvn --activate-profiles dist --no-transfer-progress package
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -24,7 +24,7 @@ dist/
|
||||
manifest.mf
|
||||
|
||||
# Mac filesystem dust
|
||||
.DS_Store/
|
||||
.DS_Store
|
||||
|
||||
# intellij
|
||||
*.iml
|
||||
|
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "native/mbedtls"]
|
||||
path = native/mbedtls
|
||||
url = https://github.com/ARMmbed/mbedtls.git
|
||||
[submodule "native/zlib"]
|
||||
path = native/zlib
|
||||
url = https://github.com/cloudflare/zlib.git
|
@@ -1,8 +0,0 @@
|
||||
sudo: false
|
||||
language: java
|
||||
jdk:
|
||||
- openjdk7
|
||||
- oraclejdk7
|
||||
- oraclejdk8
|
||||
notifications:
|
||||
email: false
|
31
README.md
31
README.md
@@ -1,19 +1,26 @@
|
||||
BungeeCord
|
||||
==========
|
||||
The most reliable Minecraft server portal suite.
|
||||
------------------------------------------------
|
||||
BungeeCord is a piece of Java software which allows a user to link multiple Minecraft servers together, allowing players to teleport between them and access advanced features. This makes it perfect for servers looking to expand their player base and spread across multiple gameplay styles.
|
||||
Layer 7 proxy designed to link Minecraft servers.
|
||||
--------------------------------------------------
|
||||
|
||||
History
|
||||
-------
|
||||
For a long time developers have tried to create these mythical 'cloud' systems as outlined above. Until now, none of them have succeeded in making a fully open source and reliable 'cloud'. You may have already noticed my quoting of the word 'cloud', and this is one of the last times you will see it in this document. BungeeCord is **NOT** a 'cloud'. The actual meaning of the aforementioned word is quoted below:
|
||||
BungeeCord is a sophisticated proxy and API designed mainly to teleport players between multiple Minecraft servers. It is the latest incarnation of similar software written by the author from 2011-present.
|
||||
|
||||
>The use of computing resources (hardware and software) that are delivered as a service over a network (typically the Internet).
|
||||
Information
|
||||
-----------
|
||||
BungeeCord is maintained by [SpigotMC](https://www.spigotmc.org/) and has its own [discussion thread](https://www.spigotmc.org/go/bungeecord) with plenty of helpful information and links.
|
||||
|
||||
BungeeCord does not do this and therefore is not a 'cloud'. Please do not refer to it as one, since the owners and operators of real clouds will get very angry, and you will be seen as ignorant and stupid. Instead I encourage you to use the term 'Server Port Suite' or something to that effect.
|
||||
### Security warning
|
||||
|
||||
Installation & Usage
|
||||
--------------------
|
||||
For and in depth guide to the installation and usage of BungeeCord you should check the current primary download location. The current link is provided below for your convenience.
|
||||
As your Minecraft servers have to run without authentication (online-mode=false) for BungeeCord to work, this poses a new security risk. Users may connect to your servers directly, under any username they wish to use. The kick "If you wish to use IP forwarding, please enable it in your BungeeCord config as well!" does not protect your Spigot servers.
|
||||
|
||||
<http://www.spigotmc.org/threads/bungeecord.392/>
|
||||
To combat this, you need to restrict access to these servers for example with a firewall (please see [firewall guide](https://www.spigotmc.org/wiki/firewall-guide/)).
|
||||
|
||||
Source
|
||||
------
|
||||
Source code is currently available on [GitHub](https://www.spigotmc.org/go/bungeecord-git).
|
||||
|
||||
Binaries
|
||||
--------
|
||||
Precompiled binaries are available for end users on [Jenkins](https://www.spigotmc.org/go/bungeecord-dl).
|
||||
|
||||
(c) 2012-2023 SpigotMC Pty. Ltd.
|
||||
|
36
api/pom.xml
36
api/pom.xml
@@ -6,13 +6,13 @@
|
||||
<parent>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-parent</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<version>1.20-R0.2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<version>1.20-R0.2-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>BungeeCord-API</name>
|
||||
@@ -43,5 +43,37 @@
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport-native-unix-common</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-resolver-provider</artifactId>
|
||||
<version>3.8.5</version>
|
||||
<!-- not part of the API proper -->
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.resolver</groupId>
|
||||
<artifactId>maven-resolver-connector-basic</artifactId>
|
||||
<version>1.7.3</version>
|
||||
<!-- not part of the API proper -->
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.resolver</groupId>
|
||||
<artifactId>maven-resolver-transport-http</artifactId>
|
||||
<version>1.7.3</version>
|
||||
<!-- not part of the API proper -->
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>2.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@@ -1,9 +1,13 @@
|
||||
package net.md_5.bungee;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.primitives.UnsignedLongs;
|
||||
import io.netty.channel.unix.DomainSocketAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -20,15 +24,35 @@ public class Util
|
||||
* @param hostline in the format of 'host:port'
|
||||
* @return the constructed hostname + port.
|
||||
*/
|
||||
public static InetSocketAddress getAddr(String hostline)
|
||||
public static SocketAddress getAddr(String hostline)
|
||||
{
|
||||
URI uri;
|
||||
URI uri = null;
|
||||
try
|
||||
{
|
||||
uri = new URI( "tcp://" + hostline );
|
||||
uri = new URI( hostline );
|
||||
} catch ( URISyntaxException ex )
|
||||
{
|
||||
throw new IllegalArgumentException( "Bad hostline", ex );
|
||||
}
|
||||
|
||||
if ( uri != null && "unix".equals( uri.getScheme() ) )
|
||||
{
|
||||
return new DomainSocketAddress( uri.getPath() );
|
||||
}
|
||||
|
||||
if ( uri == null || uri.getHost() == null )
|
||||
{
|
||||
try
|
||||
{
|
||||
uri = new URI( "tcp://" + hostline );
|
||||
} catch ( URISyntaxException ex )
|
||||
{
|
||||
throw new IllegalArgumentException( "Bad hostline: " + hostline, ex );
|
||||
}
|
||||
}
|
||||
|
||||
if ( uri.getHost() == null )
|
||||
{
|
||||
throw new IllegalArgumentException( "Invalid host/address: " + hostline );
|
||||
}
|
||||
|
||||
return new InetSocketAddress( uri.getHost(), ( uri.getPort() ) == -1 ? DEFAULT_PORT : uri.getPort() );
|
||||
@@ -45,6 +69,17 @@ public class Util
|
||||
return String.format( "0x%02X", i );
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an char as a unicode value.
|
||||
*
|
||||
* @param c the character to format
|
||||
* @return the unicode representation of the character
|
||||
*/
|
||||
public static String unicode(char c)
|
||||
{
|
||||
return "\\u" + String.format( "%04x", (int) c ).toUpperCase( Locale.ROOT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a pretty one line version of a {@link Throwable}. Useful for
|
||||
* debugging.
|
||||
@@ -53,11 +88,24 @@ public class Util
|
||||
* @return a string representing information about the {@link Throwable}
|
||||
*/
|
||||
public static String exception(Throwable t)
|
||||
{
|
||||
return exception( t, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a pretty one line version of a {@link Throwable}. Useful for
|
||||
* debugging.
|
||||
*
|
||||
* @param t the {@link Throwable} to format.
|
||||
* @param includeLineNumbers whether to include line numbers
|
||||
* @return a string representing information about the {@link Throwable}
|
||||
*/
|
||||
public static String exception(Throwable t, boolean includeLineNumbers)
|
||||
{
|
||||
// TODO: We should use clear manually written exceptions
|
||||
StackTraceElement[] trace = t.getStackTrace();
|
||||
return t.getClass().getSimpleName() + " : " + t.getMessage()
|
||||
+ ( ( trace.length > 0 ) ? " @ " + t.getStackTrace()[0].getClassName() + ":" + t.getStackTrace()[0].getLineNumber() : "" );
|
||||
+ ( ( includeLineNumbers && trace.length > 0 ) ? " @ " + t.getStackTrace()[0].getClassName() + ":" + t.getStackTrace()[0].getLineNumber() : "" );
|
||||
}
|
||||
|
||||
public static String csv(Iterable<?> objects)
|
||||
@@ -65,6 +113,16 @@ public class Util
|
||||
return format( objects, ", " );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string of objects, each separated by a separator.
|
||||
*
|
||||
* @param objects the objects to join
|
||||
* @param separators the separator
|
||||
* @return joined string
|
||||
* @see String#join(java.lang.CharSequence, java.lang.Iterable)
|
||||
* @deprecated use {@link String} join methods
|
||||
*/
|
||||
@Deprecated
|
||||
public static String format(Iterable<?> objects, String separators)
|
||||
{
|
||||
return Joiner.on( separators ).join( objects );
|
||||
@@ -78,6 +136,6 @@ public class Util
|
||||
*/
|
||||
public static UUID getUUID(String uuid)
|
||||
{
|
||||
return UUID.fromString( uuid.substring( 0, 8 ) + "-" + uuid.substring( 8, 12 ) + "-" + uuid.substring( 12, 16 ) + "-" + uuid.substring( 16, 20 ) + "-" + uuid.substring( 20, 32 ) );
|
||||
return new UUID( UnsignedLongs.parseUnsignedLong( uuid.substring( 0, 16 ), 16 ), UnsignedLongs.parseUnsignedLong( uuid.substring( 16 ), 16 ) );
|
||||
}
|
||||
}
|
||||
|
@@ -28,18 +28,13 @@ public abstract class AbstractReconnectHandler implements ReconnectHandler
|
||||
|
||||
public static ServerInfo getForcedHost(PendingConnection con)
|
||||
{
|
||||
if ( con.getVirtualHost() == null )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
String forced = con.getListener().getForcedHosts().get( con.getVirtualHost().getHostString() );
|
||||
String forced = ( con.getVirtualHost() == null ) ? null : con.getListener().getForcedHosts().get( con.getVirtualHost().getHostString() );
|
||||
|
||||
if ( forced == null && con.getListener().isForceDefault() )
|
||||
{
|
||||
forced = con.getListener().getDefaultServer();
|
||||
}
|
||||
return ProxyServer.getInstance().getServerInfo( forced );
|
||||
return ( forced == null ) ? null : ProxyServer.getInstance().getServerInfo( forced );
|
||||
}
|
||||
|
||||
protected abstract ServerInfo getStoredServer(ProxiedPlayer player);
|
||||
|
@@ -1,8 +1,7 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
|
||||
import java.util.Collection;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
|
||||
public interface CommandSender
|
||||
{
|
||||
|
@@ -1,9 +1,10 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.internal.bind.TypeAdapters;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -26,13 +27,26 @@ public class Favicon
|
||||
@Override
|
||||
public void write(JsonWriter out, Favicon value) throws IOException
|
||||
{
|
||||
TypeAdapters.STRING.write( out, value == null ? null : value.getEncoded() );
|
||||
if ( value == null )
|
||||
{
|
||||
out.nullValue();
|
||||
} else
|
||||
{
|
||||
out.value( value.getEncoded() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Favicon read(JsonReader in) throws IOException
|
||||
{
|
||||
String enc = TypeAdapters.STRING.read( in );
|
||||
JsonToken peek = in.peek();
|
||||
if ( peek == JsonToken.NULL )
|
||||
{
|
||||
in.nextNull();
|
||||
return null;
|
||||
}
|
||||
|
||||
String enc = in.nextString();
|
||||
return enc == null ? null : create( enc );
|
||||
}
|
||||
};
|
||||
@@ -59,6 +73,7 @@ public class Favicon
|
||||
*/
|
||||
public static Favicon create(BufferedImage image)
|
||||
{
|
||||
Preconditions.checkArgument( image != null, "image is null" );
|
||||
// check size
|
||||
if ( image.getWidth() != 64 || image.getHeight() != 64 )
|
||||
{
|
||||
|
@@ -1,10 +1,9 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
|
||||
/**
|
||||
* Core configuration adaptor for the proxy api.
|
||||
@@ -17,52 +16,96 @@ public interface ProxyConfig
|
||||
|
||||
/**
|
||||
* Time before users are disconnected due to no network activity.
|
||||
*
|
||||
* @return timeout
|
||||
*/
|
||||
int getTimeout();
|
||||
|
||||
/**
|
||||
* UUID used for metrics.
|
||||
*
|
||||
* @return uuid
|
||||
*/
|
||||
String getUuid();
|
||||
|
||||
/**
|
||||
* Set of all listeners.
|
||||
*
|
||||
* @return listeners
|
||||
*/
|
||||
Collection<ListenerInfo> getListeners();
|
||||
|
||||
/**
|
||||
* Set of all servers.
|
||||
*
|
||||
* @return servers
|
||||
*/
|
||||
Map<String, ServerInfo> getServers();
|
||||
|
||||
/**
|
||||
* Does the server authenticate with mojang
|
||||
* Does the server authenticate with Mojang.
|
||||
*
|
||||
* @return online mode
|
||||
*/
|
||||
boolean isOnlineMode();
|
||||
|
||||
/**
|
||||
* Whether proxy commands are logged to the proxy log
|
||||
* Whether proxy commands are logged to the proxy log.
|
||||
*
|
||||
* @return log commands
|
||||
*/
|
||||
boolean isLogCommands();
|
||||
|
||||
/**
|
||||
* Time in milliseconds to cache server list info from a ping request from
|
||||
* the proxy to a server.
|
||||
*
|
||||
* @return cache time
|
||||
*/
|
||||
int getRemotePingCache();
|
||||
|
||||
/**
|
||||
* Returns the player max.
|
||||
*
|
||||
* @return player limit
|
||||
*/
|
||||
int getPlayerLimit();
|
||||
|
||||
/**
|
||||
* A collection of disabled commands.
|
||||
*
|
||||
* @return disabled commands
|
||||
*/
|
||||
Collection<String> getDisabledCommands();
|
||||
|
||||
/**
|
||||
* Time in milliseconds before timing out a clients request to connect to a
|
||||
* server.
|
||||
*
|
||||
* @return connect timeout
|
||||
*/
|
||||
int getServerConnectTimeout();
|
||||
|
||||
/**
|
||||
* Time in milliseconds before timing out a ping request from the proxy to a
|
||||
* server when attempting to request server list info.
|
||||
*
|
||||
* @return ping timeout
|
||||
*/
|
||||
int getRemotePingTimeout();
|
||||
|
||||
/**
|
||||
* The connection throttle delay.
|
||||
*
|
||||
* @return throttle
|
||||
*/
|
||||
@Deprecated
|
||||
int getThrottle();
|
||||
|
||||
/**
|
||||
* Whether the proxy will parse IPs with spigot or not
|
||||
* Whether the proxy will parse IPs with spigot or not.
|
||||
*
|
||||
* @return ip forward
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isIpForward();
|
||||
@@ -70,6 +113,7 @@ public interface ProxyConfig
|
||||
/**
|
||||
* The encoded favicon.
|
||||
*
|
||||
* @return favicon
|
||||
* @deprecated Use #getFaviconObject instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@@ -77,6 +121,8 @@ public interface ProxyConfig
|
||||
|
||||
/**
|
||||
* The favicon used for the server ping list.
|
||||
*
|
||||
* @return favicon
|
||||
*/
|
||||
Favicon getFaviconObject();
|
||||
}
|
||||
|
@@ -1,19 +1,20 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.plugin.PluginManager;
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.io.File;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.config.ConfigurationAdapter;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.plugin.PluginManager;
|
||||
import net.md_5.bungee.api.scheduler.TaskScheduler;
|
||||
|
||||
public abstract class ProxyServer
|
||||
@@ -52,6 +53,8 @@ public abstract class ProxyServer
|
||||
/**
|
||||
* Gets a localized string from the .properties file.
|
||||
*
|
||||
* @param name translation name
|
||||
* @param args translation arguments
|
||||
* @return the localized string
|
||||
*/
|
||||
public abstract String getTranslation(String name, Object... args);
|
||||
@@ -154,14 +157,6 @@ public abstract class ProxyServer
|
||||
*/
|
||||
public abstract void stop(String reason);
|
||||
|
||||
/**
|
||||
* Start this instance so that it may accept connections.
|
||||
*
|
||||
* @throws Exception any exception thrown during startup causing the
|
||||
* instance to fail to boot
|
||||
*/
|
||||
public abstract void start() throws Exception;
|
||||
|
||||
/**
|
||||
* Register a channel for use with plugin messages. This is required by some
|
||||
* server / client implementations.
|
||||
@@ -212,6 +207,18 @@ public abstract class ProxyServer
|
||||
*/
|
||||
public abstract ServerInfo constructServerInfo(String name, InetSocketAddress address, String motd, boolean restricted);
|
||||
|
||||
/**
|
||||
* Factory method to construct an implementation specific server info
|
||||
* instance.
|
||||
*
|
||||
* @param name name of the server
|
||||
* @param address connectable Minecraft address + port of the server
|
||||
* @param motd the motd when used as a forced server
|
||||
* @param restricted whether the server info restricted property will be set
|
||||
* @return the constructed instance
|
||||
*/
|
||||
public abstract ServerInfo constructServerInfo(String name, SocketAddress address, String motd, boolean restricted);
|
||||
|
||||
/**
|
||||
* Returns the console overlord for this proxy. Being the console, this
|
||||
* command server cannot have permissions or groups, and will be able to
|
||||
|
@@ -0,0 +1,81 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.event.ServerConnectEvent;
|
||||
|
||||
/**
|
||||
* A request to connect a server.
|
||||
*/
|
||||
@Getter
|
||||
@Builder(builderClassName = "Builder")
|
||||
public class ServerConnectRequest
|
||||
{
|
||||
|
||||
/**
|
||||
* The result from this callback after request has been executed by proxy.
|
||||
*/
|
||||
public enum Result
|
||||
{
|
||||
|
||||
/**
|
||||
* ServerConnectEvent to the new server was canceled.
|
||||
*/
|
||||
EVENT_CANCEL,
|
||||
/**
|
||||
* Already connected to target server.
|
||||
*/
|
||||
ALREADY_CONNECTED,
|
||||
/**
|
||||
* Already connecting to target server.
|
||||
*/
|
||||
ALREADY_CONNECTING,
|
||||
/**
|
||||
* Successfully connected to server.
|
||||
*/
|
||||
SUCCESS,
|
||||
/**
|
||||
* Connection failed, error can be accessed from callback method handle.
|
||||
*/
|
||||
FAIL
|
||||
}
|
||||
|
||||
/**
|
||||
* Target server to connect to.
|
||||
*/
|
||||
@NonNull
|
||||
private final ServerInfo target;
|
||||
/**
|
||||
* Reason for connecting to server.
|
||||
*/
|
||||
@NonNull
|
||||
private final ServerConnectEvent.Reason reason;
|
||||
/**
|
||||
* Callback to execute post request.
|
||||
*/
|
||||
private final Callback<Result> callback;
|
||||
/**
|
||||
* Timeout in milliseconds for request.
|
||||
*/
|
||||
@Setter
|
||||
private int connectTimeout;
|
||||
/**
|
||||
* Should the player be attempted to connect to the next server in their
|
||||
* queue if the initial request fails.
|
||||
*/
|
||||
@Setter
|
||||
private boolean retry;
|
||||
|
||||
/**
|
||||
* Class that sets default properties/adds methods to the lombok builder
|
||||
* generated class.
|
||||
*/
|
||||
public static class Builder
|
||||
{
|
||||
|
||||
private int connectTimeout = ProxyServer.getInstance().getConfig().getServerConnectTimeout();
|
||||
}
|
||||
}
|
@@ -6,13 +6,17 @@ import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.Util;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
|
||||
/**
|
||||
* Represents the standard list data returned by opening a server in the
|
||||
* Minecraft client server list, or hitting it with a packet 0xFE.
|
||||
*/
|
||||
@Data
|
||||
@ToString(exclude = "favicon")
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ServerPing
|
||||
@@ -70,10 +74,11 @@ public class ServerPing
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return uniqueId.toString().replaceAll( "-", "" );
|
||||
return uniqueId.toString().replace( "-", "" );
|
||||
}
|
||||
}
|
||||
private String description;
|
||||
|
||||
private BaseComponent description;
|
||||
private Favicon favicon;
|
||||
|
||||
@Data
|
||||
@@ -100,7 +105,13 @@ public class ServerPing
|
||||
@Deprecated
|
||||
public ServerPing(Protocol version, Players players, String description, String favicon)
|
||||
{
|
||||
this( version, players, description, favicon == null ? null : Favicon.create( favicon ) );
|
||||
this( version, players, new TextComponent( TextComponent.fromLegacyText( description ) ), favicon == null ? null : Favicon.create( favicon ) );
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public ServerPing(Protocol version, Players players, String description, Favicon favicon)
|
||||
{
|
||||
this( version, players, new TextComponent( TextComponent.fromLegacyText( description ) ), favicon );
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@@ -124,4 +135,26 @@ public class ServerPing
|
||||
{
|
||||
this.favicon = favicon;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setDescription(String description)
|
||||
{
|
||||
this.description = new TextComponent( TextComponent.fromLegacyText( description ) );
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String getDescription()
|
||||
{
|
||||
return BaseComponent.toLegacyText( description );
|
||||
}
|
||||
|
||||
public void setDescriptionComponent(BaseComponent description)
|
||||
{
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public BaseComponent getDescriptionComponent()
|
||||
{
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
24
api/src/main/java/net/md_5/bungee/api/SkinConfiguration.java
Normal file
24
api/src/main/java/net/md_5/bungee/api/SkinConfiguration.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
/**
|
||||
* Represents a player's skin settings. These settings can be changed by the
|
||||
* player under Skin Configuration in the Options menu.
|
||||
*/
|
||||
public interface SkinConfiguration
|
||||
{
|
||||
|
||||
boolean hasCape();
|
||||
|
||||
boolean hasJacket();
|
||||
|
||||
boolean hasLeftSleeve();
|
||||
|
||||
boolean hasRightSleeve();
|
||||
|
||||
boolean hasLeftPants();
|
||||
|
||||
boolean hasRightPants();
|
||||
|
||||
boolean hasHat();
|
||||
|
||||
}
|
@@ -65,7 +65,7 @@ public interface Title
|
||||
* duration specified in {@link #fadeOut(int)}. The default value for the
|
||||
* official Minecraft version is 60 (3 seconds).
|
||||
*
|
||||
* @param ticks The amount of ticks (1/20 second) for the fade in effect.
|
||||
* @param ticks The amount of ticks (1/20 second) for the stay effect.
|
||||
* @return This title configuration.
|
||||
*/
|
||||
public Title stay(int ticks);
|
||||
|
@@ -1,7 +1,10 @@
|
||||
package net.md_5.bungee.api.config;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@@ -9,13 +12,14 @@ import lombok.Data;
|
||||
* multiple listeners on different ports.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class ListenerInfo
|
||||
{
|
||||
|
||||
/**
|
||||
* Host to bind to.
|
||||
*/
|
||||
private final InetSocketAddress host;
|
||||
private final SocketAddress socketAddress;
|
||||
/**
|
||||
* Displayed MOTD.
|
||||
*/
|
||||
@@ -29,14 +33,10 @@ public class ListenerInfo
|
||||
*/
|
||||
private final int tabListSize;
|
||||
/**
|
||||
* Name of the server which users will be taken to by default.
|
||||
* List of servers in order of join attempt. First attempt is the first
|
||||
* element, second attempt is the next element, etc etc.
|
||||
*/
|
||||
private final String defaultServer;
|
||||
/**
|
||||
* Name of the server which users will be taken when current server goes
|
||||
* down.
|
||||
*/
|
||||
private final String fallbackServer;
|
||||
private final List<String> serverPriority;
|
||||
/**
|
||||
* Whether reconnect locations will be used, or else the user is simply
|
||||
* transferred to the default server on connect.
|
||||
@@ -68,4 +68,51 @@ public class ListenerInfo
|
||||
* Whether to enable udp query.
|
||||
*/
|
||||
private final boolean queryEnabled;
|
||||
/**
|
||||
* Whether to support HAProxy PROXY protocol.
|
||||
*/
|
||||
private final boolean proxyProtocol;
|
||||
|
||||
@Deprecated
|
||||
public ListenerInfo(InetSocketAddress host, String motd, int maxPlayers, int tabListSize, List<String> serverPriority, boolean forceDefault, Map<String, String> forcedHosts, String tabListType, boolean setLocalAddress, boolean pingPassthrough, int queryPort, boolean queryEnabled)
|
||||
{
|
||||
this( host, motd, maxPlayers, tabListSize, serverPriority, forceDefault, forcedHosts, tabListType, setLocalAddress, pingPassthrough, queryPort, queryEnabled, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the highest priority server to join.
|
||||
*
|
||||
* @return default server
|
||||
* @deprecated replaced by {@link #serverPriority}
|
||||
*/
|
||||
@Deprecated
|
||||
public String getDefaultServer()
|
||||
{
|
||||
return serverPriority.get( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the second highest priority server to join, or else the highest
|
||||
* priority.
|
||||
*
|
||||
* @return fallback server
|
||||
* @deprecated replaced by {@link #serverPriority}
|
||||
*/
|
||||
@Deprecated
|
||||
public String getFallbackServer()
|
||||
{
|
||||
return ( serverPriority.size() > 1 ) ? serverPriority.get( 1 ) : getDefaultServer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bind address as an InetSocketAddress if possible.
|
||||
*
|
||||
* @return bind host
|
||||
* @deprecated BungeeCord can listen via Unix domain sockets
|
||||
*/
|
||||
@Deprecated
|
||||
public InetSocketAddress getHost()
|
||||
{
|
||||
return (InetSocketAddress) socketAddress;
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package net.md_5.bungee.api.config;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Collection;
|
||||
import net.md_5.bungee.api.Callback;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
@@ -26,9 +27,19 @@ public interface ServerInfo
|
||||
* class.
|
||||
*
|
||||
* @return the IP and port pair for this server
|
||||
* @deprecated BungeeCord can connect via Unix domain sockets
|
||||
*/
|
||||
@Deprecated
|
||||
InetSocketAddress getAddress();
|
||||
|
||||
/**
|
||||
* Gets the connectable address for this server. Implementations expect this
|
||||
* to be used as the unique identifier per each instance of this class.
|
||||
*
|
||||
* @return the address for this server
|
||||
*/
|
||||
SocketAddress getSocketAddress();
|
||||
|
||||
/**
|
||||
* Get the set of all players on this server.
|
||||
*
|
||||
@@ -43,6 +54,22 @@ public interface ServerInfo
|
||||
*/
|
||||
String getMotd();
|
||||
|
||||
/**
|
||||
* Whether this server is restricted and therefore only players with the
|
||||
* given permission can access it.
|
||||
*
|
||||
* @return if restricted
|
||||
*/
|
||||
boolean isRestricted();
|
||||
|
||||
/**
|
||||
* Get the permission required to access this server. Only enforced when the
|
||||
* server is restricted.
|
||||
*
|
||||
* @return access permission
|
||||
*/
|
||||
String getPermission();
|
||||
|
||||
/**
|
||||
* Whether the player can access this server. It will only return false when
|
||||
* the player has no permission and this server is restricted.
|
||||
@@ -56,6 +83,10 @@ public interface ServerInfo
|
||||
* Send data by any available means to this server. This data may be queued
|
||||
* and there is no guarantee of its timely arrival.
|
||||
*
|
||||
* In recent Minecraft versions channel names must contain a colon separator
|
||||
* and consist of [a-z0-9/._-]. This will be enforced in a future version.
|
||||
* The "BungeeCord" channel is an exception and may only take this form.
|
||||
*
|
||||
* @param channel the channel to send this data via
|
||||
* @param data the data to send
|
||||
*/
|
||||
@@ -64,6 +95,10 @@ public interface ServerInfo
|
||||
/**
|
||||
* Send data by any available means to this server.
|
||||
*
|
||||
* In recent Minecraft versions channel names must contain a colon separator
|
||||
* and consist of [a-z0-9/._-]. This will be enforced in a future version.
|
||||
* The "BungeeCord" channel is an exception and may only take this form.
|
||||
*
|
||||
* @param channel the channel to send this data via
|
||||
* @param data the data to send
|
||||
* @param queue hold the message for later sending if it cannot be sent
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package net.md_5.bungee.api.connection;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
|
||||
@@ -17,9 +17,18 @@ public interface Connection
|
||||
* Gets the remote address of this connection.
|
||||
*
|
||||
* @return the remote address
|
||||
* @deprecated BungeeCord can accept connections via Unix domain sockets
|
||||
*/
|
||||
@Deprecated
|
||||
InetSocketAddress getAddress();
|
||||
|
||||
/**
|
||||
* Gets the remote address of this connection.
|
||||
*
|
||||
* @return the remote address
|
||||
*/
|
||||
SocketAddress getSocketAddress();
|
||||
|
||||
/**
|
||||
* Disconnects this end of the connection for the specified reason. If this
|
||||
* is an {@link ProxiedPlayer} the respective server connection will be
|
||||
|
@@ -56,11 +56,16 @@ public interface PendingConnection extends Connection
|
||||
|
||||
/**
|
||||
* Set the connection's uuid
|
||||
*
|
||||
* @param uuid connection UUID
|
||||
*/
|
||||
void setUniqueId(UUID uuid);
|
||||
|
||||
/**
|
||||
* Get this connection's online mode.
|
||||
* <br>
|
||||
* See {@link #setOnlineMode(boolean)} for a description of how this option
|
||||
* works.
|
||||
*
|
||||
* @return the online mode
|
||||
*/
|
||||
@@ -68,6 +73,12 @@ public interface PendingConnection extends Connection
|
||||
|
||||
/**
|
||||
* Set this connection's online mode.
|
||||
* <br>
|
||||
* May be called only during the PlayerHandshakeEvent to set the online mode
|
||||
* configuration setting for this connection only (i.e. whether or not the
|
||||
* client will be treated as if it is connecting to an online mode server).
|
||||
*
|
||||
* @param onlineMode status
|
||||
*/
|
||||
void setOnlineMode(boolean onlineMode);
|
||||
|
||||
|
@@ -6,17 +6,49 @@ import java.util.UUID;
|
||||
import net.md_5.bungee.api.Callback;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ServerConnectRequest;
|
||||
import net.md_5.bungee.api.SkinConfiguration;
|
||||
import net.md_5.bungee.api.Title;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.event.ServerConnectEvent;
|
||||
import net.md_5.bungee.api.score.Scoreboard;
|
||||
|
||||
/**
|
||||
* Represents a player who's connection is being connected to somewhere else,
|
||||
* Represents a player whose connection is being connected to somewhere else,
|
||||
* whether it be a remote or embedded server.
|
||||
*/
|
||||
public interface ProxiedPlayer extends Connection, CommandSender
|
||||
{
|
||||
|
||||
/**
|
||||
* Represents the player's chat state.
|
||||
*/
|
||||
public enum ChatMode
|
||||
{
|
||||
|
||||
/**
|
||||
* The player will see all chat.
|
||||
*/
|
||||
SHOWN,
|
||||
/**
|
||||
* The player will only see everything except messages marked as chat.
|
||||
*/
|
||||
COMMANDS_ONLY,
|
||||
/**
|
||||
* The chat is completely disabled, the player won't see anything.
|
||||
*/
|
||||
HIDDEN;
|
||||
|
||||
}
|
||||
|
||||
public enum MainHand
|
||||
{
|
||||
|
||||
LEFT,
|
||||
RIGHT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this player's display name.
|
||||
*
|
||||
@@ -25,8 +57,7 @@ public interface ProxiedPlayer extends Connection, CommandSender
|
||||
String getDisplayName();
|
||||
|
||||
/**
|
||||
* Sets this players display name to be used as their nametag and tab list
|
||||
* name.
|
||||
* Sets this player's display name to be used by proxy commands and plugins.
|
||||
*
|
||||
* @param name the name to set
|
||||
*/
|
||||
@@ -48,6 +79,22 @@ public interface ProxiedPlayer extends Connection, CommandSender
|
||||
*/
|
||||
public void sendMessage(ChatMessageType position, BaseComponent message);
|
||||
|
||||
/**
|
||||
* Send a message to this player.
|
||||
*
|
||||
* @param sender the sender of the message
|
||||
* @param message the message to send
|
||||
*/
|
||||
public void sendMessage(UUID sender, BaseComponent... message);
|
||||
|
||||
/**
|
||||
* Send a message to this player.
|
||||
*
|
||||
* @param sender the sender of the message
|
||||
* @param message the message to send
|
||||
*/
|
||||
public void sendMessage(UUID sender, BaseComponent message);
|
||||
|
||||
/**
|
||||
* Connects / transfers this user to the specified connection, gracefully
|
||||
* closing the current one. Depending on the implementation, this method
|
||||
@@ -57,6 +104,16 @@ public interface ProxiedPlayer extends Connection, CommandSender
|
||||
*/
|
||||
void connect(ServerInfo target);
|
||||
|
||||
/**
|
||||
* Connects / transfers this user to the specified connection, gracefully
|
||||
* closing the current one. Depending on the implementation, this method
|
||||
* might return before the user has been connected.
|
||||
*
|
||||
* @param target the new server to connect to
|
||||
* @param reason the reason for connecting to the new server
|
||||
*/
|
||||
void connect(ServerInfo target, ServerConnectEvent.Reason reason);
|
||||
|
||||
/**
|
||||
* Connects / transfers this user to the specified connection, gracefully
|
||||
* closing the current one. Depending on the implementation, this method
|
||||
@@ -65,10 +122,32 @@ public interface ProxiedPlayer extends Connection, CommandSender
|
||||
* @param target the new server to connect to
|
||||
* @param callback the method called when the connection is complete, or
|
||||
* when an exception is encountered. The boolean parameter denotes success
|
||||
* or failure.
|
||||
* (true) or failure (false).
|
||||
*/
|
||||
void connect(ServerInfo target, Callback<Boolean> callback);
|
||||
|
||||
/**
|
||||
* Connects / transfers this user to the specified connection, gracefully
|
||||
* closing the current one. Depending on the implementation, this method
|
||||
* might return before the user has been connected.
|
||||
*
|
||||
* @param target the new server to connect to
|
||||
* @param callback the method called when the connection is complete, or
|
||||
* when an exception is encountered. The boolean parameter denotes success
|
||||
* (true) or failure (false).
|
||||
* @param reason the reason for connecting to the new server
|
||||
*/
|
||||
void connect(ServerInfo target, Callback<Boolean> callback, ServerConnectEvent.Reason reason);
|
||||
|
||||
/**
|
||||
* Connects / transfers this user to the specified connection, gracefully
|
||||
* closing the current one. Depending on the implementation, this method
|
||||
* might return before the user has been connected.
|
||||
*
|
||||
* @param request request to connect with
|
||||
*/
|
||||
void connect(ServerConnectRequest request);
|
||||
|
||||
/**
|
||||
* Gets the server this player is connected to.
|
||||
*
|
||||
@@ -86,6 +165,10 @@ public interface ProxiedPlayer extends Connection, CommandSender
|
||||
/**
|
||||
* Send a plugin message to this player.
|
||||
*
|
||||
* In recent Minecraft versions channel names must contain a colon separator
|
||||
* and consist of [a-z0-9/._-]. This will be enforced in a future version.
|
||||
* The "BungeeCord" channel is an exception and may only take this form.
|
||||
*
|
||||
* @param channel the channel to send this data via
|
||||
* @param data the data to send
|
||||
*/
|
||||
@@ -142,6 +225,41 @@ public interface ProxiedPlayer extends Connection, CommandSender
|
||||
*/
|
||||
Locale getLocale();
|
||||
|
||||
/**
|
||||
* Gets this player's view distance.
|
||||
*
|
||||
* @return the view distance, or a reasonable default
|
||||
*/
|
||||
byte getViewDistance();
|
||||
|
||||
/**
|
||||
* Gets this player's chat mode.
|
||||
*
|
||||
* @return the chat flags set, or a reasonable default
|
||||
*/
|
||||
ChatMode getChatMode();
|
||||
|
||||
/**
|
||||
* Gets if this player has chat colors enabled or disabled.
|
||||
*
|
||||
* @return if chat colors are enabled
|
||||
*/
|
||||
boolean hasChatColors();
|
||||
|
||||
/**
|
||||
* Gets this player's skin settings.
|
||||
*
|
||||
* @return the players skin setting
|
||||
*/
|
||||
SkinConfiguration getSkinParts();
|
||||
|
||||
/**
|
||||
* Gets this player's main hand setting.
|
||||
*
|
||||
* @return main hand setting
|
||||
*/
|
||||
MainHand getMainHand();
|
||||
|
||||
/**
|
||||
* Set the header and footer displayed in the tab player list.
|
||||
*
|
||||
@@ -211,4 +329,11 @@ public interface ProxiedPlayer extends Connection, CommandSender
|
||||
* not occurred for this {@link ProxiedPlayer} yet.
|
||||
*/
|
||||
Map<String, String> getModList();
|
||||
|
||||
/**
|
||||
* Get the {@link Scoreboard} that belongs to this player.
|
||||
*
|
||||
* @return this player's {@link Scoreboard}
|
||||
*/
|
||||
Scoreboard getScoreboard();
|
||||
}
|
||||
|
@@ -18,6 +18,10 @@ public interface Server extends Connection
|
||||
/**
|
||||
* Send data by any available means to this server.
|
||||
*
|
||||
* In recent Minecraft versions channel names must contain a colon separator
|
||||
* and consist of [a-z0-9/._-]. This will be enforced in a future version.
|
||||
* The "BungeeCord" channel is an exception and may only take this form.
|
||||
*
|
||||
* @param channel the channel to send this data via
|
||||
* @param data the data to send
|
||||
*/
|
||||
|
@@ -1,13 +1,14 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.Callback;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
@@ -19,13 +20,14 @@ import net.md_5.bungee.api.plugin.Plugin;
|
||||
* @param <T> Type of this event
|
||||
*/
|
||||
@Data
|
||||
@Getter(AccessLevel.NONE)
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class AsyncEvent<T> extends Event
|
||||
{
|
||||
|
||||
private final Callback<T> done;
|
||||
private final Set<Plugin> intents = Collections.newSetFromMap( new ConcurrentHashMap<Plugin, Boolean>() );
|
||||
private final Map<Plugin, AtomicInteger> intents = new ConcurrentHashMap<>();
|
||||
private final AtomicBoolean fired = new AtomicBoolean();
|
||||
private final AtomicInteger latch = new AtomicInteger();
|
||||
|
||||
@@ -43,37 +45,48 @@ public class AsyncEvent<T> extends Event
|
||||
/**
|
||||
* Register an intent that this plugin will continue to perform work on a
|
||||
* background task, and wishes to let the event proceed once the registered
|
||||
* background task has completed.
|
||||
* background task has completed. Multiple intents can be registered by a
|
||||
* plugin, but the plugin must complete the same amount of intents for the
|
||||
* event to proceed.
|
||||
*
|
||||
* @param plugin the plugin registering this intent
|
||||
*/
|
||||
public void registerIntent(Plugin plugin)
|
||||
{
|
||||
Preconditions.checkState( !fired.get(), "Event %s has already been fired", this );
|
||||
Preconditions.checkState( !intents.contains( plugin ), "Plugin %s already registered intent for event %s", plugin, this );
|
||||
|
||||
intents.add( plugin );
|
||||
AtomicInteger intentCount = intents.get( plugin );
|
||||
if ( intentCount == null )
|
||||
{
|
||||
intents.put( plugin, new AtomicInteger( 1 ) );
|
||||
} else
|
||||
{
|
||||
intentCount.incrementAndGet();
|
||||
}
|
||||
latch.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this event that this plugin has done all its required processing
|
||||
* and wishes to let the event proceed.
|
||||
* Notifies this event that this plugin has completed an intent and wishes
|
||||
* to let the event proceed once all intents have been completed.
|
||||
*
|
||||
* @param plugin a plugin which has an intent registered for this event
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void completeIntent(Plugin plugin)
|
||||
{
|
||||
Preconditions.checkState( intents.contains( plugin ), "Plugin %s has not registered intent for event %s", plugin, this );
|
||||
intents.remove( plugin );
|
||||
AtomicInteger intentCount = intents.get( plugin );
|
||||
Preconditions.checkState( intentCount != null && intentCount.get() > 0, "Plugin %s has not registered intents for event %s", plugin, this );
|
||||
|
||||
intentCount.decrementAndGet();
|
||||
if ( fired.get() )
|
||||
{
|
||||
if ( latch.decrementAndGet() == 0 )
|
||||
{
|
||||
done.done( (T) this, null );
|
||||
}
|
||||
} else {
|
||||
} else
|
||||
{
|
||||
latch.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
@@ -3,8 +3,11 @@ package net.md_5.bungee.api.event;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.Connection;
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
import net.md_5.bungee.api.plugin.PluginManager;
|
||||
|
||||
/**
|
||||
* Event called when a player sends a message to a server.
|
||||
@@ -39,4 +42,25 @@ public class ChatEvent extends TargetedEvent implements Cancellable
|
||||
{
|
||||
return message.length() > 0 && message.charAt( 0 ) == '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this message is run on this proxy server.
|
||||
*
|
||||
* @return if this command runs on the proxy
|
||||
* @see PluginManager#isExecutableCommand(java.lang.String,
|
||||
* net.md_5.bungee.api.CommandSender)
|
||||
*/
|
||||
public boolean isProxyCommand()
|
||||
{
|
||||
if ( !isCommand() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = message.indexOf( " " );
|
||||
String commandName = ( index == -1 ) ? message.substring( 1 ) : message.substring( 1, index );
|
||||
CommandSender sender = ( getSender() instanceof CommandSender ) ? (CommandSender) getSender() : null;
|
||||
|
||||
return ProxyServer.getInstance().getPluginManager().isExecutableCommand( commandName, sender );
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,35 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* Event called to represent an initial client connection.
|
||||
* <br>
|
||||
* Note: This event is called at an early stage of every connection, handling
|
||||
* should be <b>fast</b>.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = false)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class ClientConnectEvent extends Event implements Cancellable
|
||||
{
|
||||
|
||||
/**
|
||||
* Cancelled state.
|
||||
*/
|
||||
private boolean cancelled;
|
||||
/**
|
||||
* Remote address of connection.
|
||||
*/
|
||||
private final SocketAddress socketAddress;
|
||||
/**
|
||||
* Listener that accepted the connection.
|
||||
*/
|
||||
private final ListenerInfo listener;
|
||||
}
|
@@ -1,9 +1,13 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.Callback;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
|
||||
@@ -23,7 +27,8 @@ public class LoginEvent extends AsyncEvent<LoginEvent> implements Cancellable
|
||||
/**
|
||||
* Message to use when kicking if this event is canceled.
|
||||
*/
|
||||
private String cancelReason;
|
||||
@Setter(AccessLevel.NONE)
|
||||
private BaseComponent[] cancelReasonComponents;
|
||||
/**
|
||||
* Connection attempting to login.
|
||||
*/
|
||||
@@ -34,4 +39,31 @@ public class LoginEvent extends AsyncEvent<LoginEvent> implements Cancellable
|
||||
super( done );
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return reason to be displayed
|
||||
* @deprecated Use component methods instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public String getCancelReason()
|
||||
{
|
||||
return BaseComponent.toLegacyText( getCancelReasonComponents() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cancelReason reason to be displayed
|
||||
* @deprecated Use
|
||||
* {@link #setCancelReason(net.md_5.bungee.api.chat.BaseComponent...)}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCancelReason(String cancelReason)
|
||||
{
|
||||
setCancelReason( TextComponent.fromLegacyText( cancelReason ) );
|
||||
}
|
||||
|
||||
public void setCancelReason(BaseComponent... cancelReason)
|
||||
{
|
||||
this.cancelReasonComponents = cancelReason;
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ import net.md_5.bungee.api.plugin.Cancellable;
|
||||
* Event called when a plugin message is sent to the client or server.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@ToString(callSuper = true, exclude = "data")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PluginMessageEvent extends TargetedEvent implements Cancellable
|
||||
{
|
||||
|
@@ -1,9 +1,13 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.Callback;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
|
||||
@@ -28,7 +32,8 @@ public class PreLoginEvent extends AsyncEvent<PreLoginEvent> implements Cancella
|
||||
/**
|
||||
* Message to use when kicking if this event is canceled.
|
||||
*/
|
||||
private String cancelReason;
|
||||
@Setter(AccessLevel.NONE)
|
||||
private BaseComponent[] cancelReasonComponents;
|
||||
/**
|
||||
* Connection attempting to login.
|
||||
*/
|
||||
@@ -39,4 +44,31 @@ public class PreLoginEvent extends AsyncEvent<PreLoginEvent> implements Cancella
|
||||
super( done );
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return reason to be displayed
|
||||
* @deprecated Use component methods instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public String getCancelReason()
|
||||
{
|
||||
return BaseComponent.toLegacyText( getCancelReasonComponents() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cancelReason reason to be displayed
|
||||
* @deprecated Use
|
||||
* {@link #setCancelReason(net.md_5.bungee.api.chat.BaseComponent...)}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCancelReason(String cancelReason)
|
||||
{
|
||||
setCancelReason( TextComponent.fromLegacyText( cancelReason ) );
|
||||
}
|
||||
|
||||
public void setCancelReason(BaseComponent... cancelReason)
|
||||
{
|
||||
this.cancelReasonComponents = cancelReason;
|
||||
}
|
||||
}
|
||||
|
@@ -6,10 +6,9 @@ import lombok.ToString;
|
||||
import net.md_5.bungee.api.Callback;
|
||||
import net.md_5.bungee.api.ServerPing;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* Called when the proxy is pinged with packet 0xFE from the server list.
|
||||
* Called when the proxy is queried for status from the server list.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = false)
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.ServerConnectRequest;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
@@ -30,14 +31,75 @@ public class ServerConnectEvent extends Event implements Cancellable
|
||||
*/
|
||||
@NonNull
|
||||
private ServerInfo target;
|
||||
/**
|
||||
* Reason for connecting to a new server.
|
||||
*/
|
||||
private final Reason reason;
|
||||
/**
|
||||
* Request used to connect to given server.
|
||||
*/
|
||||
private final ServerConnectRequest request;
|
||||
/**
|
||||
* Cancelled state.
|
||||
*/
|
||||
private boolean cancelled;
|
||||
|
||||
@Deprecated
|
||||
public ServerConnectEvent(ProxiedPlayer player, ServerInfo target)
|
||||
{
|
||||
this( player, target, Reason.UNKNOWN );
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public ServerConnectEvent(ProxiedPlayer player, ServerInfo target, Reason reason)
|
||||
{
|
||||
this( player, target, reason, null );
|
||||
}
|
||||
|
||||
public ServerConnectEvent(ProxiedPlayer player, ServerInfo target, Reason reason, ServerConnectRequest request)
|
||||
{
|
||||
this.player = player;
|
||||
this.target = target;
|
||||
this.reason = reason;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public enum Reason
|
||||
{
|
||||
|
||||
/**
|
||||
* Redirection to lobby server due to being unable to connect to
|
||||
* original server
|
||||
*/
|
||||
LOBBY_FALLBACK,
|
||||
/**
|
||||
* Execution of a command
|
||||
*/
|
||||
COMMAND,
|
||||
/**
|
||||
* Redirecting to another server when client loses connection to server
|
||||
* due to an exception.
|
||||
*/
|
||||
SERVER_DOWN_REDIRECT,
|
||||
/**
|
||||
* Redirecting to another server when kicked from original server.
|
||||
*/
|
||||
KICK_REDIRECT,
|
||||
/**
|
||||
* Plugin message request.
|
||||
*/
|
||||
PLUGIN_MESSAGE,
|
||||
/**
|
||||
* Initial proxy connect.
|
||||
*/
|
||||
JOIN_PROXY,
|
||||
/**
|
||||
* Plugin initiated connect.
|
||||
*/
|
||||
PLUGIN,
|
||||
/**
|
||||
* Unknown cause.
|
||||
*/
|
||||
UNKNOWN
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,13 @@ import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* Called when the player is disconnected from a server, for example during
|
||||
* server switching.
|
||||
*
|
||||
* If the player is kicked from a server, {@link ServerKickEvent} will be called
|
||||
* instead.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@ToString(callSuper = false)
|
||||
|
@@ -3,6 +3,7 @@ package net.md_5.bungee.api.event;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
@@ -19,4 +20,9 @@ public class ServerSwitchEvent extends Event
|
||||
* Player whom the server is for.
|
||||
*/
|
||||
private final ProxiedPlayer player;
|
||||
/**
|
||||
* Server the player is switch from. May be null if initial proxy
|
||||
* connection.
|
||||
*/
|
||||
private final ServerInfo from;
|
||||
}
|
||||
|
@@ -0,0 +1,32 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* Called after a {@link ProxiedPlayer} changed one or more of the following
|
||||
* (client-side) settings:
|
||||
*
|
||||
* <ul>
|
||||
* <li>View distance</li>
|
||||
* <li>Locale</li>
|
||||
* <li>Displayed skin parts</li>
|
||||
* <li>Chat visibility</li>
|
||||
* <li>Chat colors</li>
|
||||
* <li>Main hand side (left or right)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = false)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class SettingsChangedEvent extends Event
|
||||
{
|
||||
|
||||
/**
|
||||
* Player who changed the settings.
|
||||
*/
|
||||
private final ProxiedPlayer player;
|
||||
}
|
@@ -1,11 +1,11 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.connection.Connection;
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Event called when a backend server sends a response to a player asking to
|
||||
|
@@ -4,6 +4,7 @@ import com.google.common.base.Preconditions;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
|
||||
/**
|
||||
@@ -17,6 +18,8 @@ public abstract class Command
|
||||
private final String name;
|
||||
private final String permission;
|
||||
private final String[] aliases;
|
||||
@Setter(AccessLevel.PROTECTED)
|
||||
private String permissionMessage;
|
||||
|
||||
/**
|
||||
* Construct a new command with no permissions or aliases.
|
||||
@@ -42,6 +45,7 @@ public abstract class Command
|
||||
this.name = name;
|
||||
this.permission = permission;
|
||||
this.aliases = aliases;
|
||||
this.permissionMessage = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,4 +55,15 @@ public abstract class Command
|
||||
* @param args arguments used to invoke this command
|
||||
*/
|
||||
public abstract void execute(CommandSender sender, String[] args);
|
||||
|
||||
/**
|
||||
* Check if this command can be executed by the given sender.
|
||||
*
|
||||
* @param sender the sender to check
|
||||
* @return whether the sender can execute this
|
||||
*/
|
||||
public boolean hasPermission(CommandSender sender)
|
||||
{
|
||||
return permission == null || permission.isEmpty() || sender.hasPermission( permission );
|
||||
}
|
||||
}
|
||||
|
123
api/src/main/java/net/md_5/bungee/api/plugin/LibraryLoader.java
Normal file
123
api/src/main/java/net/md_5/bungee/api/plugin/LibraryLoader.java
Normal file
@@ -0,0 +1,123 @@
|
||||
package net.md_5.bungee.api.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
|
||||
import org.eclipse.aether.DefaultRepositorySystemSession;
|
||||
import org.eclipse.aether.RepositorySystem;
|
||||
import org.eclipse.aether.artifact.Artifact;
|
||||
import org.eclipse.aether.artifact.DefaultArtifact;
|
||||
import org.eclipse.aether.collection.CollectRequest;
|
||||
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
|
||||
import org.eclipse.aether.graph.Dependency;
|
||||
import org.eclipse.aether.impl.DefaultServiceLocator;
|
||||
import org.eclipse.aether.repository.LocalRepository;
|
||||
import org.eclipse.aether.repository.RemoteRepository;
|
||||
import org.eclipse.aether.repository.RepositoryPolicy;
|
||||
import org.eclipse.aether.resolution.ArtifactResult;
|
||||
import org.eclipse.aether.resolution.DependencyRequest;
|
||||
import org.eclipse.aether.resolution.DependencyResolutionException;
|
||||
import org.eclipse.aether.resolution.DependencyResult;
|
||||
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
|
||||
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
|
||||
import org.eclipse.aether.transfer.AbstractTransferListener;
|
||||
import org.eclipse.aether.transfer.TransferCancelledException;
|
||||
import org.eclipse.aether.transfer.TransferEvent;
|
||||
import org.eclipse.aether.transport.http.HttpTransporterFactory;
|
||||
|
||||
class LibraryLoader
|
||||
{
|
||||
|
||||
private final Logger logger;
|
||||
private final RepositorySystem repository;
|
||||
private final DefaultRepositorySystemSession session;
|
||||
private final List<RemoteRepository> repositories;
|
||||
|
||||
public LibraryLoader(Logger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
|
||||
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
|
||||
locator.addService( RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class );
|
||||
locator.addService( TransporterFactory.class, HttpTransporterFactory.class );
|
||||
|
||||
this.repository = locator.getService( RepositorySystem.class );
|
||||
this.session = MavenRepositorySystemUtils.newSession();
|
||||
|
||||
session.setChecksumPolicy( RepositoryPolicy.CHECKSUM_POLICY_FAIL );
|
||||
session.setLocalRepositoryManager( repository.newLocalRepositoryManager( session, new LocalRepository( "libraries" ) ) );
|
||||
session.setTransferListener( new AbstractTransferListener()
|
||||
{
|
||||
@Override
|
||||
public void transferStarted(TransferEvent event) throws TransferCancelledException
|
||||
{
|
||||
logger.log( Level.INFO, "Downloading {0}", event.getResource().getRepositoryUrl() + event.getResource().getResourceName() );
|
||||
}
|
||||
} );
|
||||
session.setReadOnly();
|
||||
|
||||
this.repositories = repository.newResolutionRepositories( session, Arrays.asList( new RemoteRepository.Builder( "central", "default", "https://repo.maven.apache.org/maven2" ).build() ) );
|
||||
}
|
||||
|
||||
public ClassLoader createLoader(PluginDescription desc)
|
||||
{
|
||||
if ( desc.getLibraries().isEmpty() )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
logger.log( Level.INFO, "[{0}] Loading {1} libraries... please wait", new Object[]
|
||||
{
|
||||
desc.getName(), desc.getLibraries().size()
|
||||
} );
|
||||
|
||||
List<Dependency> dependencies = new ArrayList<>();
|
||||
for ( String library : desc.getLibraries() )
|
||||
{
|
||||
Artifact artifact = new DefaultArtifact( library );
|
||||
Dependency dependency = new Dependency( artifact, null );
|
||||
|
||||
dependencies.add( dependency );
|
||||
}
|
||||
|
||||
DependencyResult result;
|
||||
try
|
||||
{
|
||||
result = repository.resolveDependencies( session, new DependencyRequest( new CollectRequest( (Dependency) null, dependencies, repositories ), null ) );
|
||||
} catch ( DependencyResolutionException ex )
|
||||
{
|
||||
throw new RuntimeException( "Error resolving libraries", ex );
|
||||
}
|
||||
|
||||
List<URL> jarFiles = new ArrayList<>();
|
||||
for ( ArtifactResult artifact : result.getArtifactResults() )
|
||||
{
|
||||
File file = artifact.getArtifact().getFile();
|
||||
|
||||
URL url;
|
||||
try
|
||||
{
|
||||
url = file.toURI().toURL();
|
||||
} catch ( MalformedURLException ex )
|
||||
{
|
||||
throw new AssertionError( ex );
|
||||
}
|
||||
|
||||
jarFiles.add( url );
|
||||
logger.log( Level.INFO, "[{0}] Loaded library {1}", new Object[]
|
||||
{
|
||||
desc.getName(), file
|
||||
} );
|
||||
}
|
||||
|
||||
URLClassLoader loader = new URLClassLoader( jarFiles.toArray( new URL[ 0 ] ) );
|
||||
|
||||
return loader;
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
package net.md_5.bungee.api.plugin;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
@@ -27,6 +28,22 @@ public class Plugin
|
||||
@Getter
|
||||
private Logger logger;
|
||||
|
||||
public Plugin()
|
||||
{
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
Preconditions.checkState( classLoader instanceof PluginClassloader, "Plugin requires " + PluginClassloader.class.getName() );
|
||||
|
||||
( (PluginClassloader) classLoader ).init( this );
|
||||
}
|
||||
|
||||
protected Plugin(ProxyServer proxy, PluginDescription description)
|
||||
{
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
Preconditions.checkState( !( classLoader instanceof PluginClassloader ), "Cannot use initialization constructor at runtime" );
|
||||
|
||||
// init( proxy, description );
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the plugin has just been loaded. Most of the proxy will not
|
||||
* be initialized, so only use it for registering
|
||||
@@ -77,8 +94,8 @@ public class Plugin
|
||||
/**
|
||||
* Called by the loader to initialize the fields in this plugin.
|
||||
*
|
||||
* @param proxy current proxy instance
|
||||
* @param description the description that describes this plugin
|
||||
* @param jarfile this plugins jar or container
|
||||
*/
|
||||
final void init(ProxyServer proxy, PluginDescription description)
|
||||
{
|
||||
|
@@ -1,40 +1,89 @@
|
||||
package net.md_5.bungee.api.plugin;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.CodeSigner;
|
||||
import java.security.CodeSource;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
|
||||
public class PluginClassloader extends URLClassLoader
|
||||
@ToString(of = "desc")
|
||||
final class PluginClassloader extends URLClassLoader
|
||||
{
|
||||
|
||||
private static final Set<PluginClassloader> allLoaders = new CopyOnWriteArraySet<>();
|
||||
//
|
||||
private final ProxyServer proxy;
|
||||
private final PluginDescription desc;
|
||||
private final JarFile jar;
|
||||
private final Manifest manifest;
|
||||
private final URL url;
|
||||
private final ClassLoader libraryLoader;
|
||||
//
|
||||
private Plugin plugin;
|
||||
|
||||
static
|
||||
{
|
||||
ClassLoader.registerAsParallelCapable();
|
||||
}
|
||||
|
||||
public PluginClassloader(URL[] urls)
|
||||
public PluginClassloader(ProxyServer proxy, PluginDescription desc, File file, ClassLoader libraryLoader) throws IOException
|
||||
{
|
||||
super( urls );
|
||||
super( new URL[]
|
||||
{
|
||||
file.toURI().toURL()
|
||||
} );
|
||||
this.proxy = proxy;
|
||||
this.desc = desc;
|
||||
this.jar = new JarFile( file );
|
||||
this.manifest = jar.getManifest();
|
||||
this.url = file.toURI().toURL();
|
||||
this.libraryLoader = libraryLoader;
|
||||
|
||||
allLoaders.add( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
|
||||
{
|
||||
return loadClass0( name, resolve, true );
|
||||
return loadClass0( name, resolve, true, true );
|
||||
}
|
||||
|
||||
private Class<?> loadClass0(String name, boolean resolve, boolean checkOther) throws ClassNotFoundException
|
||||
private Class<?> loadClass0(String name, boolean resolve, boolean checkOther, boolean checkLibraries) throws ClassNotFoundException
|
||||
{
|
||||
try
|
||||
{
|
||||
return super.loadClass( name, resolve );
|
||||
Class<?> result = super.loadClass( name, resolve );
|
||||
|
||||
// SPIGOT-6749: Library classes will appear in the above, but we don't want to return them to other plugins
|
||||
if ( checkOther || result.getClassLoader() == this )
|
||||
{
|
||||
return result;
|
||||
}
|
||||
} catch ( ClassNotFoundException ex )
|
||||
{
|
||||
}
|
||||
|
||||
if ( checkLibraries && libraryLoader != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
return libraryLoader.loadClass( name );
|
||||
} catch ( ClassNotFoundException ex )
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if ( checkOther )
|
||||
{
|
||||
for ( PluginClassloader loader : allLoaders )
|
||||
@@ -43,13 +92,91 @@ public class PluginClassloader extends URLClassLoader
|
||||
{
|
||||
try
|
||||
{
|
||||
return loader.loadClass0( name, resolve, false );
|
||||
return loader.loadClass0( name, resolve, false, proxy.getPluginManager().isTransitiveDepend( desc, loader.desc ) );
|
||||
} catch ( ClassNotFoundException ex )
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new ClassNotFoundException( name );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException
|
||||
{
|
||||
String path = name.replace( '.', '/' ).concat( ".class" );
|
||||
JarEntry entry = jar.getJarEntry( path );
|
||||
|
||||
if ( entry != null )
|
||||
{
|
||||
byte[] classBytes;
|
||||
|
||||
try ( InputStream is = jar.getInputStream( entry ) )
|
||||
{
|
||||
classBytes = ByteStreams.toByteArray( is );
|
||||
} catch ( IOException ex )
|
||||
{
|
||||
throw new ClassNotFoundException( name, ex );
|
||||
}
|
||||
|
||||
int dot = name.lastIndexOf( '.' );
|
||||
if ( dot != -1 )
|
||||
{
|
||||
String pkgName = name.substring( 0, dot );
|
||||
if ( getPackage( pkgName ) == null )
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( manifest != null )
|
||||
{
|
||||
definePackage( pkgName, manifest, url );
|
||||
} else
|
||||
{
|
||||
definePackage( pkgName, null, null, null, null, null, null, null );
|
||||
}
|
||||
} catch ( IllegalArgumentException ex )
|
||||
{
|
||||
if ( getPackage( pkgName ) == null )
|
||||
{
|
||||
throw new IllegalStateException( "Cannot find package " + pkgName );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CodeSigner[] signers = entry.getCodeSigners();
|
||||
CodeSource source = new CodeSource( url, signers );
|
||||
|
||||
return defineClass( name, classBytes, 0, classBytes.length, source );
|
||||
}
|
||||
|
||||
return super.findClass( name );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
super.close();
|
||||
} finally
|
||||
{
|
||||
jar.close();
|
||||
}
|
||||
}
|
||||
|
||||
void init(Plugin plugin)
|
||||
{
|
||||
Preconditions.checkArgument( plugin != null, "plugin" );
|
||||
Preconditions.checkArgument( plugin.getClass().getClassLoader() == this, "Plugin has incorrect ClassLoader" );
|
||||
if ( this.plugin != null )
|
||||
{
|
||||
throw new IllegalArgumentException( "Plugin already initialized!" );
|
||||
}
|
||||
|
||||
this.plugin = plugin;
|
||||
plugin.init( proxy, desc );
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@ package net.md_5.bungee.api.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -48,4 +50,8 @@ public class PluginDescription
|
||||
* Optional description.
|
||||
*/
|
||||
private String description = null;
|
||||
/**
|
||||
* Optional libraries.
|
||||
*/
|
||||
private List<String> libraries = new LinkedList<>();
|
||||
}
|
||||
|
@@ -4,25 +4,28 @@ import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.google.common.graph.GraphBuilder;
|
||||
import com.google.common.graph.Graphs;
|
||||
import com.google.common.graph.MutableGraph;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Pattern;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
@@ -30,6 +33,7 @@ import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.event.EventBus;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.introspector.PropertyUtils;
|
||||
@@ -39,16 +43,17 @@ import org.yaml.snakeyaml.introspector.PropertyUtils;
|
||||
* example event handling and plugin management.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class PluginManager
|
||||
public final class PluginManager
|
||||
{
|
||||
|
||||
private static final Pattern argsSplit = Pattern.compile( " " );
|
||||
/*========================================================================*/
|
||||
private final ProxyServer proxy;
|
||||
/*========================================================================*/
|
||||
private final Yaml yaml;
|
||||
private final EventBus eventBus;
|
||||
private final Map<String, Plugin> plugins = new LinkedHashMap<>();
|
||||
private final MutableGraph<String> dependencyGraph = GraphBuilder.directed().build();
|
||||
private final LibraryLoader libraryLoader;
|
||||
private final Map<String, Command> commandMap = new HashMap<>();
|
||||
private Map<String, PluginDescription> toLoad = new HashMap<>();
|
||||
private final Multimap<Plugin, Command> commandsByPlugin = ArrayListMultimap.create();
|
||||
@@ -60,13 +65,24 @@ public class PluginManager
|
||||
this.proxy = proxy;
|
||||
|
||||
// Ignore unknown entries in the plugin descriptions
|
||||
Constructor yamlConstructor = new Constructor();
|
||||
Constructor yamlConstructor = new Constructor( new LoaderOptions() );
|
||||
PropertyUtils propertyUtils = yamlConstructor.getPropertyUtils();
|
||||
propertyUtils.setSkipMissingProperties( true );
|
||||
yamlConstructor.setPropertyUtils( propertyUtils );
|
||||
yaml = new Yaml( yamlConstructor );
|
||||
|
||||
eventBus = new EventBus( proxy.getLogger() );
|
||||
|
||||
LibraryLoader libraryLoader = null;
|
||||
try
|
||||
{
|
||||
libraryLoader = new LibraryLoader( proxy.getLogger() );
|
||||
} catch ( NoClassDefFoundError ex )
|
||||
{
|
||||
// Provided depends were not added back
|
||||
proxy.getLogger().warning( "Could not initialize LibraryLoader (missing dependencies?)" );
|
||||
}
|
||||
this.libraryLoader = libraryLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,10 +93,10 @@ public class PluginManager
|
||||
*/
|
||||
public void registerCommand(Plugin plugin, Command command)
|
||||
{
|
||||
commandMap.put( command.getName().toLowerCase(), command );
|
||||
commandMap.put( command.getName().toLowerCase( Locale.ROOT ), command );
|
||||
for ( String alias : command.getAliases() )
|
||||
{
|
||||
commandMap.put( alias.toLowerCase(), command );
|
||||
commandMap.put( alias.toLowerCase( Locale.ROOT ), command );
|
||||
}
|
||||
commandsByPlugin.put( plugin, command );
|
||||
}
|
||||
@@ -111,6 +127,32 @@ public class PluginManager
|
||||
}
|
||||
}
|
||||
|
||||
private Command getCommandIfEnabled(String commandName, CommandSender sender)
|
||||
{
|
||||
String commandLower = commandName.toLowerCase( Locale.ROOT );
|
||||
|
||||
// Check if command is disabled when a player sent the command
|
||||
if ( ( sender instanceof ProxiedPlayer ) && proxy.getDisabledCommands().contains( commandLower ) )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return commandMap.get( commandLower );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the command is registered and can possibly be executed by the
|
||||
* sender (without taking permissions into account).
|
||||
*
|
||||
* @param commandName the name of the command
|
||||
* @param sender the sender executing the command
|
||||
* @return whether the command will be handled
|
||||
*/
|
||||
public boolean isExecutableCommand(String commandName, CommandSender sender)
|
||||
{
|
||||
return getCommandIfEnabled( commandName, sender ) != null;
|
||||
}
|
||||
|
||||
public boolean dispatchCommand(CommandSender sender, String commandLine)
|
||||
{
|
||||
return dispatchCommand( sender, commandLine, null );
|
||||
@@ -122,34 +164,31 @@ public class PluginManager
|
||||
* @param sender the sender executing the command
|
||||
* @param commandLine the complete command line including command name and
|
||||
* arguments
|
||||
* @param tabResults list to place tab results into. If this list is non
|
||||
* null then the command will not be executed and tab results will be
|
||||
* returned instead.
|
||||
* @return whether the command was handled
|
||||
*/
|
||||
public boolean dispatchCommand(CommandSender sender, String commandLine, List<String> tabResults)
|
||||
{
|
||||
String[] split = argsSplit.split( commandLine, -1 );
|
||||
String[] split = commandLine.split( " ", -1 );
|
||||
// Check for chat that only contains " "
|
||||
if ( split.length == 0 )
|
||||
if ( split.length == 0 || split[0].isEmpty() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
String commandName = split[0].toLowerCase();
|
||||
if ( sender instanceof ProxiedPlayer && proxy.getDisabledCommands().contains( commandName ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Command command = commandMap.get( commandName );
|
||||
Command command = getCommandIfEnabled( split[0], sender );
|
||||
if ( command == null )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
String permission = command.getPermission();
|
||||
if ( permission != null && !permission.isEmpty() && !sender.hasPermission( permission ) )
|
||||
if ( !command.hasPermission( sender ) )
|
||||
{
|
||||
if ( !( command instanceof TabExecutor ) || tabResults == null )
|
||||
if ( tabResults == null )
|
||||
{
|
||||
sender.sendMessage( proxy.getTranslation( "no_permission" ) );
|
||||
sender.sendMessage( ( command.getPermissionMessage() == null ) ? proxy.getTranslation( "no_permission" ) : command.getPermissionMessage() );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -286,6 +325,7 @@ public class PluginManager
|
||||
status = false;
|
||||
}
|
||||
|
||||
dependencyGraph.putEdge( plugin.getName(), dependName );
|
||||
if ( !status )
|
||||
{
|
||||
break;
|
||||
@@ -297,14 +337,10 @@ public class PluginManager
|
||||
{
|
||||
try
|
||||
{
|
||||
URLClassLoader loader = new PluginClassloader( new URL[]
|
||||
{
|
||||
plugin.getFile().toURI().toURL()
|
||||
} );
|
||||
URLClassLoader loader = new PluginClassloader( proxy, plugin, plugin.getFile(), ( libraryLoader != null ) ? libraryLoader.createLoader( plugin ) : null );
|
||||
Class<?> main = loader.loadClass( plugin.getMain() );
|
||||
Plugin clazz = (Plugin) main.getDeclaredConstructor().newInstance();
|
||||
|
||||
clazz.init( proxy, plugin );
|
||||
plugins.put( plugin.getName(), clazz );
|
||||
clazz.onLoad();
|
||||
ProxyServer.getInstance().getLogger().log( Level.INFO, "Loaded plugin {0} version {1} by {2}", new Object[]
|
||||
@@ -313,7 +349,7 @@ public class PluginManager
|
||||
} );
|
||||
} catch ( Throwable t )
|
||||
{
|
||||
proxy.getLogger().log( Level.WARNING, "Error enabling plugin " + plugin.getName(), t );
|
||||
proxy.getLogger().log( Level.WARNING, "Error loading plugin " + plugin.getName(), t );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,6 +383,9 @@ public class PluginManager
|
||||
try ( InputStream in = jar.getInputStream( pdf ) )
|
||||
{
|
||||
PluginDescription desc = yaml.loadAs( in, PluginDescription.class );
|
||||
Preconditions.checkNotNull( desc.getName(), "Plugin from %s has no name", file );
|
||||
Preconditions.checkNotNull( desc.getMain(), "Plugin from %s has no main", file );
|
||||
|
||||
desc.setFile( file );
|
||||
toLoad.put( desc.getName(), desc );
|
||||
}
|
||||
@@ -374,12 +413,12 @@ public class PluginManager
|
||||
eventBus.post( event );
|
||||
event.postCall();
|
||||
|
||||
long elapsed = start - System.nanoTime();
|
||||
if ( elapsed > 250000 )
|
||||
long elapsed = System.nanoTime() - start;
|
||||
if ( elapsed > 250000000 )
|
||||
{
|
||||
ProxyServer.getInstance().getLogger().log( Level.WARNING, "Event {0} took more {1}ns to process!", new Object[]
|
||||
ProxyServer.getInstance().getLogger().log( Level.WARNING, "Event {0} took {1}ms to process!", new Object[]
|
||||
{
|
||||
event, elapsed
|
||||
event, elapsed / 1000000
|
||||
} );
|
||||
}
|
||||
return event;
|
||||
@@ -417,6 +456,8 @@ public class PluginManager
|
||||
|
||||
/**
|
||||
* Unregister all of a Plugin's listener.
|
||||
*
|
||||
* @param plugin target plugin
|
||||
*/
|
||||
public void unregisterListeners(Plugin plugin)
|
||||
{
|
||||
@@ -426,4 +467,29 @@ public class PluginManager
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an unmodifiable collection of all registered commands.
|
||||
*
|
||||
* @return commands
|
||||
*/
|
||||
public Collection<Map.Entry<String, Command>> getCommands()
|
||||
{
|
||||
return Collections.unmodifiableCollection( commandMap.entrySet() );
|
||||
}
|
||||
|
||||
boolean isTransitiveDepend(PluginDescription plugin, PluginDescription depend)
|
||||
{
|
||||
Preconditions.checkArgument( plugin != null, "plugin" );
|
||||
Preconditions.checkArgument( depend != null, "depend" );
|
||||
|
||||
if ( dependencyGraph.nodes().contains( plugin.getName() ) )
|
||||
{
|
||||
if ( Graphs.reachableNodes( dependencyGraph, plugin.getName() ).contains( depend.getName() ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ public class GroupedThreadFactory implements ThreadFactory
|
||||
|
||||
private final ThreadGroup group;
|
||||
|
||||
public static class BungeeGroup extends ThreadGroup
|
||||
public static final class BungeeGroup extends ThreadGroup
|
||||
{
|
||||
|
||||
private BungeeGroup(String name)
|
||||
|
@@ -86,6 +86,7 @@ public interface TaskScheduler
|
||||
/**
|
||||
* An executor service which underlies this scheduler.
|
||||
*
|
||||
* @param plugin owning plugin
|
||||
* @return the underlying executor service or compatible wrapper
|
||||
*/
|
||||
ExecutorService getExecutorService(Plugin plugin);
|
||||
|
@@ -22,5 +22,5 @@ public class Objective
|
||||
/**
|
||||
* Type; integer or hearts
|
||||
*/
|
||||
private final String type;
|
||||
private String type;
|
||||
}
|
||||
|
@@ -62,6 +62,11 @@ public class Scoreboard
|
||||
scores.put( score.getItemName(), score );
|
||||
}
|
||||
|
||||
public Score getScore(String name)
|
||||
{
|
||||
return scores.get( name );
|
||||
}
|
||||
|
||||
public void addTeam(Team team)
|
||||
{
|
||||
Preconditions.checkNotNull( team, "team" );
|
||||
|
@@ -18,7 +18,8 @@ public class Team
|
||||
private String suffix;
|
||||
private byte friendlyFire;
|
||||
private String nameTagVisibility;
|
||||
private byte color;
|
||||
private String collisionRule;
|
||||
private int color;
|
||||
private Set<String> players = new HashSet<>();
|
||||
|
||||
public Collection<String> getPlayers()
|
||||
|
@@ -3,6 +3,7 @@ package net.md_5.bungee.command;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import java.util.Locale;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
@@ -29,13 +30,13 @@ public abstract class PlayerCommand extends Command implements TabExecutor
|
||||
@Override
|
||||
public Iterable<String> onTabComplete(CommandSender sender, String[] args)
|
||||
{
|
||||
final String lastArg = ( args.length > 0 ) ? args[args.length - 1].toLowerCase() : "";
|
||||
final String lastArg = ( args.length > 0 ) ? args[args.length - 1].toLowerCase( Locale.ROOT ) : "";
|
||||
return Iterables.transform( Iterables.filter( ProxyServer.getInstance().getPlayers(), new Predicate<ProxiedPlayer>()
|
||||
{
|
||||
@Override
|
||||
public boolean apply(ProxiedPlayer player)
|
||||
{
|
||||
return player.getName().toLowerCase().startsWith( lastArg );
|
||||
return player.getName().toLowerCase( Locale.ROOT ).startsWith( lastArg );
|
||||
}
|
||||
} ), new Function<ProxiedPlayer, String>()
|
||||
{
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package net.md_5.bungee.util;
|
||||
|
||||
import gnu.trove.strategy.HashingStrategy;
|
||||
import java.util.Locale;
|
||||
|
||||
class CaseInsensitiveHashingStrategy implements HashingStrategy
|
||||
{
|
||||
@@ -10,12 +11,12 @@ class CaseInsensitiveHashingStrategy implements HashingStrategy
|
||||
@Override
|
||||
public int computeHashCode(Object object)
|
||||
{
|
||||
return ( (String) object ).toLowerCase().hashCode();
|
||||
return ( (String) object ).toLowerCase( Locale.ROOT ).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o1, Object o2)
|
||||
{
|
||||
return o1.equals( o2 ) || ( o1 instanceof String && o2 instanceof String && ( (String) o1 ).toLowerCase().equals( ( (String) o2 ).toLowerCase() ) );
|
||||
return o1.equals( o2 ) || ( o1 instanceof String && o2 instanceof String && ( (String) o1 ).toLowerCase( Locale.ROOT ).equals( ( (String) o2 ).toLowerCase( Locale.ROOT ) ) );
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,93 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Collection;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.ServerConnectEvent;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ServerConnectRequestTest
|
||||
{
|
||||
|
||||
private static final ServerInfo DUMMY_INFO = new ServerInfo()
|
||||
{
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getSocketAddress()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ProxiedPlayer> getPlayers()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMotd()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRestricted()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPermission()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(CommandSender sender)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendData(String channel, byte[] data)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendData(String channel, byte[] data, boolean queue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ping(Callback<ServerPing> callback)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testNullTarget()
|
||||
{
|
||||
assertThrows( NullPointerException.class, () -> ServerConnectRequest.builder().target( null ).reason( ServerConnectEvent.Reason.JOIN_PROXY ).build() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullReason()
|
||||
{
|
||||
assertThrows( NullPointerException.class, () -> ServerConnectRequest.builder().target( DUMMY_INFO ).reason( null ).build() );
|
||||
}
|
||||
}
|
@@ -1,61 +1,56 @@
|
||||
package net.md_5.bungee.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import io.netty.channel.unix.DomainSocketAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.md_5.bungee.Util;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@RunWith(Parameterized.class)
|
||||
public class AddressParseTest
|
||||
{
|
||||
|
||||
@Parameters
|
||||
public static Collection<Object[]> data()
|
||||
public static Stream<Arguments> data()
|
||||
{
|
||||
return Arrays.asList( new Object[][]
|
||||
{
|
||||
{
|
||||
"127.0.0.1", "127.0.0.1", Util.DEFAULT_PORT
|
||||
},
|
||||
{
|
||||
"127.0.0.1:1337", "127.0.0.1", 1337
|
||||
},
|
||||
{
|
||||
"[::1]", "0:0:0:0:0:0:0:1", Util.DEFAULT_PORT
|
||||
},
|
||||
{
|
||||
"[0:0:0:0::1]", "0:0:0:0:0:0:0:1", Util.DEFAULT_PORT
|
||||
},
|
||||
{
|
||||
"[0:0:0:0:0:0:0:1]", "0:0:0:0:0:0:0:1", Util.DEFAULT_PORT
|
||||
},
|
||||
{
|
||||
"[::1]:1337", "0:0:0:0:0:0:0:1", 1337
|
||||
},
|
||||
{
|
||||
"[0:0:0:0::1]:1337", "0:0:0:0:0:0:0:1", 1337
|
||||
},
|
||||
{
|
||||
"[0:0:0:0:0:0:0:1]:1337", "0:0:0:0:0:0:0:1", 1337
|
||||
}
|
||||
} );
|
||||
return Stream.of(
|
||||
Arguments.of( "127.0.0.1", "127.0.0.1", Util.DEFAULT_PORT ),
|
||||
Arguments.of( "127.0.0.1:1337", "127.0.0.1", 1337 ),
|
||||
Arguments.of( "[::1]", "0:0:0:0:0:0:0:1", Util.DEFAULT_PORT ),
|
||||
Arguments.of( "[0:0:0:0::1]", "0:0:0:0:0:0:0:1", Util.DEFAULT_PORT ),
|
||||
Arguments.of( "[0:0:0:0:0:0:0:1]", "0:0:0:0:0:0:0:1", Util.DEFAULT_PORT ),
|
||||
Arguments.of( "[::1]:1337", "0:0:0:0:0:0:0:1", 1337 ),
|
||||
Arguments.of( "[0:0:0:0::1]:1337", "0:0:0:0:0:0:0:1", 1337 ),
|
||||
Arguments.of( "[0:0:0:0:0:0:0:1]:1337", "0:0:0:0:0:0:0:1", 1337 ),
|
||||
Arguments.of( "unix:///var/run/bungee.sock", "/var/run/bungee.sock", -1 )
|
||||
);
|
||||
}
|
||||
private final String line;
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
@Test
|
||||
public void test()
|
||||
@ParameterizedTest
|
||||
@MethodSource("data")
|
||||
public void test(String line, String host, int port)
|
||||
{
|
||||
InetSocketAddress parsed = Util.getAddr( line );
|
||||
Assert.assertEquals( host, parsed.getHostString() );
|
||||
Assert.assertEquals( port, parsed.getPort() );
|
||||
SocketAddress parsed = Util.getAddr( line );
|
||||
|
||||
if ( parsed instanceof InetSocketAddress )
|
||||
{
|
||||
InetSocketAddress tcp = (InetSocketAddress) parsed;
|
||||
|
||||
assertEquals( host, tcp.getHostString() );
|
||||
assertEquals( port, tcp.getPort() );
|
||||
} else if ( parsed instanceof DomainSocketAddress )
|
||||
{
|
||||
DomainSocketAddress unix = (DomainSocketAddress) parsed;
|
||||
|
||||
assertEquals( host, unix.path() );
|
||||
assertEquals( -1, port );
|
||||
} else
|
||||
{
|
||||
throw new AssertionError( "Unknown socket " + parsed );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package net.md_5.bungee.util;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.Assert;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class CaseInsensitiveTest
|
||||
{
|
||||
@@ -13,12 +13,12 @@ public class CaseInsensitiveTest
|
||||
CaseInsensitiveMap<Object> map = new CaseInsensitiveMap<>();
|
||||
|
||||
map.put( "FOO", obj );
|
||||
Assert.assertTrue( map.contains( "foo" ) ); // Assert that contains is case insensitive
|
||||
Assert.assertTrue( map.entrySet().iterator().next().getKey().equals( "FOO" ) ); // Assert that case is preserved
|
||||
assertTrue( map.contains( "foo" ) ); // Assert that contains is case insensitive
|
||||
assertTrue( map.entrySet().iterator().next().getKey().equals( "FOO" ) ); // Assert that case is preserved
|
||||
|
||||
// Assert that remove is case insensitive
|
||||
map.remove( "FoO" );
|
||||
Assert.assertFalse( map.contains( "foo" ) );
|
||||
assertFalse( map.contains( "foo" ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -27,8 +27,8 @@ public class CaseInsensitiveTest
|
||||
CaseInsensitiveSet set = new CaseInsensitiveSet();
|
||||
|
||||
set.add( "FOO" );
|
||||
Assert.assertTrue( set.contains( "foo" ) ); // Assert that contains is case insensitive
|
||||
assertTrue( set.contains( "foo" ) ); // Assert that contains is case insensitive
|
||||
set.remove( "FoO" );
|
||||
Assert.assertFalse( set.contains( "foo" ) ); // Assert that remove is case insensitive
|
||||
assertFalse( set.contains( "foo" ) ); // Assert that remove is case insensitive
|
||||
}
|
||||
}
|
||||
|
29
api/src/test/java/net/md_5/bungee/util/UUIDTest.java
Normal file
29
api/src/test/java/net/md_5/bungee/util/UUIDTest.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package net.md_5.bungee.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import java.util.UUID;
|
||||
import net.md_5.bungee.Util;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class UUIDTest
|
||||
{
|
||||
|
||||
@Test
|
||||
public void testSingle()
|
||||
{
|
||||
UUID uuid = UUID.fromString( "af74a02d-19cb-445b-b07f-6866a861f783" );
|
||||
UUID uuid1 = Util.getUUID( "af74a02d19cb445bb07f6866a861f783" );
|
||||
assertEquals( uuid, uuid1 );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMany()
|
||||
{
|
||||
for ( int i = 0; i < 1000; i++ )
|
||||
{
|
||||
UUID expected = UUID.randomUUID();
|
||||
UUID actual = Util.getUUID( expected.toString().replace( "-", "" ) );
|
||||
assertEquals( expected, actual, "Could not parse UUID " + expected );
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,19 +6,21 @@
|
||||
<parent>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-parent</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<version>1.20-R0.2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-bootstrap</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<version>1.20-R0.2-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>BungeeCord-Bootstrap</name>
|
||||
<description>Java 1.6 loader for BungeeCord</description>
|
||||
|
||||
<properties>
|
||||
<maven.deploy.skip>true</maven.deploy.skip>
|
||||
<maven.javadoc.skip>true</maven.javadoc.skip>
|
||||
<maven.compiler.source>1.6</maven.compiler.source>
|
||||
<maven.compiler.target>1.6</maven.compiler.target>
|
||||
<maven.build.timestamp.format>yyyyMMdd</maven.build.timestamp.format>
|
||||
@@ -31,29 +33,15 @@
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.jopt-simple</groupId>
|
||||
<artifactId>jopt-simple</artifactId>
|
||||
<version>4.8</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>BungeeCord</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<!-- Don't deploy proxy to maven repo, only APIs -->
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
@@ -67,7 +55,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.1</version>
|
||||
<version>3.5.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
@@ -91,4 +79,34 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>jdk-9-release</id>
|
||||
<activation>
|
||||
<jdk>[9,)</jdk>
|
||||
</activation>
|
||||
<properties>
|
||||
<maven.compiler.release>6</maven.compiler.release>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>jdk-12-release</id>
|
||||
<activation>
|
||||
<jdk>[12,)</jdk>
|
||||
</activation>
|
||||
<properties>
|
||||
<maven.compiler.release>7</maven.compiler.release>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>jdk-20-release</id>
|
||||
<activation>
|
||||
<jdk>[20,)</jdk>
|
||||
</activation>
|
||||
<properties>
|
||||
<maven.compiler.release>8</maven.compiler.release>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
@@ -5,9 +5,9 @@ public class Bootstrap
|
||||
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
if ( Float.parseFloat( System.getProperty( "java.class.version" ) ) < 51.0 )
|
||||
if ( Float.parseFloat( System.getProperty( "java.class.version" ) ) < 52.0 )
|
||||
{
|
||||
System.err.println( "*** ERROR *** BungeeCord requires Java 7 or above to function! Please download and install it!" );
|
||||
System.err.println( "*** ERROR *** BungeeCord requires Java 8 or above to function! Please download and install it!" );
|
||||
System.out.println( "You can check your Java version with the command: java -version" );
|
||||
return;
|
||||
}
|
||||
|
11
chat/pom.xml
11
chat/pom.xml
@@ -6,28 +6,23 @@
|
||||
<parent>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-parent</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<version>1.20-R0.2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-chat</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<version>1.20-R0.2-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>BungeeCord-Chat</name>
|
||||
<description>Minecraft JSON chat API intended for use with BungeeCord</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>1.6</maven.compiler.source>
|
||||
<maven.compiler.target>1.6</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.3.1</version>
|
||||
<version>2.10.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
@@ -1,145 +1,192 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.awt.Color;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Simplistic enumeration of all supported color values for chat.
|
||||
*/
|
||||
public enum ChatColor
|
||||
public final class ChatColor
|
||||
{
|
||||
|
||||
/**
|
||||
* Represents black.
|
||||
*/
|
||||
BLACK( '0', "black" ),
|
||||
/**
|
||||
* Represents dark blue.
|
||||
*/
|
||||
DARK_BLUE( '1', "dark_blue" ),
|
||||
/**
|
||||
* Represents dark green.
|
||||
*/
|
||||
DARK_GREEN( '2', "dark_green" ),
|
||||
/**
|
||||
* Represents dark blue (aqua).
|
||||
*/
|
||||
DARK_AQUA( '3', "dark_aqua" ),
|
||||
/**
|
||||
* Represents dark red.
|
||||
*/
|
||||
DARK_RED( '4', "dark_red" ),
|
||||
/**
|
||||
* Represents dark purple.
|
||||
*/
|
||||
DARK_PURPLE( '5', "dark_purple" ),
|
||||
/**
|
||||
* Represents gold.
|
||||
*/
|
||||
GOLD( '6', "gold" ),
|
||||
/**
|
||||
* Represents gray.
|
||||
*/
|
||||
GRAY( '7', "gray" ),
|
||||
/**
|
||||
* Represents dark gray.
|
||||
*/
|
||||
DARK_GRAY( '8', "dark_gray" ),
|
||||
/**
|
||||
* Represents blue.
|
||||
*/
|
||||
BLUE( '9', "blue" ),
|
||||
/**
|
||||
* Represents green.
|
||||
*/
|
||||
GREEN( 'a', "green" ),
|
||||
/**
|
||||
* Represents aqua.
|
||||
*/
|
||||
AQUA( 'b', "aqua" ),
|
||||
/**
|
||||
* Represents red.
|
||||
*/
|
||||
RED( 'c', "red" ),
|
||||
/**
|
||||
* Represents light purple.
|
||||
*/
|
||||
LIGHT_PURPLE( 'd', "light_purple" ),
|
||||
/**
|
||||
* Represents yellow.
|
||||
*/
|
||||
YELLOW( 'e', "yellow" ),
|
||||
/**
|
||||
* Represents white.
|
||||
*/
|
||||
WHITE( 'f', "white" ),
|
||||
/**
|
||||
* Represents magical characters that change around randomly.
|
||||
*/
|
||||
MAGIC( 'k', "obfuscated" ),
|
||||
/**
|
||||
* Makes the text bold.
|
||||
*/
|
||||
BOLD( 'l', "bold" ),
|
||||
/**
|
||||
* Makes a line appear through the text.
|
||||
*/
|
||||
STRIKETHROUGH( 'm', "strikethrough" ),
|
||||
/**
|
||||
* Makes the text appear underlined.
|
||||
*/
|
||||
UNDERLINE( 'n', "underline" ),
|
||||
/**
|
||||
* Makes the text italic.
|
||||
*/
|
||||
ITALIC( 'o', "italic" ),
|
||||
/**
|
||||
* Resets all previous chat colors or formats.
|
||||
*/
|
||||
RESET( 'r', "reset" );
|
||||
/**
|
||||
* The special character which prefixes all chat colour codes. Use this if
|
||||
* you need to dynamically convert colour codes from your custom format.
|
||||
*/
|
||||
public static final char COLOR_CHAR = '\u00A7';
|
||||
public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoRr";
|
||||
public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoRrXx";
|
||||
/**
|
||||
* Pattern to remove all colour codes.
|
||||
*/
|
||||
public static final Pattern STRIP_COLOR_PATTERN = Pattern.compile( "(?i)" + String.valueOf( COLOR_CHAR ) + "[0-9A-FK-OR]" );
|
||||
public static final Pattern STRIP_COLOR_PATTERN = Pattern.compile( "(?i)" + String.valueOf( COLOR_CHAR ) + "[0-9A-FK-ORX]" );
|
||||
/**
|
||||
* Colour instances keyed by their active character.
|
||||
*/
|
||||
private static final Map<Character, ChatColor> BY_CHAR = new HashMap<Character, ChatColor>();
|
||||
/**
|
||||
* The code appended to {@link #COLOR_CHAR} to make usable colour.
|
||||
* Colour instances keyed by their name.
|
||||
*/
|
||||
private final char code;
|
||||
private static final Map<String, ChatColor> BY_NAME = new HashMap<String, ChatColor>();
|
||||
/**
|
||||
* Represents black.
|
||||
*/
|
||||
public static final ChatColor BLACK = new ChatColor( '0', "black", new Color( 0x000000 ) );
|
||||
/**
|
||||
* Represents dark blue.
|
||||
*/
|
||||
public static final ChatColor DARK_BLUE = new ChatColor( '1', "dark_blue", new Color( 0x0000AA ) );
|
||||
/**
|
||||
* Represents dark green.
|
||||
*/
|
||||
public static final ChatColor DARK_GREEN = new ChatColor( '2', "dark_green", new Color( 0x00AA00 ) );
|
||||
/**
|
||||
* Represents dark blue (aqua).
|
||||
*/
|
||||
public static final ChatColor DARK_AQUA = new ChatColor( '3', "dark_aqua", new Color( 0x00AAAA ) );
|
||||
/**
|
||||
* Represents dark red.
|
||||
*/
|
||||
public static final ChatColor DARK_RED = new ChatColor( '4', "dark_red", new Color( 0xAA0000 ) );
|
||||
/**
|
||||
* Represents dark purple.
|
||||
*/
|
||||
public static final ChatColor DARK_PURPLE = new ChatColor( '5', "dark_purple", new Color( 0xAA00AA ) );
|
||||
/**
|
||||
* Represents gold.
|
||||
*/
|
||||
public static final ChatColor GOLD = new ChatColor( '6', "gold", new Color( 0xFFAA00 ) );
|
||||
/**
|
||||
* Represents gray.
|
||||
*/
|
||||
public static final ChatColor GRAY = new ChatColor( '7', "gray", new Color( 0xAAAAAA ) );
|
||||
/**
|
||||
* Represents dark gray.
|
||||
*/
|
||||
public static final ChatColor DARK_GRAY = new ChatColor( '8', "dark_gray", new Color( 0x555555 ) );
|
||||
/**
|
||||
* Represents blue.
|
||||
*/
|
||||
public static final ChatColor BLUE = new ChatColor( '9', "blue", new Color( 0x5555FF ) );
|
||||
/**
|
||||
* Represents green.
|
||||
*/
|
||||
public static final ChatColor GREEN = new ChatColor( 'a', "green", new Color( 0x55FF55 ) );
|
||||
/**
|
||||
* Represents aqua.
|
||||
*/
|
||||
public static final ChatColor AQUA = new ChatColor( 'b', "aqua", new Color( 0x55FFFF ) );
|
||||
/**
|
||||
* Represents red.
|
||||
*/
|
||||
public static final ChatColor RED = new ChatColor( 'c', "red", new Color( 0xFF5555 ) );
|
||||
/**
|
||||
* Represents light purple.
|
||||
*/
|
||||
public static final ChatColor LIGHT_PURPLE = new ChatColor( 'd', "light_purple", new Color( 0xFF55FF ) );
|
||||
/**
|
||||
* Represents yellow.
|
||||
*/
|
||||
public static final ChatColor YELLOW = new ChatColor( 'e', "yellow", new Color( 0xFFFF55 ) );
|
||||
/**
|
||||
* Represents white.
|
||||
*/
|
||||
public static final ChatColor WHITE = new ChatColor( 'f', "white", new Color( 0xFFFFFF ) );
|
||||
/**
|
||||
* Represents magical characters that change around randomly.
|
||||
*/
|
||||
public static final ChatColor MAGIC = new ChatColor( 'k', "obfuscated" );
|
||||
/**
|
||||
* Makes the text bold.
|
||||
*/
|
||||
public static final ChatColor BOLD = new ChatColor( 'l', "bold" );
|
||||
/**
|
||||
* Makes a line appear through the text.
|
||||
*/
|
||||
public static final ChatColor STRIKETHROUGH = new ChatColor( 'm', "strikethrough" );
|
||||
/**
|
||||
* Makes the text appear underlined.
|
||||
*/
|
||||
public static final ChatColor UNDERLINE = new ChatColor( 'n', "underline" );
|
||||
/**
|
||||
* Makes the text italic.
|
||||
*/
|
||||
public static final ChatColor ITALIC = new ChatColor( 'o', "italic" );
|
||||
/**
|
||||
* Resets all previous chat colors or formats.
|
||||
*/
|
||||
public static final ChatColor RESET = new ChatColor( 'r', "reset" );
|
||||
/**
|
||||
* Count used for populating legacy ordinal.
|
||||
*/
|
||||
private static int count = 0;
|
||||
/**
|
||||
* This colour's colour char prefixed by the {@link #COLOR_CHAR}.
|
||||
*/
|
||||
private final String toString;
|
||||
@Getter
|
||||
private final String name;
|
||||
|
||||
static
|
||||
{
|
||||
for ( ChatColor colour : values() )
|
||||
{
|
||||
BY_CHAR.put( colour.code, colour );
|
||||
}
|
||||
}
|
||||
private final int ordinal;
|
||||
/**
|
||||
* The RGB color of the ChatColor. null for non-colors (formatting)
|
||||
*/
|
||||
@Getter
|
||||
private final Color color;
|
||||
|
||||
private ChatColor(char code, String name)
|
||||
{
|
||||
this.code = code;
|
||||
this( code, name, null );
|
||||
}
|
||||
|
||||
private ChatColor(char code, String name, Color color)
|
||||
{
|
||||
this.name = name;
|
||||
this.toString = new String( new char[]
|
||||
{
|
||||
COLOR_CHAR, code
|
||||
} );
|
||||
this.ordinal = count++;
|
||||
this.color = color;
|
||||
|
||||
BY_CHAR.put( code, this );
|
||||
BY_NAME.put( name.toUpperCase( Locale.ROOT ), this );
|
||||
}
|
||||
|
||||
private ChatColor(String name, String toString, int rgb)
|
||||
{
|
||||
this.name = name;
|
||||
this.toString = toString;
|
||||
this.ordinal = -1;
|
||||
this.color = new Color( rgb );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int hash = 7;
|
||||
hash = 53 * hash + Objects.hashCode( this.toString );
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if ( this == obj )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if ( obj == null || getClass() != obj.getClass() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
final ChatColor other = (ChatColor) obj;
|
||||
|
||||
return Objects.equals( this.toString, other.toString );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -188,4 +235,96 @@ public enum ChatColor
|
||||
{
|
||||
return BY_CHAR.get( code );
|
||||
}
|
||||
|
||||
public static ChatColor of(Color color)
|
||||
{
|
||||
return of( "#" + String.format( "%08x", color.getRGB() ).substring( 2 ) );
|
||||
}
|
||||
|
||||
public static ChatColor of(String string)
|
||||
{
|
||||
Preconditions.checkArgument( string != null, "string cannot be null" );
|
||||
if ( string.startsWith( "#" ) && string.length() == 7 )
|
||||
{
|
||||
int rgb;
|
||||
try
|
||||
{
|
||||
rgb = Integer.parseInt( string.substring( 1 ), 16 );
|
||||
} catch ( NumberFormatException ex )
|
||||
{
|
||||
throw new IllegalArgumentException( "Illegal hex string " + string );
|
||||
}
|
||||
|
||||
StringBuilder magic = new StringBuilder( COLOR_CHAR + "x" );
|
||||
for ( char c : string.substring( 1 ).toCharArray() )
|
||||
{
|
||||
magic.append( COLOR_CHAR ).append( c );
|
||||
}
|
||||
|
||||
return new ChatColor( string, magic.toString(), rgb );
|
||||
}
|
||||
|
||||
ChatColor defined = BY_NAME.get( string.toUpperCase( Locale.ROOT ) );
|
||||
if ( defined != null )
|
||||
{
|
||||
return defined;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException( "Could not parse ChatColor " + string );
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link Enum#valueOf(java.lang.Class, java.lang.String)}.
|
||||
*
|
||||
* @param name color name
|
||||
* @return ChatColor
|
||||
* @deprecated holdover from when this class was an enum
|
||||
*/
|
||||
@Deprecated
|
||||
public static ChatColor valueOf(String name)
|
||||
{
|
||||
Preconditions.checkNotNull( name, "Name is null" );
|
||||
|
||||
ChatColor defined = BY_NAME.get( name );
|
||||
Preconditions.checkArgument( defined != null, "No enum constant " + ChatColor.class.getName() + "." + name );
|
||||
|
||||
return defined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all defined colors and formats.
|
||||
*
|
||||
* @return copied array of all colors and formats
|
||||
* @deprecated holdover from when this class was an enum
|
||||
*/
|
||||
@Deprecated
|
||||
public static ChatColor[] values()
|
||||
{
|
||||
return BY_CHAR.values().toArray( new ChatColor[ 0 ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link Enum#name()}.
|
||||
*
|
||||
* @return constant name
|
||||
* @deprecated holdover from when this class was an enum
|
||||
*/
|
||||
@Deprecated
|
||||
public String name()
|
||||
{
|
||||
return getName().toUpperCase( Locale.ROOT );
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link Enum#ordinal()}.
|
||||
*
|
||||
* @return ordinal
|
||||
* @deprecated holdover from when this class was an enum
|
||||
*/
|
||||
@Deprecated
|
||||
public int ordinal()
|
||||
{
|
||||
Preconditions.checkArgument( ordinal >= 0, "Cannot get ordinal of hex color" );
|
||||
return ordinal;
|
||||
}
|
||||
}
|
||||
|
@@ -1,18 +1,18 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder.FormatRetention;
|
||||
|
||||
@Setter
|
||||
@ToString(exclude = "parent")
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(exclude = "parent")
|
||||
public abstract class BaseComponent
|
||||
{
|
||||
|
||||
@@ -23,6 +23,10 @@ public abstract class BaseComponent
|
||||
* The color of this component and any child components (unless overridden)
|
||||
*/
|
||||
private ChatColor color;
|
||||
/**
|
||||
* The font of this component and any child components (unless overridden)
|
||||
*/
|
||||
private String font;
|
||||
/**
|
||||
* Whether this component and any child components (unless overridden) is
|
||||
* bold
|
||||
@@ -62,38 +66,152 @@ public abstract class BaseComponent
|
||||
private List<BaseComponent> extra;
|
||||
|
||||
/**
|
||||
* The action to preform when this component (and child components) are
|
||||
* The action to perform when this component (and child components) are
|
||||
* clicked
|
||||
*/
|
||||
@Getter
|
||||
private ClickEvent clickEvent;
|
||||
/**
|
||||
* The action to preform when this component (and child components) are
|
||||
* The action to perform when this component (and child components) are
|
||||
* hovered over
|
||||
*/
|
||||
@Getter
|
||||
private HoverEvent hoverEvent;
|
||||
|
||||
/**
|
||||
* Whether this component rejects previous formatting
|
||||
*/
|
||||
@Getter
|
||||
private transient boolean reset;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*
|
||||
* @deprecated for use by internal classes only, will be removed.
|
||||
*/
|
||||
@Deprecated
|
||||
public BaseComponent()
|
||||
{
|
||||
}
|
||||
|
||||
BaseComponent(BaseComponent old)
|
||||
{
|
||||
setColor( old.getColorRaw() );
|
||||
setBold( old.isBoldRaw() );
|
||||
setItalic( old.isItalicRaw() );
|
||||
setUnderlined( old.isUnderlinedRaw() );
|
||||
setStrikethrough( old.isStrikethroughRaw() );
|
||||
setObfuscated( old.isObfuscatedRaw() );
|
||||
setInsertion( old.getInsertion() );
|
||||
setClickEvent( old.getClickEvent() );
|
||||
setHoverEvent( old.getHoverEvent() );
|
||||
copyFormatting( old, FormatRetention.ALL, true );
|
||||
|
||||
if ( old.getExtra() != null )
|
||||
{
|
||||
for ( BaseComponent component : old.getExtra() )
|
||||
for ( BaseComponent extra : old.getExtra() )
|
||||
{
|
||||
addExtra( component.duplicate() );
|
||||
addExtra( extra.duplicate() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the events and formatting of a BaseComponent. Already set
|
||||
* formatting will be replaced.
|
||||
*
|
||||
* @param component the component to copy from
|
||||
*/
|
||||
public void copyFormatting(BaseComponent component)
|
||||
{
|
||||
copyFormatting( component, FormatRetention.ALL, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the events and formatting of a BaseComponent.
|
||||
*
|
||||
* @param component the component to copy from
|
||||
* @param replace if already set formatting should be replaced by the new
|
||||
* component
|
||||
*/
|
||||
public void copyFormatting(BaseComponent component, boolean replace)
|
||||
{
|
||||
copyFormatting( component, FormatRetention.ALL, replace );
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the specified formatting of a BaseComponent.
|
||||
*
|
||||
* @param component the component to copy from
|
||||
* @param retention the formatting to copy
|
||||
* @param replace if already set formatting should be replaced by the new
|
||||
* component
|
||||
*/
|
||||
public void copyFormatting(BaseComponent component, FormatRetention retention, boolean replace)
|
||||
{
|
||||
if ( retention == FormatRetention.EVENTS || retention == FormatRetention.ALL )
|
||||
{
|
||||
if ( replace || clickEvent == null )
|
||||
{
|
||||
setClickEvent( component.getClickEvent() );
|
||||
}
|
||||
if ( replace || hoverEvent == null )
|
||||
{
|
||||
setHoverEvent( component.getHoverEvent() );
|
||||
}
|
||||
}
|
||||
if ( retention == FormatRetention.FORMATTING || retention == FormatRetention.ALL )
|
||||
{
|
||||
if ( replace || color == null )
|
||||
{
|
||||
setColor( component.getColorRaw() );
|
||||
}
|
||||
if ( replace || font == null )
|
||||
{
|
||||
setFont( component.getFontRaw() );
|
||||
}
|
||||
if ( replace || bold == null )
|
||||
{
|
||||
setBold( component.isBoldRaw() );
|
||||
}
|
||||
if ( replace || italic == null )
|
||||
{
|
||||
setItalic( component.isItalicRaw() );
|
||||
}
|
||||
if ( replace || underlined == null )
|
||||
{
|
||||
setUnderlined( component.isUnderlinedRaw() );
|
||||
}
|
||||
if ( replace || strikethrough == null )
|
||||
{
|
||||
setStrikethrough( component.isStrikethroughRaw() );
|
||||
}
|
||||
if ( replace || obfuscated == null )
|
||||
{
|
||||
setObfuscated( component.isObfuscatedRaw() );
|
||||
}
|
||||
if ( replace || insertion == null )
|
||||
{
|
||||
setInsertion( component.getInsertion() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retains only the specified formatting.
|
||||
*
|
||||
* @param retention the formatting to retain
|
||||
*/
|
||||
public void retain(FormatRetention retention)
|
||||
{
|
||||
if ( retention == FormatRetention.FORMATTING || retention == FormatRetention.NONE )
|
||||
{
|
||||
setClickEvent( null );
|
||||
setHoverEvent( null );
|
||||
}
|
||||
if ( retention == FormatRetention.EVENTS || retention == FormatRetention.NONE )
|
||||
{
|
||||
setColor( null );
|
||||
setBold( null );
|
||||
setItalic( null );
|
||||
setUnderlined( null );
|
||||
setStrikethrough( null );
|
||||
setObfuscated( null );
|
||||
setInsertion( null );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the BaseComponent and returns the clone.
|
||||
*
|
||||
@@ -101,6 +219,20 @@ public abstract class BaseComponent
|
||||
*/
|
||||
public abstract BaseComponent duplicate();
|
||||
|
||||
/**
|
||||
* Clones the BaseComponent without formatting and returns the clone.
|
||||
*
|
||||
* @return The duplicate of this BaseComponent
|
||||
* @deprecated API use discouraged, use traditional duplicate
|
||||
*/
|
||||
@Deprecated
|
||||
public BaseComponent duplicateWithoutFormatting()
|
||||
{
|
||||
BaseComponent component = duplicate();
|
||||
component.retain( FormatRetention.NONE );
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the components to a string that uses the old formatting codes
|
||||
* ({@link net.md_5.bungee.api.ChatColor#COLOR_CHAR}
|
||||
@@ -165,6 +297,36 @@ public abstract class BaseComponent
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the font of this component. This uses the parent's font if this
|
||||
* component doesn't have one.
|
||||
*
|
||||
* @return the font of this component, or null if default font
|
||||
*/
|
||||
public String getFont()
|
||||
{
|
||||
if ( font == null )
|
||||
{
|
||||
if ( parent == null )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return parent.getFont();
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the font of this component without checking the parents font. May
|
||||
* return null
|
||||
*
|
||||
* @return the font of this component
|
||||
*/
|
||||
public String getFontRaw()
|
||||
{
|
||||
return font;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this component is bold. This uses the parent's setting if
|
||||
* this component hasn't been set. false is returned if none of the parent
|
||||
@@ -343,10 +505,10 @@ public abstract class BaseComponent
|
||||
*/
|
||||
public boolean hasFormatting()
|
||||
{
|
||||
return color != null || bold != null
|
||||
return color != null || font != null || bold != null
|
||||
|| italic != null || underlined != null
|
||||
|| strikethrough != null || obfuscated != null
|
||||
|| hoverEvent != null || clickEvent != null;
|
||||
|| insertion != null || hoverEvent != null || clickEvent != null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -395,4 +557,29 @@ public abstract class BaseComponent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addFormat(StringBuilder builder)
|
||||
{
|
||||
builder.append( getColor() );
|
||||
if ( isBold() )
|
||||
{
|
||||
builder.append( ChatColor.BOLD );
|
||||
}
|
||||
if ( isItalic() )
|
||||
{
|
||||
builder.append( ChatColor.ITALIC );
|
||||
}
|
||||
if ( isUnderlined() )
|
||||
{
|
||||
builder.append( ChatColor.UNDERLINE );
|
||||
}
|
||||
if ( isStrikethrough() )
|
||||
{
|
||||
builder.append( ChatColor.STRIKETHROUGH );
|
||||
}
|
||||
if ( isObfuscated() )
|
||||
{
|
||||
builder.append( ChatColor.MAGIC );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,22 +1,23 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.chat.ClickEvent.Action;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class ClickEvent
|
||||
{
|
||||
|
||||
/**
|
||||
* The type of action to perform on click
|
||||
* The type of action to perform on click.
|
||||
*/
|
||||
private final Action action;
|
||||
/**
|
||||
* Depends on action
|
||||
* Depends on the action.
|
||||
*
|
||||
* @see Action
|
||||
*/
|
||||
@@ -27,29 +28,35 @@ public final class ClickEvent
|
||||
|
||||
/**
|
||||
* Open a url at the path given by
|
||||
* {@link net.md_5.bungee.api.chat.ClickEvent#value}
|
||||
* {@link net.md_5.bungee.api.chat.ClickEvent#value}.
|
||||
*/
|
||||
OPEN_URL,
|
||||
/**
|
||||
* Open a file at the path given by
|
||||
* {@link net.md_5.bungee.api.chat.ClickEvent#value}
|
||||
* {@link net.md_5.bungee.api.chat.ClickEvent#value}.
|
||||
*/
|
||||
OPEN_FILE,
|
||||
/**
|
||||
* Run the command given by
|
||||
* {@link net.md_5.bungee.api.chat.ClickEvent#value}
|
||||
* {@link net.md_5.bungee.api.chat.ClickEvent#value}.
|
||||
*/
|
||||
RUN_COMMAND,
|
||||
/**
|
||||
* Inserts the string given by
|
||||
* {@link net.md_5.bungee.api.chat.ClickEvent#value} into the players
|
||||
* text box
|
||||
* {@link net.md_5.bungee.api.chat.ClickEvent#value} into the player's
|
||||
* text box.
|
||||
*/
|
||||
SUGGEST_COMMAND,
|
||||
/**
|
||||
* Change to the page number given by
|
||||
* {@link net.md_5.bungee.api.chat.ClickEvent#value} in a book
|
||||
* {@link net.md_5.bungee.api.chat.ClickEvent#value} in a book.
|
||||
*/
|
||||
CHANGE_PAGE
|
||||
CHANGE_PAGE,
|
||||
/**
|
||||
* Copy the string given by
|
||||
* {@link net.md_5.bungee.api.chat.ClickEvent#value} into the player's
|
||||
* clipboard.
|
||||
*/
|
||||
COPY_TO_CLIPBOARD
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,11 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -22,11 +25,29 @@ import java.util.List;
|
||||
* part's formatting
|
||||
* </p>
|
||||
*/
|
||||
public class ComponentBuilder
|
||||
@NoArgsConstructor
|
||||
public final class ComponentBuilder
|
||||
{
|
||||
|
||||
private TextComponent current;
|
||||
/**
|
||||
* The position for the current part to modify. Modified cursors will
|
||||
* automatically reset to the last part after appending new components.
|
||||
* Default value at -1 to assert that the builder has no parts.
|
||||
*/
|
||||
@Getter
|
||||
private int cursor = -1;
|
||||
@Getter
|
||||
private final List<BaseComponent> parts = new ArrayList<BaseComponent>();
|
||||
private BaseComponent dummy;
|
||||
|
||||
private ComponentBuilder(BaseComponent[] parts)
|
||||
{
|
||||
for ( BaseComponent baseComponent : parts )
|
||||
{
|
||||
this.parts.add( baseComponent.duplicate() );
|
||||
}
|
||||
resetCursor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ComponentBuilder from the other given ComponentBuilder to clone
|
||||
@@ -36,11 +57,7 @@ public class ComponentBuilder
|
||||
*/
|
||||
public ComponentBuilder(ComponentBuilder original)
|
||||
{
|
||||
current = new TextComponent( original.current );
|
||||
for ( BaseComponent baseComponent : original.parts )
|
||||
{
|
||||
parts.add( baseComponent.duplicate() );
|
||||
}
|
||||
this( original.parts.toArray( new BaseComponent[ 0 ] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,12 +67,146 @@ public class ComponentBuilder
|
||||
*/
|
||||
public ComponentBuilder(String text)
|
||||
{
|
||||
current = new TextComponent( text );
|
||||
this( new TextComponent( text ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ComponentBuilder with the given component as the first part.
|
||||
*
|
||||
* @param component the first component element
|
||||
*/
|
||||
public ComponentBuilder(BaseComponent component)
|
||||
{
|
||||
|
||||
this( new BaseComponent[]
|
||||
{
|
||||
component
|
||||
} );
|
||||
}
|
||||
|
||||
private BaseComponent getDummy()
|
||||
{
|
||||
if ( dummy == null )
|
||||
{
|
||||
dummy = new BaseComponent()
|
||||
{
|
||||
@Override
|
||||
public BaseComponent duplicate()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
return dummy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the cursor to index of the last element.
|
||||
*
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder resetCursor()
|
||||
{
|
||||
cursor = parts.size() - 1;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the position of the current component to be modified
|
||||
*
|
||||
* @param pos the cursor position synonymous to an element position for a
|
||||
* list
|
||||
* @return this ComponentBuilder for chaining
|
||||
* @throws IndexOutOfBoundsException if the index is out of range
|
||||
* ({@code index < 0 || index >= size()})
|
||||
*/
|
||||
public ComponentBuilder setCursor(int pos) throws IndexOutOfBoundsException
|
||||
{
|
||||
if ( ( this.cursor != pos ) && ( pos < 0 || pos >= parts.size() ) )
|
||||
{
|
||||
throw new IndexOutOfBoundsException( "Cursor out of bounds (expected between 0 + " + ( parts.size() - 1 ) + ")" );
|
||||
}
|
||||
|
||||
this.cursor = pos;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a component to the builder and makes it the current target for
|
||||
* formatting. The component will have all the formatting from previous
|
||||
* part.
|
||||
*
|
||||
* @param component the component to append
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder append(BaseComponent component)
|
||||
{
|
||||
return append( component, FormatRetention.ALL );
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a component to the builder and makes it the current target for
|
||||
* formatting. You can specify the amount of formatting retained from
|
||||
* previous part.
|
||||
*
|
||||
* @param component the component to append
|
||||
* @param retention the formatting to retain
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder append(BaseComponent component, FormatRetention retention)
|
||||
{
|
||||
BaseComponent previous = ( parts.isEmpty() ) ? null : parts.get( parts.size() - 1 );
|
||||
if ( previous == null )
|
||||
{
|
||||
previous = dummy;
|
||||
dummy = null;
|
||||
}
|
||||
if ( previous != null && !component.isReset() )
|
||||
{
|
||||
component.copyFormatting( previous, retention, false );
|
||||
}
|
||||
parts.add( component );
|
||||
resetCursor();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the components to the builder and makes the last element the
|
||||
* current target for formatting. The components will have all the
|
||||
* formatting from previous part.
|
||||
*
|
||||
* @param components the components to append
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder append(BaseComponent[] components)
|
||||
{
|
||||
return append( components, FormatRetention.ALL );
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the components to the builder and makes the last element the
|
||||
* current target for formatting. You can specify the amount of formatting
|
||||
* retained from previous part.
|
||||
*
|
||||
* @param components the components to append
|
||||
* @param retention the formatting to retain
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder append(BaseComponent[] components, FormatRetention retention)
|
||||
{
|
||||
Preconditions.checkArgument( components.length != 0, "No components to append" );
|
||||
|
||||
for ( BaseComponent component : components )
|
||||
{
|
||||
append( component, retention );
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the text to the builder and makes it the current target for
|
||||
* formatting. The text will have all the formatting from the previous part.
|
||||
* formatting. The text will have all the formatting from previous part.
|
||||
*
|
||||
* @param text the text to append
|
||||
* @return this ComponentBuilder for chaining
|
||||
@@ -65,9 +216,23 @@ public class ComponentBuilder
|
||||
return append( text, FormatRetention.ALL );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse text to BaseComponent[] with colors and format, appends the text to
|
||||
* the builder and makes it the current target for formatting. The component
|
||||
* will have all the formatting from previous part.
|
||||
*
|
||||
* @param text the text to append
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder appendLegacy(String text)
|
||||
{
|
||||
return append( TextComponent.fromLegacyText( text ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the text to the builder and makes it the current target for
|
||||
* formatting. You can specify the amount of formatting retained.
|
||||
* formatting. You can specify the amount of formatting retained from
|
||||
* previous part.
|
||||
*
|
||||
* @param text the text to append
|
||||
* @param retention the formatting to retain
|
||||
@@ -75,13 +240,76 @@ public class ComponentBuilder
|
||||
*/
|
||||
public ComponentBuilder append(String text, FormatRetention retention)
|
||||
{
|
||||
parts.add( current );
|
||||
return append( new TextComponent( text ), retention );
|
||||
}
|
||||
|
||||
current = new TextComponent( current );
|
||||
current.setText( text );
|
||||
retain( retention );
|
||||
/**
|
||||
* Allows joining additional components to this builder using the given
|
||||
* {@link Joiner} and {@link FormatRetention#ALL}.
|
||||
*
|
||||
* Simply executes the provided joiner on this instance to facilitate a
|
||||
* chain pattern.
|
||||
*
|
||||
* @param joiner joiner used for operation
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder append(Joiner joiner)
|
||||
{
|
||||
return joiner.join( this, FormatRetention.ALL );
|
||||
}
|
||||
|
||||
return this;
|
||||
/**
|
||||
* Allows joining additional components to this builder using the given
|
||||
* {@link Joiner}.
|
||||
*
|
||||
* Simply executes the provided joiner on this instance to facilitate a
|
||||
* chain pattern.
|
||||
*
|
||||
* @param joiner joiner used for operation
|
||||
* @param retention the formatting to retain
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder append(Joiner joiner, FormatRetention retention)
|
||||
{
|
||||
return joiner.join( this, retention );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the component part at the position of given index.
|
||||
*
|
||||
* @param pos the index to remove at
|
||||
* @throws IndexOutOfBoundsException if the index is out of range
|
||||
* ({@code index < 0 || index >= size()})
|
||||
*/
|
||||
public void removeComponent(int pos) throws IndexOutOfBoundsException
|
||||
{
|
||||
if ( parts.remove( pos ) != null )
|
||||
{
|
||||
resetCursor();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the component part at the position of given index.
|
||||
*
|
||||
* @param pos the index to find
|
||||
* @return the component
|
||||
* @throws IndexOutOfBoundsException if the index is out of range
|
||||
* ({@code index < 0 || index >= size()})
|
||||
*/
|
||||
public BaseComponent getComponent(int pos) throws IndexOutOfBoundsException
|
||||
{
|
||||
return parts.get( pos );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the component at the position of the cursor.
|
||||
*
|
||||
* @return the active component or null if builder is empty
|
||||
*/
|
||||
public BaseComponent getCurrentComponent()
|
||||
{
|
||||
return ( cursor == -1 ) ? getDummy() : parts.get( cursor );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,7 +320,19 @@ public class ComponentBuilder
|
||||
*/
|
||||
public ComponentBuilder color(ChatColor color)
|
||||
{
|
||||
current.setColor( color );
|
||||
getCurrentComponent().setColor( color );
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the font of the current part.
|
||||
*
|
||||
* @param font the new font
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder font(String font)
|
||||
{
|
||||
getCurrentComponent().setFont( font );
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -104,7 +344,7 @@ public class ComponentBuilder
|
||||
*/
|
||||
public ComponentBuilder bold(boolean bold)
|
||||
{
|
||||
current.setBold( bold );
|
||||
getCurrentComponent().setBold( bold );
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -116,7 +356,7 @@ public class ComponentBuilder
|
||||
*/
|
||||
public ComponentBuilder italic(boolean italic)
|
||||
{
|
||||
current.setItalic( italic );
|
||||
getCurrentComponent().setItalic( italic );
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -128,7 +368,7 @@ public class ComponentBuilder
|
||||
*/
|
||||
public ComponentBuilder underlined(boolean underlined)
|
||||
{
|
||||
current.setUnderlined( underlined );
|
||||
getCurrentComponent().setUnderlined( underlined );
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -140,7 +380,7 @@ public class ComponentBuilder
|
||||
*/
|
||||
public ComponentBuilder strikethrough(boolean strikethrough)
|
||||
{
|
||||
current.setStrikethrough( strikethrough );
|
||||
getCurrentComponent().setStrikethrough( strikethrough );
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -152,7 +392,7 @@ public class ComponentBuilder
|
||||
*/
|
||||
public ComponentBuilder obfuscated(boolean obfuscated)
|
||||
{
|
||||
current.setObfuscated( obfuscated );
|
||||
getCurrentComponent().setObfuscated( obfuscated );
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -164,7 +404,7 @@ public class ComponentBuilder
|
||||
*/
|
||||
public ComponentBuilder insertion(String insertion)
|
||||
{
|
||||
current.setInsertion( insertion );
|
||||
getCurrentComponent().setInsertion( insertion );
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -176,7 +416,7 @@ public class ComponentBuilder
|
||||
*/
|
||||
public ComponentBuilder event(ClickEvent clickEvent)
|
||||
{
|
||||
current.setClickEvent( clickEvent );
|
||||
getCurrentComponent().setClickEvent( clickEvent );
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -188,7 +428,7 @@ public class ComponentBuilder
|
||||
*/
|
||||
public ComponentBuilder event(HoverEvent hoverEvent)
|
||||
{
|
||||
current.setHoverEvent( hoverEvent );
|
||||
getCurrentComponent().setHoverEvent( hoverEvent );
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -210,51 +450,61 @@ public class ComponentBuilder
|
||||
*/
|
||||
public ComponentBuilder retain(FormatRetention retention)
|
||||
{
|
||||
BaseComponent previous = current;
|
||||
|
||||
switch ( retention )
|
||||
{
|
||||
case NONE:
|
||||
current = new TextComponent( current.getText() );
|
||||
break;
|
||||
case ALL:
|
||||
// No changes are required
|
||||
break;
|
||||
case EVENTS:
|
||||
current = new TextComponent( current.getText() );
|
||||
current.setInsertion( previous.getInsertion() );
|
||||
current.setClickEvent( previous.getClickEvent() );
|
||||
current.setHoverEvent( previous.getHoverEvent() );
|
||||
break;
|
||||
case FORMATTING:
|
||||
current.setClickEvent( null );
|
||||
current.setHoverEvent( null );
|
||||
break;
|
||||
}
|
||||
getCurrentComponent().retain( retention );
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component built by this builder. If this builder is
|
||||
* empty, an empty text component will be returned.
|
||||
*
|
||||
* @return the component
|
||||
*/
|
||||
public BaseComponent build()
|
||||
{
|
||||
TextComponent base = new TextComponent();
|
||||
if ( !parts.isEmpty() )
|
||||
{
|
||||
List<BaseComponent> cloned = new ArrayList<>( parts );
|
||||
cloned.replaceAll( BaseComponent::duplicate );
|
||||
base.setExtra( cloned );
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the components needed to display the message created by this
|
||||
* builder.
|
||||
* builder.git
|
||||
* <p>
|
||||
* <strong>NOTE:</strong> {@link #build()} is preferred as it will
|
||||
* consolidate all components into a single BaseComponent with extra
|
||||
* contents as opposed to an array of components which is non-standard
|
||||
* and may result in unexpected behavior.
|
||||
*
|
||||
* @return the created components
|
||||
*/
|
||||
public BaseComponent[] create()
|
||||
{
|
||||
parts.add( current );
|
||||
return parts.toArray( new BaseComponent[ parts.size() ] );
|
||||
BaseComponent[] cloned = new BaseComponent[ parts.size() ];
|
||||
int i = 0;
|
||||
for ( BaseComponent part : parts )
|
||||
{
|
||||
cloned[i++] = part.duplicate();
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
|
||||
public static enum FormatRetention
|
||||
public enum FormatRetention
|
||||
{
|
||||
|
||||
/**
|
||||
* Specify that we do not want to retain anything from the previous component.
|
||||
* Specify that we do not want to retain anything from the previous
|
||||
* component.
|
||||
*/
|
||||
NONE,
|
||||
/**
|
||||
* Specify that we want the formatting retained from the previous component.
|
||||
* Specify that we want the formatting retained from the previous
|
||||
* component.
|
||||
*/
|
||||
FORMATTING,
|
||||
/**
|
||||
@@ -262,8 +512,30 @@ public class ComponentBuilder
|
||||
*/
|
||||
EVENTS,
|
||||
/**
|
||||
* Specify that we want to retain everything from the previous component.
|
||||
* Specify that we want to retain everything from the previous
|
||||
* component.
|
||||
*/
|
||||
ALL
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional interface to join additional components to a ComponentBuilder.
|
||||
*/
|
||||
public interface Joiner
|
||||
{
|
||||
|
||||
/**
|
||||
* Joins additional components to the provided {@link ComponentBuilder}
|
||||
* and then returns it to fulfill a chain pattern.
|
||||
*
|
||||
* Retention may be ignored and is to be understood as an optional
|
||||
* recommendation to the Joiner and not as a guarantee to have a
|
||||
* previous component in builder unmodified.
|
||||
*
|
||||
* @param componentBuilder to which to append additional components
|
||||
* @param retention the formatting to possibly retain
|
||||
* @return input componentBuilder for chaining
|
||||
*/
|
||||
ComponentBuilder join(ComponentBuilder componentBuilder, FormatRetention retention);
|
||||
}
|
||||
}
|
||||
|
@@ -1,24 +1,146 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.chat.hover.content.Content;
|
||||
import net.md_5.bungee.api.chat.hover.content.Entity;
|
||||
import net.md_5.bungee.api.chat.hover.content.Item;
|
||||
import net.md_5.bungee.api.chat.hover.content.Text;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
final public class HoverEvent
|
||||
public final class HoverEvent
|
||||
{
|
||||
|
||||
/**
|
||||
* The action of this event.
|
||||
*/
|
||||
private final Action action;
|
||||
private final BaseComponent[] value;
|
||||
/**
|
||||
* List of contents to provide for this event.
|
||||
*/
|
||||
private final List<Content> contents;
|
||||
/**
|
||||
* Returns whether this hover event is prior to 1.16
|
||||
*/
|
||||
@Setter
|
||||
private boolean legacy = false;
|
||||
|
||||
/**
|
||||
* Creates event with an action and a list of contents.
|
||||
*
|
||||
* @param action action of this event
|
||||
* @param contents array of contents, provide at least one
|
||||
*/
|
||||
public HoverEvent(Action action, Content... contents)
|
||||
{
|
||||
Preconditions.checkArgument( contents.length != 0,
|
||||
"Must contain at least one content" );
|
||||
this.action = action;
|
||||
this.contents = new ArrayList<>();
|
||||
for ( Content it : contents )
|
||||
{
|
||||
addContent( it );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy constructor to create hover event.
|
||||
*
|
||||
* @param action the action
|
||||
* @param value the value
|
||||
* @deprecated {@link #HoverEvent(Action, Content[])}
|
||||
*/
|
||||
@Deprecated
|
||||
public HoverEvent(Action action, BaseComponent[] value)
|
||||
{
|
||||
// Old plugins may have somehow hacked BaseComponent[] into
|
||||
// anything other than SHOW_TEXT action. Ideally continue support.
|
||||
this.action = action;
|
||||
this.contents = new ArrayList<>( Collections.singletonList( new Text( value ) ) );
|
||||
this.legacy = true;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public BaseComponent[] getValue()
|
||||
{
|
||||
Content content = contents.get( 0 );
|
||||
if ( content instanceof Text && ( (Text) content ).getValue() instanceof BaseComponent[] )
|
||||
{
|
||||
return (BaseComponent[]) ( (Text) content ).getValue();
|
||||
}
|
||||
|
||||
TextComponent component = new TextComponent( ComponentSerializer.toString( content ) );
|
||||
return new BaseComponent[]
|
||||
{
|
||||
component
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a content to this hover event.
|
||||
*
|
||||
* @param content the content add
|
||||
* @throws IllegalArgumentException if is a legacy component and already has
|
||||
* a content
|
||||
* @throws UnsupportedOperationException if content action does not match
|
||||
* hover event action
|
||||
*/
|
||||
public void addContent(Content content) throws UnsupportedOperationException
|
||||
{
|
||||
Preconditions.checkArgument( !legacy || contents.size() == 0,
|
||||
"Legacy HoverEvent may not have more than one content" );
|
||||
content.assertAction( action );
|
||||
contents.add( content );
|
||||
}
|
||||
|
||||
public enum Action
|
||||
{
|
||||
|
||||
SHOW_TEXT,
|
||||
SHOW_ACHIEVEMENT,
|
||||
SHOW_ITEM,
|
||||
SHOW_ENTITY
|
||||
SHOW_ENTITY,
|
||||
/**
|
||||
* Removed since 1.12. Advancements instead simply use show_text. The ID
|
||||
* of an achievement or statistic to display. Example: new
|
||||
* ComponentText( "achievement.openInventory" )
|
||||
*/
|
||||
@Deprecated
|
||||
SHOW_ACHIEVEMENT,
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate {@link Content} class for an {@link Action} for the
|
||||
* GSON serialization
|
||||
*
|
||||
* @param action the action to get for
|
||||
* @param array if to return the arrayed class
|
||||
* @return the class
|
||||
*/
|
||||
public static Class<?> getClass(HoverEvent.Action action, boolean array)
|
||||
{
|
||||
Preconditions.checkArgument( action != null, "action" );
|
||||
|
||||
switch ( action )
|
||||
{
|
||||
case SHOW_TEXT:
|
||||
return ( array ) ? Text[].class : Text.class;
|
||||
case SHOW_ENTITY:
|
||||
return ( array ) ? Entity[].class : Entity.class;
|
||||
case SHOW_ITEM:
|
||||
return ( array ) ? Item[].class : Item.class;
|
||||
default:
|
||||
throw new UnsupportedOperationException( "Action '" + action.name() + " not supported" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
73
chat/src/main/java/net/md_5/bungee/api/chat/ItemTag.java
Normal file
73
chat/src/main/java/net/md_5/bungee/api/chat/ItemTag.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import java.lang.reflect.Type;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Metadata for use in conjunction with {@link HoverEvent.Action#SHOW_ITEM}
|
||||
*/
|
||||
@Builder(builderClassName = "Builder", access = AccessLevel.PRIVATE)
|
||||
@ToString(of = "nbt")
|
||||
@EqualsAndHashCode(of = "nbt")
|
||||
@Setter
|
||||
public final class ItemTag
|
||||
{
|
||||
|
||||
@Getter
|
||||
private final String nbt;
|
||||
|
||||
/*
|
||||
TODO
|
||||
private BaseComponent name;
|
||||
@Singular("ench")
|
||||
private List<Enchantment> enchantments;
|
||||
@Singular("lore")
|
||||
private List<BaseComponent[]> lore;
|
||||
private Boolean unbreakable;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class Enchantment
|
||||
{
|
||||
|
||||
private final int level;
|
||||
private final int id;
|
||||
}
|
||||
*/
|
||||
|
||||
private ItemTag(String nbt)
|
||||
{
|
||||
this.nbt = nbt;
|
||||
}
|
||||
|
||||
public static ItemTag ofNbt(String nbt)
|
||||
{
|
||||
return new ItemTag( nbt );
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonSerializer<ItemTag>, JsonDeserializer<ItemTag>
|
||||
{
|
||||
|
||||
@Override
|
||||
public ItemTag deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
return ItemTag.ofNbt( element.getAsJsonPrimitive().getAsString() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(ItemTag itemTag, Type type, JsonSerializationContext context)
|
||||
{
|
||||
return context.serialize( itemTag.getNbt() );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class KeybindComponent extends BaseComponent
|
||||
{
|
||||
|
||||
/**
|
||||
* The keybind identifier to use.
|
||||
* <br>
|
||||
* Will be replaced with the actual key the client is using.
|
||||
*/
|
||||
private String keybind;
|
||||
|
||||
/**
|
||||
* Creates a keybind component from the original to clone it.
|
||||
*
|
||||
* @param original the original for the new keybind component.
|
||||
*/
|
||||
public KeybindComponent(KeybindComponent original)
|
||||
{
|
||||
super( original );
|
||||
setKeybind( original.getKeybind() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a keybind component with the passed internal keybind value.
|
||||
*
|
||||
* @param keybind the keybind value
|
||||
* @see Keybinds
|
||||
*/
|
||||
public KeybindComponent(String keybind)
|
||||
{
|
||||
setKeybind( keybind );
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeybindComponent duplicate()
|
||||
{
|
||||
return new KeybindComponent( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void toPlainText(StringBuilder builder)
|
||||
{
|
||||
builder.append( getKeybind() );
|
||||
super.toPlainText( builder );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void toLegacyText(StringBuilder builder)
|
||||
{
|
||||
addFormat( builder );
|
||||
builder.append( getKeybind() );
|
||||
super.toLegacyText( builder );
|
||||
}
|
||||
}
|
52
chat/src/main/java/net/md_5/bungee/api/chat/Keybinds.java
Normal file
52
chat/src/main/java/net/md_5/bungee/api/chat/Keybinds.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
/**
|
||||
* All keybind values supported by vanilla Minecraft.
|
||||
* <br>
|
||||
* Values may be removed if they are no longer supported.
|
||||
*
|
||||
* @see KeybindComponent
|
||||
*/
|
||||
public interface Keybinds
|
||||
{
|
||||
|
||||
String JUMP = "key.jump";
|
||||
String SNEAK = "key.sneak";
|
||||
String SPRINT = "key.sprint";
|
||||
String LEFT = "key.left";
|
||||
String RIGHT = "key.right";
|
||||
String BACK = "key.back";
|
||||
String FORWARD = "key.forward";
|
||||
|
||||
String ATTACK = "key.attack";
|
||||
String PICK_ITEM = "key.pickItem";
|
||||
String USE = "key.use";
|
||||
|
||||
String DROP = "key.drop";
|
||||
String HOTBAR_1 = "key.hotbar.1";
|
||||
String HOTBAR_2 = "key.hotbar.2";
|
||||
String HOTBAR_3 = "key.hotbar.3";
|
||||
String HOTBAR_4 = "key.hotbar.4";
|
||||
String HOTBAR_5 = "key.hotbar.5";
|
||||
String HOTBAR_6 = "key.hotbar.6";
|
||||
String HOTBAR_7 = "key.hotbar.7";
|
||||
String HOTBAR_8 = "key.hotbar.8";
|
||||
String HOTBAR_9 = "key.hotbar.9";
|
||||
String INVENTORY = "key.inventory";
|
||||
String SWAP_HANDS = "key.swapHands";
|
||||
|
||||
String LOAD_TOOLBAR_ACTIVATOR = "key.loadToolbarActivator";
|
||||
String SAVE_TOOLBAR_ACTIVATOR = "key.saveToolbarActivator";
|
||||
|
||||
String PLAYERLIST = "key.playerlist";
|
||||
String CHAT = "key.chat";
|
||||
String COMMAND = "key.command";
|
||||
String SOCIAL_INTERACTIONS = "key.socialInteractions";
|
||||
|
||||
String ADVANCEMENTS = "key.advancements";
|
||||
String SPECTATOR_OUTLINES = "key.spectatorOutlines";
|
||||
String SCREENSHOT = "key.screenshot";
|
||||
String SMOOTH_CAMERA = "key.smoothCamera";
|
||||
String FULLSCREEN = "key.fullscreen";
|
||||
String TOGGLE_PERSPECTIVE = "key.togglePerspective";
|
||||
}
|
101
chat/src/main/java/net/md_5/bungee/api/chat/ScoreComponent.java
Normal file
101
chat/src/main/java/net/md_5/bungee/api/chat/ScoreComponent.java
Normal file
@@ -0,0 +1,101 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* This component displays the score based on a player score on the scoreboard.
|
||||
* <br>
|
||||
* The <b>name</b> is the name of the player stored on the scoreboard, which may
|
||||
* be a "fake" player. It can also be a target selector that <b>must</b> resolve
|
||||
* to 1 target, and may target non-player entities.
|
||||
* <br>
|
||||
* With a book, /tellraw, or /title, using the wildcard '*' in the place of a
|
||||
* name or target selector will cause all players to see their own score in the
|
||||
* specified objective.
|
||||
* <br>
|
||||
* <b>Signs cannot use the '*' wildcard</b>
|
||||
* <br>
|
||||
* These values are filled in by the server-side implementation.
|
||||
* <br>
|
||||
* As of 1.12.2, a bug ( MC-56373 ) prevents full usage within hover events.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class ScoreComponent extends BaseComponent
|
||||
{
|
||||
|
||||
/**
|
||||
* The name of the entity whose score should be displayed.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* The internal name of the objective the score is attached to.
|
||||
*/
|
||||
private String objective;
|
||||
|
||||
/**
|
||||
* The optional value to use instead of the one present in the Scoreboard.
|
||||
*/
|
||||
private String value = "";
|
||||
|
||||
/**
|
||||
* Creates a new score component with the specified name and objective.<br>
|
||||
* If not specifically set, value will default to an empty string;
|
||||
* signifying that the scoreboard value should take precedence. If not null,
|
||||
* nor empty, {@code value} will override any value found in the
|
||||
* scoreboard.<br>
|
||||
* The value defaults to an empty string.
|
||||
*
|
||||
* @param name the name of the entity, or an entity selector, whose score
|
||||
* should be displayed
|
||||
* @param objective the internal name of the objective the entity's score is
|
||||
* attached to
|
||||
*/
|
||||
public ScoreComponent(String name, String objective)
|
||||
{
|
||||
setName( name );
|
||||
setObjective( objective );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a score component from the original to clone it.
|
||||
*
|
||||
* @param original the original for the new score component
|
||||
*/
|
||||
public ScoreComponent(ScoreComponent original)
|
||||
{
|
||||
super( original );
|
||||
setName( original.getName() );
|
||||
setObjective( original.getObjective() );
|
||||
setValue( original.getValue() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScoreComponent duplicate()
|
||||
{
|
||||
return new ScoreComponent( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void toPlainText(StringBuilder builder)
|
||||
{
|
||||
builder.append( this.value );
|
||||
super.toPlainText( builder );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void toLegacyText(StringBuilder builder)
|
||||
{
|
||||
addFormat( builder );
|
||||
builder.append( this.value );
|
||||
super.toLegacyText( builder );
|
||||
}
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* This component processes a target selector into a pre-formatted set of
|
||||
* discovered names.
|
||||
* <br>
|
||||
* Multiple targets may be obtained, and with commas separating each one and a
|
||||
* final "and" for the last target. The resulting format cannot be overwritten.
|
||||
* This includes all styling from team prefixes, insertions, click events, and
|
||||
* hover events.
|
||||
* <br>
|
||||
* These values are filled in by the server-side implementation.
|
||||
* <br>
|
||||
* As of 1.12.2, a bug ( MC-56373 ) prevents full usage within hover events.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class SelectorComponent extends BaseComponent
|
||||
{
|
||||
|
||||
/**
|
||||
* An entity target selector (@p, @a, @r, @e, or @s) and, optionally,
|
||||
* selector arguments (e.g. @e[r=10,type=Creeper]).
|
||||
*/
|
||||
private String selector;
|
||||
|
||||
/**
|
||||
* Creates a selector component from the original to clone it.
|
||||
*
|
||||
* @param original the original for the new selector component
|
||||
*/
|
||||
public SelectorComponent(SelectorComponent original)
|
||||
{
|
||||
super( original );
|
||||
setSelector( original.getSelector() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SelectorComponent duplicate()
|
||||
{
|
||||
return new SelectorComponent( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void toPlainText(StringBuilder builder)
|
||||
{
|
||||
builder.append( this.selector );
|
||||
super.toPlainText( builder );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void toLegacyText(StringBuilder builder)
|
||||
{
|
||||
addFormat( builder );
|
||||
builder.append( this.selector );
|
||||
super.toLegacyText( builder );
|
||||
}
|
||||
}
|
@@ -1,21 +1,20 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class TextComponent extends BaseComponent
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class TextComponent extends BaseComponent
|
||||
{
|
||||
|
||||
private static final Pattern url = Pattern.compile( "^(?:(https?)://)?([-\\w_\\.]{2,}\\.[a-z]{2,4})(/\\S*)?$" );
|
||||
@@ -30,7 +29,22 @@ public class TextComponent extends BaseComponent
|
||||
*/
|
||||
public static BaseComponent[] fromLegacyText(String message)
|
||||
{
|
||||
ArrayList<BaseComponent> components = new ArrayList<BaseComponent>();
|
||||
return fromLegacyText( message, ChatColor.WHITE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the old formatting system that used
|
||||
* {@link net.md_5.bungee.api.ChatColor#COLOR_CHAR} into the new json based
|
||||
* system.
|
||||
*
|
||||
* @param message the text to convert
|
||||
* @param defaultColor color to use when no formatting is to be applied
|
||||
* (i.e. after ChatColor.RESET).
|
||||
* @return the components needed to print the message to the client
|
||||
*/
|
||||
public static BaseComponent[] fromLegacyText(String message, ChatColor defaultColor)
|
||||
{
|
||||
ArrayList<BaseComponent> components = new ArrayList<>();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
TextComponent component = new TextComponent();
|
||||
Matcher matcher = url.matcher( message );
|
||||
@@ -40,13 +54,36 @@ public class TextComponent extends BaseComponent
|
||||
char c = message.charAt( i );
|
||||
if ( c == ChatColor.COLOR_CHAR )
|
||||
{
|
||||
i++;
|
||||
if ( ++i >= message.length() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
c = message.charAt( i );
|
||||
if ( c >= 'A' && c <= 'Z' )
|
||||
{
|
||||
c += 32;
|
||||
}
|
||||
ChatColor format = ChatColor.getByChar( c );
|
||||
ChatColor format;
|
||||
if ( c == 'x' && i + 12 < message.length() )
|
||||
{
|
||||
StringBuilder hex = new StringBuilder( "#" );
|
||||
for ( int j = 0; j < 6; j++ )
|
||||
{
|
||||
hex.append( message.charAt( i + 2 + ( j * 2 ) ) );
|
||||
}
|
||||
try
|
||||
{
|
||||
format = ChatColor.of( hex.toString() );
|
||||
} catch ( IllegalArgumentException ex )
|
||||
{
|
||||
format = null;
|
||||
}
|
||||
|
||||
i += 12;
|
||||
} else
|
||||
{
|
||||
format = ChatColor.getByChar( c );
|
||||
}
|
||||
if ( format == null )
|
||||
{
|
||||
continue;
|
||||
@@ -59,29 +96,30 @@ public class TextComponent extends BaseComponent
|
||||
builder = new StringBuilder();
|
||||
components.add( old );
|
||||
}
|
||||
switch ( format )
|
||||
if ( format == ChatColor.BOLD )
|
||||
{
|
||||
case BOLD:
|
||||
component.setBold( true );
|
||||
break;
|
||||
case ITALIC:
|
||||
component.setItalic( true );
|
||||
break;
|
||||
case UNDERLINE:
|
||||
component.setUnderlined( true );
|
||||
break;
|
||||
case STRIKETHROUGH:
|
||||
component.setStrikethrough( true );
|
||||
break;
|
||||
case MAGIC:
|
||||
component.setObfuscated( true );
|
||||
break;
|
||||
case RESET:
|
||||
format = ChatColor.WHITE;
|
||||
default:
|
||||
component = new TextComponent();
|
||||
component.setColor( format );
|
||||
break;
|
||||
component.setBold( true );
|
||||
} else if ( format == ChatColor.ITALIC )
|
||||
{
|
||||
component.setItalic( true );
|
||||
} else if ( format == ChatColor.UNDERLINE )
|
||||
{
|
||||
component.setUnderlined( true );
|
||||
} else if ( format == ChatColor.STRIKETHROUGH )
|
||||
{
|
||||
component.setStrikethrough( true );
|
||||
} else if ( format == ChatColor.MAGIC )
|
||||
{
|
||||
component.setObfuscated( true );
|
||||
} else
|
||||
{
|
||||
if ( format == ChatColor.RESET )
|
||||
{
|
||||
format = defaultColor;
|
||||
}
|
||||
component = new TextComponent();
|
||||
component.setColor( format );
|
||||
component.setReset( true );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -115,19 +153,11 @@ public class TextComponent extends BaseComponent
|
||||
}
|
||||
builder.append( c );
|
||||
}
|
||||
if ( builder.length() > 0 )
|
||||
{
|
||||
component.setText( builder.toString() );
|
||||
components.add( component );
|
||||
}
|
||||
|
||||
// The client will crash if the array is empty
|
||||
if ( components.isEmpty() )
|
||||
{
|
||||
components.add( new TextComponent( "" ) );
|
||||
}
|
||||
component.setText( builder.toString() );
|
||||
components.add( component );
|
||||
|
||||
return components.toArray( new BaseComponent[ components.size() ] );
|
||||
return components.toArray( new BaseComponent[ 0 ] );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,6 +165,14 @@ public class TextComponent extends BaseComponent
|
||||
*/
|
||||
private String text;
|
||||
|
||||
/**
|
||||
* Creates a TextComponent with blank text.
|
||||
*/
|
||||
public TextComponent()
|
||||
{
|
||||
this.text = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TextComponent with formatting and text from the passed
|
||||
* component
|
||||
@@ -155,7 +193,11 @@ public class TextComponent extends BaseComponent
|
||||
*/
|
||||
public TextComponent(BaseComponent... extras)
|
||||
{
|
||||
setText( "" );
|
||||
this();
|
||||
if ( extras.length == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
setExtra( new ArrayList<BaseComponent>( Arrays.asList( extras ) ) );
|
||||
}
|
||||
|
||||
@@ -165,7 +207,7 @@ public class TextComponent extends BaseComponent
|
||||
* @return the duplicate of this TextComponent.
|
||||
*/
|
||||
@Override
|
||||
public BaseComponent duplicate()
|
||||
public TextComponent duplicate()
|
||||
{
|
||||
return new TextComponent( this );
|
||||
}
|
||||
@@ -180,27 +222,7 @@ public class TextComponent extends BaseComponent
|
||||
@Override
|
||||
protected void toLegacyText(StringBuilder builder)
|
||||
{
|
||||
builder.append( getColor() );
|
||||
if ( isBold() )
|
||||
{
|
||||
builder.append( ChatColor.BOLD );
|
||||
}
|
||||
if ( isItalic() )
|
||||
{
|
||||
builder.append( ChatColor.ITALIC );
|
||||
}
|
||||
if ( isUnderlined() )
|
||||
{
|
||||
builder.append( ChatColor.UNDERLINE );
|
||||
}
|
||||
if ( isStrikethrough() )
|
||||
{
|
||||
builder.append( ChatColor.STRIKETHROUGH );
|
||||
}
|
||||
if ( isObfuscated() )
|
||||
{
|
||||
builder.append( ChatColor.MAGIC );
|
||||
}
|
||||
addFormat( builder );
|
||||
builder.append( text );
|
||||
super.toLegacyText( builder );
|
||||
}
|
||||
@@ -208,6 +230,6 @@ public class TextComponent extends BaseComponent
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format( "TextComponent{text=%s, %s}", text, super.toString() );
|
||||
return "TextComponent{text=" + text + ", " + super.toString() + '}';
|
||||
}
|
||||
}
|
||||
|
@@ -1,25 +1,24 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.chat.TranslationRegistry;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
public class TranslatableComponent extends BaseComponent
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class TranslatableComponent extends BaseComponent
|
||||
{
|
||||
|
||||
private final ResourceBundle locales = ResourceBundle.getBundle( "mojang-translations/en_US" );
|
||||
private final Pattern format = Pattern.compile( "%(?:(\\d+)\\$)?([A-Za-z%]|$)" );
|
||||
|
||||
/**
|
||||
@@ -31,6 +30,10 @@ public class TranslatableComponent extends BaseComponent
|
||||
* The components to substitute into the translation
|
||||
*/
|
||||
private List<BaseComponent> with;
|
||||
/**
|
||||
* The fallback, if the translation is not found
|
||||
*/
|
||||
private String fallback;
|
||||
|
||||
/**
|
||||
* Creates a translatable component from the original to clone it.
|
||||
@@ -56,28 +59,31 @@ public class TranslatableComponent extends BaseComponent
|
||||
/**
|
||||
* Creates a translatable component with the passed substitutions
|
||||
*
|
||||
* @see #translate
|
||||
* @see #setWith(java.util.List)
|
||||
* @param translate the translation key
|
||||
* @param with the {@link java.lang.String}s and
|
||||
* {@link net.md_5.bungee.api.chat.BaseComponent}s to use into the
|
||||
* translation
|
||||
* @see #translate
|
||||
* @see #setWith(java.util.List)
|
||||
*/
|
||||
public TranslatableComponent(String translate, Object... with)
|
||||
{
|
||||
setTranslate( translate );
|
||||
List<BaseComponent> temp = new ArrayList<BaseComponent>();
|
||||
for ( Object w : with )
|
||||
if ( with != null && with.length != 0 )
|
||||
{
|
||||
if ( w instanceof String )
|
||||
List<BaseComponent> temp = new ArrayList<BaseComponent>();
|
||||
for ( Object w : with )
|
||||
{
|
||||
temp.add( new TextComponent( (String) w ) );
|
||||
} else
|
||||
{
|
||||
temp.add( (BaseComponent) w );
|
||||
if ( w instanceof BaseComponent )
|
||||
{
|
||||
temp.add( (BaseComponent) w );
|
||||
} else
|
||||
{
|
||||
temp.add( new TextComponent( String.valueOf( w ) ) );
|
||||
}
|
||||
}
|
||||
setWith( temp );
|
||||
}
|
||||
setWith( temp );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,7 +92,7 @@ public class TranslatableComponent extends BaseComponent
|
||||
* @return the duplicate of this TranslatableComponent.
|
||||
*/
|
||||
@Override
|
||||
public BaseComponent duplicate()
|
||||
public TranslatableComponent duplicate()
|
||||
{
|
||||
return new TranslatableComponent( this );
|
||||
}
|
||||
@@ -136,56 +142,24 @@ public class TranslatableComponent extends BaseComponent
|
||||
@Override
|
||||
protected void toPlainText(StringBuilder builder)
|
||||
{
|
||||
String trans;
|
||||
try
|
||||
{
|
||||
trans = locales.getString( translate );
|
||||
} catch ( MissingResourceException e ) {
|
||||
trans = translate;
|
||||
}
|
||||
|
||||
Matcher matcher = format.matcher( trans );
|
||||
int position = 0;
|
||||
int i = 0;
|
||||
while ( matcher.find( position ) )
|
||||
{
|
||||
int pos = matcher.start();
|
||||
if ( pos != position )
|
||||
{
|
||||
builder.append( trans.substring( position, pos ) );
|
||||
}
|
||||
position = matcher.end();
|
||||
|
||||
String formatCode = matcher.group( 2 );
|
||||
switch ( formatCode.charAt( 0 ) )
|
||||
{
|
||||
case 's':
|
||||
case 'd':
|
||||
String withIndex = matcher.group( 1 );
|
||||
with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ).toPlainText( builder );
|
||||
break;
|
||||
case '%':
|
||||
builder.append( '%' );
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( trans.length() != position )
|
||||
{
|
||||
builder.append( trans.substring( position, trans.length() ) );
|
||||
}
|
||||
|
||||
convert( builder, false );
|
||||
super.toPlainText( builder );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void toLegacyText(StringBuilder builder)
|
||||
{
|
||||
String trans;
|
||||
try
|
||||
convert( builder, true );
|
||||
super.toLegacyText( builder );
|
||||
}
|
||||
|
||||
private void convert(StringBuilder builder, boolean applyFormat)
|
||||
{
|
||||
String trans = TranslationRegistry.INSTANCE.translate( translate );
|
||||
|
||||
if ( trans.equals( translate ) && fallback != null )
|
||||
{
|
||||
trans = locales.getString( translate );
|
||||
} catch ( MissingResourceException e ) {
|
||||
trans = translate;
|
||||
trans = fallback;
|
||||
}
|
||||
|
||||
Matcher matcher = format.matcher( trans );
|
||||
@@ -196,7 +170,10 @@ public class TranslatableComponent extends BaseComponent
|
||||
int pos = matcher.start();
|
||||
if ( pos != position )
|
||||
{
|
||||
addFormat( builder );
|
||||
if ( applyFormat )
|
||||
{
|
||||
addFormat( builder );
|
||||
}
|
||||
builder.append( trans.substring( position, pos ) );
|
||||
}
|
||||
position = matcher.end();
|
||||
@@ -207,44 +184,32 @@ public class TranslatableComponent extends BaseComponent
|
||||
case 's':
|
||||
case 'd':
|
||||
String withIndex = matcher.group( 1 );
|
||||
with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ).toLegacyText( builder );
|
||||
|
||||
BaseComponent withComponent = with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ );
|
||||
if ( applyFormat )
|
||||
{
|
||||
withComponent.toLegacyText( builder );
|
||||
} else
|
||||
{
|
||||
withComponent.toPlainText( builder );
|
||||
}
|
||||
break;
|
||||
case '%':
|
||||
addFormat( builder );
|
||||
if ( applyFormat )
|
||||
{
|
||||
addFormat( builder );
|
||||
}
|
||||
builder.append( '%' );
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( trans.length() != position )
|
||||
{
|
||||
addFormat( builder );
|
||||
if ( applyFormat )
|
||||
{
|
||||
addFormat( builder );
|
||||
}
|
||||
builder.append( trans.substring( position, trans.length() ) );
|
||||
}
|
||||
super.toLegacyText( builder );
|
||||
}
|
||||
|
||||
private void addFormat(StringBuilder builder)
|
||||
{
|
||||
builder.append( getColor() );
|
||||
if ( isBold() )
|
||||
{
|
||||
builder.append( ChatColor.BOLD );
|
||||
}
|
||||
if ( isItalic() )
|
||||
{
|
||||
builder.append( ChatColor.ITALIC );
|
||||
}
|
||||
if ( isUnderlined() )
|
||||
{
|
||||
builder.append( ChatColor.UNDERLINE );
|
||||
}
|
||||
if ( isStrikethrough() )
|
||||
{
|
||||
builder.append( ChatColor.STRIKETHROUGH );
|
||||
}
|
||||
if ( isObfuscated() )
|
||||
{
|
||||
builder.append( ChatColor.MAGIC );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,32 @@
|
||||
package net.md_5.bungee.api.chat.hover.content;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
public abstract class Content
|
||||
{
|
||||
|
||||
/**
|
||||
* Required action for this content type.
|
||||
*
|
||||
* @return action
|
||||
*/
|
||||
public abstract HoverEvent.Action requiredAction();
|
||||
|
||||
/**
|
||||
* Tests this content against an action
|
||||
*
|
||||
* @param input input to test
|
||||
* @throws UnsupportedOperationException if action incompatible
|
||||
*/
|
||||
public void assertAction(HoverEvent.Action input) throws UnsupportedOperationException
|
||||
{
|
||||
if ( input != requiredAction() )
|
||||
{
|
||||
throw new UnsupportedOperationException( "Action " + input + " not compatible! Expected " + requiredAction() );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package net.md_5.bungee.api.chat.hover.content;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Entity extends Content
|
||||
{
|
||||
|
||||
/**
|
||||
* Namespaced entity ID.
|
||||
*
|
||||
* Will use 'minecraft:pig' if null.
|
||||
*/
|
||||
private String type;
|
||||
/**
|
||||
* Entity UUID in hyphenated hexadecimal format.
|
||||
*
|
||||
* Should be valid UUID. TODO : validate?
|
||||
*/
|
||||
@NonNull
|
||||
private String id;
|
||||
/**
|
||||
* Name to display as the entity.
|
||||
*
|
||||
* This is optional and will be hidden if null.
|
||||
*/
|
||||
private BaseComponent name;
|
||||
|
||||
@Override
|
||||
public HoverEvent.Action requiredAction()
|
||||
{
|
||||
return HoverEvent.Action.SHOW_ENTITY;
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package net.md_5.bungee.api.chat.hover.content;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import java.lang.reflect.Type;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
|
||||
public class EntitySerializer implements JsonSerializer<Entity>, JsonDeserializer<Entity>
|
||||
{
|
||||
|
||||
@Override
|
||||
public Entity deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
JsonObject value = element.getAsJsonObject();
|
||||
|
||||
return new Entity(
|
||||
( value.has( "type" ) ) ? value.get( "type" ).getAsString() : null,
|
||||
value.get( "id" ).getAsString(),
|
||||
( value.has( "name" ) ) ? context.deserialize( value.get( "name" ), BaseComponent.class ) : null
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Entity content, Type type, JsonSerializationContext context)
|
||||
{
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty( "type", ( content.getType() != null ) ? content.getType() : "minecraft:pig" );
|
||||
object.addProperty( "id", content.getId() );
|
||||
if ( content.getName() != null )
|
||||
{
|
||||
object.add( "name", context.serialize( content.getName() ) );
|
||||
}
|
||||
return object;
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package net.md_5.bungee.api.chat.hover.content;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.ItemTag;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class Item extends Content
|
||||
{
|
||||
|
||||
/**
|
||||
* Namespaced item ID. Will use 'minecraft:air' if null.
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* Optional. Size of the item stack.
|
||||
*/
|
||||
private int count = -1;
|
||||
/**
|
||||
* Optional. Item tag.
|
||||
*/
|
||||
private ItemTag tag;
|
||||
|
||||
@Override
|
||||
public HoverEvent.Action requiredAction()
|
||||
{
|
||||
return HoverEvent.Action.SHOW_ITEM;
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
package net.md_5.bungee.api.chat.hover.content;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import java.lang.reflect.Type;
|
||||
import net.md_5.bungee.api.chat.ItemTag;
|
||||
|
||||
public class ItemSerializer implements JsonSerializer<Item>, JsonDeserializer<Item>
|
||||
{
|
||||
|
||||
@Override
|
||||
public Item deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
JsonObject value = element.getAsJsonObject();
|
||||
|
||||
int count = -1;
|
||||
if ( value.has( "Count" ) )
|
||||
{
|
||||
JsonPrimitive countObj = value.get( "Count" ).getAsJsonPrimitive();
|
||||
|
||||
if ( countObj.isNumber() )
|
||||
{
|
||||
count = countObj.getAsInt();
|
||||
} else if ( countObj.isString() )
|
||||
{
|
||||
String cString = countObj.getAsString();
|
||||
char last = cString.charAt( cString.length() - 1 );
|
||||
// Check for all number suffixes
|
||||
if ( last == 'b' || last == 's' || last == 'l' || last == 'f' || last == 'd' )
|
||||
{
|
||||
cString = cString.substring( 0, cString.length() - 1 );
|
||||
}
|
||||
try
|
||||
{
|
||||
count = Integer.parseInt( cString );
|
||||
} catch ( NumberFormatException ex )
|
||||
{
|
||||
throw new JsonParseException( "Could not parse count: " + ex );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Item(
|
||||
( value.has( "id" ) ) ? value.get( "id" ).getAsString() : null,
|
||||
count,
|
||||
( value.has( "tag" ) ) ? context.deserialize( value.get( "tag" ), ItemTag.class ) : null
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Item content, Type type, JsonSerializationContext context)
|
||||
{
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty( "id", ( content.getId() == null ) ? "minecraft:air" : content.getId() );
|
||||
if ( content.getCount() != -1 )
|
||||
{
|
||||
object.addProperty( "Count", content.getCount() );
|
||||
}
|
||||
if ( content.getTag() != null )
|
||||
{
|
||||
object.add( "tag", context.serialize( content.getTag() ) );
|
||||
}
|
||||
return object;
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
package net.md_5.bungee.api.chat.hover.content;
|
||||
|
||||
import java.util.Arrays;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class Text extends Content
|
||||
{
|
||||
|
||||
/**
|
||||
* The value.
|
||||
*
|
||||
* May be a component or raw text depending on constructor used.
|
||||
*/
|
||||
private final Object value;
|
||||
|
||||
public Text(BaseComponent[] value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Text(BaseComponent value)
|
||||
{
|
||||
// For legacy serialization reasons, this has to be an array of components
|
||||
this( new BaseComponent[]{value} );
|
||||
}
|
||||
|
||||
public Text(String value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HoverEvent.Action requiredAction()
|
||||
{
|
||||
return HoverEvent.Action.SHOW_TEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if ( value instanceof BaseComponent[] )
|
||||
{
|
||||
return o instanceof Text
|
||||
&& ( (Text) o ).value instanceof BaseComponent[]
|
||||
&& Arrays.equals( (BaseComponent[]) value, (BaseComponent[]) ( (Text) o ).value );
|
||||
} else
|
||||
{
|
||||
return value.equals( o );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return ( value instanceof BaseComponent[] ) ? Arrays.hashCode( (BaseComponent[]) value ) : value.hashCode();
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
package net.md_5.bungee.api.chat.hover.content;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import java.lang.reflect.Type;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
|
||||
public class TextSerializer implements JsonSerializer<Text>, JsonDeserializer<Text>
|
||||
{
|
||||
|
||||
@Override
|
||||
public Text deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
if ( element.isJsonArray() )
|
||||
{
|
||||
return new Text( context.<BaseComponent[]>deserialize( element, BaseComponent[].class ) );
|
||||
} else if ( element.isJsonPrimitive() )
|
||||
{
|
||||
return new Text( element.getAsJsonPrimitive().getAsString() );
|
||||
} else
|
||||
{
|
||||
return new Text( new BaseComponent[]
|
||||
{
|
||||
context.deserialize( element, BaseComponent.class )
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Text content, Type type, JsonSerializationContext context)
|
||||
{
|
||||
return context.serialize( content.getValue() );
|
||||
}
|
||||
}
|
@@ -2,25 +2,26 @@ package net.md_5.bungee.chat;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Locale;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import net.md_5.bungee.api.chat.hover.content.Content;
|
||||
|
||||
public class BaseComponentSerializer
|
||||
{
|
||||
|
||||
protected void deserialize(JsonObject object, BaseComponent component, JsonDeserializationContext context)
|
||||
{
|
||||
if ( object.has( "color" ) )
|
||||
{
|
||||
component.setColor( ChatColor.valueOf( object.get( "color" ).getAsString().toUpperCase() ) );
|
||||
}
|
||||
if ( object.has( "bold" ) )
|
||||
{
|
||||
component.setBold( object.get( "bold" ).getAsBoolean() );
|
||||
@@ -41,38 +42,85 @@ public class BaseComponentSerializer
|
||||
{
|
||||
component.setObfuscated( object.get( "obfuscated" ).getAsBoolean() );
|
||||
}
|
||||
if ( object.has( "color" ) )
|
||||
{
|
||||
component.setColor( ChatColor.of( object.get( "color" ).getAsString() ) );
|
||||
}
|
||||
if ( object.has( "insertion" ) )
|
||||
{
|
||||
component.setInsertion( object.get( "insertion" ).getAsString() );
|
||||
}
|
||||
if ( object.has( "extra" ) )
|
||||
{
|
||||
component.setExtra( Arrays.<BaseComponent>asList( context.<BaseComponent[]>deserialize( object.get( "extra" ), BaseComponent[].class ) ) );
|
||||
}
|
||||
|
||||
//Events
|
||||
if ( object.has( "clickEvent" ) )
|
||||
{
|
||||
JsonObject event = object.getAsJsonObject( "clickEvent" );
|
||||
component.setClickEvent( new ClickEvent(
|
||||
ClickEvent.Action.valueOf( event.get( "action" ).getAsString().toUpperCase() ),
|
||||
event.get( "value" ).getAsString() ) );
|
||||
ClickEvent.Action.valueOf( event.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) ),
|
||||
( event.has( "value" ) ) ? event.get( "value" ).getAsString() : "" ) );
|
||||
}
|
||||
if ( object.has( "hoverEvent" ) )
|
||||
{
|
||||
JsonObject event = object.getAsJsonObject( "hoverEvent" );
|
||||
BaseComponent[] res;
|
||||
if ( event.get( "value" ).isJsonArray() )
|
||||
HoverEvent hoverEvent = null;
|
||||
HoverEvent.Action action = HoverEvent.Action.valueOf( event.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) );
|
||||
|
||||
for ( String type : Arrays.asList( "value", "contents" ) )
|
||||
{
|
||||
res = context.deserialize( event.get( "value" ), BaseComponent[].class );
|
||||
} else
|
||||
{
|
||||
res = new BaseComponent[]
|
||||
if ( !event.has( type ) )
|
||||
{
|
||||
context.<BaseComponent>deserialize( event.get( "value" ), BaseComponent.class )
|
||||
};
|
||||
continue;
|
||||
}
|
||||
JsonElement contents = event.get( type );
|
||||
try
|
||||
{
|
||||
|
||||
// Plugins previously had support to pass BaseComponent[] into any action.
|
||||
// If the GSON is possible to be parsed as BaseComponent, attempt to parse as so.
|
||||
BaseComponent[] components;
|
||||
if ( contents.isJsonArray() )
|
||||
{
|
||||
components = context.deserialize( contents, BaseComponent[].class );
|
||||
} else
|
||||
{
|
||||
components = new BaseComponent[]
|
||||
{
|
||||
context.deserialize( contents, BaseComponent.class )
|
||||
};
|
||||
}
|
||||
hoverEvent = new HoverEvent( action, components );
|
||||
} catch ( JsonParseException ex )
|
||||
{
|
||||
Content[] list;
|
||||
if ( contents.isJsonArray() )
|
||||
{
|
||||
list = context.deserialize( contents, HoverEvent.getClass( action, true ) );
|
||||
} else
|
||||
{
|
||||
list = new Content[]
|
||||
{
|
||||
context.deserialize( contents, HoverEvent.getClass( action, false ) )
|
||||
};
|
||||
}
|
||||
hoverEvent = new HoverEvent( action, new ArrayList<>( Arrays.asList( list ) ) );
|
||||
}
|
||||
|
||||
// stop the loop as soon as either one is found
|
||||
break;
|
||||
}
|
||||
component.setHoverEvent( new HoverEvent( HoverEvent.Action.valueOf( event.get( "action" ).getAsString().toUpperCase() ), res ) );
|
||||
if ( hoverEvent != null )
|
||||
{
|
||||
component.setHoverEvent( hoverEvent );
|
||||
}
|
||||
}
|
||||
|
||||
if ( object.has( "font" ) )
|
||||
{
|
||||
component.setFont( object.get( "font" ).getAsString() );
|
||||
}
|
||||
if ( object.has( "extra" ) )
|
||||
{
|
||||
component.setExtra( Arrays.asList( context.<BaseComponent[]>deserialize( object.get( "extra" ), BaseComponent[].class ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,16 +130,12 @@ public class BaseComponentSerializer
|
||||
if ( ComponentSerializer.serializedComponents.get() == null )
|
||||
{
|
||||
first = true;
|
||||
ComponentSerializer.serializedComponents.set( new HashSet<BaseComponent>() );
|
||||
ComponentSerializer.serializedComponents.set( Collections.newSetFromMap( new IdentityHashMap<BaseComponent, Boolean>() ) );
|
||||
}
|
||||
try
|
||||
{
|
||||
Preconditions.checkArgument( !ComponentSerializer.serializedComponents.get().contains( component ), "Component loop" );
|
||||
ComponentSerializer.serializedComponents.get().add( component );
|
||||
if ( component.getColorRaw() != null )
|
||||
{
|
||||
object.addProperty( "color", component.getColorRaw().getName() );
|
||||
}
|
||||
if ( component.isBoldRaw() != null )
|
||||
{
|
||||
object.addProperty( "bold", component.isBoldRaw() );
|
||||
@@ -112,31 +156,46 @@ public class BaseComponentSerializer
|
||||
{
|
||||
object.addProperty( "obfuscated", component.isObfuscatedRaw() );
|
||||
}
|
||||
if ( component.getColorRaw() != null )
|
||||
{
|
||||
object.addProperty( "color", component.getColorRaw().getName() );
|
||||
}
|
||||
if ( component.getInsertion() != null )
|
||||
{
|
||||
object.addProperty( "insertion", component.getInsertion() );
|
||||
}
|
||||
|
||||
if ( component.getExtra() != null )
|
||||
{
|
||||
object.add( "extra", context.serialize( component.getExtra() ) );
|
||||
}
|
||||
|
||||
//Events
|
||||
if ( component.getClickEvent() != null )
|
||||
{
|
||||
JsonObject clickEvent = new JsonObject();
|
||||
clickEvent.addProperty( "action", component.getClickEvent().getAction().toString().toLowerCase() );
|
||||
clickEvent.addProperty( "action", component.getClickEvent().getAction().toString().toLowerCase( Locale.ROOT ) );
|
||||
clickEvent.addProperty( "value", component.getClickEvent().getValue() );
|
||||
object.add( "clickEvent", clickEvent );
|
||||
}
|
||||
if ( component.getHoverEvent() != null )
|
||||
{
|
||||
JsonObject hoverEvent = new JsonObject();
|
||||
hoverEvent.addProperty( "action", component.getHoverEvent().getAction().toString().toLowerCase() );
|
||||
hoverEvent.add( "value", context.serialize( component.getHoverEvent().getValue() ) );
|
||||
hoverEvent.addProperty( "action", component.getHoverEvent().getAction().toString().toLowerCase( Locale.ROOT ) );
|
||||
if ( component.getHoverEvent().isLegacy() )
|
||||
{
|
||||
hoverEvent.add( "value", context.serialize( component.getHoverEvent().getContents().get( 0 ) ) );
|
||||
} else
|
||||
{
|
||||
hoverEvent.add( "contents", context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
|
||||
? component.getHoverEvent().getContents().get( 0 ) : component.getHoverEvent().getContents() ) );
|
||||
}
|
||||
object.add( "hoverEvent", hoverEvent );
|
||||
}
|
||||
|
||||
if ( component.getFontRaw() != null )
|
||||
{
|
||||
object.addProperty( "font", component.getFontRaw() );
|
||||
}
|
||||
if ( component.getExtra() != null )
|
||||
{
|
||||
object.add( "extra", context.serialize( component.getExtra() ) );
|
||||
}
|
||||
} finally
|
||||
{
|
||||
ComponentSerializer.serializedComponents.get().remove( component );
|
||||
|
@@ -7,34 +7,96 @@ import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Set;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ItemTag;
|
||||
import net.md_5.bungee.api.chat.KeybindComponent;
|
||||
import net.md_5.bungee.api.chat.ScoreComponent;
|
||||
import net.md_5.bungee.api.chat.SelectorComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.chat.TranslatableComponent;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashSet;
|
||||
import net.md_5.bungee.api.chat.hover.content.Entity;
|
||||
import net.md_5.bungee.api.chat.hover.content.EntitySerializer;
|
||||
import net.md_5.bungee.api.chat.hover.content.Item;
|
||||
import net.md_5.bungee.api.chat.hover.content.ItemSerializer;
|
||||
import net.md_5.bungee.api.chat.hover.content.Text;
|
||||
import net.md_5.bungee.api.chat.hover.content.TextSerializer;
|
||||
|
||||
public class ComponentSerializer implements JsonDeserializer<BaseComponent>
|
||||
{
|
||||
|
||||
private final static Gson gson = new GsonBuilder().
|
||||
private static final Gson gson = new GsonBuilder().
|
||||
registerTypeAdapter( BaseComponent.class, new ComponentSerializer() ).
|
||||
registerTypeAdapter( TextComponent.class, new TextComponentSerializer() ).
|
||||
registerTypeAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() ).
|
||||
registerTypeAdapter( KeybindComponent.class, new KeybindComponentSerializer() ).
|
||||
registerTypeAdapter( ScoreComponent.class, new ScoreComponentSerializer() ).
|
||||
registerTypeAdapter( SelectorComponent.class, new SelectorComponentSerializer() ).
|
||||
registerTypeAdapter( Entity.class, new EntitySerializer() ).
|
||||
registerTypeAdapter( Text.class, new TextSerializer() ).
|
||||
registerTypeAdapter( Item.class, new ItemSerializer() ).
|
||||
registerTypeAdapter( ItemTag.class, new ItemTag.Serializer() ).
|
||||
create();
|
||||
|
||||
public final static ThreadLocal<HashSet<BaseComponent>> serializedComponents = new ThreadLocal<HashSet<BaseComponent>>();
|
||||
public static final ThreadLocal<Set<BaseComponent>> serializedComponents = new ThreadLocal<Set<BaseComponent>>();
|
||||
|
||||
/**
|
||||
* Parse a JSON-compliant String as an array of base components. The input
|
||||
* can be one of either an array of components, or a single component object.
|
||||
* If the input is an array, each component will be parsed individually and
|
||||
* returned in the order that they were parsed. If the input is a single
|
||||
* component object, a single-valued array with the component will be returned.
|
||||
* <p>
|
||||
* <strong>NOTE:</strong> {@link #deserialize(String)} is preferred as it will
|
||||
* parse only one component as opposed to an array of components which is non-
|
||||
* standard behavior. This method is still appropriate for parsing multiple
|
||||
* components at once, although such use case is rarely (if at all) exhibited
|
||||
* in vanilla Minecraft.
|
||||
*
|
||||
* @param json the component json to parse
|
||||
* @return an array of all parsed components
|
||||
*/
|
||||
public static BaseComponent[] parse(String json)
|
||||
{
|
||||
if ( json.startsWith( "[" ) )
|
||||
{ //Array
|
||||
return gson.fromJson( json, BaseComponent[].class );
|
||||
}
|
||||
return new BaseComponent[]
|
||||
JsonElement jsonElement = JsonParser.parseString( json );
|
||||
|
||||
if ( jsonElement.isJsonArray() )
|
||||
{
|
||||
gson.fromJson( json, BaseComponent.class )
|
||||
};
|
||||
return gson.fromJson( jsonElement, BaseComponent[].class );
|
||||
} else
|
||||
{
|
||||
return new BaseComponent[]
|
||||
{
|
||||
gson.fromJson( jsonElement, BaseComponent.class )
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a JSON-compliant String as a single component. The input is
|
||||
* expected to be a JSON object that represents only one component.
|
||||
*
|
||||
* @param json the component json to parse
|
||||
* @return the deserialized component
|
||||
* @throws IllegalArgumentException if anything other than a JSON object is
|
||||
* passed as input
|
||||
*/
|
||||
public static BaseComponent deserialize(String json)
|
||||
{
|
||||
JsonElement jsonElement = JsonParser.parseString( json );
|
||||
if ( !jsonElement.isJsonObject() )
|
||||
{
|
||||
throw new IllegalArgumentException( "Malformatted JSON. Expected object, got array for input \"" + json + "\"." );
|
||||
}
|
||||
|
||||
return gson.fromJson( jsonElement, BaseComponent.class );
|
||||
}
|
||||
|
||||
public static String toString(Object object)
|
||||
{
|
||||
return gson.toJson( object );
|
||||
}
|
||||
|
||||
public static String toString(BaseComponent component)
|
||||
@@ -44,7 +106,13 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
|
||||
|
||||
public static String toString(BaseComponent... components)
|
||||
{
|
||||
return gson.toJson( new TextComponent( components ) );
|
||||
if ( components.length == 1 )
|
||||
{
|
||||
return gson.toJson( components[0] );
|
||||
} else
|
||||
{
|
||||
return gson.toJson( new TextComponent( components ) );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,6 +127,18 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
|
||||
{
|
||||
return context.deserialize( json, TranslatableComponent.class );
|
||||
}
|
||||
if ( object.has( "keybind" ) )
|
||||
{
|
||||
return context.deserialize( json, KeybindComponent.class );
|
||||
}
|
||||
if ( object.has( "score" ) )
|
||||
{
|
||||
return context.deserialize( json, ScoreComponent.class );
|
||||
}
|
||||
if ( object.has( "selector" ) )
|
||||
{
|
||||
return context.deserialize( json, SelectorComponent.class );
|
||||
}
|
||||
return context.deserialize( json, TextComponent.class );
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,38 @@
|
||||
package net.md_5.bungee.chat;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import java.lang.reflect.Type;
|
||||
import net.md_5.bungee.api.chat.KeybindComponent;
|
||||
|
||||
public class KeybindComponentSerializer extends BaseComponentSerializer implements JsonSerializer<KeybindComponent>, JsonDeserializer<KeybindComponent>
|
||||
{
|
||||
|
||||
@Override
|
||||
public KeybindComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
JsonObject object = json.getAsJsonObject();
|
||||
if ( !object.has( "keybind" ) )
|
||||
{
|
||||
throw new JsonParseException( "Could not parse JSON: missing 'keybind' property" );
|
||||
}
|
||||
KeybindComponent component = new KeybindComponent();
|
||||
deserialize( object, component, context );
|
||||
component.setKeybind( object.get( "keybind" ).getAsString() );
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(KeybindComponent src, Type typeOfSrc, JsonSerializationContext context)
|
||||
{
|
||||
JsonObject object = new JsonObject();
|
||||
serialize( object, src, context );
|
||||
object.addProperty( "keybind", src.getKeybind() );
|
||||
return object;
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
package net.md_5.bungee.chat;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import java.lang.reflect.Type;
|
||||
import net.md_5.bungee.api.chat.ScoreComponent;
|
||||
|
||||
public class ScoreComponentSerializer extends BaseComponentSerializer implements JsonSerializer<ScoreComponent>, JsonDeserializer<ScoreComponent>
|
||||
{
|
||||
|
||||
@Override
|
||||
public ScoreComponent deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
JsonObject json = element.getAsJsonObject();
|
||||
if ( !json.has( "score" ) )
|
||||
{
|
||||
throw new JsonParseException( "Could not parse JSON: missing 'score' property" );
|
||||
}
|
||||
JsonObject score = json.get( "score" ).getAsJsonObject();
|
||||
if ( !score.has( "name" ) || !score.has( "objective" ) )
|
||||
{
|
||||
throw new JsonParseException( "A score component needs at least a name and an objective" );
|
||||
}
|
||||
|
||||
String name = score.get( "name" ).getAsString();
|
||||
String objective = score.get( "objective" ).getAsString();
|
||||
ScoreComponent component = new ScoreComponent( name, objective );
|
||||
if ( score.has( "value" ) && !score.get( "value" ).getAsString().isEmpty() )
|
||||
{
|
||||
component.setValue( score.get( "value" ).getAsString() );
|
||||
}
|
||||
|
||||
deserialize( json, component, context );
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(ScoreComponent component, Type type, JsonSerializationContext context)
|
||||
{
|
||||
JsonObject root = new JsonObject();
|
||||
serialize( root, component, context );
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty( "name", component.getName() );
|
||||
json.addProperty( "objective", component.getObjective() );
|
||||
json.addProperty( "value", component.getValue() );
|
||||
root.add( "score", json );
|
||||
return root;
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package net.md_5.bungee.chat;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import java.lang.reflect.Type;
|
||||
import net.md_5.bungee.api.chat.SelectorComponent;
|
||||
|
||||
public class SelectorComponentSerializer extends BaseComponentSerializer implements JsonSerializer<SelectorComponent>, JsonDeserializer<SelectorComponent>
|
||||
{
|
||||
|
||||
@Override
|
||||
public SelectorComponent deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
JsonObject object = element.getAsJsonObject();
|
||||
if ( !object.has( "selector" ) )
|
||||
{
|
||||
throw new JsonParseException( "Could not parse JSON: missing 'selector' property" );
|
||||
}
|
||||
SelectorComponent component = new SelectorComponent( object.get( "selector" ).getAsString() );
|
||||
deserialize( object, component, context );
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(SelectorComponent component, Type type, JsonSerializationContext context)
|
||||
{
|
||||
JsonObject object = new JsonObject();
|
||||
serialize( object, component, context );
|
||||
object.addProperty( "selector", component.getSelector() );
|
||||
return object;
|
||||
}
|
||||
}
|
@@ -5,14 +5,10 @@ import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
|
||||
public class TextComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TextComponent>, JsonDeserializer<TextComponent>
|
||||
{
|
||||
@@ -22,19 +18,18 @@ public class TextComponentSerializer extends BaseComponentSerializer implements
|
||||
{
|
||||
TextComponent component = new TextComponent();
|
||||
JsonObject object = json.getAsJsonObject();
|
||||
deserialize( object, component, context );
|
||||
if ( !object.has( "text" ) )
|
||||
{
|
||||
throw new JsonParseException( "Could not parse JSON: missing 'text' property" );
|
||||
}
|
||||
component.setText( object.get( "text" ).getAsString() );
|
||||
deserialize( object, component, context );
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(TextComponent src, Type typeOfSrc, JsonSerializationContext context)
|
||||
{
|
||||
List<BaseComponent> extra = src.getExtra();
|
||||
if ( !src.hasFormatting() && ( extra == null || extra.isEmpty() ) )
|
||||
{
|
||||
return new JsonPrimitive( src.getText() );
|
||||
}
|
||||
JsonObject object = new JsonObject();
|
||||
serialize( object, src, context );
|
||||
object.addProperty( "text", src.getText() );
|
||||
|
@@ -7,11 +7,10 @@ import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TranslatableComponent;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TranslatableComponent;
|
||||
|
||||
public class TranslatableComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TranslatableComponent>, JsonDeserializer<TranslatableComponent>
|
||||
{
|
||||
@@ -22,10 +21,18 @@ public class TranslatableComponentSerializer extends BaseComponentSerializer imp
|
||||
TranslatableComponent component = new TranslatableComponent();
|
||||
JsonObject object = json.getAsJsonObject();
|
||||
deserialize( object, component, context );
|
||||
if ( !object.has( "translate" ) )
|
||||
{
|
||||
throw new JsonParseException( "Could not parse JSON: missing 'translate' property" );
|
||||
}
|
||||
component.setTranslate( object.get( "translate" ).getAsString() );
|
||||
if ( object.has( "with" ) )
|
||||
{
|
||||
component.setWith( Arrays.asList( (BaseComponent[]) context.deserialize( object.get( "with" ), BaseComponent[].class ) ) );
|
||||
component.setWith( Arrays.asList( context.deserialize( object.get( "with" ), BaseComponent[].class ) ) );
|
||||
}
|
||||
if ( object.has( "fallback" ) )
|
||||
{
|
||||
component.setFallback( object.get( "fallback" ).getAsString() );
|
||||
}
|
||||
return component;
|
||||
}
|
||||
@@ -40,6 +47,10 @@ public class TranslatableComponentSerializer extends BaseComponentSerializer imp
|
||||
{
|
||||
object.add( "with", context.serialize( src.getWith() ) );
|
||||
}
|
||||
if ( src.getFallback() != null )
|
||||
{
|
||||
object.addProperty( "fallback", src.getFallback() );
|
||||
}
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
121
chat/src/main/java/net/md_5/bungee/chat/TranslationRegistry.java
Normal file
121
chat/src/main/java/net/md_5/bungee/chat/TranslationRegistry.java
Normal file
@@ -0,0 +1,121 @@
|
||||
package net.md_5.bungee.chat;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public final class TranslationRegistry
|
||||
{
|
||||
|
||||
public static final TranslationRegistry INSTANCE = new TranslationRegistry();
|
||||
//
|
||||
private final List<TranslationProvider> providers = new LinkedList<>();
|
||||
|
||||
static
|
||||
{
|
||||
try
|
||||
{
|
||||
INSTANCE.addProvider( new JsonProvider( "/assets/minecraft/lang/en_us.json" ) );
|
||||
} catch ( Exception ex )
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
INSTANCE.addProvider( new JsonProvider( "/mojang-translations/en_us.json" ) );
|
||||
} catch ( Exception ex )
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
INSTANCE.addProvider( new ResourceBundleProvider( "mojang-translations/en_US" ) );
|
||||
} catch ( Exception ex )
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void addProvider(TranslationProvider provider)
|
||||
{
|
||||
providers.add( provider );
|
||||
}
|
||||
|
||||
public String translate(String s)
|
||||
{
|
||||
for ( TranslationProvider provider : providers )
|
||||
{
|
||||
String translation = provider.translate( s );
|
||||
|
||||
if ( translation != null )
|
||||
{
|
||||
return translation;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private interface TranslationProvider
|
||||
{
|
||||
|
||||
String translate(String s);
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class ResourceBundleProvider implements TranslationProvider
|
||||
{
|
||||
|
||||
private final ResourceBundle bundle;
|
||||
|
||||
public ResourceBundleProvider(String bundlePath)
|
||||
{
|
||||
this.bundle = ResourceBundle.getBundle( bundlePath );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String translate(String s)
|
||||
{
|
||||
return ( bundle.containsKey( s ) ) ? bundle.getString( s ) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@ToString(exclude = "translations")
|
||||
private static class JsonProvider implements TranslationProvider
|
||||
{
|
||||
|
||||
private final Map<String, String> translations = new HashMap<>();
|
||||
|
||||
public JsonProvider(String resourcePath) throws IOException
|
||||
{
|
||||
try ( InputStreamReader rd = new InputStreamReader( JsonProvider.class.getResourceAsStream( resourcePath ), Charsets.UTF_8 ) )
|
||||
{
|
||||
JsonObject obj = new Gson().fromJson( rd, JsonObject.class );
|
||||
for ( Map.Entry<String, JsonElement> entries : obj.entrySet() )
|
||||
{
|
||||
translations.put( entries.getKey(), entries.getValue().getAsString() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String translate(String s)
|
||||
{
|
||||
return translations.get( s );
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
4324
chat/src/main/resources/mojang-translations/en_us.json
Normal file
4324
chat/src/main/resources/mojang-translations/en_us.json
Normal file
File diff suppressed because it is too large
Load Diff
842
chat/src/test/java/net/md_5/bungee/api/chat/ComponentsTest.java
Normal file
842
chat/src/test/java/net/md_5/bungee/api/chat/ComponentsTest.java
Normal file
@@ -0,0 +1,842 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import java.awt.Color;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.ObjIntConsumer;
|
||||
import java.util.function.Supplier;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.hover.content.Item;
|
||||
import net.md_5.bungee.api.chat.hover.content.Text;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ComponentsTest
|
||||
{
|
||||
|
||||
public static void testDissembleReassemble(BaseComponent[] components)
|
||||
{
|
||||
String json = ComponentSerializer.toString( components );
|
||||
BaseComponent[] parsed = ComponentSerializer.parse( json );
|
||||
assertEquals( TextComponent.toLegacyText( parsed ), TextComponent.toLegacyText( components ) );
|
||||
}
|
||||
|
||||
public static void testDissembleReassemble(BaseComponent component)
|
||||
{
|
||||
String json = ComponentSerializer.toString( component );
|
||||
BaseComponent[] parsed = ComponentSerializer.parse( json );
|
||||
assertEquals( TextComponent.toLegacyText( parsed ), TextComponent.toLegacyText( component ) );
|
||||
}
|
||||
|
||||
public static void testAssembleDissemble(String json, boolean modern)
|
||||
{
|
||||
if ( modern )
|
||||
{
|
||||
BaseComponent deserialized = ComponentSerializer.deserialize( json );
|
||||
assertEquals( json, ComponentSerializer.toString( deserialized ) );
|
||||
} else
|
||||
{
|
||||
BaseComponent[] parsed = ComponentSerializer.parse( json );
|
||||
assertEquals( json, ComponentSerializer.toString( parsed ) );
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testItemParse()
|
||||
{
|
||||
// Declare all commonly used variables for reuse.
|
||||
BaseComponent[] components;
|
||||
TextComponent textComponent;
|
||||
String json;
|
||||
|
||||
textComponent = new TextComponent( "Test" );
|
||||
textComponent.setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_ITEM, new BaseComponent[]
|
||||
{
|
||||
new TextComponent( "{id:\"minecraft:netherrack\",Count:47b}" )
|
||||
} ) );
|
||||
testDissembleReassemble( new BaseComponent[]
|
||||
{
|
||||
textComponent
|
||||
} );
|
||||
testDissembleReassemble( textComponent );
|
||||
json = "{\"hoverEvent\":{\"action\":\"show_item\",\"value\":[{\"text\":\"{id:\\\"minecraft:netherrack\\\",Count:47b}\"}]},\"text\":\"Test\"}";
|
||||
testAssembleDissemble( json, false );
|
||||
testAssembleDissemble( json, true );
|
||||
//////////
|
||||
String hoverVal = "{\"text\":\"{id:\\\"minecraft:dirt\\\",Count:1b}\"}";
|
||||
json = "{\"extra\":[{\"text\":\"[\"},{\"extra\":[{\"translate\":\"block.minecraft.dirt\"}],\"text\":\"\"},{\"text\":\"]\"}],\"hoverEvent\":{\"action\":\"show_item\",\"value\":[" + hoverVal + "]},\"text\":\"\"}";
|
||||
components = ComponentSerializer.parse( json );
|
||||
Text contentText = ( (Text) components[0].getHoverEvent().getContents().get( 0 ) );
|
||||
assertEquals( hoverVal, ComponentSerializer.toString( (BaseComponent[]) contentText.getValue() ) );
|
||||
testDissembleReassemble( components );
|
||||
//////////
|
||||
TextComponent component1 = new TextComponent( "HoverableText" );
|
||||
String nbt = "{display:{Name:{text:Hello},Lore:[{text:Line_1},{text:Line_2}]},ench:[{id:49,lvl:5}],Unbreakable:1}}";
|
||||
Item contentItem = new Item( "minecraft:wood", 1, ItemTag.ofNbt( nbt ) );
|
||||
HoverEvent hoverEvent = new HoverEvent( HoverEvent.Action.SHOW_ITEM, contentItem );
|
||||
component1.setHoverEvent( hoverEvent );
|
||||
json = ComponentSerializer.toString( component1 );
|
||||
components = ComponentSerializer.parse( json );
|
||||
Item parsedContentItem = ( (Item) components[0].getHoverEvent().getContents().get( 0 ) );
|
||||
assertEquals( contentItem, parsedContentItem );
|
||||
assertEquals( contentItem.getCount(), parsedContentItem.getCount() );
|
||||
assertEquals( contentItem.getId(), parsedContentItem.getId() );
|
||||
assertEquals( nbt, parsedContentItem.getTag().getNbt() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyComponentBuilderCreate()
|
||||
{
|
||||
this.testEmptyComponentBuilder(
|
||||
ComponentBuilder::create,
|
||||
(components) -> assertEquals( components.length, 0 ),
|
||||
(components, size) -> assertEquals( size, components.length )
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyComponentBuilderBuild()
|
||||
{
|
||||
this.testEmptyComponentBuilder(
|
||||
ComponentBuilder::build,
|
||||
(component) -> assertNull( component.getExtra() ),
|
||||
(component, size) -> assertEquals( component.getExtra().size(), size )
|
||||
);
|
||||
}
|
||||
|
||||
private <T> void testEmptyComponentBuilder(Function<ComponentBuilder, T> componentBuilder, Consumer<T> emptyAssertion, ObjIntConsumer<T> sizedAssertion)
|
||||
{
|
||||
ComponentBuilder builder = new ComponentBuilder();
|
||||
|
||||
T component = componentBuilder.apply( builder );
|
||||
emptyAssertion.accept( component );
|
||||
|
||||
for ( int i = 0; i < 3; i++ )
|
||||
{
|
||||
builder.append( "part:" + i );
|
||||
component = componentBuilder.apply( builder );
|
||||
sizedAssertion.accept( component, i + 1 );
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDummyRetaining()
|
||||
{
|
||||
ComponentBuilder builder = new ComponentBuilder();
|
||||
assertNotNull( builder.getCurrentComponent() );
|
||||
builder.color( ChatColor.GREEN );
|
||||
builder.append( "test ", ComponentBuilder.FormatRetention.ALL );
|
||||
assertEquals( builder.getCurrentComponent().getColor(), ChatColor.GREEN );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComponentGettingExceptions()
|
||||
{
|
||||
ComponentBuilder builder = new ComponentBuilder();
|
||||
assertThrows( IndexOutOfBoundsException.class, () -> builder.getComponent( -1 ) );
|
||||
assertThrows( IndexOutOfBoundsException.class, () -> builder.getComponent( 0 ) );
|
||||
assertThrows( IndexOutOfBoundsException.class, () -> builder.getComponent( 1 ) );
|
||||
BaseComponent component = new TextComponent( "Hello" );
|
||||
builder.append( component );
|
||||
assertEquals( builder.getComponent( 0 ), component );
|
||||
assertThrows( IndexOutOfBoundsException.class, () -> builder.getComponent( 1 ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComponentParting()
|
||||
{
|
||||
ComponentBuilder builder = new ComponentBuilder();
|
||||
TextComponent apple = new TextComponent( "apple" );
|
||||
builder.append( apple );
|
||||
assertEquals( builder.getCurrentComponent(), apple );
|
||||
assertEquals( builder.getComponent( 0 ), apple );
|
||||
|
||||
TextComponent mango = new TextComponent( "mango" );
|
||||
TextComponent orange = new TextComponent( "orange" );
|
||||
builder.append( mango );
|
||||
builder.append( orange );
|
||||
builder.removeComponent( 1 ); // Removing mango
|
||||
assertEquals( builder.getComponent( 0 ), apple );
|
||||
assertEquals( builder.getComponent( 1 ), orange );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToLegacyFromLegacy()
|
||||
{
|
||||
String text = "§a§lHello §f§kworld§7!";
|
||||
assertEquals( text, TextComponent.toLegacyText( TextComponent.fromLegacyText( text ) ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComponentBuilderCursorInvalidPos()
|
||||
{
|
||||
ComponentBuilder builder = new ComponentBuilder();
|
||||
builder.append( new TextComponent( "Apple, " ) );
|
||||
builder.append( new TextComponent( "Orange, " ) );
|
||||
assertThrows( IndexOutOfBoundsException.class, () -> builder.setCursor( -1 ) );
|
||||
assertThrows( IndexOutOfBoundsException.class, () -> builder.setCursor( 2 ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComponentBuilderCursor()
|
||||
{
|
||||
TextComponent t1, t2, t3;
|
||||
ComponentBuilder builder = new ComponentBuilder();
|
||||
assertEquals( builder.getCursor(), -1 );
|
||||
builder.append( t1 = new TextComponent( "Apple, " ) );
|
||||
assertEquals( builder.getCursor(), 0 );
|
||||
builder.append( t2 = new TextComponent( "Orange, " ) );
|
||||
builder.append( t3 = new TextComponent( "Mango, " ) );
|
||||
assertEquals( builder.getCursor(), 2 );
|
||||
|
||||
builder.setCursor( 0 );
|
||||
assertEquals( builder.getCurrentComponent(), t1 );
|
||||
|
||||
// Test that appending new components updates the position to the new list size
|
||||
// after having previously set it to 0 (first component)
|
||||
builder.append( new TextComponent( "and Grapefruit" ) );
|
||||
assertEquals( builder.getCursor(), 3 );
|
||||
|
||||
builder.setCursor( 0 );
|
||||
builder.resetCursor();
|
||||
assertEquals( builder.getCursor(), 3 );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyComponentBuilderAppend()
|
||||
{
|
||||
String text = "§a§lHello §r§kworld§7!";
|
||||
BaseComponent[] components = TextComponent.fromLegacyText( text );
|
||||
BaseComponent[] builderComponents = new ComponentBuilder().append( components ).create();
|
||||
assertArrayEquals( components, builderComponents );
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
public void testItemTag()
|
||||
{
|
||||
TextComponent component = new TextComponent( "Hello world" );
|
||||
HoverEvent.ContentItem content = new HoverEvent.ContentItem();
|
||||
content.setId( "minecraft:diamond_sword" );
|
||||
content.setCount( 1 );
|
||||
content.setTag( ItemTag.builder()
|
||||
.ench( new ItemTag.Enchantment( 5, 16 ) )
|
||||
.name( new TextComponent( "Sharp Sword" ) )
|
||||
.unbreakable( true )
|
||||
.lore( new ComponentBuilder( "Line1" ).create() )
|
||||
.lore( new ComponentBuilder( "Line2" ).create() )
|
||||
.build() );
|
||||
HoverEvent event = new HoverEvent( HoverEvent.Action.SHOW_ITEM, content );
|
||||
component.setHoverEvent( event );
|
||||
String serialised = ComponentSerializer.toString( component );
|
||||
BaseComponent[] deserialised = ComponentSerializer.parse( serialised );
|
||||
assertEquals( TextComponent.toLegacyText( deserialised ), TextComponent.toLegacyText( component ) );
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testModernShowAdvancement()
|
||||
{
|
||||
String advancement = "achievement.openInventory";
|
||||
// First do the text using the newer contents system
|
||||
HoverEvent hoverEvent = new HoverEvent(
|
||||
HoverEvent.Action.SHOW_TEXT,
|
||||
new Text( advancement )
|
||||
);
|
||||
TextComponent component = new TextComponent( "test" );
|
||||
component.setHoverEvent( hoverEvent );
|
||||
assertEquals( component.getHoverEvent().getContents().size(), 1 );
|
||||
assertTrue( component.getHoverEvent().getContents().get( 0 ) instanceof Text );
|
||||
assertEquals( ( (Text) component.getHoverEvent().getContents().get( 0 ) ).getValue(), advancement );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHoverEventContentsCreate()
|
||||
{
|
||||
// First do the text using the newer contents system
|
||||
HoverEvent hoverEvent = new HoverEvent(
|
||||
HoverEvent.Action.SHOW_TEXT,
|
||||
new Text( new ComponentBuilder( "First" ).create() ),
|
||||
new Text( new ComponentBuilder( "Second" ).create() )
|
||||
);
|
||||
|
||||
this.testHoverEventContents(
|
||||
hoverEvent,
|
||||
ComponentSerializer::parse,
|
||||
(components) -> components[0].getHoverEvent(),
|
||||
ComponentsTest::testDissembleReassemble // BaseComponent
|
||||
);
|
||||
|
||||
// check the test still works with the value method
|
||||
hoverEvent = new HoverEvent( HoverEvent.Action.SHOW_TEXT, new ComponentBuilder( "Sample text" ).create() );
|
||||
TextComponent component = new TextComponent( "Sample text" );
|
||||
component.setHoverEvent( hoverEvent );
|
||||
|
||||
assertEquals( hoverEvent.getContents().size(), 1 );
|
||||
assertTrue( hoverEvent.isLegacy() );
|
||||
String serialized = ComponentSerializer.toString( component );
|
||||
BaseComponent[] deserialized = ComponentSerializer.parse( serialized );
|
||||
assertEquals( component.getHoverEvent(), deserialized[0].getHoverEvent() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHoverEventContentsBuild()
|
||||
{
|
||||
// First do the text using the newer contents system
|
||||
HoverEvent hoverEvent = new HoverEvent(
|
||||
HoverEvent.Action.SHOW_TEXT,
|
||||
new Text( new ComponentBuilder( "First" ).build() ),
|
||||
new Text( new ComponentBuilder( "Second" ).build() )
|
||||
);
|
||||
|
||||
this.testHoverEventContents(
|
||||
hoverEvent,
|
||||
ComponentSerializer::deserialize,
|
||||
BaseComponent::getHoverEvent,
|
||||
ComponentsTest::testDissembleReassemble // BaseComponent
|
||||
);
|
||||
}
|
||||
|
||||
private <T> void testHoverEventContents(HoverEvent hoverEvent, Function<String, T> deserializer, Function<T, HoverEvent> hoverEventGetter, Consumer<T> dissembleReassembleTest)
|
||||
{
|
||||
TextComponent component = new TextComponent( "Sample text" );
|
||||
component.setHoverEvent( hoverEvent );
|
||||
assertEquals( hoverEvent.getContents().size(), 2 );
|
||||
assertFalse( hoverEvent.isLegacy() );
|
||||
|
||||
String serialized = ComponentSerializer.toString( component );
|
||||
T deserialized = deserializer.apply( serialized );
|
||||
assertEquals( component.getHoverEvent(), hoverEventGetter.apply( deserialized ) );
|
||||
|
||||
// Test single content:
|
||||
String json = "{\"italic\":true,\"color\":\"gray\",\"translate\":\"chat.type.admin\",\"with\":[{\"text\":\"@\"}"
|
||||
+ ",{\"translate\":\"commands.give.success.single\",\"with\":[\"1\",{\"color\":\"white\""
|
||||
+ ",\"hoverEvent\":{\"action\":\"show_item\",\"contents\":{\"id\":\"minecraft:diamond_sword\",\"tag\":\""
|
||||
+ "{Damage:0,display:{Lore:['\\\"test lore'!\\\"'],Name:'\\\"test\\\"'}}\"}},"
|
||||
+ "\"extra\":[{\"italic\":true,\"extra\":[{\"text\":\"test\"}],\"text\":\"\"},{\"text\":\"]\"}],"
|
||||
+ "\"text\":\"[\"},{\"insertion\":\"Name\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":"
|
||||
+ "\"/tell Name \"},\"hoverEvent\":{\"action\":\"show_entity\",\"contents\":"
|
||||
+ "{\"type\":\"minecraft:player\",\"id\":\"00000000-0000-0000-0000-00000000000000\",\"name\":"
|
||||
+ "{\"text\":\"Name\"}}},\"text\":\"Name\"}]}]}";
|
||||
dissembleReassembleTest.accept( deserializer.apply( json ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatRetentionCopyFormattingCreate()
|
||||
{
|
||||
this.testFormatRetentionCopyFormatting( () -> new HoverEvent( HoverEvent.Action.SHOW_TEXT, new ComponentBuilder( "Test" ).create() ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatRetentionCopyFormattingBuild()
|
||||
{
|
||||
this.testFormatRetentionCopyFormatting( () -> new HoverEvent( HoverEvent.Action.SHOW_TEXT, new Text( new ComponentBuilder( "Test" ).build() ) ) );
|
||||
}
|
||||
|
||||
private void testFormatRetentionCopyFormatting(Supplier<HoverEvent> hoverEventSupplier)
|
||||
{
|
||||
TextComponent first = new TextComponent( "Hello" );
|
||||
first.setBold( true );
|
||||
first.setColor( ChatColor.RED );
|
||||
first.setClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, "test" ) );
|
||||
first.setHoverEvent( hoverEventSupplier.get() );
|
||||
|
||||
TextComponent second = new TextComponent( " world" );
|
||||
second.copyFormatting( first, ComponentBuilder.FormatRetention.ALL, true );
|
||||
assertEquals( first.isBold(), second.isBold() );
|
||||
assertEquals( first.getColor(), second.getColor() );
|
||||
assertEquals( first.getClickEvent(), second.getClickEvent() );
|
||||
assertEquals( first.getHoverEvent(), second.getHoverEvent() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderCloneCreate()
|
||||
{
|
||||
this.testBuilderClone( (builder) -> TextComponent.toLegacyText( builder.create() ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderCloneBuild()
|
||||
{
|
||||
this.testBuilderClone( (builder) -> TextComponent.toLegacyText( builder.build() ) );
|
||||
}
|
||||
|
||||
private void testBuilderClone(Function<ComponentBuilder, String> legacyTextFunction)
|
||||
{
|
||||
ComponentBuilder builder = new ComponentBuilder( "Hello " ).color( ChatColor.RED ).append( "world" ).color( ChatColor.DARK_RED );
|
||||
ComponentBuilder cloned = new ComponentBuilder( builder );
|
||||
|
||||
assertEquals( legacyTextFunction.apply( builder ), legacyTextFunction.apply( cloned ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderAppendCreateMixedComponents()
|
||||
{
|
||||
this.testBuilderAppendMixedComponents(
|
||||
ComponentBuilder::create,
|
||||
(components, index) -> components[index]
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderAppendBuildMixedComponents()
|
||||
{
|
||||
this.testBuilderAppendMixedComponents(
|
||||
ComponentBuilder::build,
|
||||
(component, index) -> component.getExtra().get( index )
|
||||
);
|
||||
}
|
||||
|
||||
private <T> void testBuilderAppendMixedComponents(Function<ComponentBuilder, T> componentBuilder, BiFunction<T, Integer, BaseComponent> extraGetter)
|
||||
{
|
||||
ComponentBuilder builder = new ComponentBuilder( "Hello " );
|
||||
TextComponent textComponent = new TextComponent( "world " );
|
||||
TranslatableComponent translatableComponent = new TranslatableComponent( "item.swordGold.name" );
|
||||
// array based BaseComponent append
|
||||
builder.append( new BaseComponent[]
|
||||
{
|
||||
textComponent,
|
||||
translatableComponent
|
||||
} );
|
||||
ScoreComponent scoreComponent = new ScoreComponent( "myscore", "myobjective" );
|
||||
builder.append( scoreComponent ); // non array based BaseComponent append
|
||||
T component = componentBuilder.apply( builder );
|
||||
assertEquals( "Hello ", extraGetter.apply( component, 0 ).toPlainText() );
|
||||
assertEquals( textComponent.toPlainText(), extraGetter.apply( component, 1 ).toPlainText() );
|
||||
assertEquals( translatableComponent.toPlainText(), extraGetter.apply( component, 2 ).toPlainText() );
|
||||
assertEquals( scoreComponent.toPlainText(), extraGetter.apply( component, 3 ).toPlainText() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScore()
|
||||
{
|
||||
BaseComponent[] component = ComponentSerializer.parse( "{\"score\":{\"name\":\"@p\",\"objective\":\"TEST\",\"value\":\"hello\"}}" );
|
||||
String text = ComponentSerializer.toString( component );
|
||||
BaseComponent[] reparsed = ComponentSerializer.parse( text );
|
||||
|
||||
assertArrayEquals( component, reparsed );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderAppendCreate()
|
||||
{
|
||||
this.testBuilderAppend(
|
||||
() -> new HoverEvent( HoverEvent.Action.SHOW_TEXT, new ComponentBuilder( "Hello world" ).create() ),
|
||||
ComponentBuilder::create,
|
||||
(components, index) -> components[index],
|
||||
BaseComponent::toPlainText,
|
||||
ChatColor.YELLOW + "Hello " + ChatColor.GREEN + "world!",
|
||||
BaseComponent::toLegacyText
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderAppendBuild()
|
||||
{
|
||||
this.testBuilderAppend(
|
||||
() -> new HoverEvent( HoverEvent.Action.SHOW_TEXT, new Text( new ComponentBuilder( "Hello world" ).build() ) ),
|
||||
ComponentBuilder::build,
|
||||
(component, index) -> component.getExtra().get( index ),
|
||||
(component) -> BaseComponent.toPlainText( component ),
|
||||
// An extra format code is appended to the beginning because there is an empty TextComponent at the start of every component
|
||||
ChatColor.WHITE.toString() + ChatColor.YELLOW + "Hello " + ChatColor.GREEN + "world!",
|
||||
(component) -> BaseComponent.toLegacyText( component )
|
||||
);
|
||||
}
|
||||
|
||||
private <T> void testBuilderAppend(Supplier<HoverEvent> hoverEventSupplier, Function<ComponentBuilder, T> componentBuilder, BiFunction<T, Integer, BaseComponent> extraGetter, Function<T, String> toPlainTextFunction, String expectedLegacyText, Function<T, String> toLegacyTextFunction)
|
||||
{
|
||||
ClickEvent clickEvent = new ClickEvent( ClickEvent.Action.RUN_COMMAND, "/help " );
|
||||
HoverEvent hoverEvent = hoverEventSupplier.get();
|
||||
|
||||
ComponentBuilder builder = new ComponentBuilder( "Hello " ).color( ChatColor.YELLOW );
|
||||
builder.append( new ComponentBuilder( "world!" ).color( ChatColor.GREEN ).event( hoverEvent ).event( clickEvent ).create() ); // Intentionally using create() to append multiple individual components
|
||||
|
||||
T component = componentBuilder.apply( builder );
|
||||
|
||||
assertEquals( extraGetter.apply( component, 1 ).getHoverEvent(), hoverEvent );
|
||||
assertEquals( extraGetter.apply( component, 1 ).getClickEvent(), clickEvent );
|
||||
assertEquals( "Hello world!", toPlainTextFunction.apply( component ) );
|
||||
assertEquals( expectedLegacyText, toLegacyTextFunction.apply( component ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderAppendLegacyCreate()
|
||||
{
|
||||
this.testBuilderAppendLegacy(
|
||||
ComponentBuilder::create,
|
||||
BaseComponent::toPlainText,
|
||||
ChatColor.YELLOW + "Hello " + ChatColor.GREEN + "world!",
|
||||
BaseComponent::toLegacyText
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderAppendLegacyBuild()
|
||||
{
|
||||
this.testBuilderAppendLegacy(
|
||||
ComponentBuilder::build,
|
||||
(component) -> BaseComponent.toPlainText( component ),
|
||||
// An extra format code is appended to the beginning because there is an empty TextComponent at the start of every component
|
||||
ChatColor.WHITE.toString() + ChatColor.YELLOW + "Hello " + ChatColor.GREEN + "world!",
|
||||
(component) -> BaseComponent.toLegacyText( component )
|
||||
);
|
||||
}
|
||||
|
||||
private <T> void testBuilderAppendLegacy(Function<ComponentBuilder, T> componentBuilder, Function<T, String> toPlainTextFunction, String expectedLegacyString, Function<T, String> toLegacyTextFunction)
|
||||
{
|
||||
ComponentBuilder builder = new ComponentBuilder( "Hello " ).color( ChatColor.YELLOW );
|
||||
builder.appendLegacy( "§aworld!" );
|
||||
|
||||
T component = componentBuilder.apply( builder );
|
||||
|
||||
assertEquals( "Hello world!", toPlainTextFunction.apply( component ) );
|
||||
assertEquals( expectedLegacyString, toLegacyTextFunction.apply( component ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicComponent()
|
||||
{
|
||||
TextComponent textComponent = new TextComponent( "Hello world" );
|
||||
textComponent.setColor( ChatColor.RED );
|
||||
|
||||
assertEquals( "Hello world", textComponent.toPlainText() );
|
||||
assertEquals( ChatColor.RED + "Hello world", textComponent.toLegacyText() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyConverter()
|
||||
{
|
||||
BaseComponent[] test1 = TextComponent.fromLegacyText( ChatColor.AQUA + "Aqua " + ChatColor.RED + ChatColor.BOLD + "RedBold" );
|
||||
|
||||
assertEquals( "Aqua RedBold", BaseComponent.toPlainText( test1 ) );
|
||||
assertEquals( ChatColor.AQUA + "Aqua " + ChatColor.RED + ChatColor.BOLD + "RedBold", BaseComponent.toLegacyText( test1 ) );
|
||||
|
||||
BaseComponent[] test2 = TextComponent.fromLegacyText( "Text http://spigotmc.org " + ChatColor.GREEN + "google.com/test" );
|
||||
|
||||
assertEquals( "Text http://spigotmc.org google.com/test", BaseComponent.toPlainText( test2 ) );
|
||||
//The extra ChatColor instances are sometimes inserted when not needed but it doesn't change the result
|
||||
assertEquals( ChatColor.WHITE + "Text " + ChatColor.WHITE + "http://spigotmc.org" + ChatColor.WHITE
|
||||
+ " " + ChatColor.GREEN + "google.com/test" + ChatColor.GREEN, BaseComponent.toLegacyText( test2 ) );
|
||||
|
||||
ClickEvent url1 = test2[1].getClickEvent();
|
||||
assertNotNull( url1 );
|
||||
assertTrue( url1.getAction() == ClickEvent.Action.OPEN_URL );
|
||||
assertEquals( "http://spigotmc.org", url1.getValue() );
|
||||
|
||||
ClickEvent url2 = test2[3].getClickEvent();
|
||||
assertNotNull( url2 );
|
||||
assertTrue( url2.getAction() == ClickEvent.Action.OPEN_URL );
|
||||
assertEquals( "http://google.com/test", url2.getValue() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTranslateComponent()
|
||||
{
|
||||
TranslatableComponent item = new TranslatableComponent( "item.swordGold.name" );
|
||||
item.setColor( ChatColor.AQUA );
|
||||
TranslatableComponent translatableComponent = new TranslatableComponent( "commands.give.success",
|
||||
item, "5",
|
||||
"thinkofdeath" );
|
||||
|
||||
assertEquals( "Given Golden Sword * 5 to thinkofdeath", translatableComponent.toPlainText() );
|
||||
assertEquals( ChatColor.WHITE + "Given " + ChatColor.AQUA + "Golden Sword" + ChatColor.WHITE
|
||||
+ " * " + ChatColor.WHITE + "5" + ChatColor.WHITE + " to " + ChatColor.WHITE + "thinkofdeath",
|
||||
translatableComponent.toLegacyText() );
|
||||
|
||||
TranslatableComponent positional = new TranslatableComponent( "book.pageIndicator", "5", "50" );
|
||||
|
||||
assertEquals( "Page 5 of 50", positional.toPlainText() );
|
||||
assertEquals( ChatColor.WHITE + "Page " + ChatColor.WHITE + "5" + ChatColor.WHITE + " of " + ChatColor.WHITE + "50", positional.toLegacyText() );
|
||||
|
||||
TranslatableComponent one_four_two = new TranslatableComponent( "filled_map.buried_treasure" );
|
||||
assertEquals( "Buried Treasure Map", one_four_two.toPlainText() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderCreate()
|
||||
{
|
||||
this.testBuilder(
|
||||
ComponentBuilder::create,
|
||||
BaseComponent::toPlainText,
|
||||
ChatColor.RED + "Hello " + ChatColor.BLUE + ChatColor.BOLD
|
||||
+ "World" + ChatColor.YELLOW + ChatColor.BOLD + "!",
|
||||
BaseComponent::toLegacyText
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderBuild()
|
||||
{
|
||||
this.testBuilder(
|
||||
ComponentBuilder::build,
|
||||
(component) -> BaseComponent.toPlainText( component ),
|
||||
// An extra format code is appended to the beginning because there is an empty TextComponent at the start of every component
|
||||
ChatColor.WHITE.toString() + ChatColor.RED + "Hello " + ChatColor.BLUE + ChatColor.BOLD
|
||||
+ "World" + ChatColor.YELLOW + ChatColor.BOLD + "!",
|
||||
(component) -> BaseComponent.toLegacyText( component )
|
||||
);
|
||||
}
|
||||
|
||||
private <T> void testBuilder(Function<ComponentBuilder, T> componentBuilder, Function<T, String> toPlainTextFunction, String expectedLegacyString, Function<T, String> toLegacyTextFunction)
|
||||
{
|
||||
T component = componentBuilder.apply( new ComponentBuilder( "Hello " ).color( ChatColor.RED ).
|
||||
append( "World" ).bold( true ).color( ChatColor.BLUE ).
|
||||
append( "!" ).color( ChatColor.YELLOW ) );
|
||||
|
||||
assertEquals( "Hello World!", toPlainTextFunction.apply( component ) );
|
||||
assertEquals( expectedLegacyString, toLegacyTextFunction.apply( component ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderCreateReset()
|
||||
{
|
||||
this.testBuilderReset(
|
||||
ComponentBuilder::create,
|
||||
(components, index) -> components[index]
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderBuildReset()
|
||||
{
|
||||
this.testBuilderReset(
|
||||
ComponentBuilder::build,
|
||||
(component, index) -> component.getExtra().get( index )
|
||||
);
|
||||
}
|
||||
|
||||
private <T> void testBuilderReset(Function<ComponentBuilder, T> componentBuilder, BiFunction<T, Integer, BaseComponent> extraGetter)
|
||||
{
|
||||
T component = componentBuilder.apply( new ComponentBuilder( "Hello " ).color( ChatColor.RED )
|
||||
.append( "World" ).reset() );
|
||||
|
||||
assertEquals( ChatColor.RED, extraGetter.apply( component, 0 ).getColor() );
|
||||
assertEquals( ChatColor.WHITE, extraGetter.apply( component, 1 ).getColor() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderCreateFormatRetention()
|
||||
{
|
||||
this.testBuilderFormatRetention(
|
||||
ComponentBuilder::create,
|
||||
(components, index) -> components[index]
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderBuildFormatRetention()
|
||||
{
|
||||
this.testBuilderFormatRetention(
|
||||
ComponentBuilder::build,
|
||||
(component, index) -> component.getExtra().get( index )
|
||||
);
|
||||
}
|
||||
|
||||
private <T> void testBuilderFormatRetention(Function<ComponentBuilder, T> componentBuilder, BiFunction<T, Integer, BaseComponent> extraGetter)
|
||||
{
|
||||
T noneRetention = componentBuilder.apply( new ComponentBuilder( "Hello " ).color( ChatColor.RED )
|
||||
.append( "World", ComponentBuilder.FormatRetention.NONE ) );
|
||||
|
||||
assertEquals( ChatColor.RED, extraGetter.apply( noneRetention, 0 ).getColor() );
|
||||
assertEquals( ChatColor.WHITE, extraGetter.apply( noneRetention, 1 ).getColor() );
|
||||
|
||||
HoverEvent testEvent = new HoverEvent( HoverEvent.Action.SHOW_TEXT, new Text( new ComponentBuilder( "test" ).build() ) );
|
||||
|
||||
T formattingRetention = componentBuilder.apply( new ComponentBuilder( "Hello " ).color( ChatColor.RED )
|
||||
.event( testEvent ).append( "World", ComponentBuilder.FormatRetention.FORMATTING ) );
|
||||
|
||||
assertEquals( ChatColor.RED, extraGetter.apply( formattingRetention, 0 ).getColor() );
|
||||
assertEquals( testEvent, extraGetter.apply( formattingRetention, 0 ).getHoverEvent() );
|
||||
assertEquals( ChatColor.RED, extraGetter.apply( formattingRetention, 1 ).getColor() );
|
||||
assertNull( extraGetter.apply( formattingRetention, 1 ).getHoverEvent() );
|
||||
|
||||
ClickEvent testClickEvent = new ClickEvent( ClickEvent.Action.OPEN_URL, "http://www.example.com" );
|
||||
|
||||
T eventRetention = componentBuilder.apply( new ComponentBuilder( "Hello " ).color( ChatColor.RED )
|
||||
.event( testEvent ).event( testClickEvent ).append( "World", ComponentBuilder.FormatRetention.EVENTS ) );
|
||||
|
||||
assertEquals( ChatColor.RED, extraGetter.apply( eventRetention, 0 ).getColor() );
|
||||
assertEquals( testEvent, extraGetter.apply( eventRetention, 0 ).getHoverEvent() );
|
||||
assertEquals( testClickEvent, extraGetter.apply( eventRetention, 0 ).getClickEvent() );
|
||||
assertEquals( ChatColor.WHITE, extraGetter.apply( eventRetention, 1 ).getColor() );
|
||||
assertEquals( testEvent, extraGetter.apply( eventRetention, 1 ).getHoverEvent() );
|
||||
assertEquals( testClickEvent, extraGetter.apply( eventRetention, 1 ).getClickEvent() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoopSimple()
|
||||
{
|
||||
TextComponent component = new TextComponent( "Testing" );
|
||||
component.addExtra( component );
|
||||
assertThrows( IllegalArgumentException.class, () -> ComponentSerializer.toString( component ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoopComplex()
|
||||
{
|
||||
TextComponent a = new TextComponent( "A" );
|
||||
TextComponent b = new TextComponent( "B" );
|
||||
b.setColor( ChatColor.AQUA );
|
||||
TextComponent c = new TextComponent( "C" );
|
||||
c.setColor( ChatColor.RED );
|
||||
a.addExtra( b );
|
||||
b.addExtra( c );
|
||||
c.addExtra( a );
|
||||
assertThrows( IllegalArgumentException.class, () -> ComponentSerializer.toString( a ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeated()
|
||||
{
|
||||
TextComponent a = new TextComponent( "A" );
|
||||
TextComponent b = new TextComponent( "B" );
|
||||
b.setColor( ChatColor.AQUA );
|
||||
a.addExtra( b );
|
||||
a.addExtra( b );
|
||||
ComponentSerializer.toString( a );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatedError()
|
||||
{
|
||||
TextComponent a = new TextComponent( "A" );
|
||||
TextComponent b = new TextComponent( "B" );
|
||||
b.setColor( ChatColor.AQUA );
|
||||
TextComponent c = new TextComponent( "C" );
|
||||
c.setColor( ChatColor.RED );
|
||||
a.addExtra( b );
|
||||
a.addExtra( c );
|
||||
c.addExtra( a );
|
||||
a.addExtra( b );
|
||||
assertThrows( IllegalArgumentException.class, () -> ComponentSerializer.toString( a ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidColorCodes()
|
||||
{
|
||||
StringBuilder allInvalidColorCodes = new StringBuilder();
|
||||
|
||||
// collect all invalid color codes (e.g. §z, §g, ...)
|
||||
for ( char alphChar : "0123456789abcdefghijklmnopqrstuvwxyz".toCharArray() )
|
||||
{
|
||||
if ( ChatColor.ALL_CODES.indexOf( alphChar ) == -1 )
|
||||
{
|
||||
allInvalidColorCodes.append( ChatColor.COLOR_CHAR );
|
||||
allInvalidColorCodes.append( alphChar );
|
||||
}
|
||||
}
|
||||
|
||||
// last char is a single '§'
|
||||
allInvalidColorCodes.append( ChatColor.COLOR_CHAR );
|
||||
|
||||
String invalidColorCodesLegacyText = fromAndToLegacyText( allInvalidColorCodes.toString() );
|
||||
String emptyLegacyText = fromAndToLegacyText( "" );
|
||||
|
||||
// all invalid color codes and the trailing '§' should be ignored
|
||||
assertEquals( emptyLegacyText, invalidColorCodesLegacyText );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormattingOnlyTextConversion()
|
||||
{
|
||||
String text = "§a";
|
||||
|
||||
BaseComponent[] converted = TextComponent.fromLegacyText( text );
|
||||
assertEquals( ChatColor.GREEN, converted[0].getColor() );
|
||||
|
||||
String roundtripLegacyText = BaseComponent.toLegacyText( converted );
|
||||
|
||||
// color code should not be lost during conversion
|
||||
assertEquals( text, roundtripLegacyText );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals()
|
||||
{
|
||||
TextComponent first = new TextComponent( "Hello, " );
|
||||
first.addExtra( new TextComponent( "World!" ) );
|
||||
|
||||
TextComponent second = new TextComponent( "Hello, " );
|
||||
second.addExtra( new TextComponent( "World!" ) );
|
||||
|
||||
assertEquals( first, second );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotEquals()
|
||||
{
|
||||
TextComponent first = new TextComponent( "Hello, " );
|
||||
first.addExtra( new TextComponent( "World." ) );
|
||||
|
||||
TextComponent second = new TextComponent( "Hello, " );
|
||||
second.addExtra( new TextComponent( "World!" ) );
|
||||
|
||||
assertNotEquals( first, second );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyHack()
|
||||
{
|
||||
BaseComponent[] hexColored = new ComponentBuilder().color( ChatColor.of( Color.GRAY ) ).append( "Test" ).create();
|
||||
String legacy = TextComponent.toLegacyText( hexColored );
|
||||
|
||||
BaseComponent[] reColored = TextComponent.fromLegacyText( legacy );
|
||||
|
||||
assertArrayEquals( hexColored, reColored );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyResetInBuilderCreate()
|
||||
{
|
||||
this.testLegacyResetInBuilder(
|
||||
ComponentBuilder::create,
|
||||
ComponentSerializer::toString
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyResetInBuilderBuild()
|
||||
{
|
||||
this.testLegacyResetInBuilder(
|
||||
ComponentBuilder::build,
|
||||
ComponentSerializer::toString
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* In legacy chat, colors and reset both reset all formatting.
|
||||
* Make sure it works in combination with ComponentBuilder.
|
||||
*/
|
||||
private <T> void testLegacyResetInBuilder(Function<ComponentBuilder, T> componentBuilder, Function<T, String> componentSerializer)
|
||||
{
|
||||
ComponentBuilder builder = new ComponentBuilder();
|
||||
BaseComponent[] a = TextComponent.fromLegacyText( "§4§n44444§rdd§6§l6666" );
|
||||
|
||||
String expected = "{\"extra\":[{\"underlined\":true,\"color\":\"dark_red\",\"text\":\"44444\"},{\"color\":"
|
||||
+ "\"white\",\"text\":\"dd\"},{\"bold\":true,\"color\":\"gold\",\"text\":\"6666\"}],\"text\":\"\"}";
|
||||
assertEquals( expected, ComponentSerializer.toString( a ) );
|
||||
|
||||
builder.append( a );
|
||||
|
||||
String test1 = componentSerializer.apply( componentBuilder.apply( builder ) );
|
||||
assertEquals( expected, test1 );
|
||||
|
||||
BaseComponent[] b = TextComponent.fromLegacyText( "§rrrrr" );
|
||||
builder.append( b );
|
||||
|
||||
String test2 = componentSerializer.apply( componentBuilder.apply( builder ) );
|
||||
assertEquals(
|
||||
"{\"extra\":[{\"underlined\":true,\"color\":\"dark_red\",\"text\":\"44444\"},"
|
||||
+ "{\"color\":\"white\",\"text\":\"dd\"},{\"bold\":true,\"color\":\"gold\",\"text\":\"6666\"},"
|
||||
+ "{\"color\":\"white\",\"text\":\"rrrr\"}],\"text\":\"\"}",
|
||||
test2 );
|
||||
}
|
||||
|
||||
private static String fromAndToLegacyText(String legacyText)
|
||||
{
|
||||
return BaseComponent.toLegacyText( TextComponent.fromLegacyText( legacyText ) );
|
||||
}
|
||||
}
|
@@ -1,16 +1,28 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import net.md_5.bungee.api.chat.TranslatableComponent;
|
||||
import org.junit.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
public class TranslatableComponentTest
|
||||
{
|
||||
|
||||
public class TranslatableComponentTest {
|
||||
@Test
|
||||
public void testMissingPlaceholdersAdded()
|
||||
{
|
||||
TranslatableComponent testComponent = new TranslatableComponent( "Test string with %s placeholders: %s", "2", "aoeu" );
|
||||
TranslatableComponent testComponent = new TranslatableComponent( "Test string with %s placeholders: %s", 2, "aoeu" );
|
||||
assertEquals( "Test string with 2 placeholders: aoeu", testComponent.toPlainText() );
|
||||
assertEquals( "§fTest string with §f2§f placeholders: §faoeu", testComponent.toLegacyText() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJsonSerialisation()
|
||||
{
|
||||
TranslatableComponent testComponent = new TranslatableComponent( "Test string with %s placeholder", "a" );
|
||||
String jsonString = ComponentSerializer.toString( testComponent );
|
||||
BaseComponent[] baseComponents = ComponentSerializer.parse( jsonString );
|
||||
|
||||
assertEquals( "Test string with a placeholder", TextComponent.toPlainText( baseComponents ) );
|
||||
assertEquals( "§fTest string with §fa§f placeholder", TextComponent.toLegacyText( baseComponents ) );
|
||||
}
|
||||
}
|
||||
|
89
checkstyle.xml
Normal file
89
checkstyle.xml
Normal file
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
|
||||
<module name="Checker">
|
||||
<!-- See http://checkstyle.sourceforge.net/config_misc.html#NewlineAtEndOfFile -->
|
||||
<module name="NewlineAtEndOfFile">
|
||||
<property name="lineSeparator" value="lf_cr_crlf"/>
|
||||
</module>
|
||||
|
||||
<!-- See http://checkstyle.sourceforge.net/config_whitespace.html -->
|
||||
<module name="FileTabCharacter"/>
|
||||
|
||||
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
|
||||
<module name="RegexpSingleline">
|
||||
<property name="format" value="\s+$"/>
|
||||
<property name="minimum" value="0"/>
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="message" value="Line has trailing spaces."/>
|
||||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<!-- See https://checkstyle.org/config_javadoc.html -->
|
||||
<module name="AtclauseOrder"/>
|
||||
<module name="InvalidJavadocPosition"/>
|
||||
<module name="JavadocBlockTagLocation"/>
|
||||
<module name="JavadocContentLocationCheck"/>
|
||||
<module name="JavadocMethod"/>
|
||||
<module name="JavadocType"/>
|
||||
<module name="MissingJavadocPackage"/>
|
||||
<module name="NonEmptyAtclauseDescription"/>
|
||||
|
||||
<!-- See http://checkstyle.sourceforge.net/config_filters.html -->
|
||||
<module name="SuppressionCommentFilter"/>
|
||||
<module name="SuppressWarningsHolder"/>
|
||||
|
||||
<!-- See http://checkstyle.sourceforge.net/config_imports.html -->
|
||||
<module name="ImportOrder">
|
||||
<property name="option" value="above"/>
|
||||
<property name="ordered" value="true"/>
|
||||
<property name="separatedStaticGroups" value="true"/>
|
||||
<property name="sortStaticImportsAlphabetically" value="true"/>
|
||||
</module>
|
||||
<module name="RedundantImport"/>
|
||||
<module name="UnusedImports"/>
|
||||
|
||||
<!-- See https://checkstyle.org/config_whitespace.html -->
|
||||
<module name="GenericWhitespace"/>
|
||||
<module name="MethodParamPad"/>
|
||||
<module name="NoLineWrap"/>
|
||||
<module name="NoWhitespaceAfter"/>
|
||||
<module name="NoWhitespaceBefore"/>
|
||||
<module name="OperatorWrap"/>
|
||||
<module name="ParenPad">
|
||||
<property name="option" value="nospace"/>
|
||||
<property name="tokens" value="ANNOTATION, CTOR_DEF, METHOD_DEF, LAMBDA"/>
|
||||
</module>
|
||||
<module name="ParenPad">
|
||||
<property name="option" value="space"/>
|
||||
<property name="tokens" value="ANNOTATION_FIELD_DEF, CTOR_CALL, DOT, ENUM_CONSTANT_DEF, EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW, LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, RECORD_DEF"/>
|
||||
</module>
|
||||
<module name="SingleSpaceSeparator"/>
|
||||
<module name="TypecastParenPad"/>
|
||||
<module name="WhitespaceAfter"/>
|
||||
<module name="WhitespaceAround"/>
|
||||
|
||||
<!-- See http://checkstyle.sourceforge.net/config_modifiers.html -->
|
||||
<module name="ModifierOrder"/>
|
||||
|
||||
<!-- See https://checkstyle.org/config_blocks.html -->
|
||||
<module name="AvoidNestedBlocks"/>
|
||||
<module name="LeftCurly">
|
||||
<property name="option" value="nl"/>
|
||||
</module>
|
||||
<module name="RightCurly"/>
|
||||
|
||||
<!-- See http://checkstyle.sourceforge.net/config_design.html -->
|
||||
<module name="FinalClass"/>
|
||||
|
||||
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
|
||||
<module name="ArrayTypeStyle"/>
|
||||
<module name="CommentsIndentation"/>
|
||||
<module name="Indentation"/>
|
||||
<module name="UpperEll"/>
|
||||
</module>
|
||||
|
||||
<module name="SuppressWarningsFilter"/>
|
||||
</module>
|
@@ -6,24 +6,32 @@
|
||||
<parent>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-parent</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<version>1.20-R0.2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-config</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<version>1.20-R0.2-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>BungeeCord-Config</name>
|
||||
<description>Generic java configuration API intended for use with BungeeCord</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.10.1</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.14</version>
|
||||
<version>2.2</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@@ -1,16 +1,13 @@
|
||||
package net.md_5.bungee.config;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
public final class Configuration
|
||||
{
|
||||
|
||||
@@ -28,6 +25,25 @@ public final class Configuration
|
||||
this( new LinkedHashMap<String, Object>(), defaults );
|
||||
}
|
||||
|
||||
Configuration(Map<?, ?> map, Configuration defaults)
|
||||
{
|
||||
this.self = new LinkedHashMap<>();
|
||||
this.defaults = defaults;
|
||||
|
||||
for ( Map.Entry<?, ?> entry : map.entrySet() )
|
||||
{
|
||||
String key = ( entry.getKey() == null ) ? "null" : entry.getKey().toString();
|
||||
|
||||
if ( entry.getValue() instanceof Map )
|
||||
{
|
||||
this.self.put( key, new Configuration( (Map) entry.getValue(), ( defaults == null ) ? null : defaults.getSection( key ) ) );
|
||||
} else
|
||||
{
|
||||
this.self.put( key, entry.getValue() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Configuration getSectionFor(String path)
|
||||
{
|
||||
int index = path.indexOf( SEPARATOR );
|
||||
@@ -40,15 +56,11 @@ public final class Configuration
|
||||
Object section = self.get( root );
|
||||
if ( section == null )
|
||||
{
|
||||
section = new LinkedHashMap<>();
|
||||
section = new Configuration( ( defaults == null ) ? null : defaults.getSection( root ) );
|
||||
self.put( root, section );
|
||||
}
|
||||
if ( section instanceof Configuration )
|
||||
{
|
||||
return (Configuration) section;
|
||||
}
|
||||
|
||||
return new Configuration( (Map) section, ( defaults == null ) ? null : defaults.getSectionFor( path ) );
|
||||
return (Configuration) section;
|
||||
}
|
||||
|
||||
private String getChild(String path)
|
||||
@@ -71,9 +83,19 @@ public final class Configuration
|
||||
val = section.get( getChild( path ), def );
|
||||
}
|
||||
|
||||
if ( val == null && def instanceof Configuration )
|
||||
{
|
||||
self.put( path, def );
|
||||
}
|
||||
|
||||
return ( val != null ) ? (T) val : def;
|
||||
}
|
||||
|
||||
public boolean contains(String path)
|
||||
{
|
||||
return get( path, null ) != null;
|
||||
}
|
||||
|
||||
public Object get(String path)
|
||||
{
|
||||
return get( path, getDefault( path ) );
|
||||
@@ -86,6 +108,11 @@ public final class Configuration
|
||||
|
||||
public void set(String path, Object value)
|
||||
{
|
||||
if ( value instanceof Map )
|
||||
{
|
||||
value = new Configuration( (Map) value, ( defaults == null ) ? null : defaults.getSection( path ) );
|
||||
}
|
||||
|
||||
Configuration section = getSectionFor( path );
|
||||
if ( section == this )
|
||||
{
|
||||
@@ -106,7 +133,7 @@ public final class Configuration
|
||||
public Configuration getSection(String path)
|
||||
{
|
||||
Object def = getDefault( path );
|
||||
return new Configuration( (Map) ( get( path, ( def instanceof Map ) ? def : Collections.EMPTY_MAP ) ), ( defaults == null ) ? null : defaults.getSection( path ) );
|
||||
return (Configuration) get( path, ( def instanceof Configuration ) ? def : new Configuration( ( defaults == null ) ? null : defaults.getSection( path ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,7 +143,7 @@ public final class Configuration
|
||||
*/
|
||||
public Collection<String> getKeys()
|
||||
{
|
||||
return Sets.newLinkedHashSet( self.keySet() );
|
||||
return new LinkedHashSet<>( self.keySet() );
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
@@ -15,15 +15,29 @@ public abstract class ConfigurationProvider
|
||||
|
||||
static
|
||||
{
|
||||
providers.put( YamlConfiguration.class, new YamlConfiguration() );
|
||||
try
|
||||
{
|
||||
providers.put( YamlConfiguration.class, new YamlConfiguration() );
|
||||
} catch ( NoClassDefFoundError ex )
|
||||
{
|
||||
// Ignore, no SnakeYAML
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
providers.put( JsonConfiguration.class, new JsonConfiguration() );
|
||||
} catch ( NoClassDefFoundError ex )
|
||||
{
|
||||
// Ignore, no Gson
|
||||
}
|
||||
}
|
||||
|
||||
public static ConfigurationProvider getProvider(Class<? extends ConfigurationProvider> provider)
|
||||
{
|
||||
return providers.get( provider );
|
||||
}
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
public abstract void save(Configuration config, File file) throws IOException;
|
||||
|
||||
public abstract void save(Configuration config, Writer writer);
|
||||
|
@@ -0,0 +1,114 @@
|
||||
package net.md_5.bungee.config;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
public class JsonConfiguration extends ConfigurationProvider
|
||||
{
|
||||
|
||||
private final Gson json = new GsonBuilder().serializeNulls().setPrettyPrinting().registerTypeAdapter( Configuration.class, new JsonSerializer<Configuration>()
|
||||
{
|
||||
@Override
|
||||
public JsonElement serialize(Configuration src, Type typeOfSrc, JsonSerializationContext context)
|
||||
{
|
||||
return context.serialize( ( (Configuration) src ).self );
|
||||
}
|
||||
} ).create();
|
||||
|
||||
@Override
|
||||
public void save(Configuration config, File file) throws IOException
|
||||
{
|
||||
try ( Writer writer = new OutputStreamWriter( new FileOutputStream( file ), Charsets.UTF_8 ) )
|
||||
{
|
||||
save( config, writer );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Configuration config, Writer writer)
|
||||
{
|
||||
json.toJson( config.self, writer );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configuration load(File file) throws IOException
|
||||
{
|
||||
return load( file, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configuration load(File file, Configuration defaults) throws IOException
|
||||
{
|
||||
try ( FileInputStream is = new FileInputStream( file ) )
|
||||
{
|
||||
return load( is, defaults );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configuration load(Reader reader)
|
||||
{
|
||||
return load( reader, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Configuration load(Reader reader, Configuration defaults)
|
||||
{
|
||||
Map<String, Object> map = json.fromJson( reader, LinkedHashMap.class );
|
||||
if ( map == null )
|
||||
{
|
||||
map = new LinkedHashMap<>();
|
||||
}
|
||||
return new Configuration( map, defaults );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configuration load(InputStream is)
|
||||
{
|
||||
return load( is, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configuration load(InputStream is, Configuration defaults)
|
||||
{
|
||||
return load( new InputStreamReader( is, Charsets.UTF_8 ), defaults );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configuration load(String string)
|
||||
{
|
||||
return load( string, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Configuration load(String string, Configuration defaults)
|
||||
{
|
||||
Map<String, Object> map = json.fromJson( string, LinkedHashMap.class );
|
||||
if ( map == null )
|
||||
{
|
||||
map = new LinkedHashMap<>();
|
||||
}
|
||||
return new Configuration( map, defaults );
|
||||
}
|
||||
}
|
@@ -1,10 +1,12 @@
|
||||
package net.md_5.bungee.config;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -12,7 +14,12 @@ import java.util.Map;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
import org.yaml.snakeyaml.representer.Represent;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
public class YamlConfiguration extends ConfigurationProvider
|
||||
@@ -25,14 +32,29 @@ public class YamlConfiguration extends ConfigurationProvider
|
||||
{
|
||||
DumperOptions options = new DumperOptions();
|
||||
options.setDefaultFlowStyle( DumperOptions.FlowStyle.BLOCK );
|
||||
return new Yaml( options );
|
||||
|
||||
Representer representer = new Representer( options )
|
||||
{
|
||||
{
|
||||
representers.put( Configuration.class, new Represent()
|
||||
{
|
||||
@Override
|
||||
public Node representData(Object data)
|
||||
{
|
||||
return represent( ( (Configuration) data ).self );
|
||||
}
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
return new Yaml( new Constructor( new LoaderOptions() ), representer, options );
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void save(Configuration config, File file) throws IOException
|
||||
{
|
||||
try ( FileWriter writer = new FileWriter( file ) )
|
||||
try ( Writer writer = new OutputStreamWriter( new FileOutputStream( file ), Charsets.UTF_8 ) )
|
||||
{
|
||||
save( config, writer );
|
||||
}
|
||||
@@ -53,9 +75,9 @@ public class YamlConfiguration extends ConfigurationProvider
|
||||
@Override
|
||||
public Configuration load(File file, Configuration defaults) throws IOException
|
||||
{
|
||||
try ( FileReader reader = new FileReader( file ) )
|
||||
try ( FileInputStream is = new FileInputStream( file ) )
|
||||
{
|
||||
return load( reader, defaults );
|
||||
return load( is, defaults );
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,222 @@
|
||||
package net.md_5.bungee.config;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class CompoundConfigurationTest
|
||||
{
|
||||
|
||||
public static Stream<Arguments> data()
|
||||
{
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
// provider
|
||||
YamlConfiguration.class,
|
||||
// testDocument
|
||||
""
|
||||
+ "receipt: Oz-Ware Purchase Invoice\n"
|
||||
+ "date: 2012-08-06\n"
|
||||
+ "customer:\n"
|
||||
+ " given: Dorothy\n"
|
||||
+ " family: Gale\n"
|
||||
+ "\n"
|
||||
+ "items:\n"
|
||||
+ " - part_no: A4786\n"
|
||||
+ " descrip: Water Bucket (Filled)\n"
|
||||
+ " price: 1.47\n"
|
||||
+ " quantity: 4\n"
|
||||
+ "\n"
|
||||
+ " - part_no: E1628\n"
|
||||
+ " descrip: High Heeled \"Ruby\" Slippers\n"
|
||||
+ " size: 8\n"
|
||||
+ " price: 100.27\n"
|
||||
+ " quantity: 1\n"
|
||||
+ "\n"
|
||||
+ "bill-to: &id001\n"
|
||||
+ " street: |\n"
|
||||
+ " 123 Tornado Alley\n"
|
||||
+ " Suite 16\n"
|
||||
+ " city: East Centerville\n"
|
||||
+ " state: KS\n"
|
||||
+ "\n"
|
||||
+ "ship-to: *id001\n"
|
||||
+ "\n"
|
||||
+ "specialDelivery: >\n"
|
||||
+ " Follow the Yellow Brick\n"
|
||||
+ " Road to the Emerald City.\n"
|
||||
+ " Pay no attention to the\n"
|
||||
+ " man behind the curtain.",
|
||||
// numberTest
|
||||
""
|
||||
+ "someKey:\n"
|
||||
+ " 1: 1\n"
|
||||
+ " 2: 2\n"
|
||||
+ " 3: 3\n"
|
||||
+ " 4: 4",
|
||||
// nullTest
|
||||
""
|
||||
+ "null:\n"
|
||||
+ " null: object\n"
|
||||
+ " object: null\n"
|
||||
),
|
||||
Arguments.of(
|
||||
// provider
|
||||
JsonConfiguration.class,
|
||||
// testDocument
|
||||
""
|
||||
+ "{\n"
|
||||
+ " \"customer\": {\n"
|
||||
+ " \"given\": \"Dorothy\", \n"
|
||||
+ " \"family\": \"Gale\"\n"
|
||||
+ " }, \n"
|
||||
+ " \"ship-to\": {\n"
|
||||
+ " \"city\": \"East Centerville\", \n"
|
||||
+ " \"state\": \"KS\", \n"
|
||||
+ " \"street\": \"123 Tornado Alley\\nSuite 16\\n\"\n"
|
||||
+ " }, \n"
|
||||
+ " \"bill-to\": {\n"
|
||||
+ " \"city\": \"East Centerville\", \n"
|
||||
+ " \"state\": \"KS\", \n"
|
||||
+ " \"street\": \"123 Tornado Alley\\nSuite 16\\n\"\n"
|
||||
+ " }, \n"
|
||||
+ " \"date\": \"2012-08-06\", \n"
|
||||
+ " \"items\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"part_no\": \"A4786\", \n"
|
||||
+ " \"price\": 1.47, \n"
|
||||
+ " \"descrip\": \"Water Bucket (Filled)\", \n"
|
||||
+ " \"quantity\": 4\n"
|
||||
+ " }, \n"
|
||||
+ " {\n"
|
||||
+ " \"part_no\": \"E1628\", \n"
|
||||
+ " \"descrip\": \"High Heeled \\\"Ruby\\\" Slippers\", \n"
|
||||
+ " \"price\": 100.27, \n"
|
||||
+ " \"quantity\": 1, \n"
|
||||
+ " \"size\": 8\n"
|
||||
+ " }\n"
|
||||
+ " ], \n"
|
||||
+ " \"receipt\": \"Oz-Ware Purchase Invoice\", \n"
|
||||
+ " \"specialDelivery\": \"Follow the Yellow Brick Road to the Emerald City. Pay no attention to the man behind the curtain.\"\n"
|
||||
+ "}",
|
||||
// numberTest
|
||||
""
|
||||
+ "{\n"
|
||||
+ " \"someKey\": {\n"
|
||||
+ " \"1\": 1, \n"
|
||||
+ " \"2\": 2, \n"
|
||||
+ " \"3\": 3, \n"
|
||||
+ " \"4\": 4\n"
|
||||
+ " }\n"
|
||||
+ "}",
|
||||
// nullTest
|
||||
""
|
||||
+ "{\n"
|
||||
+ " \"null\": {\n"
|
||||
+ " \"null\": \"object\", \n"
|
||||
+ " \"object\": null\n"
|
||||
+ " }\n"
|
||||
+ "}"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("data")
|
||||
public void testConfig(Class<? extends ConfigurationProvider> provider, String testDocument, String numberTest, String nullTest) throws Exception
|
||||
{
|
||||
Configuration conf = ConfigurationProvider.getProvider( provider ).load( testDocument );
|
||||
testSection( conf );
|
||||
|
||||
StringWriter sw = new StringWriter();
|
||||
ConfigurationProvider.getProvider( provider ).save( conf, sw );
|
||||
|
||||
// Check nulls were saved, see #1094
|
||||
assertFalse( sw.toString().contains( "null" ), "Config contains null" );
|
||||
|
||||
conf = ConfigurationProvider.getProvider( provider ).load( new StringReader( sw.toString() ) );
|
||||
conf.set( "receipt", "Oz-Ware Purchase Invoice" ); // Add it back
|
||||
testSection( conf );
|
||||
}
|
||||
|
||||
private void testSection(Configuration conf)
|
||||
{
|
||||
assertEquals( "Oz-Ware Purchase Invoice", conf.getString( "receipt" ), "receipt" );
|
||||
// assertEquals( "2012-08-06", conf.get( "date" ).toString(), "date" );
|
||||
|
||||
Configuration customer = conf.getSection( "customer" );
|
||||
assertEquals( "Dorothy", customer.getString( "given" ), "customer.given" );
|
||||
assertEquals( "Dorothy", conf.getString( "customer.given" ), "customer.given" );
|
||||
|
||||
List items = conf.getList( "items" );
|
||||
Map item = (Map) items.get( 0 );
|
||||
assertEquals( "A4786", item.get( "part_no" ), "items[0].part_no" );
|
||||
|
||||
conf.set( "receipt", null );
|
||||
assertEquals( null, conf.get( "receipt" ) );
|
||||
assertEquals( "foo", conf.get( "receipt", "foo" ) );
|
||||
|
||||
Configuration newSection = conf.getSection( "new.section" );
|
||||
newSection.set( "value", "foo" );
|
||||
assertEquals( "foo", conf.get( "new.section.value" ) );
|
||||
|
||||
conf.set( "other.new.section", "bar" );
|
||||
assertEquals( "bar", conf.get( "other.new.section" ) );
|
||||
|
||||
assertTrue( conf.contains( "customer.given" ) );
|
||||
assertTrue( customer.contains( "given" ) );
|
||||
|
||||
assertFalse( conf.contains( "customer.foo" ) );
|
||||
assertFalse( customer.contains( "foo" ) );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("data")
|
||||
public void testNumberedKeys(Class<? extends ConfigurationProvider> provider, String testDocument, String numberTest, String nullTest)
|
||||
{
|
||||
Configuration conf = ConfigurationProvider.getProvider( provider ).load( numberTest );
|
||||
|
||||
Configuration section = conf.getSection( "someKey" );
|
||||
for ( String key : section.getKeys() )
|
||||
{
|
||||
// empty
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("data")
|
||||
public void testNull(Class<? extends ConfigurationProvider> provider, String testDocument, String numberTest, String nullTest)
|
||||
{
|
||||
Configuration conf = ConfigurationProvider.getProvider( provider ).load( nullTest );
|
||||
|
||||
assertEquals( "object", conf.get( "null.null" ) );
|
||||
assertEquals( "object", conf.getSection( "null" ).get( "null" ) );
|
||||
|
||||
assertEquals( null, conf.get( "null.object" ) );
|
||||
assertEquals( "", conf.getString( "null.object" ) );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("data")
|
||||
public void testMapAddition(Class<? extends ConfigurationProvider> provider, String testDocument, String numberTest, String nullTest)
|
||||
{
|
||||
Configuration conf = ConfigurationProvider.getProvider( provider ).load( testDocument );
|
||||
|
||||
conf.set( "addition", Collections.singletonMap( "foo", "bar" ) );
|
||||
|
||||
// Order matters
|
||||
assertEquals( "bar", conf.getSection( "addition" ).getString( "foo" ) );
|
||||
assertEquals( "bar", conf.getString( "addition.foo" ) );
|
||||
|
||||
assertTrue( conf.get( "addition" ) instanceof Configuration );
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package net.md_5.bungee.config;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class DefaultConfigurationTest
|
||||
{
|
||||
|
||||
@Test
|
||||
public void testDefaultValues()
|
||||
{
|
||||
Configuration defaultConfig = new Configuration();
|
||||
defaultConfig.set( "setting", 10 );
|
||||
defaultConfig.set( "nested.setting", 11 );
|
||||
defaultConfig.set( "double.nested.setting", 12 );
|
||||
|
||||
Configuration actualConfig = new Configuration( defaultConfig );
|
||||
|
||||
assertEquals( 10, actualConfig.getInt( "setting" ) );
|
||||
assertEquals( 11, actualConfig.getInt( "nested.setting" ) );
|
||||
assertEquals( 12, actualConfig.getInt( "double.nested.setting" ) );
|
||||
}
|
||||
}
|
@@ -1,81 +0,0 @@
|
||||
package net.md_5.bungee.config;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class YamlConfigurationTest
|
||||
{
|
||||
|
||||
private String document = ""
|
||||
+ "receipt: Oz-Ware Purchase Invoice\n"
|
||||
+ "date: 2012-08-06\n"
|
||||
+ "customer:\n"
|
||||
+ " given: Dorothy\n"
|
||||
+ " family: Gale\n"
|
||||
+ "\n"
|
||||
+ "items:\n"
|
||||
+ " - part_no: A4786\n"
|
||||
+ " descrip: Water Bucket (Filled)\n"
|
||||
+ " price: 1.47\n"
|
||||
+ " quantity: 4\n"
|
||||
+ "\n"
|
||||
+ " - part_no: E1628\n"
|
||||
+ " descrip: High Heeled \"Ruby\" Slippers\n"
|
||||
+ " size: 8\n"
|
||||
+ " price: 100.27\n"
|
||||
+ " quantity: 1\n"
|
||||
+ "\n"
|
||||
+ "bill-to: &id001\n"
|
||||
+ " street: |\n"
|
||||
+ " 123 Tornado Alley\n"
|
||||
+ " Suite 16\n"
|
||||
+ " city: East Centerville\n"
|
||||
+ " state: KS\n"
|
||||
+ "\n"
|
||||
+ "ship-to: *id001\n"
|
||||
+ "\n"
|
||||
+ "specialDelivery: >\n"
|
||||
+ " Follow the Yellow Brick\n"
|
||||
+ " Road to the Emerald City.\n"
|
||||
+ " Pay no attention to the\n"
|
||||
+ " man behind the curtain.";
|
||||
|
||||
@Test
|
||||
public void testConfig() throws Exception
|
||||
{
|
||||
Configuration conf = ConfigurationProvider.getProvider( YamlConfiguration.class ).load( document );
|
||||
testSection( conf );
|
||||
|
||||
StringWriter sw = new StringWriter();
|
||||
ConfigurationProvider.getProvider( YamlConfiguration.class ).save( conf, sw );
|
||||
|
||||
// Check nulls were saved, see #1094
|
||||
Assert.assertFalse( "Config contains null", sw.toString().contains( "null" ) );
|
||||
|
||||
conf = ConfigurationProvider.getProvider( YamlConfiguration.class ).load( new StringReader( sw.toString() ) );
|
||||
conf.set( "receipt", "Oz-Ware Purchase Invoice" ); // Add it back
|
||||
testSection( conf );
|
||||
}
|
||||
|
||||
private void testSection(Configuration conf)
|
||||
{
|
||||
Assert.assertEquals( "receipt", "Oz-Ware Purchase Invoice", conf.getString( "receipt" ) );
|
||||
// Assert.assertEquals( "date", "2012-08-06", conf.get( "date" ).toString() );
|
||||
|
||||
Configuration customer = conf.getSection( "customer" );
|
||||
Assert.assertEquals( "customer.given", "Dorothy", customer.getString( "given" ) );
|
||||
Assert.assertEquals( "customer.given", "Dorothy", conf.getString( "customer.given" ) );
|
||||
|
||||
List items = conf.getList( "items" );
|
||||
Map item = (Map) items.get( 0 );
|
||||
Assert.assertEquals( "items[0].part_no", "A4786", item.get( "part_no" ) );
|
||||
|
||||
conf.set( "receipt", null );
|
||||
Assert.assertEquals( null, conf.get( "receipt" ) );
|
||||
Assert.assertEquals( "foo", conf.get( "receipt", "foo" ) );
|
||||
}
|
||||
}
|
@@ -6,13 +6,13 @@
|
||||
<parent>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-parent</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<version>1.20-R0.2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-event</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<version>1.20-R0.2-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>BungeeCord-Event</name>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user