Compare commits
1280 Commits
Minecraft-
...
for-pandac
Author | SHA1 | Date | |
---|---|---|---|
93cbdab1f6 | |||
46c233d032 | |||
89053d2254 | |||
5932730744 | |||
3d89980065 | |||
21880c668a | |||
![]() |
7340f1a035 | ||
![]() |
8a80435e64 | ||
![]() |
20a71b06a9 | ||
![]() |
b376f61578 | ||
![]() |
373dab05ad | ||
![]() |
f6b40b1186 | ||
![]() |
81b118a8ba | ||
![]() |
7a42f12716 | ||
![]() |
4886c4be01 | ||
![]() |
7338d0f444 | ||
![]() |
8212e10c7c | ||
![]() |
2593130b3e | ||
![]() |
6ea49962c5 | ||
![]() |
672db9fe47 | ||
![]() |
2bacf6572b | ||
![]() |
9813e46e66 | ||
![]() |
01a5f36012 | ||
![]() |
f0a30c43cd | ||
![]() |
acb85e30fa | ||
![]() |
9437cedc48 | ||
![]() |
a89cf5f36d | ||
![]() |
b309e4ac50 | ||
![]() |
477ea5983c | ||
![]() |
eca6090f1e | ||
![]() |
8f8c270f3b | ||
![]() |
84ac7ab944 | ||
![]() |
5fbcc6b119 | ||
![]() |
79f85a2ce2 | ||
![]() |
d32eedd333 | ||
![]() |
e1d4b6adc7 | ||
![]() |
534148763f | ||
![]() |
cd56fb32c2 | ||
![]() |
6b612302e1 | ||
![]() |
e49759025f | ||
![]() |
c310e3339f | ||
![]() |
b64615e298 | ||
![]() |
45d2f44003 | ||
![]() |
a57adcce00 | ||
![]() |
8b195d1d21 | ||
![]() |
cda4537fba | ||
![]() |
df413f62db | ||
![]() |
8a88ce464e | ||
![]() |
006a14a75c | ||
![]() |
07df657f3c | ||
![]() |
b8b373a53e | ||
![]() |
e7e0b97cff | ||
![]() |
52ab21b1ff | ||
![]() |
8e8a635361 | ||
![]() |
18eae8a1a6 | ||
![]() |
6e1751733f | ||
![]() |
6335af840b | ||
![]() |
336333acb1 | ||
![]() |
d110f6629b | ||
![]() |
6f70b15e2e | ||
![]() |
b30499e2b6 | ||
![]() |
5079181c28 | ||
![]() |
ee02d98cb2 | ||
![]() |
c7ff3b8a14 | ||
![]() |
de60af0d7b | ||
![]() |
a9218a7aa7 | ||
![]() |
67c65e0464 | ||
![]() |
1be25b6c74 | ||
![]() |
8525b44961 | ||
![]() |
1fca510a08 | ||
![]() |
3384185285 | ||
![]() |
3075d2c19d | ||
![]() |
bc528d5d98 | ||
![]() |
25cf8d682b | ||
![]() |
17e23d5c3f | ||
![]() |
d6c5197cb9 | ||
![]() |
dd96f0f878 | ||
![]() |
8a9501ffe4 | ||
![]() |
5e25c63c5a | ||
![]() |
bd963501ec | ||
![]() |
da795a7094 | ||
![]() |
84d0ea73fa | ||
![]() |
0851e39197 | ||
![]() |
86e6fdf8a2 | ||
![]() |
6ab0f5eba7 | ||
![]() |
f224787222 | ||
![]() |
82684c7b6b | ||
![]() |
c2f73d32b8 | ||
![]() |
e642b9dde1 | ||
![]() |
db623d10c5 | ||
![]() |
61bb9f5b93 | ||
![]() |
9551b45328 | ||
![]() |
dc680b87eb | ||
![]() |
156eda78c6 | ||
![]() |
31be68af51 | ||
![]() |
ffa011c7b1 | ||
![]() |
22536c11bd | ||
![]() |
2394e204fa | ||
![]() |
1b88a84710 | ||
![]() |
7606d4437b | ||
![]() |
3e9a7e45c4 | ||
![]() |
f6c5332c1a | ||
![]() |
d0fa62d424 | ||
![]() |
464ed0184c | ||
![]() |
eda268b481 | ||
![]() |
3e1007527c | ||
![]() |
b52b14696c | ||
![]() |
94d5b0d03c | ||
![]() |
c3f228f626 | ||
![]() |
02c5c1ee76 | ||
![]() |
c69acf728c | ||
![]() |
a1cd694363 | ||
![]() |
3e2bc8e2d7 | ||
![]() |
ad7163d2d6 | ||
![]() |
19918c694f | ||
![]() |
21c8f2815a | ||
![]() |
737d545fb6 | ||
![]() |
b23a51825e | ||
![]() |
708c5b6254 | ||
![]() |
f5af11193c | ||
![]() |
b711e4033f | ||
![]() |
3deaaadc3a | ||
![]() |
51b9a6b0b8 | ||
![]() |
1a807731a5 | ||
![]() |
772ad9951f | ||
![]() |
2431c40a5c | ||
![]() |
8144ae8d7b | ||
![]() |
0757c39a6f | ||
![]() |
231024ba42 | ||
![]() |
8ce7a7f8b6 | ||
![]() |
e1462ccdd1 | ||
![]() |
70f346c1dc | ||
![]() |
197bf13a28 | ||
![]() |
0925c06f9b | ||
![]() |
16298a75f2 | ||
![]() |
39b10c0b16 | ||
![]() |
bd8d114992 | ||
![]() |
30e12c6fe0 | ||
![]() |
bd009ca52d | ||
![]() |
65d8edf62d | ||
![]() |
f5157f12a4 | ||
![]() |
df20effacc | ||
![]() |
c92581d0dc | ||
![]() |
e442c3da5c | ||
![]() |
f903c54d55 | ||
![]() |
0d45378986 | ||
![]() |
0f5f09b6c5 | ||
![]() |
e5c80d0044 | ||
![]() |
9cdb2ba3ea | ||
![]() |
d0e5cf7ce5 | ||
![]() |
c8568764f6 | ||
![]() |
a7dbbc2f0a | ||
![]() |
68b2df2b1e | ||
![]() |
1ef4d27dbe | ||
![]() |
94a1fb5117 | ||
![]() |
78aef86a8f | ||
![]() |
b34cfcde5a | ||
![]() |
86e079a4b1 | ||
![]() |
1c42c34081 | ||
![]() |
fed646d18b | ||
![]() |
653f1691d7 | ||
![]() |
3cb7a12738 | ||
![]() |
f3397b3003 | ||
![]() |
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 | ||
![]() |
7d2c2ab074 | ||
![]() |
0646a3090a | ||
![]() |
afc02082e6 | ||
![]() |
848cad2a59 | ||
![]() |
9c4380a201 | ||
![]() |
8490d611bf | ||
![]() |
a0f2c42d38 | ||
![]() |
40c0618a3a | ||
![]() |
fa3678bcdd | ||
![]() |
e556fd7150 | ||
![]() |
841c81cdc4 | ||
![]() |
052131c1fa | ||
![]() |
79dbdea107 | ||
![]() |
7fb1f4b81f | ||
![]() |
255d7fde9a | ||
![]() |
7907610eeb | ||
![]() |
83e27f07e6 | ||
![]() |
5cff0b2171 | ||
![]() |
2c86592ecd | ||
![]() |
f5552963b8 | ||
![]() |
1182affa09 | ||
![]() |
a1895c556f | ||
![]() |
aa214c0b54 | ||
![]() |
81bd3b5f71 | ||
![]() |
b6e26e0c09 | ||
![]() |
19f2e7b13e | ||
![]() |
ba448b5670 | ||
![]() |
013320fd9e | ||
![]() |
8a1030e21c | ||
![]() |
c626254825 | ||
![]() |
4e94c278da | ||
![]() |
06ad0f9310 | ||
![]() |
c4a3a052d7 | ||
![]() |
7ec1a1aa4e | ||
![]() |
59208aad86 | ||
![]() |
bd07be8772 | ||
![]() |
b3d15d53d6 | ||
![]() |
6343416c0c | ||
![]() |
81d1c46a0d | ||
![]() |
6e5132f914 | ||
![]() |
f3c14cf064 | ||
![]() |
a3a31fd2dd | ||
![]() |
cc3a8c067e | ||
![]() |
918d7229c2 | ||
![]() |
6c4e684de9 | ||
![]() |
9cf57ca929 | ||
![]() |
70564d9f44 | ||
![]() |
8622cf3af4 | ||
![]() |
5236dd301a | ||
![]() |
ddfd76ebda | ||
![]() |
93959cab4b | ||
![]() |
585ab4f453 | ||
![]() |
76052b92d3 | ||
![]() |
f9773a69c3 | ||
![]() |
88e71ead05 | ||
![]() |
d1e1ce4cdb | ||
![]() |
fa828eba31 | ||
![]() |
d76c8d4f33 | ||
![]() |
a48c458306 | ||
![]() |
3973c511f5 | ||
![]() |
78ea41015f | ||
![]() |
bd2eaf6879 | ||
![]() |
f2d17cb216 | ||
![]() |
f2673c5876 | ||
![]() |
e1951c5d66 | ||
![]() |
988490ba87 | ||
![]() |
62981e4c70 | ||
![]() |
5699e86270 | ||
![]() |
61cee2d27c | ||
![]() |
2055c98ebe | ||
![]() |
415d5860e4 | ||
![]() |
3776feb559 | ||
![]() |
668cdabdf7 | ||
![]() |
3e26eecd4e | ||
![]() |
7c1f232e85 | ||
![]() |
2b49358bea | ||
![]() |
b4997f6379 | ||
![]() |
eeaa44e1e7 | ||
![]() |
f4ae511af0 | ||
![]() |
32693aeaff | ||
![]() |
0d569ac0d1 | ||
![]() |
e6da9cbba8 | ||
![]() |
61d2765715 | ||
![]() |
e68ed48fc3 | ||
![]() |
cf722de1d2 | ||
![]() |
28496e0471 | ||
![]() |
4809f1f80a | ||
![]() |
ae12554316 | ||
![]() |
5091515f0b | ||
![]() |
52b75cd18b | ||
![]() |
a5f2b423d4 | ||
![]() |
54c9ade1a6 | ||
![]() |
bc6d7719a7 | ||
![]() |
2ab0715226 | ||
![]() |
1711223b02 | ||
![]() |
cd15b82361 | ||
![]() |
22084b2c75 | ||
![]() |
972b4c1fe5 | ||
![]() |
36c4af35de | ||
![]() |
02d3660f32 | ||
![]() |
37e37e9a55 | ||
![]() |
a03c21cc09 | ||
![]() |
45bf7a9ab9 | ||
![]() |
ef364d9053 | ||
![]() |
f19cc7fe4f | ||
![]() |
772c8d7f2b | ||
![]() |
830f18a357 | ||
![]() |
c21275b877 | ||
![]() |
532a94382b | ||
![]() |
56c372a3ce | ||
![]() |
faf903469e | ||
![]() |
4d389df7c8 | ||
![]() |
cfad2c65d4 | ||
![]() |
8715c5fd82 | ||
![]() |
cbb190cfd3 | ||
![]() |
acf5f2f443 | ||
![]() |
57a07dc2e2 | ||
![]() |
6fcfb5aecb | ||
![]() |
4e3b5670a0 | ||
![]() |
ee3b209c2d | ||
![]() |
25ee8a1496 | ||
![]() |
988905331f | ||
![]() |
dd1a28ea1e | ||
![]() |
0a0146b68a | ||
![]() |
ca2227bad4 | ||
![]() |
3cd4f169bd | ||
![]() |
5ec36efb52 | ||
![]() |
dcc9be9dfe | ||
![]() |
efdedbd4e8 | ||
![]() |
1623fb6952 | ||
![]() |
4e353e9277 | ||
![]() |
d6b7157c1c | ||
![]() |
bc48ab3fb8 | ||
![]() |
65ae8b4c6a | ||
![]() |
73d7e0cf99 | ||
![]() |
2cec5f344a | ||
![]() |
4573285a70 | ||
![]() |
5282a8f45a | ||
![]() |
6eedc77954 | ||
![]() |
f15eed338d | ||
![]() |
faa284c8fc | ||
![]() |
4bb3850b40 | ||
![]() |
1f132876e6 | ||
![]() |
c822c48fef | ||
![]() |
26521cf2ff | ||
![]() |
e99bbff22e | ||
![]() |
19b48672af | ||
![]() |
f81b8a3550 | ||
![]() |
87797ef719 | ||
![]() |
86c5e321f2 | ||
![]() |
41ccf3f9d3 | ||
![]() |
e573f7b89c | ||
![]() |
664c66fb97 | ||
![]() |
caa562c4a1 | ||
![]() |
483805067d | ||
![]() |
5e0aa2e60d | ||
![]() |
f9f9c3213d | ||
![]() |
be35e283ec | ||
![]() |
dd9bd2a2e3 | ||
![]() |
3188d946b3 | ||
![]() |
072e360d0f | ||
![]() |
ee3efd75d7 | ||
![]() |
d85400bc69 | ||
![]() |
b544bb34cb | ||
![]() |
8676dd47f6 | ||
![]() |
089a8dd311 | ||
![]() |
d1d4cc7bbf | ||
![]() |
74f5ffd08b | ||
![]() |
9fc862cb74 | ||
![]() |
0d174b51c5 | ||
![]() |
1b18e64fb2 | ||
![]() |
c42d3a375f | ||
![]() |
fc0a21f548 | ||
![]() |
65eba06980 | ||
![]() |
87a64c3f3e | ||
![]() |
be13a00386 | ||
![]() |
949f150ea0 | ||
![]() |
c965e60f5e | ||
![]() |
02e219262a | ||
![]() |
a0e8b172ef | ||
![]() |
215b70dcd7 | ||
![]() |
a6095c680f | ||
![]() |
9d5c886045 | ||
![]() |
19bb8f72c7 | ||
![]() |
4dfd510583 | ||
![]() |
705b554b3b | ||
![]() |
6615500f08 | ||
![]() |
d63d5a2791 | ||
![]() |
2444dd15ab | ||
![]() |
2dd3d2101d | ||
![]() |
8ce26e0370 | ||
![]() |
5d1b660e32 | ||
![]() |
7347daf203 | ||
![]() |
02cb1fc65b | ||
![]() |
7318750ed0 | ||
![]() |
21be93a1b1 | ||
![]() |
bb69af5cd0 | ||
![]() |
a668da76d0 | ||
![]() |
4cc009a9c0 | ||
![]() |
4ef58d53b1 | ||
![]() |
afa37505c5 | ||
![]() |
a53b63720b | ||
![]() |
348457f613 | ||
![]() |
fbb2f695b0 | ||
![]() |
350cbd7bb6 | ||
![]() |
489242b1ef | ||
![]() |
4ac117fb4c | ||
![]() |
500b0af782 | ||
![]() |
2b304ecebc | ||
![]() |
81d83bdd8a | ||
![]() |
b9f2f3cfae | ||
![]() |
923aa05d4a | ||
![]() |
e54388a5e0 | ||
![]() |
59ba644623 | ||
![]() |
fbc69543fd | ||
![]() |
4d1f0cbb26 | ||
![]() |
e849afbb23 | ||
![]() |
1a7efeabc4 | ||
![]() |
f6e41c856c | ||
![]() |
151344aaff | ||
![]() |
b167a45690 | ||
![]() |
31bd836203 | ||
![]() |
4dce37cd13 | ||
![]() |
107d6b011d | ||
![]() |
f23691df23 | ||
![]() |
77f6930280 | ||
![]() |
9604a9a31e | ||
![]() |
91989564e5 | ||
![]() |
3c938c03c7 | ||
![]() |
9226df86f0 | ||
![]() |
00db351dd6 | ||
![]() |
ad2ff54b76 | ||
![]() |
ece641da23 | ||
![]() |
2af8dac70c | ||
![]() |
33a098f4ba | ||
![]() |
e4e01ccb55 | ||
![]() |
6c9e6abc9f | ||
![]() |
84c7e073e0 | ||
![]() |
9e46739343 | ||
![]() |
8fe72383a1 | ||
![]() |
a56bbe38b2 | ||
![]() |
312a74c5f1 | ||
![]() |
71c86f9f90 | ||
![]() |
6475385f87 | ||
![]() |
6775b9230c | ||
![]() |
c8e6b6fd7a | ||
![]() |
bca3663a1f | ||
![]() |
f71272a1c0 | ||
![]() |
ca7c755ecd | ||
![]() |
5a638f2290 | ||
![]() |
3715756be7 | ||
![]() |
1a1a51b38d | ||
![]() |
bc2b4db419 | ||
![]() |
994a996981 | ||
![]() |
e2eba52162 | ||
![]() |
13decac4b9 | ||
![]() |
7ebe5184a4 | ||
![]() |
b08f1995f6 | ||
![]() |
a642346a2c | ||
![]() |
18316eb5f8 | ||
![]() |
3ced0b675d | ||
![]() |
66a70fef5b | ||
![]() |
dc2da29c16 | ||
![]() |
3b71a2b570 | ||
![]() |
1aa5379030 | ||
![]() |
153bca00be | ||
![]() |
747628f40c | ||
![]() |
bf9521472b | ||
![]() |
86ef046544 | ||
![]() |
cd518690fd | ||
![]() |
1d3adc5317 | ||
![]() |
13848def72 | ||
![]() |
a4dd0dba88 | ||
![]() |
e025ad8ed7 | ||
![]() |
720f5df2f4 | ||
![]() |
52bf6184c7 | ||
![]() |
f7a5748464 | ||
![]() |
61e3d27ae9 | ||
![]() |
1bbbfdb0d5 | ||
![]() |
a8584f81ed | ||
![]() |
5ae2e24c84 | ||
![]() |
c29676e4fc | ||
![]() |
92ebce2ec6 | ||
![]() |
9cd7c1ac03 | ||
![]() |
924dcaab38 | ||
![]() |
8502ab54c0 | ||
![]() |
3301c95066 | ||
![]() |
49f4dcb5b7 | ||
![]() |
13d679e7d7 | ||
![]() |
72cadac76e | ||
![]() |
227301ec73 | ||
![]() |
f2b0e3e3c3 | ||
![]() |
575a6b6ea0 | ||
![]() |
5beafed279 | ||
![]() |
2a7ad3c2b2 | ||
![]() |
00352f585a | ||
![]() |
290e31b4c5 | ||
![]() |
1fb7a3bf1d | ||
![]() |
52fbceec54 | ||
![]() |
e609145a0d | ||
![]() |
4a7f8015e5 | ||
![]() |
003a1973d4 | ||
![]() |
db7f3c770d | ||
![]() |
eec3c09c32 | ||
![]() |
565af4d53e | ||
![]() |
37ed331515 | ||
![]() |
38f12840ca | ||
![]() |
941450b4e4 | ||
![]() |
e87d25c321 | ||
![]() |
886f7499fb | ||
![]() |
8064a3d4fb | ||
![]() |
5039922fa7 | ||
![]() |
eb753c8109 | ||
![]() |
7bfa024c23 | ||
![]() |
d98ade5a9b | ||
![]() |
a272afd693 | ||
![]() |
0f24eaeea3 | ||
![]() |
56e9e6a245 | ||
![]() |
87f3706736 | ||
![]() |
90104b03b7 | ||
![]() |
a9b2660aa8 | ||
![]() |
0b7789035f | ||
![]() |
3f7850dc5a | ||
![]() |
2e80bf30dd | ||
![]() |
b3627652f2 | ||
![]() |
b5216148d6 | ||
![]() |
4faf507ad9 | ||
![]() |
9a4f0a6f59 | ||
![]() |
98a5db9abf | ||
![]() |
015dc0c65a | ||
![]() |
c1b9e9032f | ||
![]() |
5e5038c839 | ||
![]() |
36ea27454d | ||
![]() |
2eb2953442 | ||
![]() |
4abffa9f24 | ||
![]() |
f08df9555c | ||
![]() |
4c5689d10e | ||
![]() |
224db6596e | ||
![]() |
df82720ade | ||
![]() |
33f87498be | ||
![]() |
d0af22a0f2 | ||
![]() |
4fa137a465 | ||
![]() |
0d7ee821d2 | ||
![]() |
1f1cdb47e4 | ||
![]() |
ddab9a84c4 | ||
![]() |
5adc0000d8 | ||
![]() |
a0cc5d84be | ||
![]() |
edce7f7c3d | ||
![]() |
19b4c09a16 | ||
![]() |
35a5230b52 | ||
![]() |
90fcfecb7c | ||
![]() |
542d2c2a5b | ||
![]() |
649f83dee2 | ||
![]() |
842392d59c | ||
![]() |
b2f517fa63 | ||
![]() |
5c12f900b3 | ||
![]() |
6641d199b3 | ||
![]() |
83b0229277 | ||
![]() |
5f7963b0c4 | ||
![]() |
dae96ac18b | ||
![]() |
aa91354666 | ||
![]() |
05f4e69afd | ||
![]() |
71e64bf532 | ||
![]() |
219d485835 | ||
![]() |
b698fa9806 | ||
![]() |
6602c22147 | ||
![]() |
f1b329bf21 | ||
![]() |
d4e4796739 | ||
![]() |
ab1aacbdc9 | ||
![]() |
a426a5ec22 | ||
![]() |
93cf50b4e1 | ||
![]() |
7483b4d276 | ||
![]() |
4be58a7c00 | ||
![]() |
90d68bd38e | ||
![]() |
7dba8c8a87 | ||
![]() |
cb4f70ecc7 | ||
![]() |
2100da2a9f | ||
![]() |
986f52b1e1 | ||
![]() |
7faefde51b | ||
![]() |
8c367d86e7 | ||
![]() |
80caa2b669 | ||
![]() |
9f8c04ef86 | ||
![]() |
b0a8371570 | ||
![]() |
0d7cd78901 | ||
![]() |
e7f1a88e6e | ||
![]() |
28c8bf47ff | ||
![]() |
5e57356e6a | ||
![]() |
f3766bc10b | ||
![]() |
6c795a25ff | ||
![]() |
d3159fe6ca | ||
![]() |
93ba9b3a3e | ||
![]() |
c184667a26 | ||
![]() |
5ea4763ae9 | ||
![]() |
d0d0f4ec9f | ||
![]() |
0ff1f4724a | ||
![]() |
1baba3cd7d | ||
![]() |
608eaace1c | ||
![]() |
075518b643 | ||
![]() |
f7d3dfd61d | ||
![]() |
b713ccff10 | ||
![]() |
f0e1625078 | ||
![]() |
60d6f31876 | ||
![]() |
359e2b2a16 | ||
![]() |
0dcba749dc | ||
![]() |
00ac965d42 | ||
![]() |
32c6ab710a | ||
![]() |
5d68b422e5 | ||
![]() |
6093cde93f | ||
![]() |
4537055caa | ||
![]() |
17fc120e07 | ||
![]() |
230cca0f9e | ||
![]() |
cfda905d98 | ||
![]() |
bc746a546f | ||
![]() |
70bb3ddcce | ||
![]() |
4ef15ae764 | ||
![]() |
3f9ca85831 | ||
![]() |
c17fa03ccd | ||
![]() |
0040955204 | ||
![]() |
60e2e6bfa4 | ||
![]() |
2cb3b6f934 | ||
![]() |
c7e590e286 | ||
![]() |
696679809d | ||
![]() |
e3e551d825 | ||
![]() |
854b6faf0e | ||
![]() |
890fac27c5 | ||
![]() |
35c1b26a20 | ||
![]() |
2c8b15cb1e | ||
![]() |
c20d8f9cd6 | ||
![]() |
85c27f30ee | ||
![]() |
0446351f9d | ||
![]() |
54d307da57 | ||
![]() |
81e43aab98 | ||
![]() |
18db187347 | ||
![]() |
63d49ac296 | ||
![]() |
aad83d787f | ||
![]() |
af751dae5a | ||
![]() |
f7851b0436 | ||
![]() |
d7e78d0945 | ||
![]() |
94ee61cd35 | ||
![]() |
897a59254c | ||
![]() |
c1ba555553 | ||
![]() |
c70006a36c | ||
![]() |
12ef019d69 | ||
![]() |
c18537f294 | ||
![]() |
fc189e81d5 | ||
![]() |
aaa8b4a53d | ||
![]() |
e39428ea0b | ||
![]() |
e4602f027e | ||
![]() |
2f2406206e | ||
![]() |
3ae8308a4b | ||
![]() |
8a38921f21 | ||
![]() |
cc0d3a8e49 | ||
![]() |
f81bf8e7c5 | ||
![]() |
e755573fb3 | ||
![]() |
a201b5897a | ||
![]() |
b4cd88c13d | ||
![]() |
8e390b5714 | ||
![]() |
14371a1a8c | ||
![]() |
61326db3ee | ||
![]() |
90625bc196 | ||
![]() |
155e274e72 | ||
![]() |
04b52aa4f4 | ||
![]() |
4040d9f20a | ||
![]() |
02619c6132 | ||
![]() |
26863032a1 | ||
![]() |
a0d3bf97d1 | ||
![]() |
3becbe4d38 | ||
![]() |
7205e69ce6 | ||
![]() |
c84d6f0035 | ||
![]() |
20b1b37e54 | ||
![]() |
2117a6b7de | ||
![]() |
e6c1015027 | ||
![]() |
8665784bb5 | ||
![]() |
efd5bd58e4 | ||
![]() |
e006673550 | ||
![]() |
2129cb3614 | ||
![]() |
6ce43fb876 | ||
![]() |
b9158b7322 | ||
![]() |
5dfd14fbe5 | ||
![]() |
e1f7b7b126 | ||
![]() |
4dff25f880 | ||
![]() |
5dc91e3a01 | ||
![]() |
e2e32100cd | ||
![]() |
a7e4854661 | ||
![]() |
6e69d476ef | ||
![]() |
1e2eda94db | ||
![]() |
37dc600fe0 | ||
![]() |
07d9a56567 | ||
![]() |
0952e53d11 | ||
![]() |
2101964330 | ||
![]() |
cdf47d84d8 | ||
![]() |
b7babd2888 | ||
![]() |
da5fa4bb7c | ||
![]() |
00854988fb | ||
![]() |
4f8085678c | ||
![]() |
6341ad4c5a | ||
![]() |
9b84e75eaa | ||
![]() |
194b09b2dd | ||
![]() |
3b9af0ab85 | ||
![]() |
18db20fe42 | ||
![]() |
062dd38b2b | ||
![]() |
69b209bcc6 | ||
![]() |
d96e561a6f | ||
![]() |
26be0566f4 | ||
![]() |
1551bf6f3a | ||
![]() |
e0ebf1af21 | ||
![]() |
b8c9330bd6 | ||
![]() |
1b41682e37 | ||
![]() |
b358fd25f5 | ||
![]() |
dbdae87ec6 | ||
![]() |
7121c20338 | ||
![]() |
d900417d95 | ||
![]() |
7be929bb08 | ||
![]() |
4257b81d8c | ||
![]() |
96acdb97fd |
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
|
27
.github/dependabot.yml
vendored
Normal file
27
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
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"
|
||||
# Needs to be synchronised with maven-resolver-provider dependencies
|
||||
- dependency-name: "org.apache.maven.resolver:maven-resolver-connector-basic"
|
||||
- dependency-name: "org.apache.maven.resolver:maven-resolver-transport-http"
|
||||
# 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@v4
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: ${{ matrix.java }}
|
||||
- run: java -version && mvn --version
|
||||
- run: mvn --activate-profiles dist --no-transfer-progress package
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -24,7 +24,7 @@ dist/
|
||||
manifest.mf
|
||||
|
||||
# Mac filesystem dust
|
||||
.DS_Store/
|
||||
.DS_Store
|
||||
|
||||
# intellij
|
||||
*.iml
|
||||
@ -34,4 +34,6 @@ manifest.mf
|
||||
|
||||
# other files
|
||||
*.log*
|
||||
*.yml
|
||||
|
||||
# delombok
|
||||
*/src/main/lombok
|
||||
|
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,5 +0,0 @@
|
||||
language: java
|
||||
jdk:
|
||||
- openjdk7
|
||||
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-2024 SpigotMC Pty. Ltd.
|
||||
|
51
api/pom.xml
51
api/pom.xml
@ -4,15 +4,14 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>net.md-5</groupId>
|
||||
<groupId>fr.pandacube.bungeecord</groupId>
|
||||
<artifactId>bungeecord-parent</artifactId>
|
||||
<version>1.6.4-SNAPSHOT</version>
|
||||
<version>1.21-R0.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.6.4-SNAPSHOT</version>
|
||||
<version>1.21-R0.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>BungeeCord-API</name>
|
||||
@ -20,28 +19,60 @@
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>14.0.1</version>
|
||||
<groupId>fr.pandacube.bungeecord</groupId>
|
||||
<artifactId>bungeecord-chat</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<groupId>fr.pandacube.bungeecord</groupId>
|
||||
<artifactId>bungeecord-config</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<groupId>fr.pandacube.bungeecord</groupId>
|
||||
<artifactId>bungeecord-event</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<groupId>fr.pandacube.bungeecord</groupId>
|
||||
<artifactId>bungeecord-protocol</artifactId>
|
||||
<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.9.6</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.9.18</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.9.18</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>
|
||||
|
141
api/src/main/java/net/md_5/bungee/Util.java
Normal file
141
api/src/main/java/net/md_5/bungee/Util.java
Normal file
@ -0,0 +1,141 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Series of utility classes to perform various operations.
|
||||
*/
|
||||
public class Util
|
||||
{
|
||||
|
||||
public static final int DEFAULT_PORT = 25565;
|
||||
|
||||
/**
|
||||
* Method to transform human readable addresses into usable address objects.
|
||||
*
|
||||
* @param hostline in the format of 'host:port'
|
||||
* @return the constructed hostname + port.
|
||||
*/
|
||||
public static SocketAddress getAddr(String hostline)
|
||||
{
|
||||
URI uri = null;
|
||||
try
|
||||
{
|
||||
uri = new URI( hostline );
|
||||
} catch ( URISyntaxException 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() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an integer as a hex value.
|
||||
*
|
||||
* @param i the integer to format
|
||||
* @return the hex representation of the integer
|
||||
*/
|
||||
public static String hex(int i)
|
||||
{
|
||||
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.
|
||||
*
|
||||
* @param t the {@link Throwable} to format.
|
||||
* @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()
|
||||
+ ( ( includeLineNumbers && trace.length > 0 ) ? " @ " + t.getStackTrace()[0].getClassName() + ":" + t.getStackTrace()[0].getLineNumber() : "" );
|
||||
}
|
||||
|
||||
public static String csv(Iterable<?> objects)
|
||||
{
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a String to a UUID
|
||||
*
|
||||
* @param uuid The string to be converted
|
||||
* @return The result
|
||||
*/
|
||||
public static UUID getUUID(String uuid)
|
||||
{
|
||||
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,186 +0,0 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Simplistic enumeration of all supported color values for chat.
|
||||
*/
|
||||
public enum ChatColor
|
||||
{
|
||||
|
||||
/**
|
||||
* Represents black.
|
||||
*/
|
||||
BLACK( '0' ),
|
||||
/**
|
||||
* Represents dark blue.
|
||||
*/
|
||||
DARK_BLUE( '1' ),
|
||||
/**
|
||||
* Represents dark green.
|
||||
*/
|
||||
DARK_GREEN( '2' ),
|
||||
/**
|
||||
* Represents dark blue (aqua).
|
||||
*/
|
||||
DARK_AQUA( '3' ),
|
||||
/**
|
||||
* Represents dark red.
|
||||
*/
|
||||
DARK_RED( '4' ),
|
||||
/**
|
||||
* Represents dark purple.
|
||||
*/
|
||||
DARK_PURPLE( '5' ),
|
||||
/**
|
||||
* Represents gold.
|
||||
*/
|
||||
GOLD( '6' ),
|
||||
/**
|
||||
* Represents gray.
|
||||
*/
|
||||
GRAY( '7' ),
|
||||
/**
|
||||
* Represents dark gray.
|
||||
*/
|
||||
DARK_GRAY( '8' ),
|
||||
/**
|
||||
* Represents blue.
|
||||
*/
|
||||
BLUE( '9' ),
|
||||
/**
|
||||
* Represents green.
|
||||
*/
|
||||
GREEN( 'a' ),
|
||||
/**
|
||||
* Represents aqua.
|
||||
*/
|
||||
AQUA( 'b' ),
|
||||
/**
|
||||
* Represents red.
|
||||
*/
|
||||
RED( 'c' ),
|
||||
/**
|
||||
* Represents light purple.
|
||||
*/
|
||||
LIGHT_PURPLE( 'd' ),
|
||||
/**
|
||||
* Represents yellow.
|
||||
*/
|
||||
YELLOW( 'e' ),
|
||||
/**
|
||||
* Represents white.
|
||||
*/
|
||||
WHITE( 'f' ),
|
||||
/**
|
||||
* Represents magical characters that change around randomly.
|
||||
*/
|
||||
MAGIC( 'k' ),
|
||||
/**
|
||||
* Makes the text bold.
|
||||
*/
|
||||
BOLD( 'l' ),
|
||||
/**
|
||||
* Makes a line appear through the text.
|
||||
*/
|
||||
STRIKETHROUGH( 'm' ),
|
||||
/**
|
||||
* Makes the text appear underlined.
|
||||
*/
|
||||
UNDERLINE( 'n' ),
|
||||
/**
|
||||
* Makes the text italic.
|
||||
*/
|
||||
ITALIC( 'o' ),
|
||||
/**
|
||||
* Resets all previous chat colors or formats.
|
||||
*/
|
||||
RESET( 'r' );
|
||||
/**
|
||||
* 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';
|
||||
/**
|
||||
* Pattern to remove all colour codes.
|
||||
*/
|
||||
private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile( "(?i)" + String.valueOf( COLOR_CHAR ) + "[0-9A-FK-OR]" );
|
||||
/**
|
||||
* Colour instances keyed by their active character.
|
||||
*/
|
||||
private static final Map<Character, ChatColor> BY_CHAR = new HashMap<>();
|
||||
/**
|
||||
* The code appended to {@link #COLOR_CHAR} to make usable colour.
|
||||
*/
|
||||
private final char code;
|
||||
/**
|
||||
* This colour's colour char prefixed by the {@link #COLOR_CHAR}.
|
||||
*/
|
||||
private final String toString;
|
||||
|
||||
static
|
||||
{
|
||||
for ( ChatColor colour : values() )
|
||||
{
|
||||
BY_CHAR.put( colour.code, colour );
|
||||
}
|
||||
}
|
||||
|
||||
private ChatColor(char code)
|
||||
{
|
||||
this.code = code;
|
||||
this.toString = new String( new char[]
|
||||
{
|
||||
COLOR_CHAR, code
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return toString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the given message of all color codes
|
||||
*
|
||||
* @param input String to strip of color
|
||||
* @return A copy of the input string, without any coloring
|
||||
*/
|
||||
public static String stripColor(final String input)
|
||||
{
|
||||
if ( input == null )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return STRIP_COLOR_PATTERN.matcher( input ).replaceAll( "" );
|
||||
}
|
||||
|
||||
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate)
|
||||
{
|
||||
char[] b = textToTranslate.toCharArray();
|
||||
for ( int i = 0; i < b.length - 1; i++ )
|
||||
{
|
||||
if ( b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf( b[i + 1] ) > -1 )
|
||||
{
|
||||
b[i] = ChatColor.COLOR_CHAR;
|
||||
b[i + 1] = Character.toLowerCase( b[i + 1] );
|
||||
}
|
||||
}
|
||||
return new String( b );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the colour represented by the specified code.
|
||||
*
|
||||
* @param code the code to search for
|
||||
* @return the mapped colour, or null if non exists
|
||||
*/
|
||||
public static ChatColor getByChar(char code)
|
||||
{
|
||||
return BY_CHAR.get( code );
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import java.util.Collection;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
|
||||
public interface CommandSender
|
||||
{
|
||||
@ -17,6 +18,7 @@ public interface CommandSender
|
||||
*
|
||||
* @param message the message to send
|
||||
*/
|
||||
@Deprecated
|
||||
public void sendMessage(String message);
|
||||
|
||||
/**
|
||||
@ -25,8 +27,23 @@ public interface CommandSender
|
||||
*
|
||||
* @param messages the messages to send
|
||||
*/
|
||||
@Deprecated
|
||||
public void sendMessages(String... messages);
|
||||
|
||||
/**
|
||||
* Send a message to this sender.
|
||||
*
|
||||
* @param message the message to send
|
||||
*/
|
||||
public void sendMessage(BaseComponent... message);
|
||||
|
||||
/**
|
||||
* Send a message to this sender.
|
||||
*
|
||||
* @param message the message to send
|
||||
*/
|
||||
public void sendMessage(BaseComponent message);
|
||||
|
||||
/**
|
||||
* Get all groups this user is part of. This returns an unmodifiable
|
||||
* collection.
|
||||
@ -64,4 +81,12 @@ public interface CommandSender
|
||||
* @param value the value of the node
|
||||
*/
|
||||
public void setPermission(String permission, boolean value);
|
||||
|
||||
/**
|
||||
* Get all Permissions which this CommandSender has
|
||||
*
|
||||
* @return a unmodifiable Collection of Strings which represent their
|
||||
* permissions
|
||||
*/
|
||||
public Collection<String> getPermissions();
|
||||
}
|
||||
|
121
api/src/main/java/net/md_5/bungee/api/Favicon.java
Normal file
121
api/src/main/java/net/md_5/bungee/api/Favicon.java
Normal file
@ -0,0 +1,121 @@
|
||||
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.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* Favicon shown in the server list.
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class Favicon
|
||||
{
|
||||
|
||||
private static final TypeAdapter<Favicon> FAVICON_TYPE_ADAPTER = new TypeAdapter<Favicon>()
|
||||
{
|
||||
@Override
|
||||
public void write(JsonWriter out, Favicon value) throws IOException
|
||||
{
|
||||
if ( value == null )
|
||||
{
|
||||
out.nullValue();
|
||||
} else
|
||||
{
|
||||
out.value( value.getEncoded() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Favicon read(JsonReader in) throws IOException
|
||||
{
|
||||
JsonToken peek = in.peek();
|
||||
if ( peek == JsonToken.NULL )
|
||||
{
|
||||
in.nextNull();
|
||||
return null;
|
||||
}
|
||||
|
||||
String enc = in.nextString();
|
||||
return enc == null ? null : create( enc );
|
||||
}
|
||||
};
|
||||
|
||||
public static TypeAdapter<Favicon> getFaviconTypeAdapter()
|
||||
{
|
||||
return FAVICON_TYPE_ADAPTER;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base64 encoded favicon, including MIME header.
|
||||
*/
|
||||
@NonNull
|
||||
@Getter
|
||||
private final String encoded;
|
||||
|
||||
/**
|
||||
* Creates a favicon from an image.
|
||||
*
|
||||
* @param image the image to create on
|
||||
* @return the created favicon instance
|
||||
* @throws IllegalArgumentException if the favicon is larger than
|
||||
* {@link Short#MAX_VALUE} or not of dimensions 64x64 pixels.
|
||||
*/
|
||||
public static Favicon create(BufferedImage image)
|
||||
{
|
||||
Preconditions.checkArgument( image != null, "image is null" );
|
||||
// check size
|
||||
if ( image.getWidth() != 64 || image.getHeight() != 64 )
|
||||
{
|
||||
throw new IllegalArgumentException( "Server icon must be exactly 64x64 pixels" );
|
||||
}
|
||||
|
||||
// dump image PNG
|
||||
byte[] imageBytes;
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
ImageIO.write( image, "PNG", stream );
|
||||
imageBytes = stream.toByteArray();
|
||||
} catch ( IOException e )
|
||||
{
|
||||
// ByteArrayOutputStream should never throw this
|
||||
throw new AssertionError( e );
|
||||
}
|
||||
|
||||
// encode with header
|
||||
String encoded = "data:image/png;base64," + BaseEncoding.base64().encode( imageBytes );
|
||||
|
||||
// check encoded image size
|
||||
if ( encoded.length() > Short.MAX_VALUE )
|
||||
{
|
||||
throw new IllegalArgumentException( "Favicon file too large for server to process" );
|
||||
}
|
||||
|
||||
// create
|
||||
return new Favicon( encoded );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Favicon from an encoded PNG.
|
||||
*
|
||||
* @param encodedString a base64 mime encoded PNG string
|
||||
* @return the created favicon
|
||||
* @deprecated Use #create(java.awt.image.BufferedImage) instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static Favicon create(String encodedString)
|
||||
{
|
||||
return new Favicon( encodedString );
|
||||
}
|
||||
}
|
128
api/src/main/java/net/md_5/bungee/api/ProxyConfig.java
Normal file
128
api/src/main/java/net/md_5/bungee/api/ProxyConfig.java
Normal file
@ -0,0 +1,128 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
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.
|
||||
*
|
||||
* @deprecated This class is subject to rapid change between releases
|
||||
*/
|
||||
@Deprecated
|
||||
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.
|
||||
*
|
||||
* @return online mode
|
||||
*/
|
||||
boolean isOnlineMode();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return ip forward
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isIpForward();
|
||||
|
||||
/**
|
||||
* The encoded favicon.
|
||||
*
|
||||
* @return favicon
|
||||
* @deprecated Use #getFaviconObject instead.
|
||||
*/
|
||||
@Deprecated
|
||||
String getFavicon();
|
||||
|
||||
/**
|
||||
* The favicon used for the server ping list.
|
||||
*
|
||||
* @return favicon
|
||||
*/
|
||||
Favicon getFaviconObject();
|
||||
}
|
@ -1,19 +1,21 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
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;
|
||||
import net.md_5.bungee.api.tab.CustomTabList;
|
||||
|
||||
public abstract class ProxyServer
|
||||
{
|
||||
@ -51,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);
|
||||
@ -78,6 +82,14 @@ public abstract class ProxyServer
|
||||
*/
|
||||
public abstract ProxiedPlayer getPlayer(String name);
|
||||
|
||||
/**
|
||||
* Gets a connected player via their UUID
|
||||
*
|
||||
* @param uuid of the player
|
||||
* @return their player instance
|
||||
*/
|
||||
public abstract ProxiedPlayer getPlayer(UUID uuid);
|
||||
|
||||
/**
|
||||
* Return all servers registered to this proxy, keyed by name. Unlike the
|
||||
* methods in {@link ConfigurationAdapter#getServers()}, this will not
|
||||
@ -139,12 +151,11 @@ public abstract class ProxyServer
|
||||
public abstract void stop();
|
||||
|
||||
/**
|
||||
* Start this instance so that it may accept connections.
|
||||
* Gracefully mark this instance for shutdown.
|
||||
*
|
||||
* @throws Exception any exception thrown during startup causing the
|
||||
* instance to fail to boot
|
||||
* @param reason the reason for stopping. This will be shown to players.
|
||||
*/
|
||||
public abstract void start() throws Exception;
|
||||
public abstract void stop(String reason);
|
||||
|
||||
/**
|
||||
* Register a channel for use with plugin messages. This is required by some
|
||||
@ -173,6 +184,7 @@ public abstract class ProxyServer
|
||||
*
|
||||
* @return the supported Minecraft version
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract String getGameVersion();
|
||||
|
||||
/**
|
||||
@ -180,7 +192,8 @@ public abstract class ProxyServer
|
||||
*
|
||||
* @return the Minecraft protocol version
|
||||
*/
|
||||
public abstract byte getProtocolVersion();
|
||||
@Deprecated
|
||||
public abstract int getProtocolVersion();
|
||||
|
||||
/**
|
||||
* Factory method to construct an implementation specific server info
|
||||
@ -194,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
|
||||
@ -231,15 +256,22 @@ public abstract class ProxyServer
|
||||
*
|
||||
* @param message the message to broadcast
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract void broadcast(String message);
|
||||
|
||||
/**
|
||||
* Gets a new instance of this proxies custom tab list.
|
||||
* Send the specified message to the console and all connected players.
|
||||
*
|
||||
* @param player the player to generate this list in the context of
|
||||
* @return a new {@link CustomTabList} instance
|
||||
* @param message the message to broadcast
|
||||
*/
|
||||
public abstract CustomTabList customTabList(ProxiedPlayer player);
|
||||
public abstract void broadcast(BaseComponent... message);
|
||||
|
||||
/**
|
||||
* Send the specified message to the console and all connected players.
|
||||
*
|
||||
* @param message the message to broadcast
|
||||
*/
|
||||
public abstract void broadcast(BaseComponent message);
|
||||
|
||||
/**
|
||||
* Gets the commands which are disabled and will not be run on this proxy.
|
||||
@ -247,4 +279,36 @@ public abstract class ProxyServer
|
||||
* @return the set of disabled commands
|
||||
*/
|
||||
public abstract Collection<String> getDisabledCommands();
|
||||
|
||||
/**
|
||||
* Gets BungeeCord's core config.
|
||||
*
|
||||
* @return the config.
|
||||
*/
|
||||
public abstract ProxyConfig getConfig();
|
||||
|
||||
/**
|
||||
* Attempts to match any players with the given name, and returns a list of
|
||||
* all possible matches.
|
||||
*
|
||||
* The exact algorithm to use to match players is implementation specific,
|
||||
* but in general you can expect this method to return player's whose names
|
||||
* begin with the specified prefix.
|
||||
*
|
||||
* @param match the (partial) name to match
|
||||
* @return list of all possible players, singleton if there is an exact
|
||||
* match
|
||||
*/
|
||||
public abstract Collection<ProxiedPlayer> matchPlayer(String match);
|
||||
|
||||
/**
|
||||
* Creates a new empty title configuration. In most cases you will want to
|
||||
* {@link Title#reset()} the current title first so your title won't be
|
||||
* affected by a previous one.
|
||||
*
|
||||
* @return A new empty title configuration.
|
||||
* @see Title
|
||||
*/
|
||||
public abstract Title createTitle();
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,33 +1,160 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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
|
||||
{
|
||||
|
||||
/**
|
||||
* Numeric protocol version supported by the server.
|
||||
*/
|
||||
private final byte protocolVersion;
|
||||
/**
|
||||
* Human readable game version.
|
||||
*/
|
||||
private final String gameVersion;
|
||||
/**
|
||||
* Server MOTD.
|
||||
*/
|
||||
private final String motd;
|
||||
/**
|
||||
* Current amount of players on the server.
|
||||
*/
|
||||
private final int currentPlayers;
|
||||
/**
|
||||
* Max amount of players the server will allow.
|
||||
*/
|
||||
private final int maxPlayers;
|
||||
private Protocol version;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class Protocol
|
||||
{
|
||||
|
||||
private String name;
|
||||
private int protocol;
|
||||
}
|
||||
private Players players;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class Players
|
||||
{
|
||||
|
||||
private int max;
|
||||
private int online;
|
||||
private PlayerInfo[] sample;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class PlayerInfo
|
||||
{
|
||||
|
||||
private String name;
|
||||
private UUID uniqueId;
|
||||
|
||||
private static final UUID md5UUID = Util.getUUID( "af74a02d19cb445bb07f6866a861f783" );
|
||||
|
||||
public PlayerInfo(String name, String id)
|
||||
{
|
||||
setName( name );
|
||||
setId( id );
|
||||
}
|
||||
|
||||
public void setId(String id)
|
||||
{
|
||||
try
|
||||
{
|
||||
uniqueId = Util.getUUID( id );
|
||||
} catch ( Exception e )
|
||||
{
|
||||
// Fallback on a valid uuid otherwise Minecraft complains
|
||||
uniqueId = md5UUID;
|
||||
}
|
||||
}
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return uniqueId.toString().replace( "-", "" );
|
||||
}
|
||||
}
|
||||
|
||||
private BaseComponent description;
|
||||
private Favicon favicon;
|
||||
|
||||
@Data
|
||||
public static class ModInfo
|
||||
{
|
||||
|
||||
private String type = "FML";
|
||||
private List<ModItem> modList = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class ModItem
|
||||
{
|
||||
|
||||
private String modid;
|
||||
private String version;
|
||||
}
|
||||
|
||||
// Right now, we don't get the mods from the user, so we just use a stock ModInfo object to
|
||||
// create the server ping. Vanilla clients will ignore this.
|
||||
private final ModInfo modinfo = new ModInfo();
|
||||
|
||||
@Deprecated
|
||||
public ServerPing(Protocol version, Players players, String description, String favicon)
|
||||
{
|
||||
this( version, players, TextComponent.fromLegacy( description ), favicon == null ? null : Favicon.create( favicon ) );
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public ServerPing(Protocol version, Players players, String description, Favicon favicon)
|
||||
{
|
||||
this( version, players, TextComponent.fromLegacy( description ), favicon );
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String getFavicon()
|
||||
{
|
||||
return getFaviconObject() == null ? null : getFaviconObject().getEncoded();
|
||||
}
|
||||
|
||||
public Favicon getFaviconObject()
|
||||
{
|
||||
return this.favicon;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setFavicon(String favicon)
|
||||
{
|
||||
setFavicon( favicon == null ? null : Favicon.create( favicon ) );
|
||||
}
|
||||
|
||||
public void setFavicon(Favicon favicon)
|
||||
{
|
||||
this.favicon = favicon;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setDescription(String description)
|
||||
{
|
||||
this.description = TextComponent.fromLegacy( 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();
|
||||
|
||||
}
|
106
api/src/main/java/net/md_5/bungee/api/Title.java
Normal file
106
api/src/main/java/net/md_5/bungee/api/Title.java
Normal file
@ -0,0 +1,106 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
/**
|
||||
* Represents a configuration of a title. A title in Minecraft consists of a
|
||||
* main title and a sub title. It will {@link #fadeIn(int)}, {@link #stay(int)},
|
||||
* and {@link #fadeOut(int)} for a specified amount of time. In most cases you
|
||||
* will want to {@link #reset()} the current title first so your title won't be
|
||||
* affected by a previous one.
|
||||
* <p>
|
||||
* You can create a new configuration by calling
|
||||
* {@link ProxyServer#createTitle()}.
|
||||
*/
|
||||
public interface Title
|
||||
{
|
||||
|
||||
/**
|
||||
* Set the title to send to the player.
|
||||
*
|
||||
* @param text The text to use as the title.
|
||||
* @return This title configuration.
|
||||
*/
|
||||
public Title title(BaseComponent text);
|
||||
|
||||
/**
|
||||
* Set the title to send to the player.
|
||||
*
|
||||
* @param text The text to use as the title.
|
||||
* @return This title configuration.
|
||||
*/
|
||||
public Title title(BaseComponent... text);
|
||||
|
||||
/**
|
||||
* Set the subtitle to send to the player.
|
||||
*
|
||||
* @param text The text to use as the subtitle.
|
||||
* @return This title configuration.
|
||||
*/
|
||||
public Title subTitle(BaseComponent text);
|
||||
|
||||
/**
|
||||
* Set the subtitle to send to the player.
|
||||
*
|
||||
* @param text The text to use as the subtitle.
|
||||
* @return This title configuration.
|
||||
*/
|
||||
public Title subTitle(BaseComponent... text);
|
||||
|
||||
/**
|
||||
* Set the duration in ticks of the fade in effect of the title. Once this
|
||||
* period of time is over the title will stay for the amount of time
|
||||
* specified in {@link #stay(int)}. The default value for the official
|
||||
* Minecraft version is 20 (1 second).
|
||||
*
|
||||
* @param ticks The amount of ticks (1/20 second) for the fade in effect.
|
||||
* @return This title configuration.
|
||||
*/
|
||||
public Title fadeIn(int ticks);
|
||||
|
||||
/**
|
||||
* Set the duration in ticks how long the title should stay on the screen.
|
||||
* Once this period of time is over the title will fade out using the
|
||||
* 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 stay effect.
|
||||
* @return This title configuration.
|
||||
*/
|
||||
public Title stay(int ticks);
|
||||
|
||||
/**
|
||||
* Set the duration in ticks of the fade out effect of the title. The
|
||||
* default value for the official Minecraft version is 20 (1 second).
|
||||
*
|
||||
* @param ticks The amount of ticks (1/20 second) for the fade out effect.
|
||||
* @return This title configuration.
|
||||
*/
|
||||
public Title fadeOut(int ticks);
|
||||
|
||||
/**
|
||||
* Remove the currently displayed title from the player's screen. This will
|
||||
* keep the currently used display times and will only remove the title.
|
||||
*
|
||||
* @return This title configuration.
|
||||
*/
|
||||
public Title clear();
|
||||
|
||||
/**
|
||||
* Remove the currently displayed title from the player's screen and set the
|
||||
* configuration back to the default values.
|
||||
*
|
||||
* @return This title configuration.
|
||||
*/
|
||||
public Title reset();
|
||||
|
||||
/**
|
||||
* Send this title configuration to the specified player. This is the same
|
||||
* as calling {@link ProxiedPlayer#sendTitle(Title)}.
|
||||
*
|
||||
* @param player The player to send the title to.
|
||||
* @return This title configuration.
|
||||
*/
|
||||
public Title send(ProxiedPlayer player);
|
||||
}
|
@ -1,22 +1,25 @@
|
||||
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;
|
||||
import net.md_5.bungee.api.tab.TabListHandler;
|
||||
|
||||
/**
|
||||
* Class representing the configuration of a server listener. Used for allowing
|
||||
* multiple listeners on different ports.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class ListenerInfo
|
||||
{
|
||||
|
||||
/**
|
||||
* Host to bind to.
|
||||
*/
|
||||
private final InetSocketAddress host;
|
||||
private final SocketAddress socketAddress;
|
||||
/**
|
||||
* Displayed MOTD.
|
||||
*/
|
||||
@ -30,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.
|
||||
@ -49,9 +48,9 @@ public class ListenerInfo
|
||||
*/
|
||||
private final Map<String, String> forcedHosts;
|
||||
/**
|
||||
* Class used to build tab lists for this player.
|
||||
* The type of tab list to use
|
||||
*/
|
||||
private final Class<? extends TabListHandler> tabList;
|
||||
private final String tabListType;
|
||||
/**
|
||||
* Whether to set the local address when connecting to servers.
|
||||
*/
|
||||
@ -69,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.
|
||||
@ -53,13 +80,35 @@ public interface ServerInfo
|
||||
boolean canAccess(CommandSender sender);
|
||||
|
||||
/**
|
||||
* Send data by any available means to this server.
|
||||
* 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
|
||||
*/
|
||||
void sendData(String channel, byte[] data);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* immediately.
|
||||
* @return <code>true</code> if the message was sent immediately,
|
||||
* <code>false</code> otherwise if queue is true, it has been queued, if it
|
||||
* is false it has been discarded.
|
||||
*/
|
||||
boolean sendData(String channel, byte[] data, boolean queue);
|
||||
|
||||
/**
|
||||
* Asynchronously gets the current player count on this server.
|
||||
*
|
||||
|
@ -1,7 +1,9 @@
|
||||
package net.md_5.bungee.api.connection;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import net.md_5.bungee.protocol.packet.DefinedPacket;
|
||||
import java.net.SocketAddress;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
|
||||
/**
|
||||
* A proxy connection is defined as a connection directly connected to a socket.
|
||||
@ -15,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
|
||||
@ -26,8 +37,37 @@ public interface Connection
|
||||
* @param reason the reason shown to the player / sent to the server on
|
||||
* disconnect
|
||||
*/
|
||||
@Deprecated
|
||||
void disconnect(String reason);
|
||||
|
||||
/**
|
||||
* Disconnects this end of the connection for the specified reason. If this
|
||||
* is an {@link ProxiedPlayer} the respective server connection will be
|
||||
* closed too.
|
||||
*
|
||||
* @param reason the reason shown to the player / sent to the server on
|
||||
* disconnect
|
||||
*/
|
||||
void disconnect(BaseComponent... reason);
|
||||
|
||||
/**
|
||||
* Disconnects this end of the connection for the specified reason. If this
|
||||
* is an {@link ProxiedPlayer} the respective server connection will be
|
||||
* closed too.
|
||||
*
|
||||
* @param reason the reason shown to the player / sent to the server on
|
||||
* disconnect
|
||||
*/
|
||||
void disconnect(BaseComponent reason);
|
||||
|
||||
/**
|
||||
* Gets whether this connection is currently open, ie: not disconnected, and
|
||||
* able to send / receive data.
|
||||
*
|
||||
* @return current connection status
|
||||
*/
|
||||
boolean isConnected();
|
||||
|
||||
/**
|
||||
* Get the unsafe methods of this class.
|
||||
*
|
||||
|
@ -1,7 +1,10 @@
|
||||
package net.md_5.bungee.api.connection;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Represents a user attempting to log into the proxy.
|
||||
@ -21,7 +24,7 @@ public interface PendingConnection extends Connection
|
||||
*
|
||||
* @return the protocol version of the remote client
|
||||
*/
|
||||
byte getVersion();
|
||||
int getVersion();
|
||||
|
||||
/**
|
||||
* Get the requested virtual host that the client tried to connect to.
|
||||
@ -36,4 +39,92 @@ public interface PendingConnection extends Connection
|
||||
* @return the accepting listener
|
||||
*/
|
||||
ListenerInfo getListener();
|
||||
|
||||
/**
|
||||
* Get this connection's UUID, if set.
|
||||
*
|
||||
* @return the UUID
|
||||
* @deprecated In favour of {@link #getUniqueId()}
|
||||
*/
|
||||
@Deprecated
|
||||
String getUUID();
|
||||
|
||||
/**
|
||||
* Get this connection's UUID, if set.
|
||||
*
|
||||
* @return the UUID
|
||||
*/
|
||||
UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
boolean isOnlineMode();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Check if the client is using the older unsupported Minecraft protocol
|
||||
* used by Minecraft clients older than 1.7.
|
||||
*
|
||||
* @return Whether the client is using a legacy client.
|
||||
*/
|
||||
boolean isLegacy();
|
||||
|
||||
/**
|
||||
* Gets if this connection has been transferred from another server.
|
||||
*
|
||||
* @return true if the connection has been transferred
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
boolean isTransferred();
|
||||
|
||||
/**
|
||||
* Retrieves a cookie from this pending connection.
|
||||
*
|
||||
* @param cookie the resource location of the cookie, for example
|
||||
* "bungeecord:my_cookie"
|
||||
* @return a {@link CompletableFuture} that will be completed when the
|
||||
* Cookie response is received. If the cookie is not set in the client, the
|
||||
* {@link CompletableFuture} will complete with a null value
|
||||
* @throws IllegalStateException if the player's version is not at least
|
||||
* 1.20.5
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
CompletableFuture<byte[]> retrieveCookie(String cookie);
|
||||
|
||||
/**
|
||||
* Sends a login payload request to the client.
|
||||
*
|
||||
* @param channel the channel to send this data via
|
||||
* @param data the data to send
|
||||
* @return a {@link CompletableFuture} that will be completed when the Login
|
||||
* Payload response is received. If the Vanilla client doesn't know the
|
||||
* channel, the {@link CompletableFuture} will complete with a null value
|
||||
* @throws IllegalStateException if the player's version is not at least
|
||||
* 1.13
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
CompletableFuture<byte[]> sendData(String channel, byte[] data);
|
||||
}
|
||||
|
@ -1,16 +1,56 @@
|
||||
package net.md_5.bungee.api.connection;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
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.tab.TabListHandler;
|
||||
import net.md_5.bungee.api.event.ServerConnectEvent;
|
||||
import net.md_5.bungee.api.score.Scoreboard;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@ -19,13 +59,44 @@ 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
|
||||
*/
|
||||
void setDisplayName(String name);
|
||||
|
||||
/**
|
||||
* Send a message to the specified screen position of this player.
|
||||
*
|
||||
* @param position the screen position
|
||||
* @param message the message to send
|
||||
*/
|
||||
public void sendMessage(ChatMessageType position, BaseComponent... message);
|
||||
|
||||
/**
|
||||
* Send a message to the specified screen position of this player.
|
||||
*
|
||||
* @param position the screen position
|
||||
* @param message the message to send
|
||||
*/
|
||||
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
|
||||
@ -35,6 +106,50 @@ 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
|
||||
* 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).
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@ -52,6 +167,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
|
||||
*/
|
||||
@ -71,21 +190,6 @@ public interface ProxiedPlayer extends Connection, CommandSender
|
||||
*/
|
||||
void chat(String message);
|
||||
|
||||
/**
|
||||
* Sets the new tab list for the user. At this stage it is not advisable to
|
||||
* change after the user has logged in!
|
||||
*
|
||||
* @param list the new list
|
||||
*/
|
||||
void setTabList(TabListHandler list);
|
||||
|
||||
/**
|
||||
* Get the current tab list.
|
||||
*
|
||||
* @return the tab list in use by this user
|
||||
*/
|
||||
TabListHandler getTabList();
|
||||
|
||||
/**
|
||||
* Get the server which this player will be sent to next time the log in.
|
||||
*
|
||||
@ -99,4 +203,183 @@ public interface ProxiedPlayer extends Connection, CommandSender
|
||||
* @param server the server to set
|
||||
*/
|
||||
void setReconnectServer(ServerInfo server);
|
||||
|
||||
/**
|
||||
* Get this connection's UUID, if set.
|
||||
*
|
||||
* @return the UUID
|
||||
* @deprecated In favour of {@link #getUniqueId()}
|
||||
*/
|
||||
@Deprecated
|
||||
String getUUID();
|
||||
|
||||
/**
|
||||
* Get this connection's UUID, if set.
|
||||
*
|
||||
* @return the UUID
|
||||
*/
|
||||
UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* Gets this player's locale.
|
||||
*
|
||||
* @return the locale
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param header The header for the tab player list, null to clear it.
|
||||
* @param footer The footer for the tab player list, null to clear it.
|
||||
*/
|
||||
void setTabHeader(BaseComponent header, BaseComponent footer);
|
||||
|
||||
/**
|
||||
* Set the header and footer displayed in the tab player list.
|
||||
*
|
||||
* @param header The header for the tab player list, null to clear it.
|
||||
* @param footer The footer for the tab player list, null to clear it.
|
||||
*/
|
||||
void setTabHeader(BaseComponent[] header, BaseComponent[] footer);
|
||||
|
||||
/**
|
||||
* Clears the header and footer displayed in the tab player list.
|
||||
*/
|
||||
void resetTabHeader();
|
||||
|
||||
/**
|
||||
* Sends a {@link Title} to this player. This is the same as calling
|
||||
* {@link Title#send(ProxiedPlayer)}.
|
||||
*
|
||||
* @param title The title to send to the player.
|
||||
* @see Title
|
||||
*/
|
||||
void sendTitle(Title title);
|
||||
|
||||
/**
|
||||
* Gets whether this player is using a FML client.
|
||||
* <p>
|
||||
* This method is only reliable if BungeeCord links Minecraft 1.8 servers
|
||||
* together, as Bungee can pick up whether a user is a Forge user with the
|
||||
* initial handshake. If this is used for a 1.7 network, this might return
|
||||
* <code>false</code> even if the user is a FML user, as Bungee can only
|
||||
* determine this information if a handshake successfully completes.
|
||||
* </p>
|
||||
*
|
||||
* @return <code>true</code> if it is known that the user is using a FML
|
||||
* client, <code>false</code> otherwise.
|
||||
*/
|
||||
boolean isForgeUser();
|
||||
|
||||
/**
|
||||
* Gets this player's Forge Mod List, if the player has sent this
|
||||
* information during the lifetime of their connection to Bungee. There is
|
||||
* no guarantee that information is available at any time, as it is only
|
||||
* sent during a FML handshake. Therefore, this will only contain
|
||||
* information for a user that has attempted joined a Forge server.
|
||||
* <p>
|
||||
* Consumers of this API should be aware that an empty mod list does
|
||||
* <em>not</em> indicate that a user is not a Forge user, and so should not
|
||||
* use this API to check for this. See the {@link #isForgeUser()
|
||||
* isForgeUser} method instead.
|
||||
* </p>
|
||||
* <p>
|
||||
* Calling this when handling a
|
||||
* {@link net.md_5.bungee.api.event.ServerConnectedEvent} may be the best
|
||||
* place to do so as this event occurs after a FML handshake has completed,
|
||||
* if any has occurred.
|
||||
* </p>
|
||||
*
|
||||
* @return A {@link Map} of mods, where the key is the name of the mod, and
|
||||
* the value is the version. Returns an empty list if the FML handshake has
|
||||
* 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}
|
||||
* @deprecated for internal use only, setters will not have the expected
|
||||
* effect, will not update client state, and may corrupt proxy state
|
||||
*/
|
||||
@Deprecated
|
||||
Scoreboard getScoreboard();
|
||||
|
||||
/**
|
||||
* Retrieves a cookie from this player.
|
||||
*
|
||||
* @param cookie the resource location of the cookie, for example
|
||||
* "bungeecord:my_cookie"
|
||||
* @return a {@link CompletableFuture} that will be completed when the
|
||||
* Cookie response is received. If the cookie is not set in the client, the
|
||||
* {@link CompletableFuture} will complete with a null value
|
||||
* @throws IllegalStateException if the player's version is not at least
|
||||
* 1.20.5
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
CompletableFuture<byte[]> retrieveCookie(String cookie);
|
||||
|
||||
/**
|
||||
* Stores a cookie in this player's client.
|
||||
*
|
||||
* @param cookie the resource location of the cookie, for example
|
||||
* "bungeecord:my_cookie"
|
||||
* @param data the data to store in the cookie
|
||||
* @throws IllegalStateException if the player's version is not at least
|
||||
* 1.20.5
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
void storeCookie(String cookie, byte[] data);
|
||||
|
||||
/**
|
||||
* Requests this player to connect to a different server specified by host
|
||||
* and port.
|
||||
*
|
||||
* This is a client-side transfer - host and port should not specify a
|
||||
* BungeeCord backend server.
|
||||
*
|
||||
* @param host the host of the server to transfer to
|
||||
* @param port the port of the server to transfer to
|
||||
* @throws IllegalStateException if the players version is not at least
|
||||
* 1.20.5
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
void transfer(String host, int port);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
@ -33,43 +35,59 @@ public class AsyncEvent<T> extends Event
|
||||
@SuppressWarnings("unchecked")
|
||||
public void postCall()
|
||||
{
|
||||
fired.set( true );
|
||||
if ( latch.get() == 0 )
|
||||
{
|
||||
done.done( (T) this, null );
|
||||
}
|
||||
fired.set( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
if ( latch.decrementAndGet() == 0 && fired.get() )
|
||||
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() )
|
||||
{
|
||||
done.done( (T) this, null );
|
||||
if ( latch.decrementAndGet() == 0 )
|
||||
{
|
||||
done.done( (T) this, null );
|
||||
}
|
||||
} else
|
||||
{
|
||||
latch.decrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,14 @@ 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, or a server sends a
|
||||
* message to a player.
|
||||
* Event called when a player sends a message to a server.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@ -40,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;
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.ArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.RootCommandNode;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.connection.Connection;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.plugin.PluginManager;
|
||||
import net.md_5.bungee.api.plugin.TabExecutor;
|
||||
|
||||
/**
|
||||
* Event called when a downstream server (on 1.13+) sends the command structure
|
||||
* to a player, but before BungeeCord adds the dummy command nodes of
|
||||
* registered commands.
|
||||
* <p>
|
||||
* BungeeCord will not overwrite the modifications made by the listeners.
|
||||
*
|
||||
* <h2>Usage example</h2>
|
||||
* Here is a usage example of this event, to declare a command structure.
|
||||
* This illustrates the commands /server and /send of Bungee.
|
||||
* <pre>
|
||||
* event.getRoot().addChild( LiteralArgumentBuilder.<CommandSender>literal( "server" )
|
||||
* .requires( sender -> sender.hasPermission( "bungeecord.command.server" ) )
|
||||
* .executes( a -> 0 )
|
||||
* .then( RequiredArgumentBuilder.argument( "serverName", StringArgumentType.greedyString() )
|
||||
* .suggests( SuggestionRegistry.ASK_SERVER )
|
||||
* )
|
||||
* .build()
|
||||
* );
|
||||
* event.getRoot().addChild( LiteralArgumentBuilder.<CommandSender>literal( "send" )
|
||||
* .requires( sender -> sender.hasPermission( "bungeecord.command.send" ) )
|
||||
* .then( RequiredArgumentBuilder.argument( "playerName", StringArgumentType.word() )
|
||||
* .suggests( SuggestionRegistry.ASK_SERVER )
|
||||
* .then( RequiredArgumentBuilder.argument( "serverName", StringArgumentType.greedyString() )
|
||||
* .suggests( SuggestionRegistry.ASK_SERVER )
|
||||
* )
|
||||
* )
|
||||
* .build()
|
||||
* );
|
||||
* </pre>
|
||||
*
|
||||
* <h2>Flag a {@link CommandNode} as executable or not</h2>
|
||||
* The implementation of a {@link com.mojang.brigadier.Command Command} used in
|
||||
* {@link ArgumentBuilder#executes(com.mojang.brigadier.Command)} will never be
|
||||
* executed. This will only tell to the client if the current node is
|
||||
* executable or not.
|
||||
* <ul>
|
||||
* <li>
|
||||
* {@code builder.executes(null)} (default) to mark the node as not
|
||||
* executable.
|
||||
* </li>
|
||||
* <li>
|
||||
* {@code builder.executes(a -> 0)}, or any non null argument, to mark
|
||||
* the node as executable (the child arguments are displayed as
|
||||
* optional).
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>{@link CommandNode}’s suggestions management</h2>
|
||||
* The implementation of a SuggestionProvider used in
|
||||
* {@link RequiredArgumentBuilder#suggests(SuggestionProvider)} will never be
|
||||
* executed. This will only tell to the client how to deal with the
|
||||
* auto-completion of the argument.
|
||||
* <ul>
|
||||
* <li>
|
||||
* {@code builder.suggests(null)} (default) to disable auto-completion
|
||||
* for this argument.
|
||||
* </li>
|
||||
* <li>
|
||||
* {@code builder.suggests(SuggestionRegistry.ALL_RECIPES)} to suggest
|
||||
* Minecraft’s recipes.
|
||||
* </li>
|
||||
* <li>
|
||||
* {@code builder.suggests(SuggestionRegistry.AVAILABLE_SOUNDS)} to
|
||||
* suggest Minecraft’s default sound identifiers.
|
||||
* </li>
|
||||
* <li>
|
||||
* {@code builder.suggests(SuggestionRegistry.SUMMONABLE_ENTITIES)} to
|
||||
* suggest Minecraft’s default summonable entities identifiers.
|
||||
* </li>
|
||||
* <li>
|
||||
* {@code builder.suggests(SuggestionRegistry.ASK_SERVER)}, or any
|
||||
* other non null argument, to make the Minecraft client ask
|
||||
* auto-completion to the server. Any specified implementation of
|
||||
* {@link SuggestionProvider} will never be executed.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Argument types</h2>
|
||||
* When building a new argument command node using
|
||||
* {@link RequiredArgumentBuilder#argument(String, ArgumentType)}, you have to
|
||||
* specify an {@link ArgumentType}. You can use all subclasses of
|
||||
* {@link ArgumentType} provided with brigadier (for instance,
|
||||
* {@link StringArgumentType} or {@link IntegerArgumentType}), or call any
|
||||
* {@code ArgumentRegistry.minecraft*()} methods to use a {@code minecraft:*}
|
||||
* argument type.
|
||||
*
|
||||
* <h2>Limitations with brigadier API</h2>
|
||||
* This event is only used for the client to show command syntax, suggest
|
||||
* sub-commands and color the arguments in the chat box. The command execution
|
||||
* needs to be implemented using {@link PluginManager#registerCommand(Plugin,
|
||||
* Command)} and the server-side tab-completion using {@link TabCompleteEvent}
|
||||
* or {@link TabExecutor}.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class CommandsDeclareEvent extends TargetedEvent
|
||||
{
|
||||
/**
|
||||
* Wether or not the command tree is modified by this event.
|
||||
*
|
||||
* If this value is set to true, BungeeCord will ensure that the
|
||||
* modifications made in the command tree, will be sent to the player.
|
||||
* If this is false, the modifications may not be taken into account.
|
||||
*
|
||||
* When calling {@link #getRoot()}, this value is automatically set
|
||||
* to true.
|
||||
*/
|
||||
@Setter(value = AccessLevel.NONE)
|
||||
private boolean modified = false;
|
||||
|
||||
/**
|
||||
* The root command node of the command structure that will be send to the
|
||||
* player.
|
||||
*/
|
||||
private final RootCommandNode<CommandSender> root;
|
||||
|
||||
public CommandsDeclareEvent(Connection sender, Connection receiver, RootCommandNode<CommandSender> root)
|
||||
{
|
||||
super( sender, receiver );
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
/**
|
||||
* The root command node of the command structure that will be send to the
|
||||
* player.
|
||||
* @return The root command node
|
||||
*/
|
||||
public RootCommandNode<CommandSender> getRoot()
|
||||
{
|
||||
modified = true;
|
||||
return root;
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
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 +25,7 @@ public class LoginEvent extends AsyncEvent<LoginEvent> implements Cancellable
|
||||
/**
|
||||
* Message to use when kicking if this event is canceled.
|
||||
*/
|
||||
private String cancelReason;
|
||||
private BaseComponent reason;
|
||||
/**
|
||||
* Connection attempting to login.
|
||||
*/
|
||||
@ -34,4 +36,47 @@ 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 TextComponent.toLegacyText( getReason() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cancelReason reason to be displayed
|
||||
* @deprecated use component methods instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCancelReason(String cancelReason)
|
||||
{
|
||||
setReason( TextComponent.fromLegacy( cancelReason ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return reason to be displayed
|
||||
* @deprecated use single component methods instead
|
||||
*/
|
||||
@Deprecated
|
||||
public BaseComponent[] getCancelReasonComponents()
|
||||
{
|
||||
return new BaseComponent[]
|
||||
{
|
||||
getReason()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cancelReason reason to be displayed
|
||||
* @deprecated use single component methods instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCancelReason(BaseComponent... cancelReason)
|
||||
{
|
||||
setReason( TextComponent.fromArray( cancelReason ) );
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.protocol.packet.Packet2Handshake;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
import net.md_5.bungee.protocol.packet.Handshake;
|
||||
|
||||
/**
|
||||
* Event called to represent a player first making their presence and username
|
||||
@ -24,9 +24,9 @@ public class PlayerHandshakeEvent extends Event
|
||||
/**
|
||||
* The handshake.
|
||||
*/
|
||||
private final Packet2Handshake handshake;
|
||||
private final Handshake handshake;
|
||||
|
||||
public PlayerHandshakeEvent(PendingConnection connection, Packet2Handshake handshake)
|
||||
public PlayerHandshakeEvent(PendingConnection connection, Handshake handshake)
|
||||
{
|
||||
this.connection = connection;
|
||||
this.handshake = handshake;
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -3,21 +3,33 @@ package net.md_5.bungee.api.event;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.Callback;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* Event called as soon as a connection has an {@link ProxiedPlayer} and is
|
||||
* ready to be connected to a server.
|
||||
* Event called as soon as a connection has a {@link ProxiedPlayer} and is ready
|
||||
* to be connected to a server.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = false)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class PostLoginEvent extends Event
|
||||
public class PostLoginEvent extends AsyncEvent<PostLoginEvent>
|
||||
{
|
||||
|
||||
/**
|
||||
* The player involved with this event.
|
||||
*/
|
||||
private final ProxiedPlayer player;
|
||||
/**
|
||||
* The server to which the player will initially be connected.
|
||||
*/
|
||||
private ServerInfo target;
|
||||
|
||||
public PostLoginEvent(ProxiedPlayer player, ServerInfo target, Callback<PostLoginEvent> done)
|
||||
{
|
||||
super( done );
|
||||
this.player = player;
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Event called to represent a player first making their presence and username
|
||||
* known.
|
||||
*
|
||||
* This will NOT contain many attributes relating to the player which are filled
|
||||
* in after authentication with Mojang's servers. Examples of attributes which
|
||||
* are not available include their UUID.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = false)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class PreLoginEvent extends AsyncEvent<PreLoginEvent> implements Cancellable
|
||||
{
|
||||
|
||||
/**
|
||||
* Cancelled state.
|
||||
*/
|
||||
private boolean cancelled;
|
||||
/**
|
||||
* Message to use when kicking if this event is canceled.
|
||||
*/
|
||||
private BaseComponent reason;
|
||||
/**
|
||||
* Connection attempting to login.
|
||||
*/
|
||||
private final PendingConnection connection;
|
||||
|
||||
public PreLoginEvent(PendingConnection connection, Callback<PreLoginEvent> done)
|
||||
{
|
||||
super( done );
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return reason to be displayed
|
||||
* @deprecated use component methods instead
|
||||
*/
|
||||
@Deprecated
|
||||
public String getCancelReason()
|
||||
{
|
||||
return BaseComponent.toLegacyText( getReason() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cancelReason reason to be displayed
|
||||
* @deprecated Use component methods instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCancelReason(String cancelReason)
|
||||
{
|
||||
setReason( TextComponent.fromLegacy( cancelReason ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return reason to be displayed
|
||||
* @deprecated use single component methods instead
|
||||
*/
|
||||
@Deprecated
|
||||
public BaseComponent[] getCancelReasonComponents()
|
||||
{
|
||||
return new BaseComponent[]
|
||||
{
|
||||
getReason()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cancelReason reason to be displayed
|
||||
* @deprecated use single component methods instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCancelReason(BaseComponent... cancelReason)
|
||||
{
|
||||
setReason( TextComponent.fromArray( cancelReason ) );
|
||||
}
|
||||
}
|
@ -1,21 +1,19 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
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
|
||||
@AllArgsConstructor
|
||||
@ToString(callSuper = false)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class ProxyPingEvent extends Event
|
||||
public class ProxyPingEvent extends AsyncEvent<ProxyPingEvent>
|
||||
{
|
||||
|
||||
/**
|
||||
@ -26,4 +24,11 @@ public class ProxyPingEvent extends Event
|
||||
* The data to respond with.
|
||||
*/
|
||||
private ServerPing response;
|
||||
|
||||
public ProxyPingEvent(PendingConnection connection, ServerPing response, Callback<ProxyPingEvent> done)
|
||||
{
|
||||
super( done );
|
||||
this.connection = connection;
|
||||
this.response = response;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* Called when somebody reloads BungeeCord
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class ProxyReloadEvent extends Event
|
||||
{
|
||||
|
||||
/**
|
||||
* Creator of the action.
|
||||
*/
|
||||
private final CommandSender sender;
|
||||
}
|
@ -4,11 +4,18 @@ 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;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* Called when deciding to connect to a server. At the time when this event is
|
||||
* called, no connection has actually been made. Cancelling the event will
|
||||
* ensure that the connection does not proceed and can be useful to prevent
|
||||
* certain players from accessing certain servers.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = false)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@ -24,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
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NonNull;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class ServerDisconnectEvent extends Event
|
||||
{
|
||||
|
||||
/**
|
||||
* Player disconnecting from a server.
|
||||
*/
|
||||
@NonNull
|
||||
private final ProxiedPlayer player;
|
||||
/**
|
||||
* Server the player is disconnecting from.
|
||||
*/
|
||||
@NonNull
|
||||
private final ServerInfo target;
|
||||
}
|
@ -3,6 +3,8 @@ package net.md_5.bungee.api.event;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
@ -25,10 +27,15 @@ public class ServerKickEvent extends Event implements Cancellable
|
||||
* Player being kicked.
|
||||
*/
|
||||
private final ProxiedPlayer player;
|
||||
/**
|
||||
* The server the player was kicked from, should be used in preference to
|
||||
* {@link ProxiedPlayer#getServer()}.
|
||||
*/
|
||||
private final ServerInfo kickedFrom;
|
||||
/**
|
||||
* Kick reason.
|
||||
*/
|
||||
private String kickReason;
|
||||
private BaseComponent reason;
|
||||
/**
|
||||
* Server to send player to if this event is cancelled.
|
||||
*/
|
||||
@ -44,16 +51,73 @@ public class ServerKickEvent extends Event implements Cancellable
|
||||
CONNECTING, CONNECTED, UNKNOWN;
|
||||
}
|
||||
|
||||
public ServerKickEvent(ProxiedPlayer player, String kickReason, ServerInfo cancelServer)
|
||||
@Deprecated
|
||||
public ServerKickEvent(ProxiedPlayer player, BaseComponent[] kickReasonComponent, ServerInfo cancelServer)
|
||||
{
|
||||
this( player, kickReason, cancelServer, State.UNKNOWN );
|
||||
this( player, kickReasonComponent, cancelServer, State.UNKNOWN );
|
||||
}
|
||||
|
||||
public ServerKickEvent(ProxiedPlayer player, String kickReason, ServerInfo cancelServer, State state)
|
||||
@Deprecated
|
||||
public ServerKickEvent(ProxiedPlayer player, BaseComponent[] kickReasonComponent, ServerInfo cancelServer, State state)
|
||||
{
|
||||
this( player, player.getServer().getInfo(), kickReasonComponent, cancelServer, state );
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public ServerKickEvent(ProxiedPlayer player, ServerInfo kickedFrom, BaseComponent[] kickReasonComponent, ServerInfo cancelServer, State state)
|
||||
{
|
||||
this( player, kickedFrom, TextComponent.fromArray( kickReasonComponent ), cancelServer, state );
|
||||
}
|
||||
|
||||
public ServerKickEvent(ProxiedPlayer player, ServerInfo kickedFrom, BaseComponent reason, ServerInfo cancelServer, State state)
|
||||
{
|
||||
this.player = player;
|
||||
this.kickReason = kickReason;
|
||||
this.kickedFrom = kickedFrom;
|
||||
this.reason = reason;
|
||||
this.cancelServer = cancelServer;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the kick reason
|
||||
* @deprecated use component methods instead
|
||||
*/
|
||||
@Deprecated
|
||||
public String getKickReason()
|
||||
{
|
||||
return BaseComponent.toLegacyText( getReason() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param reason the kick reason
|
||||
* @deprecated use component methods instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setKickReason(String reason)
|
||||
{
|
||||
this.setReason( TextComponent.fromLegacy( reason ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the kick reason
|
||||
* @deprecated use single component methods instead
|
||||
*/
|
||||
@Deprecated
|
||||
public BaseComponent[] getKickReasonComponent()
|
||||
{
|
||||
return new BaseComponent[]
|
||||
{
|
||||
getReason()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param kickReasonComponent the kick reason
|
||||
* @deprecated use single component methods instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setKickReasonComponent(BaseComponent[] kickReasonComponent)
|
||||
{
|
||||
this.setReason( TextComponent.fromArray( kickReasonComponent ) );
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Event called when a player uses tab completion.
|
||||
* @deprecated please use {@link TabCompleteRequestEvent} to support 1.13+ suggestions.
|
||||
*/
|
||||
@Deprecated
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TabCompleteEvent extends TargetedEvent implements Cancellable
|
||||
{
|
||||
|
||||
/**
|
||||
* Cancelled state.
|
||||
*/
|
||||
private boolean cancelled;
|
||||
/**
|
||||
* The message the player has already entered.
|
||||
*/
|
||||
private final String cursor;
|
||||
/**
|
||||
* The suggestions that will be sent to the client. This list is mutable. If
|
||||
* this list is empty, the request will be forwarded to the server.
|
||||
*/
|
||||
private final List<String> suggestions;
|
||||
|
||||
public TabCompleteEvent(Connection sender, Connection receiver, String cursor, List<String> suggestions)
|
||||
{
|
||||
super( sender, receiver );
|
||||
this.cursor = cursor;
|
||||
this.suggestions = suggestions;
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.mojang.brigadier.context.StringRange;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.connection.Connection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
import net.md_5.bungee.protocol.ProtocolConstants;
|
||||
|
||||
/**
|
||||
* Event called when a player uses tab completion.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TabCompleteRequestEvent extends TargetedEvent implements Cancellable
|
||||
{
|
||||
|
||||
/**
|
||||
* Cancelled state.
|
||||
*/
|
||||
private boolean cancelled;
|
||||
/**
|
||||
* The message the player has already entered.
|
||||
*/
|
||||
private final String cursor;
|
||||
/**
|
||||
* Range corresponding to the last word of {@link #getCursor()}.
|
||||
* If you want your suggestions to be compatible with 1.12 and older
|
||||
* clients, you need to {@link #setSuggestions(Suggestions)} with
|
||||
* a range equals to this one.
|
||||
* For 1.13 and newer clients, any other range that cover any part of
|
||||
* {@link #getCursor()} is fine.<br>
|
||||
* To check if the client supports custom ranges, use
|
||||
* {@link #supportsCustomRange()}.
|
||||
*/
|
||||
private final StringRange legacyCompatibleRange;
|
||||
/**
|
||||
* The suggestions that will be sent to the client. If this list is empty,
|
||||
* the request will be forwarded to the server.
|
||||
*/
|
||||
private Suggestions suggestions;
|
||||
|
||||
public TabCompleteRequestEvent(Connection sender, Connection receiver, String cursor, StringRange legacyCompatibleRange, Suggestions suggestions)
|
||||
{
|
||||
super( sender, receiver );
|
||||
this.cursor = cursor;
|
||||
this.legacyCompatibleRange = legacyCompatibleRange;
|
||||
this.suggestions = suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suggestions that will be sent to the client.
|
||||
* If this list is empty, the request will be forwarded to the server.
|
||||
* @param suggestions the new Suggestions. Cannot be null.
|
||||
* @throws IllegalArgumentException if the client is on 1.12 or lower and
|
||||
* {@code suggestions.getRange()} is not equals to {@link #legacyCompatibleRange}.
|
||||
*/
|
||||
public void setSuggestions(Suggestions suggestions)
|
||||
{
|
||||
Preconditions.checkNotNull( suggestions );
|
||||
Preconditions.checkArgument( supportsCustomRange() || legacyCompatibleRange.equals( suggestions.getRange() ),
|
||||
"Clients on 1.12 or lower versions don't support the provided range for tab-completion: " + suggestions.getRange()
|
||||
+ ". Please use TabCompleteRequestEvent.getLegacyCompatibleRange() for legacy clients." );
|
||||
this.suggestions = suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to tell if the client supports custom range for
|
||||
* suggestions.
|
||||
* If the client is on 1.13 or above, this methods returns true, and any
|
||||
* range can be used for {@link #setSuggestions(Suggestions)}. Otherwise,
|
||||
* it returns false and the defined range must be equals to
|
||||
* {@link #legacyCompatibleRange}.
|
||||
* @return true if the client is on 1.13 or newer version, false otherwise.
|
||||
*/
|
||||
public boolean supportsCustomRange()
|
||||
{
|
||||
return ( (ProxiedPlayer) getSender() ).getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_1_13;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Event called when a backend server sends a response to a player asking to
|
||||
* tab-complete a chat message or command. Note that this is not called when
|
||||
* BungeeCord or a plugin responds to a tab-complete request. Use
|
||||
* {@link TabCompleteEvent} for that.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TabCompleteResponseEvent extends TargetedEvent implements Cancellable
|
||||
{
|
||||
|
||||
/**
|
||||
* Whether the event is cancelled.
|
||||
*/
|
||||
private boolean cancelled;
|
||||
|
||||
/**
|
||||
* Mutable list of suggestions sent back to the player. If this list is
|
||||
* empty, an empty list is sent back to the client.
|
||||
*/
|
||||
private final List<String> suggestions;
|
||||
|
||||
public TabCompleteResponseEvent(Connection sender, Connection receiver, List<String> suggestions)
|
||||
{
|
||||
super( sender, receiver );
|
||||
this.suggestions = suggestions;
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
128
api/src/main/java/net/md_5/bungee/api/plugin/LibraryLoader.java
Normal file
128
api/src/main/java/net/md_5/bungee/api/plugin/LibraryLoader.java
Normal file
@ -0,0 +1,128 @@
|
||||
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() );
|
||||
}
|
||||
} );
|
||||
|
||||
// SPIGOT-7638: Add system properties,
|
||||
// since JdkVersionProfileActivator needs 'java.version' when a profile has the 'jdk' element
|
||||
// otherwise it will silently fail and not resolves the dependencies in the affected pom.
|
||||
session.setSystemProperties( System.getProperties() );
|
||||
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,11 +1,16 @@
|
||||
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;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Logger;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ConfigurationAdapter;
|
||||
import net.md_5.bungee.api.scheduler.GroupedThreadFactory;
|
||||
|
||||
/**
|
||||
* Represents any Plugin that may be loaded at runtime to enhance existing
|
||||
@ -23,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
|
||||
@ -73,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)
|
||||
{
|
||||
@ -83,4 +104,20 @@ public class Plugin
|
||||
this.file = description.getFile();
|
||||
this.logger = new PluginLogger( this );
|
||||
}
|
||||
|
||||
//
|
||||
private ExecutorService service;
|
||||
|
||||
@Deprecated
|
||||
public ExecutorService getExecutorService()
|
||||
{
|
||||
if ( service == null )
|
||||
{
|
||||
String name = ( getDescription() == null ) ? "unknown" : getDescription().getName();
|
||||
service = Executors.newCachedThreadPool( new ThreadFactoryBuilder().setNameFormat( name + " Pool Thread #%1$d" )
|
||||
.setThreadFactory( new GroupedThreadFactory( this, name ) ).build() );
|
||||
}
|
||||
return service;
|
||||
}
|
||||
//
|
||||
}
|
||||
|
@ -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;
|
||||
@ -36,8 +38,20 @@ public class PluginDescription
|
||||
* Plugin hard dependencies.
|
||||
*/
|
||||
private Set<String> depends = new HashSet<>();
|
||||
/**
|
||||
* Plugin soft dependencies.
|
||||
*/
|
||||
private Set<String> softDepends = new HashSet<>();
|
||||
/**
|
||||
* File we were loaded from.
|
||||
*/
|
||||
private File file = null;
|
||||
/**
|
||||
* Optional description.
|
||||
*/
|
||||
private String description = null;
|
||||
/**
|
||||
* Optional libraries.
|
||||
*/
|
||||
private List<String> libraries = new LinkedList<>();
|
||||
}
|
||||
|
@ -2,23 +2,23 @@ package net.md_5.bungee.api.plugin;
|
||||
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
|
||||
public class PluginLogger extends Logger
|
||||
{
|
||||
|
||||
private String pluginName;
|
||||
private final String pluginName;
|
||||
|
||||
protected PluginLogger(Plugin plugin)
|
||||
{
|
||||
super( plugin.getClass().getCanonicalName(), null );
|
||||
pluginName = "[" + plugin.getDescription().getName() + "] ";
|
||||
setParent( plugin.getProxy().getLogger() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(LogRecord logRecord)
|
||||
{
|
||||
logRecord.setMessage( pluginName + logRecord.getMessage() );
|
||||
ProxyServer.getInstance().getLogger().log( logRecord );
|
||||
super.log( logRecord );
|
||||
}
|
||||
}
|
||||
|
@ -4,56 +4,85 @@ 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;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Class to manage bridging between plugin duties and implementation duties, for
|
||||
* 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 = new Yaml();
|
||||
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 Multimap<Plugin, Command> commandsByPlugin = ArrayListMultimap.create();
|
||||
private Multimap<Plugin, Listener> listenersByPlugin = ArrayListMultimap.create();
|
||||
private final Multimap<Plugin, Command> commandsByPlugin = ArrayListMultimap.create();
|
||||
private final Multimap<Plugin, Listener> listenersByPlugin = ArrayListMultimap.create();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public PluginManager(ProxyServer proxy)
|
||||
{
|
||||
this.proxy = proxy;
|
||||
|
||||
// Ignore unknown entries in the plugin descriptions
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,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 );
|
||||
}
|
||||
@ -79,7 +108,7 @@ public class PluginManager
|
||||
*/
|
||||
public void unregisterCommand(Command command)
|
||||
{
|
||||
commandMap.values().remove( command );
|
||||
while ( commandMap.values().remove( command ) );
|
||||
commandsByPlugin.values().remove( command );
|
||||
}
|
||||
|
||||
@ -92,11 +121,38 @@ public class PluginManager
|
||||
{
|
||||
for ( Iterator<Command> it = commandsByPlugin.get( plugin ).iterator(); it.hasNext(); )
|
||||
{
|
||||
commandMap.values().remove( it.next() );
|
||||
Command command = it.next();
|
||||
while ( commandMap.values().remove( command ) );
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
@ -108,32 +164,32 @@ 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 );
|
||||
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 ( 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 ) )
|
||||
{
|
||||
sender.sendMessage( proxy.getTranslation( "no_permission" ) );
|
||||
if ( tabResults == null )
|
||||
{
|
||||
sender.sendMessage( ( command.getPermissionMessage() == null ) ? proxy.getTranslation( "no_permission" ) : command.getPermissionMessage() );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -142,8 +198,15 @@ public class PluginManager
|
||||
{
|
||||
if ( tabResults == null )
|
||||
{
|
||||
if ( proxy.getConfig().isLogCommands() )
|
||||
{
|
||||
proxy.getLogger().log( Level.INFO, "{0} executed command: /{1}", new Object[]
|
||||
{
|
||||
sender.getName(), commandLine
|
||||
} );
|
||||
}
|
||||
command.execute( sender, args );
|
||||
} else if ( command instanceof TabExecutor )
|
||||
} else if ( commandLine.contains( " " ) && command instanceof TabExecutor )
|
||||
{
|
||||
for ( String s : ( (TabExecutor) command ).onTabComplete( sender, args ) )
|
||||
{
|
||||
@ -179,7 +242,7 @@ public class PluginManager
|
||||
return plugins.get( name );
|
||||
}
|
||||
|
||||
public void loadAndEnablePlugins()
|
||||
public void loadPlugins()
|
||||
{
|
||||
Map<PluginDescription, Boolean> pluginStatuses = new HashMap<>();
|
||||
for ( Map.Entry<String, PluginDescription> entry : toLoad.entrySet() )
|
||||
@ -187,12 +250,15 @@ public class PluginManager
|
||||
PluginDescription plugin = entry.getValue();
|
||||
if ( !enablePlugin( pluginStatuses, new Stack<PluginDescription>(), plugin ) )
|
||||
{
|
||||
ProxyServer.getInstance().getLogger().warning( "Failed to enable " + entry.getKey() );
|
||||
ProxyServer.getInstance().getLogger().log( Level.WARNING, "Failed to enable {0}", entry.getKey() );
|
||||
}
|
||||
}
|
||||
toLoad.clear();
|
||||
toLoad = null;
|
||||
}
|
||||
|
||||
public void enablePlugins()
|
||||
{
|
||||
for ( Plugin plugin : plugins.values() )
|
||||
{
|
||||
try
|
||||
@ -216,11 +282,16 @@ public class PluginManager
|
||||
return pluginStatuses.get( plugin );
|
||||
}
|
||||
|
||||
// combine all dependencies for 'for loop'
|
||||
Set<String> dependencies = new HashSet<>();
|
||||
dependencies.addAll( plugin.getDepends() );
|
||||
dependencies.addAll( plugin.getSoftDepends() );
|
||||
|
||||
// success status
|
||||
boolean status = true;
|
||||
|
||||
// try to load dependencies first
|
||||
for ( String dependName : plugin.getDepends() )
|
||||
for ( String dependName : dependencies )
|
||||
{
|
||||
PluginDescription depend = toLoad.get( dependName );
|
||||
Boolean dependStatus = ( depend != null ) ? pluginStatuses.get( depend ) : Boolean.FALSE;
|
||||
@ -235,7 +306,7 @@ public class PluginManager
|
||||
dependencyGraph.append( element.getName() ).append( " -> " );
|
||||
}
|
||||
dependencyGraph.append( plugin.getName() ).append( " -> " ).append( dependName );
|
||||
ProxyServer.getInstance().getLogger().log( Level.WARNING, "Circular dependency detected: " + dependencyGraph );
|
||||
ProxyServer.getInstance().getLogger().log( Level.WARNING, "Circular dependency detected: {0}", dependencyGraph );
|
||||
status = false;
|
||||
} else
|
||||
{
|
||||
@ -245,15 +316,16 @@ public class PluginManager
|
||||
}
|
||||
}
|
||||
|
||||
if ( dependStatus == Boolean.FALSE )
|
||||
if ( dependStatus == Boolean.FALSE && plugin.getDepends().contains( dependName ) ) // only fail if this wasn't a soft dependency
|
||||
{
|
||||
ProxyServer.getInstance().getLogger().log( Level.WARNING, "{0} (required by {1}) is unavailable", new Object[]
|
||||
{
|
||||
String.valueOf( depend.getName() ), plugin.getName()
|
||||
String.valueOf( dependName ), plugin.getName()
|
||||
} );
|
||||
status = false;
|
||||
}
|
||||
|
||||
dependencyGraph.putEdge( plugin.getName(), dependName );
|
||||
if ( !status )
|
||||
{
|
||||
break;
|
||||
@ -265,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[]
|
||||
@ -281,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 );
|
||||
}
|
||||
}
|
||||
|
||||
@ -305,12 +373,19 @@ public class PluginManager
|
||||
{
|
||||
try ( JarFile jar = new JarFile( file ) )
|
||||
{
|
||||
JarEntry pdf = jar.getJarEntry( "plugin.yml" );
|
||||
Preconditions.checkNotNull( pdf, "Plugin must have a plugin.yml" );
|
||||
JarEntry pdf = jar.getJarEntry( "bungee.yml" );
|
||||
if ( pdf == null )
|
||||
{
|
||||
pdf = jar.getJarEntry( "plugin.yml" );
|
||||
}
|
||||
Preconditions.checkNotNull( pdf, "Plugin must have a plugin.yml or bungee.yml" );
|
||||
|
||||
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 );
|
||||
}
|
||||
@ -338,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;
|
||||
@ -382,7 +457,7 @@ public class PluginManager
|
||||
/**
|
||||
* Unregister all of a Plugin's listener.
|
||||
*
|
||||
* @param plugin
|
||||
* @param plugin target plugin
|
||||
*/
|
||||
public void unregisterListeners(Plugin plugin)
|
||||
{
|
||||
@ -392,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;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package net.md_5.bungee.api.plugin;
|
||||
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
|
||||
|
||||
public interface TabExecutor
|
||||
{
|
||||
|
||||
|
@ -0,0 +1,34 @@
|
||||
package net.md_5.bungee.api.scheduler;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import lombok.Data;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
|
||||
@Data
|
||||
@Deprecated
|
||||
public class GroupedThreadFactory implements ThreadFactory
|
||||
{
|
||||
|
||||
private final ThreadGroup group;
|
||||
|
||||
public static final class BungeeGroup extends ThreadGroup
|
||||
{
|
||||
|
||||
private BungeeGroup(String name)
|
||||
{
|
||||
super( name );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public GroupedThreadFactory(Plugin plugin, String name)
|
||||
{
|
||||
this.group = new BungeeGroup( name );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r)
|
||||
{
|
||||
return new Thread( group, r );
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package net.md_5.bungee.api.scheduler;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
|
||||
@ -65,10 +66,29 @@ public interface TaskScheduler
|
||||
*
|
||||
* @param owner the plugin owning this task
|
||||
* @param task the task to run
|
||||
* @param delay the delay in milliseconds before this task will be executed
|
||||
* @param delay the delay before this task will be executed
|
||||
* @param period the interval before subsequent executions of this task
|
||||
* @param unit the unit in which the delay and period will be measured
|
||||
* @return the scheduled task
|
||||
*/
|
||||
ScheduledTask schedule(Plugin owner, Runnable task, long delay, long period, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Get the unsafe methods of this class.
|
||||
*
|
||||
* @return the unsafe method interface
|
||||
*/
|
||||
Unsafe unsafe();
|
||||
|
||||
interface Unsafe
|
||||
{
|
||||
|
||||
/**
|
||||
* An executor service which underlies this scheduler.
|
||||
*
|
||||
* @param plugin owning plugin
|
||||
* @return the underlying executor service or compatible wrapper
|
||||
*/
|
||||
ExecutorService getExecutorService(Plugin plugin);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
package net.md_5.bungee.api.score;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Represents an objective entry.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class Objective
|
||||
{
|
||||
|
||||
@ -16,5 +18,9 @@ public class Objective
|
||||
/**
|
||||
* Value of the objective.
|
||||
*/
|
||||
private final String value; // displayName
|
||||
private String value;
|
||||
/**
|
||||
* Type; integer or hearts
|
||||
*/
|
||||
private String type;
|
||||
}
|
||||
|
@ -6,5 +6,23 @@ package net.md_5.bungee.api.score;
|
||||
public enum Position
|
||||
{
|
||||
|
||||
LIST, SIDEBAR, BELOW;
|
||||
LIST,
|
||||
SIDEBAR,
|
||||
BELOW,
|
||||
SIDEBAR_BLACK,
|
||||
SIDEBAR_DARK_BLUE,
|
||||
SIDEBAR_DARK_GREEN,
|
||||
SIDEBAR_DARK_AQUA,
|
||||
SIDEBAR_DARK_RED,
|
||||
SIDEBAR_DARK_PURPLE,
|
||||
SIDEBAR_GOLD,
|
||||
SIDEBAR_GRAY,
|
||||
SIDEBAR_DARK_GRAY,
|
||||
SIDEBAR_BLUE,
|
||||
SIDEBAR_GREEN,
|
||||
SIDEBAR_AQUA,
|
||||
SIDEBAR_RED,
|
||||
SIDEBAR_LIGHT_PURPLE,
|
||||
SIDEBAR_YELLOW,
|
||||
SIDEBAR_WHITE;
|
||||
}
|
||||
|
@ -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" );
|
||||
@ -74,6 +79,11 @@ public class Scoreboard
|
||||
return teams.get( name );
|
||||
}
|
||||
|
||||
public Objective getObjective(String name)
|
||||
{
|
||||
return objectives.get( name );
|
||||
}
|
||||
|
||||
public void removeObjective(String objectiveName)
|
||||
{
|
||||
objectives.remove( objectiveName );
|
||||
|
@ -16,7 +16,10 @@ public class Team
|
||||
private String displayName;
|
||||
private String prefix;
|
||||
private String suffix;
|
||||
private boolean friendlyFire;
|
||||
private byte friendlyFire;
|
||||
private String nameTagVisibility;
|
||||
private String collisionRule;
|
||||
private int color;
|
||||
private Set<String> players = new HashSet<>();
|
||||
|
||||
public Collection<String> getPlayers()
|
||||
|
@ -1,60 +0,0 @@
|
||||
package net.md_5.bungee.api.tab;
|
||||
|
||||
/**
|
||||
* Represents a custom tab list, which may have slots manipulated.
|
||||
*/
|
||||
public interface CustomTabList extends TabListHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* Blank out this tab list and update immediately.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Gets the columns in this list.
|
||||
*
|
||||
* @return the width of this list
|
||||
*/
|
||||
int getColumns();
|
||||
|
||||
/**
|
||||
* Gets the rows in this list.
|
||||
*
|
||||
* @return the height of this list
|
||||
*/
|
||||
int getRows();
|
||||
|
||||
/**
|
||||
* Get the total size of this list.
|
||||
*
|
||||
* @return {@link #getRows()} * {@link #getColumns()}
|
||||
*/
|
||||
int getSize();
|
||||
|
||||
/**
|
||||
* Set the text in the specified slot and update immediately.
|
||||
*
|
||||
* @param row the row to set
|
||||
* @param column the column to set
|
||||
* @param text the text to set
|
||||
* @return the padded text
|
||||
*/
|
||||
String setSlot(int row, int column, String text);
|
||||
|
||||
/**
|
||||
* Set the text in the specified slot.
|
||||
*
|
||||
* @param row the row to set
|
||||
* @param column the column to set
|
||||
* @param text the text to set
|
||||
* @param update whether or not to invoke {@link #update()} upon completion
|
||||
* @return the padded text
|
||||
*/
|
||||
String setSlot(int row, int column, String text, boolean update);
|
||||
|
||||
/**
|
||||
* Flush all queued changes to the user.
|
||||
*/
|
||||
void update();
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package net.md_5.bungee.api.tab;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
@NoArgsConstructor
|
||||
public abstract class TabListAdapter implements TabListHandler
|
||||
{
|
||||
|
||||
@Getter
|
||||
private ProxiedPlayer player;
|
||||
|
||||
@Override
|
||||
public void init(ProxiedPlayer player)
|
||||
{
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnect()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerChange()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPingChange(int ping)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package net.md_5.bungee.api.tab;
|
||||
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
public interface TabListHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* Called so that this class may set member fields to keep track of its
|
||||
* internal state. You should not do any packet sending or manipulation of
|
||||
* the passed player, other than storing it.
|
||||
*
|
||||
* @param player the player to be associated with this list
|
||||
*/
|
||||
void init(ProxiedPlayer player);
|
||||
|
||||
/**
|
||||
* Called when this player first connects to the proxy.
|
||||
*/
|
||||
void onConnect();
|
||||
|
||||
/**
|
||||
* Called when a player first connects to the proxy.
|
||||
*
|
||||
* @param player the connecting player
|
||||
*/
|
||||
void onServerChange();
|
||||
|
||||
/**
|
||||
* Called when a players ping changes. The new ping will have not updated in
|
||||
* the player instance until this method returns.
|
||||
*
|
||||
* @param player the player who's ping changed
|
||||
* @param ping the player's new ping.
|
||||
*/
|
||||
void onPingChange(int ping);
|
||||
|
||||
/**
|
||||
* Called when a player disconnects.
|
||||
*
|
||||
* @param player the disconnected player
|
||||
*/
|
||||
void onDisconnect();
|
||||
|
||||
/**
|
||||
* Called when a list update packet is sent from server to client.
|
||||
*
|
||||
* @param player receiving this packet
|
||||
* @param name the player which this packet is relevant to
|
||||
* @param online whether the subject player is online
|
||||
* @param ping ping of the subject player
|
||||
* @return whether to send the packet to the client
|
||||
*/
|
||||
boolean onListUpdate(String name, boolean online, int ping);
|
||||
}
|
@ -3,12 +3,17 @@ 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;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.TabExecutor;
|
||||
|
||||
/**
|
||||
* @deprecated internal use only
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class PlayerCommand extends Command implements TabExecutor
|
||||
{
|
||||
|
||||
@ -25,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] : "";
|
||||
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().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() );
|
||||
}
|
||||
}
|
56
api/src/test/java/net/md_5/bungee/util/AddressParseTest.java
Normal file
56
api/src/test/java/net/md_5/bungee/util/AddressParseTest.java
Normal file
@ -0,0 +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.net.SocketAddress;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.md_5.bungee.Util;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class AddressParseTest
|
||||
{
|
||||
|
||||
public static Stream<Arguments> data()
|
||||
{
|
||||
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 )
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("data")
|
||||
public void test(String line, String host, int port)
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package net.md_5.bungee.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class CaseInsensitiveTest
|
||||
{
|
||||
|
||||
@Test
|
||||
public void testMaps()
|
||||
{
|
||||
Object obj = new Object();
|
||||
CaseInsensitiveMap<Object> map = new CaseInsensitiveMap<>();
|
||||
|
||||
map.put( "FOO", obj );
|
||||
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" );
|
||||
assertFalse( map.contains( "foo" ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSets()
|
||||
{
|
||||
CaseInsensitiveSet set = new CaseInsensitiveSet();
|
||||
|
||||
set.add( "FOO" );
|
||||
assertTrue( set.contains( "foo" ) ); // Assert that contains is case insensitive
|
||||
set.remove( "FoO" );
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
@ -4,59 +4,41 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>net.md-5</groupId>
|
||||
<groupId>fr.pandacube.bungeecord</groupId>
|
||||
<artifactId>bungeecord-parent</artifactId>
|
||||
<version>1.6.4-SNAPSHOT</version>
|
||||
<version>1.21-R0.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-bootstrap</artifactId>
|
||||
<version>1.6.4-SNAPSHOT</version>
|
||||
<version>1.21-R0.1-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.build.timestamp.format>yyyyMMdd</maven.build.timestamp.format>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<groupId>fr.pandacube.bungeecord</groupId>
|
||||
<artifactId>bungeecord-proxy</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.jopt-simple</groupId>
|
||||
<artifactId>jopt-simple</artifactId>
|
||||
<version>4.5</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>BungeeCord</finalName>
|
||||
<finalName>BungeeCord-${project.version}-${build.number}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>2.5.1</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<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.4.1</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
@ -70,7 +52,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.1</version>
|
||||
<version>3.5.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
@ -94,4 +76,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>
|
||||
|
@ -1,81 +1,17 @@
|
||||
package net.md_5.bungee;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.command.ConsoleCommandSender;
|
||||
|
||||
public class Bootstrap
|
||||
{
|
||||
|
||||
private static List<String> list(String... params)
|
||||
{
|
||||
return Arrays.asList( params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new instance of BungeeCord.
|
||||
*
|
||||
* @param args command line arguments, currently none are used
|
||||
* @throws Exception when the server cannot be started
|
||||
*/
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
OptionParser parser = new OptionParser();
|
||||
parser.allowsUnrecognizedOptions();
|
||||
parser.acceptsAll( list( "v", "version" ) );
|
||||
|
||||
OptionSet options = parser.parse( args );
|
||||
|
||||
if ( options.has( "version" ) )
|
||||
if ( Float.parseFloat( System.getProperty( "java.class.version" ) ) < 52.0 )
|
||||
{
|
||||
System.out.println( Bootstrap.class.getPackage().getImplementationVersion() );
|
||||
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;
|
||||
}
|
||||
|
||||
if ( !System.getProperty( "java.version" ).startsWith( "1.7" ) )
|
||||
{
|
||||
System.err.println( "*** ERROR *** BungeeCord requires Java 7 to function!" );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( BungeeCord.class.getPackage().getSpecificationVersion() != null )
|
||||
{
|
||||
Calendar deadline = Calendar.getInstance();
|
||||
deadline.add( Calendar.WEEK_OF_YEAR, 2 );
|
||||
if ( Calendar.getInstance().after( new SimpleDateFormat( "yyyyMMdd" ).parse( BungeeCord.class.getPackage().getSpecificationVersion() ) ) )
|
||||
{
|
||||
System.err.println( "*** Warning, this build is outdated ***" );
|
||||
System.err.println( "*** Please download a new build from http://ci.md-5.net/job/BungeeCord ***" );
|
||||
System.err.println( "*** You will get NO support regarding this build ***" );
|
||||
System.err.println( "*** Server will start in 30 seconds ***" );
|
||||
Thread.sleep( TimeUnit.SECONDS.toMillis( 30 ) );
|
||||
}
|
||||
}
|
||||
|
||||
System.setProperty( "java.net.preferIPv4Stack", "true" );
|
||||
|
||||
BungeeCord bungee = new BungeeCord();
|
||||
ProxyServer.setInstance( bungee );
|
||||
bungee.getLogger().info( "Enabled BungeeCord version " + bungee.getVersion() );
|
||||
bungee.start();
|
||||
|
||||
while ( bungee.isRunning )
|
||||
{
|
||||
String line = bungee.getConsoleReader().readLine( ">" );
|
||||
if ( line != null )
|
||||
{
|
||||
if ( !bungee.getPluginManager().dispatchCommand( ConsoleCommandSender.getInstance(), line ) )
|
||||
{
|
||||
bungee.getConsole().sendMessage( ChatColor.RED + "Command not found" );
|
||||
}
|
||||
}
|
||||
}
|
||||
BungeeCordLauncher.main( args );
|
||||
}
|
||||
}
|
||||
|
31
chat/nb-configuration.xml
Normal file
31
chat/nb-configuration.xml
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project-shared-configuration>
|
||||
<!--
|
||||
This file contains additional configuration written by modules in the NetBeans IDE.
|
||||
The configuration is intended to be shared among all the users of project and
|
||||
therefore it is assumed to be part of version control checkout.
|
||||
Without this configuration present, some functionality in the IDE may be limited or fail altogether.
|
||||
-->
|
||||
<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
|
||||
<!--
|
||||
Properties that influence various parts of the IDE, especially code formatting and the like.
|
||||
You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
|
||||
That way multiple projects can share the same settings (useful for formatting rules for example).
|
||||
Any value defined here will override the pom.xml file value but is only applicable to the current project.
|
||||
-->
|
||||
<org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>
|
||||
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>
|
||||
</properties>
|
||||
</project-shared-configuration>
|
28
chat/pom.xml
Normal file
28
chat/pom.xml
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>fr.pandacube.bungeecord</groupId>
|
||||
<artifactId>bungeecord-parent</artifactId>
|
||||
<version>1.21-R0.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>bungeecord-chat</artifactId>
|
||||
<version>1.21-R0.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>BungeeCord-Chat</name>
|
||||
<description>Minecraft JSON chat API intended for use with BungeeCord</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.11.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
330
chat/src/main/java/net/md_5/bungee/api/ChatColor.java
Normal file
330
chat/src/main/java/net/md_5/bungee/api/ChatColor.java
Normal file
@ -0,0 +1,330 @@
|
||||
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 final class ChatColor
|
||||
{
|
||||
|
||||
/**
|
||||
* 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 = "0123456789AaBbCcDdEeFfKkLlMmNnOoRrXx";
|
||||
/**
|
||||
* Pattern to remove all colour codes.
|
||||
*/
|
||||
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>();
|
||||
/**
|
||||
* Colour instances keyed by their name.
|
||||
*/
|
||||
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;
|
||||
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, 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
|
||||
public String toString()
|
||||
{
|
||||
return toString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the given message of all color codes
|
||||
*
|
||||
* @param input String to strip of color
|
||||
* @return A copy of the input string, without any coloring
|
||||
*/
|
||||
public static String stripColor(final String input)
|
||||
{
|
||||
if ( input == null )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return STRIP_COLOR_PATTERN.matcher( input ).replaceAll( "" );
|
||||
}
|
||||
|
||||
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate)
|
||||
{
|
||||
char[] b = textToTranslate.toCharArray();
|
||||
for ( int i = 0; i < b.length - 1; i++ )
|
||||
{
|
||||
if ( b[i] == altColorChar && ALL_CODES.indexOf( b[i + 1] ) > -1 )
|
||||
{
|
||||
b[i] = ChatColor.COLOR_CHAR;
|
||||
b[i + 1] = Character.toLowerCase( b[i + 1] );
|
||||
}
|
||||
}
|
||||
return new String( b );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the colour represented by the specified code.
|
||||
*
|
||||
* @param code the code to search for
|
||||
* @return the mapped colour, or null if non exists
|
||||
*/
|
||||
public static ChatColor getByChar(char code)
|
||||
{
|
||||
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.length() == 7 && string.charAt( 0 ) == '#' )
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
12
chat/src/main/java/net/md_5/bungee/api/ChatMessageType.java
Normal file
12
chat/src/main/java/net/md_5/bungee/api/ChatMessageType.java
Normal file
@ -0,0 +1,12 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
/**
|
||||
* Represents the position on the screen where a message will appear.
|
||||
*/
|
||||
public enum ChatMessageType
|
||||
{
|
||||
|
||||
CHAT,
|
||||
SYSTEM,
|
||||
ACTION_BAR
|
||||
}
|
743
chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java
Normal file
743
chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java
Normal file
@ -0,0 +1,743 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import java.awt.Color;
|
||||
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")
|
||||
@EqualsAndHashCode(exclude = "parent")
|
||||
public abstract class BaseComponent
|
||||
{
|
||||
|
||||
@Setter(AccessLevel.NONE)
|
||||
BaseComponent parent;
|
||||
|
||||
/**
|
||||
* The component's style.
|
||||
*/
|
||||
@Getter
|
||||
private ComponentStyle style = new ComponentStyle();
|
||||
/**
|
||||
* The text to insert into the chat when this component (and child
|
||||
* components) are clicked while pressing the shift key
|
||||
*/
|
||||
@Getter
|
||||
private String insertion;
|
||||
|
||||
/**
|
||||
* Appended components that inherit this component's formatting and events
|
||||
*/
|
||||
@Getter
|
||||
private List<BaseComponent> extra;
|
||||
|
||||
/**
|
||||
* The action to perform when this component (and child components) are
|
||||
* clicked
|
||||
*/
|
||||
@Getter
|
||||
private ClickEvent clickEvent;
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
copyFormatting( old, FormatRetention.ALL, true );
|
||||
|
||||
if ( old.getExtra() != null )
|
||||
{
|
||||
for ( BaseComponent extra : old.getExtra() )
|
||||
{
|
||||
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 || !style.hasColor() )
|
||||
{
|
||||
setColor( component.getColorRaw() );
|
||||
}
|
||||
if ( replace || !style.hasShadowColor() )
|
||||
{
|
||||
setShadowColor( component.getShadowColorRaw() );
|
||||
}
|
||||
if ( replace || !style.hasFont() )
|
||||
{
|
||||
setFont( component.getFontRaw() );
|
||||
}
|
||||
if ( replace || style.isBoldRaw() == null )
|
||||
{
|
||||
setBold( component.isBoldRaw() );
|
||||
}
|
||||
if ( replace || style.isItalicRaw() == null )
|
||||
{
|
||||
setItalic( component.isItalicRaw() );
|
||||
}
|
||||
if ( replace || style.isUnderlinedRaw() == null )
|
||||
{
|
||||
setUnderlined( component.isUnderlinedRaw() );
|
||||
}
|
||||
if ( replace || style.isStrikethroughRaw() == null )
|
||||
{
|
||||
setStrikethrough( component.isStrikethroughRaw() );
|
||||
}
|
||||
if ( replace || style.isObfuscatedRaw() == 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 );
|
||||
setShadowColor( null );
|
||||
setBold( null );
|
||||
setItalic( null );
|
||||
setUnderlined( null );
|
||||
setStrikethrough( null );
|
||||
setObfuscated( null );
|
||||
setInsertion( null );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the BaseComponent and returns the clone.
|
||||
*
|
||||
* @return The duplicate of this 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}
|
||||
*
|
||||
* @param components the components to convert
|
||||
* @return the string in the old format
|
||||
*/
|
||||
public static String toLegacyText(BaseComponent... components)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for ( BaseComponent msg : components )
|
||||
{
|
||||
builder.append( msg.toLegacyText() );
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the components into a string without any formatting
|
||||
*
|
||||
* @param components the components to convert
|
||||
* @return the string as plain text
|
||||
*/
|
||||
public static String toPlainText(BaseComponent... components)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for ( BaseComponent msg : components )
|
||||
{
|
||||
builder.append( msg.toPlainText() );
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ComponentStyle} for this component.
|
||||
* <p>
|
||||
* Unlike {@link #applyStyle(ComponentStyle)}, this method will overwrite
|
||||
* all style values on this component.
|
||||
*
|
||||
* @param style the style to set, or null to set all style values to default
|
||||
*/
|
||||
public void setStyle(ComponentStyle style)
|
||||
{
|
||||
this.style = ( style != null ) ? style.clone() : new ComponentStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this component's color.
|
||||
* <p>
|
||||
* <b>Warning: This should be a color, not formatting code (ie,
|
||||
* {@link ChatColor#color} should not be null).</b>
|
||||
*
|
||||
* @param color the component color, or null to use the default
|
||||
*/
|
||||
public void setColor(ChatColor color)
|
||||
{
|
||||
this.style.setColor( color );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color of this component. This uses the parent's color if this
|
||||
* component doesn't have one. {@link net.md_5.bungee.api.ChatColor#WHITE}
|
||||
* is returned if no color is found.
|
||||
*
|
||||
* @return the color of this component
|
||||
*/
|
||||
public ChatColor getColor()
|
||||
{
|
||||
if ( !style.hasColor() )
|
||||
{
|
||||
if ( parent == null )
|
||||
{
|
||||
return ChatColor.WHITE;
|
||||
}
|
||||
return parent.getColor();
|
||||
}
|
||||
return style.getColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color of this component without checking the parents color.
|
||||
* May return null
|
||||
*
|
||||
* @return the color of this component
|
||||
*/
|
||||
public ChatColor getColorRaw()
|
||||
{
|
||||
return style.getColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this component's shadow color.
|
||||
*
|
||||
* @param color the component shadow color, or null to use the default
|
||||
*/
|
||||
public void setShadowColor(Color color)
|
||||
{
|
||||
this.style.setShadowColor( color );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the shadow color of this component. This uses the parent's shadow color if this
|
||||
* component doesn't have one. null is returned if no shadow color is found.
|
||||
*
|
||||
* @return the shadow color of this component
|
||||
*/
|
||||
public Color getShadowColor()
|
||||
{
|
||||
if ( !style.hasShadowColor() )
|
||||
{
|
||||
if ( parent == null )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return parent.getShadowColor();
|
||||
}
|
||||
return style.getShadowColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the shadow color of this component without checking the parents
|
||||
* shadow color. May return null
|
||||
*
|
||||
* @return the shadow color of this component
|
||||
*/
|
||||
public Color getShadowColorRaw()
|
||||
{
|
||||
return style.getShadowColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this component's font.
|
||||
*
|
||||
* @param font the font to set, or null to use the default
|
||||
*/
|
||||
public void setFont(String font)
|
||||
{
|
||||
this.style.setFont( font );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ( !style.hasFont() )
|
||||
{
|
||||
if ( parent == null )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return parent.getFont();
|
||||
}
|
||||
return style.getFont();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the font of this component without checking the parents font. May
|
||||
* return null
|
||||
*
|
||||
* @return the font of this component
|
||||
*/
|
||||
public String getFontRaw()
|
||||
{
|
||||
return style.getFont();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this component is bold.
|
||||
*
|
||||
* @param bold the new bold state, or null to use the default
|
||||
*/
|
||||
public void setBold(Boolean bold)
|
||||
{
|
||||
this.style.setBold( bold );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* chain has been set.
|
||||
*
|
||||
* @return whether the component is bold
|
||||
*/
|
||||
public boolean isBold()
|
||||
{
|
||||
if ( style.isBoldRaw() == null )
|
||||
{
|
||||
return parent != null && parent.isBold();
|
||||
}
|
||||
return style.isBold();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this component is bold without checking the parents
|
||||
* setting. May return null
|
||||
*
|
||||
* @return whether the component is bold
|
||||
*/
|
||||
public Boolean isBoldRaw()
|
||||
{
|
||||
return style.isBoldRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this component is italic.
|
||||
*
|
||||
* @param italic the new italic state, or null to use the default
|
||||
*/
|
||||
public void setItalic(Boolean italic)
|
||||
{
|
||||
this.style.setItalic( italic );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this component is italic. This uses the parent's setting
|
||||
* if this component hasn't been set. false is returned if none of the
|
||||
* parent chain has been set.
|
||||
*
|
||||
* @return whether the component is italic
|
||||
*/
|
||||
public boolean isItalic()
|
||||
{
|
||||
if ( style.isItalicRaw() == null )
|
||||
{
|
||||
return parent != null && parent.isItalic();
|
||||
}
|
||||
return style.isItalic();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this component is italic without checking the parents
|
||||
* setting. May return null
|
||||
*
|
||||
* @return whether the component is italic
|
||||
*/
|
||||
public Boolean isItalicRaw()
|
||||
{
|
||||
return style.isItalicRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this component is underlined.
|
||||
*
|
||||
* @param underlined the new underlined state, or null to use the default
|
||||
*/
|
||||
public void setUnderlined(Boolean underlined)
|
||||
{
|
||||
this.style.setUnderlined( underlined );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this component is underlined. This uses the parent's
|
||||
* setting if this component hasn't been set. false is returned if none of
|
||||
* the parent chain has been set.
|
||||
*
|
||||
* @return whether the component is underlined
|
||||
*/
|
||||
public boolean isUnderlined()
|
||||
{
|
||||
if ( style.isUnderlinedRaw() == null )
|
||||
{
|
||||
return parent != null && parent.isUnderlined();
|
||||
}
|
||||
return style.isUnderlined();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this component is underlined without checking the parents
|
||||
* setting. May return null
|
||||
*
|
||||
* @return whether the component is underlined
|
||||
*/
|
||||
public Boolean isUnderlinedRaw()
|
||||
{
|
||||
return style.isUnderlinedRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this component is strikethrough.
|
||||
*
|
||||
* @param strikethrough the new strikethrough state, or null to use the
|
||||
* default
|
||||
*/
|
||||
public void setStrikethrough(Boolean strikethrough)
|
||||
{
|
||||
this.style.setStrikethrough( strikethrough );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this component is strikethrough. This uses the parent's
|
||||
* setting if this component hasn't been set. false is returned if none of
|
||||
* the parent chain has been set.
|
||||
*
|
||||
* @return whether the component is strikethrough
|
||||
*/
|
||||
public boolean isStrikethrough()
|
||||
{
|
||||
if ( style.isStrikethroughRaw() == null )
|
||||
{
|
||||
return parent != null && parent.isStrikethrough();
|
||||
}
|
||||
return style.isStrikethrough();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this component is strikethrough without checking the
|
||||
* parents setting. May return null
|
||||
*
|
||||
* @return whether the component is strikethrough
|
||||
*/
|
||||
public Boolean isStrikethroughRaw()
|
||||
{
|
||||
return style.isStrikethroughRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this component is obfuscated.
|
||||
*
|
||||
* @param obfuscated the new obfuscated state, or null to use the default
|
||||
*/
|
||||
public void setObfuscated(Boolean obfuscated)
|
||||
{
|
||||
this.style.setObfuscated( obfuscated );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this component is obfuscated. This uses the parent's
|
||||
* setting if this component hasn't been set. false is returned if none of
|
||||
* the parent chain has been set.
|
||||
*
|
||||
* @return whether the component is obfuscated
|
||||
*/
|
||||
public boolean isObfuscated()
|
||||
{
|
||||
if ( style.isObfuscatedRaw() == null )
|
||||
{
|
||||
return parent != null && parent.isObfuscated();
|
||||
}
|
||||
return style.isObfuscated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this component is obfuscated without checking the parents
|
||||
* setting. May return null
|
||||
*
|
||||
* @return whether the component is obfuscated
|
||||
*/
|
||||
public Boolean isObfuscatedRaw()
|
||||
{
|
||||
return style.isObfuscatedRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the style from the given {@link ComponentStyle} to this component.
|
||||
* <p>
|
||||
* Any style values that have been explicitly set in the style will be
|
||||
* applied to this component. If a value is not set in the style, it will
|
||||
* not override the style set in this component.
|
||||
*
|
||||
* @param style the style to apply
|
||||
*/
|
||||
public void applyStyle(ComponentStyle style)
|
||||
{
|
||||
if ( style.hasColor() )
|
||||
{
|
||||
setColor( style.getColor() );
|
||||
}
|
||||
if ( style.hasShadowColor() )
|
||||
{
|
||||
setShadowColor( style.getShadowColor() );
|
||||
}
|
||||
if ( style.hasFont() )
|
||||
{
|
||||
setFont( style.getFont() );
|
||||
}
|
||||
if ( style.isBoldRaw() != null )
|
||||
{
|
||||
setBold( style.isBoldRaw() );
|
||||
}
|
||||
if ( style.isItalicRaw() != null )
|
||||
{
|
||||
setItalic( style.isItalicRaw() );
|
||||
}
|
||||
if ( style.isUnderlinedRaw() != null )
|
||||
{
|
||||
setUnderlined( style.isUnderlinedRaw() );
|
||||
}
|
||||
if ( style.isStrikethroughRaw() != null )
|
||||
{
|
||||
setStrikethrough( style.isStrikethroughRaw() );
|
||||
}
|
||||
if ( style.isObfuscatedRaw() != null )
|
||||
{
|
||||
setObfuscated( style.isObfuscatedRaw() );
|
||||
}
|
||||
}
|
||||
|
||||
public void setExtra(List<BaseComponent> components)
|
||||
{
|
||||
for ( BaseComponent component : components )
|
||||
{
|
||||
component.parent = this;
|
||||
}
|
||||
extra = components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a text element to the component. The text will inherit this
|
||||
* component's formatting
|
||||
*
|
||||
* @param text the text to append
|
||||
*/
|
||||
public void addExtra(String text)
|
||||
{
|
||||
addExtra( new TextComponent( text ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a component to the component. The text will inherit this
|
||||
* component's formatting
|
||||
*
|
||||
* @param component the component to append
|
||||
*/
|
||||
public void addExtra(BaseComponent component)
|
||||
{
|
||||
if ( extra == null )
|
||||
{
|
||||
extra = new ArrayList<BaseComponent>();
|
||||
}
|
||||
component.parent = this;
|
||||
extra.add( component );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the component has any styling applied to it.
|
||||
*
|
||||
* @return Whether any styling is applied
|
||||
*/
|
||||
public boolean hasStyle()
|
||||
{
|
||||
return !style.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the component has any formatting or events applied to it
|
||||
*
|
||||
* @return Whether any formatting or events are applied
|
||||
*/
|
||||
public boolean hasFormatting()
|
||||
{
|
||||
return hasStyle() || insertion != null
|
||||
|| hoverEvent != null || clickEvent != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the component into a string without any formatting
|
||||
*
|
||||
* @return the string as plain text
|
||||
*/
|
||||
public String toPlainText()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
toPlainText( builder );
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
void toPlainText(StringBuilder builder)
|
||||
{
|
||||
if ( extra != null )
|
||||
{
|
||||
for ( BaseComponent e : extra )
|
||||
{
|
||||
e.toPlainText( builder );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the component to a string that uses the old formatting codes
|
||||
* ({@link net.md_5.bungee.api.ChatColor#COLOR_CHAR}
|
||||
*
|
||||
* @return the string in the old format
|
||||
*/
|
||||
public String toLegacyText()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
toLegacyText( builder );
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
void toLegacyText(StringBuilder builder)
|
||||
{
|
||||
if ( extra != null )
|
||||
{
|
||||
for ( BaseComponent e : extra )
|
||||
{
|
||||
e.toLegacyText( builder );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
62
chat/src/main/java/net/md_5/bungee/api/chat/ClickEvent.java
Normal file
62
chat/src/main/java/net/md_5/bungee/api/chat/ClickEvent.java
Normal file
@ -0,0 +1,62 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public final class ClickEvent
|
||||
{
|
||||
|
||||
/**
|
||||
* The type of action to perform on click.
|
||||
*/
|
||||
private final Action action;
|
||||
/**
|
||||
* Depends on the action.
|
||||
*
|
||||
* @see Action
|
||||
*/
|
||||
private final String value;
|
||||
|
||||
public enum Action
|
||||
{
|
||||
|
||||
/**
|
||||
* Open a url at the path given by
|
||||
* {@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}.
|
||||
*/
|
||||
OPEN_FILE,
|
||||
/**
|
||||
* Run the command given by
|
||||
* {@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 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.
|
||||
*/
|
||||
CHANGE_PAGE,
|
||||
/**
|
||||
* Copy the string given by
|
||||
* {@link net.md_5.bungee.api.chat.ClickEvent#value} into the player's
|
||||
* clipboard.
|
||||
*/
|
||||
COPY_TO_CLIPBOARD
|
||||
}
|
||||
}
|
@ -0,0 +1,580 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
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>
|
||||
* ComponentBuilder simplifies creating basic messages by allowing the use of a
|
||||
* chainable builder.
|
||||
* </p>
|
||||
* <pre>
|
||||
* new ComponentBuilder("Hello ").color(ChatColor.RED).
|
||||
* append("World").color(ChatColor.BLUE). append("!").bold(true).create();
|
||||
* </pre>
|
||||
* <p>
|
||||
* All methods (excluding {@link #append(String)} and {@link #create()} work on
|
||||
* the last part appended to the builder, so in the example above "Hello " would
|
||||
* be {@link net.md_5.bungee.api.ChatColor#RED} and "World" would be
|
||||
* {@link net.md_5.bungee.api.ChatColor#BLUE} but "!" would be bold and
|
||||
* {@link net.md_5.bungee.api.ChatColor#BLUE} because append copies the previous
|
||||
* part's formatting
|
||||
* </p>
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public final class ComponentBuilder
|
||||
{
|
||||
|
||||
/**
|
||||
* 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
|
||||
* it.
|
||||
*
|
||||
* @param original the original for the new ComponentBuilder.
|
||||
*/
|
||||
public ComponentBuilder(ComponentBuilder original)
|
||||
{
|
||||
this( original.parts.toArray( new BaseComponent[ 0 ] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ComponentBuilder with the given text as the first part.
|
||||
*
|
||||
* @param text the first text element
|
||||
*/
|
||||
public ComponentBuilder(String 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 {@link TranslationProvider} object to the builder and makes
|
||||
* the last element the current target for formatting. The components will
|
||||
* have all the formatting from previous part.
|
||||
*
|
||||
* @param translatable the translatable object to append
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder append(TranslationProvider translatable)
|
||||
{
|
||||
return append( translatable, FormatRetention.ALL );
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the {@link TranslationProvider} object 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 translatable the translatable object to append
|
||||
* @param retention the formatting to retain
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder append(TranslationProvider translatable, FormatRetention retention)
|
||||
{
|
||||
return append( translatable.asTranslatableComponent(), retention );
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the text to the builder and makes it the current target for
|
||||
* formatting. The text will have all the formatting from previous part.
|
||||
*
|
||||
* @param text the text to append
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder append(String text)
|
||||
{
|
||||
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 from
|
||||
* previous part.
|
||||
*
|
||||
* @param text the text to append
|
||||
* @param retention the formatting to retain
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder append(String text, FormatRetention retention)
|
||||
{
|
||||
return append( new TextComponent( text ), 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of the current part.
|
||||
*
|
||||
* @param color the new color
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder color(ChatColor 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the current part is bold.
|
||||
*
|
||||
* @param bold whether this part is bold
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder bold(boolean bold)
|
||||
{
|
||||
getCurrentComponent().setBold( bold );
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the current part is italic.
|
||||
*
|
||||
* @param italic whether this part is italic
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder italic(boolean italic)
|
||||
{
|
||||
getCurrentComponent().setItalic( italic );
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the current part is underlined.
|
||||
*
|
||||
* @param underlined whether this part is underlined
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder underlined(boolean underlined)
|
||||
{
|
||||
getCurrentComponent().setUnderlined( underlined );
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the current part is strikethrough.
|
||||
*
|
||||
* @param strikethrough whether this part is strikethrough
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder strikethrough(boolean strikethrough)
|
||||
{
|
||||
getCurrentComponent().setStrikethrough( strikethrough );
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the current part is obfuscated.
|
||||
*
|
||||
* @param obfuscated whether this part is obfuscated
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder obfuscated(boolean obfuscated)
|
||||
{
|
||||
getCurrentComponent().setObfuscated( obfuscated );
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the provided {@link ComponentStyle} to the current part.
|
||||
*
|
||||
* @param style the style to apply
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder style(ComponentStyle style)
|
||||
{
|
||||
getCurrentComponent().applyStyle( style );
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the insertion text for the current part.
|
||||
*
|
||||
* @param insertion the insertion text
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder insertion(String insertion)
|
||||
{
|
||||
getCurrentComponent().setInsertion( insertion );
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the click event for the current part.
|
||||
*
|
||||
* @param clickEvent the click event
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder event(ClickEvent clickEvent)
|
||||
{
|
||||
getCurrentComponent().setClickEvent( clickEvent );
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hover event for the current part.
|
||||
*
|
||||
* @param hoverEvent the hover event
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder event(HoverEvent hoverEvent)
|
||||
{
|
||||
getCurrentComponent().setHoverEvent( hoverEvent );
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current part back to normal settings. Only text is kept.
|
||||
*
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder reset()
|
||||
{
|
||||
return retain( FormatRetention.NONE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retains only the specified formatting. Text is not modified.
|
||||
*
|
||||
* @param retention the formatting to retain
|
||||
* @return this ComponentBuilder for chaining
|
||||
*/
|
||||
public ComponentBuilder retain(FormatRetention retention)
|
||||
{
|
||||
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.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()
|
||||
{
|
||||
BaseComponent[] cloned = new BaseComponent[ parts.size() ];
|
||||
int i = 0;
|
||||
for ( BaseComponent part : parts )
|
||||
{
|
||||
cloned[i++] = part.duplicate();
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
|
||||
public enum FormatRetention
|
||||
{
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
FORMATTING,
|
||||
/**
|
||||
* Specify that we want the events retained from the previous component.
|
||||
*/
|
||||
EVENTS,
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
263
chat/src/main/java/net/md_5/bungee/api/chat/ComponentStyle.java
Normal file
263
chat/src/main/java/net/md_5/bungee/api/chat/ComponentStyle.java
Normal file
@ -0,0 +1,263 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import java.awt.Color;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
/**
|
||||
* Represents a style that may be applied to a {@link BaseComponent}.
|
||||
*/
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public final class ComponentStyle implements Cloneable
|
||||
{
|
||||
|
||||
/**
|
||||
* The color of this style.
|
||||
* <p>
|
||||
* <b>Warning: This should be a color, not formatting code (ie,
|
||||
* {@link ChatColor#color} should not be null).</b>
|
||||
*/
|
||||
private ChatColor color;
|
||||
/**
|
||||
* The shadow color of this style.
|
||||
*/
|
||||
private Color shadowColor;
|
||||
/**
|
||||
* The font of this style.
|
||||
*/
|
||||
private String font;
|
||||
/**
|
||||
* Whether this style is bold.
|
||||
*/
|
||||
private Boolean bold;
|
||||
/**
|
||||
* Whether this style is italic.
|
||||
*/
|
||||
private Boolean italic;
|
||||
/**
|
||||
* Whether this style is underlined.
|
||||
*/
|
||||
private Boolean underlined;
|
||||
/**
|
||||
* Whether this style is strikethrough.
|
||||
*/
|
||||
private Boolean strikethrough;
|
||||
/**
|
||||
* Whether this style is obfuscated.
|
||||
*/
|
||||
private Boolean obfuscated;
|
||||
|
||||
/**
|
||||
* Returns the color of this style. May return null.
|
||||
*
|
||||
* @return the color of this style, or null if default color
|
||||
*/
|
||||
public ChatColor getColor()
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this style has a color set.
|
||||
*
|
||||
* @return whether a color is set
|
||||
*/
|
||||
public boolean hasColor()
|
||||
{
|
||||
return ( color != null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the shadow color of this style. May return null.
|
||||
*
|
||||
* @return the shadow color of this style, or null if default color
|
||||
*/
|
||||
public Color getShadowColor()
|
||||
{
|
||||
return shadowColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this style has a shadow color set.
|
||||
*
|
||||
* @return whether a shadow color is set
|
||||
*/
|
||||
public boolean hasShadowColor()
|
||||
{
|
||||
return ( shadowColor != null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the font of this style. May return null.
|
||||
*
|
||||
* @return the font of this style, or null if default font
|
||||
*/
|
||||
public String getFont()
|
||||
{
|
||||
return font;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this style has a font set.
|
||||
*
|
||||
* @return whether a font is set
|
||||
*/
|
||||
public boolean hasFont()
|
||||
{
|
||||
return ( font != null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this style is bold.
|
||||
*
|
||||
* @return whether the style is bold
|
||||
*/
|
||||
public boolean isBold()
|
||||
{
|
||||
return ( bold != null ) && bold.booleanValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this style is bold. May return null.
|
||||
*
|
||||
* @return whether the style is bold, or null if not set
|
||||
*/
|
||||
public Boolean isBoldRaw()
|
||||
{
|
||||
return bold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this style is italic. May return null.
|
||||
*
|
||||
* @return whether the style is italic
|
||||
*/
|
||||
public boolean isItalic()
|
||||
{
|
||||
return ( italic != null ) && italic.booleanValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this style is italic. May return null.
|
||||
*
|
||||
* @return whether the style is italic, or null if not set
|
||||
*/
|
||||
public Boolean isItalicRaw()
|
||||
{
|
||||
return italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this style is underlined.
|
||||
*
|
||||
* @return whether the style is underlined
|
||||
*/
|
||||
public boolean isUnderlined()
|
||||
{
|
||||
return ( underlined != null ) && underlined.booleanValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this style is underlined. May return null.
|
||||
*
|
||||
* @return whether the style is underlined, or null if not set
|
||||
*/
|
||||
public Boolean isUnderlinedRaw()
|
||||
{
|
||||
return underlined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this style is strikethrough
|
||||
*
|
||||
* @return whether the style is strikethrough
|
||||
*/
|
||||
public boolean isStrikethrough()
|
||||
{
|
||||
return ( strikethrough != null ) && strikethrough.booleanValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this style is strikethrough. May return null.
|
||||
*
|
||||
* @return whether the style is strikethrough, or null if not set
|
||||
*/
|
||||
public Boolean isStrikethroughRaw()
|
||||
{
|
||||
return strikethrough;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this style is obfuscated.
|
||||
*
|
||||
* @return whether the style is obfuscated
|
||||
*/
|
||||
public boolean isObfuscated()
|
||||
{
|
||||
return ( obfuscated != null ) && obfuscated.booleanValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this style is obfuscated. May return null.
|
||||
*
|
||||
* @return whether the style is obfuscated, or null if not set
|
||||
*/
|
||||
public Boolean isObfuscatedRaw()
|
||||
{
|
||||
return obfuscated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this style has no formatting explicitly set.
|
||||
*
|
||||
* @return true if no value is set, false if at least one is set
|
||||
*/
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return color == null && shadowColor == null && font == null && bold == null
|
||||
&& italic == null && underlined == null
|
||||
&& strikethrough == null && obfuscated == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentStyle clone()
|
||||
{
|
||||
return new ComponentStyle( color, shadowColor, font, bold, italic, underlined, strikethrough, obfuscated );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new {@link ComponentStyleBuilder}.
|
||||
*
|
||||
* @return the builder
|
||||
*/
|
||||
public static ComponentStyleBuilder builder()
|
||||
{
|
||||
return new ComponentStyleBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new {@link ComponentStyleBuilder} with values initialized to the
|
||||
* style values of the supplied {@link ComponentStyle}.
|
||||
*
|
||||
* @param other the component style whose values to copy into the builder
|
||||
* @return the builder
|
||||
*/
|
||||
public static ComponentStyleBuilder builder(ComponentStyle other)
|
||||
{
|
||||
return new ComponentStyleBuilder()
|
||||
.color( other.color )
|
||||
.shadowColor( other.shadowColor )
|
||||
.font( other.font )
|
||||
.bold( other.bold )
|
||||
.italic( other.italic )
|
||||
.underlined( other.underlined )
|
||||
.strikethrough( other.strikethrough )
|
||||
.obfuscated( other.obfuscated );
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import java.awt.Color;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ComponentStyleBuilder simplifies creating component styles by allowing the
|
||||
* use of a chainable builder.
|
||||
* </p>
|
||||
* <pre>
|
||||
* ComponentStyle style = ComponentStyle.builder()
|
||||
* .color(ChatColor.RED)
|
||||
* .font("custom:font")
|
||||
* .bold(true).italic(true).create();
|
||||
*
|
||||
* BaseComponent component = new ComponentBuilder("Hello world").style(style).create();
|
||||
* // Or it can be used directly on a component
|
||||
* TextComponent text = new TextComponent("Hello world");
|
||||
* text.applyStyle(style);
|
||||
* </pre>
|
||||
*
|
||||
* @see ComponentStyle#builder()
|
||||
* @see ComponentStyle#builder(ComponentStyle)
|
||||
*/
|
||||
public final class ComponentStyleBuilder
|
||||
{
|
||||
|
||||
private ChatColor color;
|
||||
private Color shadowColor;
|
||||
private String font;
|
||||
private Boolean bold, italic, underlined, strikethrough, obfuscated;
|
||||
|
||||
/**
|
||||
* Set the style color.
|
||||
*
|
||||
* @param color the color to set, or null to use the default
|
||||
* @return this ComponentStyleBuilder for chaining
|
||||
*/
|
||||
public ComponentStyleBuilder color(ChatColor color)
|
||||
{
|
||||
this.color = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the style shadow color.
|
||||
*
|
||||
* @param shadowColor the shadow color to set, or null to use the default
|
||||
* @return this ComponentStyleBuilder for chaining
|
||||
*/
|
||||
public ComponentStyleBuilder shadowColor(Color shadowColor)
|
||||
{
|
||||
this.shadowColor = shadowColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the style font.
|
||||
*
|
||||
* @param font the font key to set, or null to use the default
|
||||
* @return this ComponentStyleBuilder for chaining
|
||||
*/
|
||||
public ComponentStyleBuilder font(String font)
|
||||
{
|
||||
this.font = font;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the style's bold property.
|
||||
*
|
||||
* @param bold the bold value to set, or null to use the default
|
||||
* @return this ComponentStyleBuilder for chaining
|
||||
*/
|
||||
public ComponentStyleBuilder bold(Boolean bold)
|
||||
{
|
||||
this.bold = bold;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the style's italic property.
|
||||
*
|
||||
* @param italic the italic value to set, or null to use the default
|
||||
* @return this ComponentStyleBuilder for chaining
|
||||
*/
|
||||
public ComponentStyleBuilder italic(Boolean italic)
|
||||
{
|
||||
this.italic = italic;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the style's underlined property.
|
||||
*
|
||||
* @param underlined the underlined value to set, or null to use the default
|
||||
* @return this ComponentStyleBuilder for chaining
|
||||
*/
|
||||
public ComponentStyleBuilder underlined(Boolean underlined)
|
||||
{
|
||||
this.underlined = underlined;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the style's strikethrough property.
|
||||
*
|
||||
* @param strikethrough the strikethrough value to set, or null to use the
|
||||
* default
|
||||
* @return this ComponentStyleBuilder for chaining
|
||||
*/
|
||||
public ComponentStyleBuilder strikethrough(Boolean strikethrough)
|
||||
{
|
||||
this.strikethrough = strikethrough;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the style's obfuscated property.
|
||||
*
|
||||
* @param obfuscated the obfuscated value to set, or null to use the default
|
||||
* @return this ComponentStyleBuilder for chaining
|
||||
*/
|
||||
public ComponentStyleBuilder obfuscated(Boolean obfuscated)
|
||||
{
|
||||
this.obfuscated = obfuscated;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the {@link ComponentStyle} using the values set in this builder.
|
||||
*
|
||||
* @return the created ComponentStyle
|
||||
*/
|
||||
public ComponentStyle build()
|
||||
{
|
||||
return new ComponentStyle( color, shadowColor, font, bold, italic, underlined, strikethrough, obfuscated );
|
||||
}
|
||||
}
|
146
chat/src/main/java/net/md_5/bungee/api/chat/HoverEvent.java
Normal file
146
chat/src/main/java/net/md_5/bungee/api/chat/HoverEvent.java
Normal file
@ -0,0 +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
|
||||
public final class HoverEvent
|
||||
{
|
||||
|
||||
/**
|
||||
* The action of this event.
|
||||
*/
|
||||
private final Action action;
|
||||
/**
|
||||
* 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_ITEM,
|
||||
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,85 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* The separator of multiple selected entities.
|
||||
* <br>
|
||||
* The default is {@code {"color": "gray", "text": ", "}}.
|
||||
*/
|
||||
private BaseComponent separator;
|
||||
|
||||
/**
|
||||
* 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() );
|
||||
setSeparator( original.getSeparator() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a selector component from the selector
|
||||
*
|
||||
* @param selector the selector as a String
|
||||
*/
|
||||
public SelectorComponent(String selector)
|
||||
{
|
||||
setSelector( selector );
|
||||
}
|
||||
|
||||
@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 );
|
||||
}
|
||||
}
|
302
chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java
Normal file
302
chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java
Normal file
@ -0,0 +1,302 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
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
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class TextComponent extends BaseComponent
|
||||
{
|
||||
|
||||
private static final Pattern url = Pattern.compile( "^(?:(https?)://)?([-\\w_\\.]{2,}\\.[a-z]{2,4})(/\\S*)?$" );
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return the components needed to print the message to the client
|
||||
*/
|
||||
public static BaseComponent fromLegacy(String message)
|
||||
{
|
||||
return fromLegacy( 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 fromLegacy(String message, ChatColor defaultColor)
|
||||
{
|
||||
ComponentBuilder componentBuilder = new ComponentBuilder();
|
||||
populateComponentStructure( message, defaultColor, componentBuilder::append );
|
||||
return componentBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return the components needed to print the message to the client
|
||||
* @deprecated {@link #fromLegacy(String)} 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.
|
||||
*/
|
||||
@Deprecated
|
||||
public static BaseComponent[] fromLegacyText(String message)
|
||||
{
|
||||
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
|
||||
* @deprecated {@link #fromLegacy(String, ChatColor)} 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.
|
||||
*/
|
||||
@Deprecated
|
||||
public static BaseComponent[] fromLegacyText(String message, ChatColor defaultColor)
|
||||
{
|
||||
ArrayList<BaseComponent> components = new ArrayList<>();
|
||||
populateComponentStructure( message, defaultColor, components::add );
|
||||
return components.toArray( new BaseComponent[ 0 ] );
|
||||
}
|
||||
|
||||
private static void populateComponentStructure(String message, ChatColor defaultColor, Consumer<BaseComponent> appender)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
TextComponent component = new TextComponent();
|
||||
Matcher matcher = url.matcher( message );
|
||||
|
||||
for ( int i = 0; i < message.length(); i++ )
|
||||
{
|
||||
char c = message.charAt( i );
|
||||
if ( c == ChatColor.COLOR_CHAR )
|
||||
{
|
||||
if ( ++i >= message.length() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
c = message.charAt( i );
|
||||
if ( c >= 'A' && c <= 'Z' )
|
||||
{
|
||||
c += 32;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if ( builder.length() > 0 )
|
||||
{
|
||||
TextComponent old = component;
|
||||
component = new TextComponent( old );
|
||||
old.setText( builder.toString() );
|
||||
builder = new StringBuilder();
|
||||
appender.accept( old );
|
||||
}
|
||||
if ( format == ChatColor.BOLD )
|
||||
{
|
||||
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;
|
||||
}
|
||||
int pos = message.indexOf( ' ', i );
|
||||
if ( pos == -1 )
|
||||
{
|
||||
pos = message.length();
|
||||
}
|
||||
if ( matcher.region( i, pos ).find() )
|
||||
{ //Web link handling
|
||||
|
||||
if ( builder.length() > 0 )
|
||||
{
|
||||
TextComponent old = component;
|
||||
component = new TextComponent( old );
|
||||
old.setText( builder.toString() );
|
||||
builder = new StringBuilder();
|
||||
appender.accept( old );
|
||||
}
|
||||
|
||||
TextComponent old = component;
|
||||
component = new TextComponent( old );
|
||||
String urlString = message.substring( i, pos );
|
||||
component.setText( urlString );
|
||||
component.setClickEvent( new ClickEvent( ClickEvent.Action.OPEN_URL,
|
||||
urlString.startsWith( "http" ) ? urlString : "http://" + urlString ) );
|
||||
appender.accept( component );
|
||||
i += pos - i - 1;
|
||||
component = old;
|
||||
continue;
|
||||
}
|
||||
builder.append( c );
|
||||
}
|
||||
|
||||
component.setText( builder.toString() );
|
||||
appender.accept( component );
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal compatibility method to transform an array of components to a
|
||||
* single component.
|
||||
*
|
||||
* @param components array
|
||||
* @return single component
|
||||
*/
|
||||
public static BaseComponent fromArray(BaseComponent... components)
|
||||
{
|
||||
if ( components == null )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( components.length == 1 )
|
||||
{
|
||||
return components[0];
|
||||
}
|
||||
|
||||
return new TextComponent( components );
|
||||
}
|
||||
|
||||
/**
|
||||
* The text of the component that will be displayed to the client
|
||||
*/
|
||||
private String text;
|
||||
|
||||
/**
|
||||
* Creates a TextComponent with blank text.
|
||||
*/
|
||||
public TextComponent()
|
||||
{
|
||||
this.text = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TextComponent with formatting and text from the passed
|
||||
* component
|
||||
*
|
||||
* @param textComponent the component to copy from
|
||||
*/
|
||||
public TextComponent(TextComponent textComponent)
|
||||
{
|
||||
super( textComponent );
|
||||
setText( textComponent.getText() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TextComponent with blank text and the extras set to the passed
|
||||
* array
|
||||
*
|
||||
* @param extras the extras to set
|
||||
*/
|
||||
public TextComponent(BaseComponent... extras)
|
||||
{
|
||||
this();
|
||||
if ( extras.length == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
setExtra( new ArrayList<BaseComponent>( Arrays.asList( extras ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a duplicate of this TextComponent.
|
||||
*
|
||||
* @return the duplicate of this TextComponent.
|
||||
*/
|
||||
@Override
|
||||
public TextComponent duplicate()
|
||||
{
|
||||
return new TextComponent( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void toPlainText(StringBuilder builder)
|
||||
{
|
||||
builder.append( text );
|
||||
super.toPlainText( builder );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void toLegacyText(StringBuilder builder)
|
||||
{
|
||||
addFormat( builder );
|
||||
builder.append( text );
|
||||
super.toLegacyText( builder );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "TextComponent{text=" + text + ", " + super.toString() + '}';
|
||||
}
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
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 lombok.ToString;
|
||||
import net.md_5.bungee.chat.TranslationRegistry;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class TranslatableComponent extends BaseComponent
|
||||
{
|
||||
|
||||
private static final Pattern FORMAT = Pattern.compile( "%(?:(\\d+)\\$)?([A-Za-z%]|$)" );
|
||||
|
||||
/**
|
||||
* The key into the Minecraft locale files to use for the translation. The
|
||||
* text depends on the client's locale setting. The console is always en_US
|
||||
*/
|
||||
private String translate;
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param original the original for the new translatable component.
|
||||
*/
|
||||
public TranslatableComponent(TranslatableComponent original)
|
||||
{
|
||||
super( original );
|
||||
setTranslate( original.getTranslate() );
|
||||
setFallback( original.getFallback() );
|
||||
|
||||
if ( original.getWith() != null )
|
||||
{
|
||||
List<BaseComponent> temp = new ArrayList<>();
|
||||
for ( BaseComponent baseComponent : original.getWith() )
|
||||
{
|
||||
temp.add( baseComponent.duplicate() );
|
||||
}
|
||||
setWith( temp );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a translatable component with the passed substitutions
|
||||
*
|
||||
* @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 );
|
||||
if ( with != null && with.length != 0 )
|
||||
{
|
||||
List<BaseComponent> temp = new ArrayList<BaseComponent>();
|
||||
for ( Object w : with )
|
||||
{
|
||||
if ( w instanceof BaseComponent )
|
||||
{
|
||||
temp.add( (BaseComponent) w );
|
||||
} else
|
||||
{
|
||||
temp.add( new TextComponent( String.valueOf( w ) ) );
|
||||
}
|
||||
}
|
||||
setWith( temp );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a translatable component with the passed substitutions
|
||||
*
|
||||
* @param translatable the translatable object
|
||||
* @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(TranslationProvider translatable, Object... with)
|
||||
{
|
||||
this( translatable.getTranslationKey(), with );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a duplicate of this TranslatableComponent.
|
||||
*
|
||||
* @return the duplicate of this TranslatableComponent.
|
||||
*/
|
||||
@Override
|
||||
public TranslatableComponent duplicate()
|
||||
{
|
||||
return new TranslatableComponent( this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the translation substitutions to be used in this component. Removes
|
||||
* any previously set substitutions
|
||||
*
|
||||
* @param components the components to substitute
|
||||
*/
|
||||
public void setWith(List<BaseComponent> components)
|
||||
{
|
||||
for ( BaseComponent component : components )
|
||||
{
|
||||
component.parent = this;
|
||||
}
|
||||
with = components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a text substitution to the component. The text will inherit this
|
||||
* component's formatting
|
||||
*
|
||||
* @param text the text to substitute
|
||||
*/
|
||||
public void addWith(String text)
|
||||
{
|
||||
addWith( new TextComponent( text ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a component substitution to the component. The text will inherit
|
||||
* this component's formatting
|
||||
*
|
||||
* @param component the component to substitute
|
||||
*/
|
||||
public void addWith(BaseComponent component)
|
||||
{
|
||||
if ( with == null )
|
||||
{
|
||||
with = new ArrayList<BaseComponent>();
|
||||
}
|
||||
component.parent = this;
|
||||
with.add( component );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void toPlainText(StringBuilder builder)
|
||||
{
|
||||
convert( builder, false );
|
||||
super.toPlainText( builder );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void toLegacyText(StringBuilder builder)
|
||||
{
|
||||
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 = fallback;
|
||||
}
|
||||
|
||||
Matcher matcher = FORMAT.matcher( trans );
|
||||
int position = 0;
|
||||
int i = 0;
|
||||
while ( matcher.find( position ) )
|
||||
{
|
||||
int pos = matcher.start();
|
||||
if ( pos != position )
|
||||
{
|
||||
if ( applyFormat )
|
||||
{
|
||||
addFormat( builder );
|
||||
}
|
||||
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 );
|
||||
|
||||
BaseComponent withComponent = with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ );
|
||||
if ( applyFormat )
|
||||
{
|
||||
withComponent.toLegacyText( builder );
|
||||
} else
|
||||
{
|
||||
withComponent.toPlainText( builder );
|
||||
}
|
||||
break;
|
||||
case '%':
|
||||
if ( applyFormat )
|
||||
{
|
||||
addFormat( builder );
|
||||
}
|
||||
builder.append( '%' );
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( trans.length() != position )
|
||||
{
|
||||
if ( applyFormat )
|
||||
{
|
||||
addFormat( builder );
|
||||
}
|
||||
builder.append( trans.substring( position, trans.length() ) );
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package net.md_5.bungee.api.chat;
|
||||
|
||||
/**
|
||||
* An object capable of being translated by the client in a
|
||||
* {@link TranslatableComponent}.
|
||||
*/
|
||||
public interface TranslationProvider
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the translation key.
|
||||
*
|
||||
* @return the translation key
|
||||
*/
|
||||
String getTranslationKey();
|
||||
|
||||
/**
|
||||
* Get this translatable object as a {@link TranslatableComponent}.
|
||||
*
|
||||
* @return the translatable component
|
||||
*/
|
||||
default TranslatableComponent asTranslatableComponent()
|
||||
{
|
||||
return asTranslatableComponent( (Object[]) null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this translatable object as a {@link TranslatableComponent}.
|
||||
*
|
||||
* @param with the {@link String Strings} and
|
||||
* {@link BaseComponent BaseComponents} to use in the translation
|
||||
* @return the translatable component
|
||||
*/
|
||||
default TranslatableComponent asTranslatableComponent(Object... with)
|
||||
{
|
||||
return new TranslatableComponent( this, with );
|
||||
}
|
||||
}
|
@ -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,56 @@
|
||||
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 java.util.UUID;
|
||||
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();
|
||||
|
||||
String idString;
|
||||
JsonElement id = value.get( "id" );
|
||||
if ( id.isJsonArray() )
|
||||
{
|
||||
idString = parseUUID( context.deserialize( id, int[].class ) ).toString();
|
||||
} else
|
||||
{
|
||||
idString = id.getAsString();
|
||||
}
|
||||
|
||||
return new Entity(
|
||||
( value.has( "type" ) ) ? value.get( "type" ).getAsString() : null,
|
||||
idString,
|
||||
( 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;
|
||||
}
|
||||
|
||||
private static UUID parseUUID(int[] array)
|
||||
{
|
||||
return new UUID( (long) array[0] << 32 | (long) array[1] & 0XFFFFFFFFL, (long) array[2] << 32 | (long) array[3] & 0XFFFFFFFFL );
|
||||
}
|
||||
}
|
@ -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,65 @@
|
||||
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() );
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
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.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.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentStyle;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.hover.content.Content;
|
||||
|
||||
public class BaseComponentSerializer
|
||||
{
|
||||
|
||||
protected void deserialize(JsonObject object, BaseComponent component, JsonDeserializationContext context)
|
||||
{
|
||||
component.applyStyle( context.deserialize( object, ComponentStyle.class ) );
|
||||
|
||||
JsonElement insertion = object.get( "insertion" );
|
||||
if ( insertion != null )
|
||||
{
|
||||
component.setInsertion( insertion.getAsString() );
|
||||
}
|
||||
|
||||
//Events
|
||||
JsonObject clickEvent = object.getAsJsonObject( "clickEvent" );
|
||||
if ( clickEvent != null )
|
||||
{
|
||||
component.setClickEvent( new ClickEvent(
|
||||
ClickEvent.Action.valueOf( clickEvent.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) ),
|
||||
( clickEvent.has( "value" ) ) ? clickEvent.get( "value" ).getAsString() : "" ) );
|
||||
}
|
||||
JsonObject hoverEventJson = object.getAsJsonObject( "hoverEvent" );
|
||||
if ( hoverEventJson != null )
|
||||
{
|
||||
HoverEvent hoverEvent = null;
|
||||
HoverEvent.Action action = HoverEvent.Action.valueOf( hoverEventJson.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) );
|
||||
|
||||
JsonElement value = hoverEventJson.get( "value" );
|
||||
if ( value != null )
|
||||
{
|
||||
|
||||
// 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 ( value.isJsonArray() )
|
||||
{
|
||||
components = context.deserialize( value, BaseComponent[].class );
|
||||
} else
|
||||
{
|
||||
components = new BaseComponent[]
|
||||
{
|
||||
context.deserialize( value, BaseComponent.class )
|
||||
};
|
||||
}
|
||||
hoverEvent = new HoverEvent( action, components );
|
||||
} else
|
||||
{
|
||||
JsonElement contents = hoverEventJson.get( "contents" );
|
||||
if ( contents != null )
|
||||
{
|
||||
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 ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( hoverEvent != null )
|
||||
{
|
||||
component.setHoverEvent( hoverEvent );
|
||||
}
|
||||
}
|
||||
|
||||
JsonElement extra = object.get( "extra" );
|
||||
if ( extra != null )
|
||||
{
|
||||
component.setExtra( Arrays.asList( context.deserialize( extra, BaseComponent[].class ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
protected void serialize(JsonObject object, BaseComponent component, JsonSerializationContext context)
|
||||
{
|
||||
boolean first = false;
|
||||
if ( ComponentSerializer.serializedComponents.get() == null )
|
||||
{
|
||||
first = true;
|
||||
ComponentSerializer.serializedComponents.set( Collections.newSetFromMap( new IdentityHashMap<BaseComponent, Boolean>() ) );
|
||||
}
|
||||
try
|
||||
{
|
||||
Preconditions.checkArgument( !ComponentSerializer.serializedComponents.get().contains( component ), "Component loop" );
|
||||
ComponentSerializer.serializedComponents.get().add( component );
|
||||
|
||||
ComponentStyleSerializer.serializeTo( component.getStyle(), object );
|
||||
|
||||
if ( component.getInsertion() != null )
|
||||
{
|
||||
object.addProperty( "insertion", component.getInsertion() );
|
||||
}
|
||||
|
||||
//Events
|
||||
if ( component.getClickEvent() != null )
|
||||
{
|
||||
JsonObject clickEvent = new JsonObject();
|
||||
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( 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.getExtra() != null )
|
||||
{
|
||||
object.add( "extra", context.serialize( component.getExtra() ) );
|
||||
}
|
||||
} finally
|
||||
{
|
||||
ComponentSerializer.serializedComponents.get().remove( component );
|
||||
if ( first )
|
||||
{
|
||||
ComponentSerializer.serializedComponents.set( null );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
231
chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java
Normal file
231
chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java
Normal file
@ -0,0 +1,231 @@
|
||||
package net.md_5.bungee.chat;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
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.JsonParser;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Set;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ComponentStyle;
|
||||
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 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.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 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( ComponentStyle.class, new ComponentStyleSerializer() ).
|
||||
registerTypeAdapter( Entity.class, new EntitySerializer() ).
|
||||
registerTypeAdapter( Text.class, new TextSerializer() ).
|
||||
registerTypeAdapter( Item.class, new ItemSerializer() ).
|
||||
registerTypeAdapter( ItemTag.class, new ItemTag.Serializer() ).
|
||||
create();
|
||||
|
||||
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)
|
||||
{
|
||||
JsonElement jsonElement = JsonParser.parseString( json );
|
||||
|
||||
if ( jsonElement.isJsonArray() )
|
||||
{
|
||||
return gson.fromJson( jsonElement, BaseComponent[].class );
|
||||
} else
|
||||
{
|
||||
return new BaseComponent[]
|
||||
{
|
||||
gson.fromJson( jsonElement, BaseComponent.class )
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a JSON-compliant String as a single component.
|
||||
*
|
||||
* @param json the component json to parse
|
||||
* @return the deserialized component
|
||||
* @throws IllegalArgumentException if anything other than a valid JSON
|
||||
* component string is passed as input
|
||||
*/
|
||||
public static BaseComponent deserialize(String json)
|
||||
{
|
||||
JsonElement jsonElement = JsonParser.parseString( json );
|
||||
|
||||
return deserialize( jsonElement );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a JSON element as a single component.
|
||||
*
|
||||
* @param jsonElement the component json to parse
|
||||
* @return the deserialized component
|
||||
* @throws IllegalArgumentException if anything other than a valid JSON
|
||||
* component is passed as input
|
||||
*/
|
||||
public static BaseComponent deserialize(JsonElement jsonElement)
|
||||
{
|
||||
if ( jsonElement instanceof JsonPrimitive )
|
||||
{
|
||||
JsonPrimitive primitive = (JsonPrimitive) jsonElement;
|
||||
if ( primitive.isString() )
|
||||
{
|
||||
return new TextComponent( primitive.getAsString() );
|
||||
}
|
||||
} else if ( jsonElement instanceof JsonArray )
|
||||
{
|
||||
BaseComponent[] array = gson.fromJson( jsonElement, BaseComponent[].class );
|
||||
return TextComponent.fromArray( array );
|
||||
}
|
||||
|
||||
return gson.fromJson( jsonElement, BaseComponent.class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a JSON-compliant String as a component style.
|
||||
*
|
||||
* @param json the component style json to parse
|
||||
* @return the deserialized component style
|
||||
* @throws IllegalArgumentException if anything other than a valid JSON
|
||||
* component style string is passed as input
|
||||
*/
|
||||
public static ComponentStyle deserializeStyle(String json)
|
||||
{
|
||||
JsonElement jsonElement = JsonParser.parseString( json );
|
||||
|
||||
return deserializeStyle( jsonElement );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a JSON element as a component style.
|
||||
*
|
||||
* @param jsonElement the component style json to parse
|
||||
* @return the deserialized component style
|
||||
* @throws IllegalArgumentException if anything other than a valid JSON
|
||||
* component style is passed as input
|
||||
*/
|
||||
public static ComponentStyle deserializeStyle(JsonElement jsonElement)
|
||||
{
|
||||
return gson.fromJson( jsonElement, ComponentStyle.class );
|
||||
}
|
||||
|
||||
public static JsonElement toJson(BaseComponent component)
|
||||
{
|
||||
return gson.toJsonTree( component );
|
||||
}
|
||||
|
||||
public static JsonElement toJson(ComponentStyle style)
|
||||
{
|
||||
return gson.toJsonTree( style );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object the object to serialize
|
||||
* @return the JSON string representation of the object
|
||||
* @deprecated Error-prone, be careful which object you input here
|
||||
*/
|
||||
@Deprecated
|
||||
public static String toString(Object object)
|
||||
{
|
||||
return gson.toJson( object );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param content the content to serialize
|
||||
* @return the JSON string representation of the object
|
||||
* @deprecated for legacy internal use only
|
||||
*/
|
||||
@Deprecated
|
||||
public static String toString(Content content)
|
||||
{
|
||||
return gson.toJson( content );
|
||||
}
|
||||
|
||||
public static String toString(BaseComponent component)
|
||||
{
|
||||
return gson.toJson( component );
|
||||
}
|
||||
|
||||
public static String toString(BaseComponent... components)
|
||||
{
|
||||
if ( components.length == 1 )
|
||||
{
|
||||
return gson.toJson( components[0] );
|
||||
} else
|
||||
{
|
||||
return gson.toJson( new TextComponent( components ) );
|
||||
}
|
||||
}
|
||||
|
||||
public static String toString(ComponentStyle style)
|
||||
{
|
||||
return gson.toJson( style );
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
if ( json.isJsonPrimitive() )
|
||||
{
|
||||
return new TextComponent( json.getAsString() );
|
||||
}
|
||||
JsonObject object = json.getAsJsonObject();
|
||||
if ( object.has( "translate" ) )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user